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.
10 # Only a single Debugger instance should be created
14 self.private_class_method :new
16 # Returns the singleton instance of the +Debugger+.
18 @semaphore.synchronize do
27 def self.__clear_instance
28 @semaphore.synchronize do
33 # Initializes a new +Debugger+ instance
35 @breakpoint_tracker = BreakpointTracker.new do |thread, ctxt, bp|
36 activate_debugger thread, ctxt, bp
39 # Register this debugger as the default debug channel listener
40 Rubinius::VM.debug_channel = @breakpoint_tracker.debug_channel
43 @breakpoint_listener = Thread.new do
46 @debug_thread = @breakpoint_tracker.wait_for_breakpoint
48 # An exception has occurred in the breakpoint or debugger code
49 STDERR.puts "An exception occured while processing a breakpoint:"
51 STDERR.puts e.awesome_backtrace
53 @breakpoint_tracker.wake_target(@debug_thread) unless @quit # defer wake until we cleanup
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
65 # @debug_thread will be nil if debugger was quit from other than this thread
66 @breakpoint_tracker.wake_target(@debug_thread)
68 @breakpoint_tracker.release_waiting_threads
70 Thread.pass until waiting_for_breakpoint?
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)
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)
85 removed << @breakpoint_tracker.remove_breakpoint(bp)
89 @breakpoint_tracker.remove_breakpoint(bp)
93 # Temporarily removes a breakpoint, but does not delete it
94 def disable_breakpoint(bp)
98 # Re-installs a disabled breakpoint
99 def enable_breakpoint(bp)
104 @breakpoint_tracker.step(selector)
107 # Clears all breakpoints
108 def clear_breakpoints
109 @breakpoint_tracker.clear_breakpoints
112 # Returns details of all breakpoints that are being managed by the debugger.
113 # Note: This excludes transitory step breakpoints.
115 @breakpoint_tracker.global_breakpoints
118 # Returns the breakpoint for the specified compiled method and IP
119 def get_breakpoint(cm, ip)
120 @breakpoint_tracker.get_breakpoint(cm, ip)
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'
128 # Sets the quit flag to true, so that the debugger shuts down.
132 # If quit! is called from other than a command, we need to interrupt the
133 # breakpoint listener thread
135 @breakpoint_tracker.debug_channel.send nil
136 @breakpoint_listener.join
140 # Returns true if the debugger is shutting down
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)
161 # Retrieves the source code for the specified file, if it exists
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)
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.
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
184 Breakpoint.encoder.replace_instruction(bc, bp.ip, bp.original_instruction)
186 @last_cm, @last_asm = cm, cm.decode(bc)
188 @last_cm, @last_asm = cm, cm.decode