Coerce elements which respond to #to_ary for Array#flatten
[rbx.git] / lib / debugger / debugger.rb
blob133d52536931a37191e83fd923cc7750d5265959
1 require 'thread'
3 # A debugger, providing a CLI for debugging Ruby code running under Rubinius.
4 # Takes advantage of Rubinius's support for full-speed debugging, via the
5 # +Breakpoint+ and +BreakpointTracker+ classes.
6 class Debugger
7   @instance = nil
8   @semaphore = Mutex.new
10   # Only a single Debugger instance should be created
11   def self.new
12     super
13   end
14   self.private_class_method :new
16   # Returns the singleton instance of the +Debugger+.
17   def self.instance
18     @semaphore.synchronize do
19       @instance ||= new()
20     end
21   end
23   def self.__instance__
24     @instance
25   end
27   def self.__clear_instance
28     @semaphore.synchronize do
29       @instance = nil
30     end
31   end
33   # Initializes a new +Debugger+ instance
34   def initialize
35     @breakpoint_tracker = BreakpointTracker.new do |thread, ctxt, bp|
36       activate_debugger thread, ctxt, bp
37     end
39     # Register this debugger as the default debug channel listener
40     Rubinius::VM.debug_channel = @breakpoint_tracker.debug_channel
42     @quit = false
43     @breakpoint_listener = Thread.new do
44       until @quit do
45         begin
46           @debug_thread = @breakpoint_tracker.wait_for_breakpoint
47         rescue Exception => e
48           # An exception has occurred in the breakpoint or debugger code
49           STDERR.puts "An exception occured while processing a breakpoint:"
50           STDERR.puts e.to_s
51           STDERR.puts e.awesome_backtrace
52         end
53         @breakpoint_tracker.wake_target(@debug_thread) unless @quit  # defer wake until we cleanup
54       end
55       # Release singleton, since our loop thread is exiting
56       Debugger.__clear_instance
58       # Remove all remaining breakpoints
59       @breakpoint_tracker.clear_breakpoints
61       # De-register debugger on the global debug channel
62       Rubinius::VM.debug_channel = nil
64       if @debug_thread
65         # @debug_thread will be nil if debugger was quit from other than this thread
66         @breakpoint_tracker.wake_target(@debug_thread)
67       end
68       @breakpoint_tracker.release_waiting_threads
69     end
70     Thread.pass until waiting_for_breakpoint?
71   end
73   # Sets a breakpoint on a +CompiledMethod+ at the specified address.
74   def set_breakpoint(cm, ip, condition=nil)
75     @breakpoint_tracker.on(cm, :ip => ip, :condition => condition)
76   end
78   # Removes the breakpoint(s) specified.
79   # +bp+ may be either a single Breakpoint instance or id, or an Array of
80   # Breakpoint instances or ids.
81   def remove_breakpoint(bp)
82     if bp.kind_of? Array
83       removed = []
84       bp.each do |bp|
85         removed << @breakpoint_tracker.remove_breakpoint(bp)
86       end
87       removed
88     else
89       @breakpoint_tracker.remove_breakpoint(bp)
90     end
91   end
93   # Temporarily removes a breakpoint, but does not delete it
94   def disable_breakpoint(bp)
95     bp.disable
96   end
98   # Re-installs a disabled breakpoint
99   def enable_breakpoint(bp)
100     bp.enable
101   end
103   def step(selector)
104     @breakpoint_tracker.step(selector)
105   end
107   # Clears all breakpoints
108   def clear_breakpoints
109     @breakpoint_tracker.clear_breakpoints
110   end
112   # Returns details of all breakpoints that are being managed by the debugger.
113   # Note: This excludes transitory step breakpoints.
114   def breakpoints
115     @breakpoint_tracker.global_breakpoints
116   end
118   # Returns the breakpoint for the specified compiled method and IP
119   def get_breakpoint(cm, ip)
120     @breakpoint_tracker.get_breakpoint(cm, ip)
121   end
123   # True if the debugger is sleeping, i.e. waiting for a breakpoint to be hit.
124   def waiting_for_breakpoint?
125     @breakpoint_listener.status == 'sleep'
126   end
128   # Sets the quit flag to true, so that the debugger shuts down.
129   def quit!
130     @quit = true
132     # If quit! is called from other than a command, we need to interrupt the
133     # breakpoint listener thread
134     unless @debug_thread
135       @breakpoint_tracker.debug_channel.send nil
136       @breakpoint_listener.join
137     end
138   end
140   # Returns true if the debugger is shutting down
141   def quit?
142     @quit
143   end
145   # The interface used to interact with the debugger. A debugger interface must
146   # implement a #process_commands method taking the following arguments:
147   # - A reference to the Debugger instance
148   # - The thread that has hit the breakpoint
149   # - The context in which the breakpoint was hit
150   # - An array of Breakpoint instances representing each of the breakpoints that
151   #   were triggered at the current breakpoint location
152   attr_accessor :interface
154   # Activates the debugger after a breakpoint has been hit, and responds to
155   # debgging commands until a continue command is recevied.
156   def activate_debugger(thread, ctxt, bp_list)
157     @debug_thread = thread
158     @interface.at_breakpoint(self, thread, ctxt, bp_list)
159   end
161   # Retrieves the source code for the specified file, if it exists
162   def source_for(file)
163     return @last_lines if file == @last_file
165     @last_file, @last_lines = file, nil
166     if File.exists?(file)
167       @last_lines = File.readlines(file)
168     end
169     @last_lines
170   end
172   # Returns the decoded instruction sequence for the specified CompiledMethod.
173   # This should be used in preference to calling #decode directly on the method,
174   # since it returns the original instruction sequence, not the current iseq
175   # which may contain yield_debugger instructions.
176   def asm_for(cm)
177     return @last_asm if cm == @last_cm
179     # Remove yield_debugger instructions (if any)
180     if bp_list = @breakpoint_tracker.get_breakpoints_on(cm)
181       @last_cm, @last_asm = cm, cm.decode
182       bc = cm.bytecodes.dup
183       bp_list.each do |bp|
184         Breakpoint.encoder.replace_instruction(bc, bp.ip, bp.original_instruction)
185       end
186       @last_cm, @last_asm = cm, cm.decode(bc)
187     else
188       @last_cm, @last_asm = cm, cm.decode
189     end
190     @last_asm
191   end