require 'ruby_debug.so'
require 'rubygems'
require 'linecache'
module Debugger
# Default options to Debugger.start
DEFAULT_START_SETTINGS = {
:init => true, # Set $0 and save ARGV?
:post_mortem => false # post-mortem debugging on uncaught exception?
} unless defined?(DEFAULT_START_SETTINGS)
class Context
def interrupt
self.stop_next = 1
end
alias __c_frame_binding frame_binding
def frame_binding(frame)
__c_frame_binding(frame) || hbinding(frame)
end
private
def hbinding(frame)
hash = frame_locals(frame)
code = hash.keys.map{|k| "#{k} = hash['#{k}']"}.join(';') + ';binding'
if obj = frame_self(frame)
obj.instance_eval code
else
eval code, TOPLEVEL_BINDING
end
end
def handler
Debugger.handler or raise 'No interface loaded'
end
def at_breakpoint(breakpoint)
handler.at_breakpoint(self, breakpoint)
end
def at_catchpoint(excpt)
handler.at_catchpoint(self, excpt)
end
def at_tracing(file, line)
handler.at_tracing(self, file, line)
end
def at_line(file, line)
handler.at_line(self, file, line)
end
def at_return(file, line)
handler.at_return(self, file, line)
end
end
@reload_source_on_change = false
class << self
# interface modules provide +handler+ object
attr_accessor :handler
# if true, checks the modification time of source files and reloads if it was modified
attr_accessor :reload_source_on_change
attr_accessor :last_exception
Debugger.last_exception = nil
#
# Interrupts the current thread
#
def interrupt
current_context.interrupt
end
#
# Interrupts the last debugged thread
#
def interrupt_last
if context = last_context
return nil unless context.thread.alive?
context.interrupt
end
context
end
def source_reload
LineCache::clear_file_cache(true)
end
# Get line +line_number+ from file named +filename+. Return "\n"
# there was a problem. Leaking blanks are stripped off.
def line_at(filename, line_number) # :nodoc:
@reload_on_change=nil unless defined?(@reload_on_change)
line = LineCache::getline(filename, line_number, @reload_on_change)
return "\n" unless line
return "#{line.gsub(/^\s+/, '').chomp}\n"
end
#
# Activates the post-mortem mode. There are two ways of using it:
#
# == Global post-mortem mode
# By calling Debugger.post_mortem method without a block, you install
# at_exit hook that intercepts any unhandled by your script exceptions
# and enables post-mortem mode.
#
# == Local post-mortem mode
#
# If you know that a particular block of code raises an exception you can
# enable post-mortem mode by wrapping this block with Debugger.post_mortem, e.g.
#
# def offender
# raise 'error'
# end
# Debugger.post_mortem do
# ...
# offender
# ...
# end
def post_mortem
if block_given?
old_post_mortem = self.post_mortem?
begin
self.post_mortem = true
yield
rescue Exception => exp
handle_post_mortem(exp)
raise
ensure
self.post_mortem = old_post_mortem
end
else
return if post_mortem?
self.post_mortem = true
debug_at_exit do
handle_post_mortem($!) if $! && post_mortem?
end
end
end
def handle_post_mortem(exp)
return if !exp || !exp.__debug_context ||
exp.__debug_context.stack_size == 0
Debugger.suspend
orig_tracing = Debugger.tracing, Debugger.current_context.tracing
Debugger.tracing = Debugger.current_context.tracing = false
Debugger.last_exception = exp
handler.at_line(exp.__debug_context, exp.__debug_file, exp.__debug_line)
ensure
Debugger.tracing, Debugger.current_context.tracing = orig_tracing
Debugger.resume
end
# private :handle_post_mortem
end
class DebugThread # :nodoc:
end
class ThreadsTable # :nodoc:
end
# Debugger.start(options) -> bool
# Debugger.start(options) { ... } -> obj
#
# This method is internal and activates the debugger. Use
# Debugger.start (from ruby-debug-base.rb) instead.
#
# If it's called without a block it returns +true+, unless debugger
# was already started. If a block is given, it starts debugger and
# yields to block. When the block is finished executing it stops
# the debugger with Debugger.stop method.
#
# Note that if you want to stop debugger, you must call
# Debugger.stop as many time as you called Debugger.start
# method.
#
# +options+ is a hash used to set various debugging options.
# Set :init true if you want to save ARGV and some variables which
# make a debugger restart possible. Only the first time :init is set true
# will values get set. Since ARGV is saved, you should make sure
# it hasn't been changed before the (first) call.
# Set :post_mortem true if you want to enter post-mortem debugging
# on an uncaught exception. Once post-mortem debugging is set, it can't
# be unset.
def start(options={}, &block)
options = Debugger::DEFAULT_START_SETTINGS.merge(options)
if options[:init]
Debugger.const_set('ARGV', ARGV.clone) unless
defined? Debugger::ARGV
Debugger.const_set('PROG_SCRIPT', $0) unless
defined? Debugger::PROG_SCRIPT
Debugger.const_set('INITIAL_DIR', Dir.pwd) unless
defined? Debugger::INITIAL_DIR
end
retval = Debugger.started? ? nil : Debugger.start_(&block)
if options[:post_mortem]
post_mortem
end
return retval
end
module_function :start
end
module Kernel
# Enters the debugger in the current thread after _steps_ line events occur.
# Before entering the debugger startup script is read.
#
# Setting _steps_ to 0 will cause a break in the debugger subroutine
# and not wait for a line event to occur. You will have to go "up 1"
# in order to be back in your debugged program rather than the
# debugger. Settings _stess_ to 0 could be useful you want to stop
# right after the last statement in some scope, because the next
# step will take you out of some scope.
def debugger(steps = 1)
Debugger.start unless Debugger.started?
Debugger.run_init_script(StringIO.new)
if 0 == steps
Debugger.current_context.stop_frame = 0
else
Debugger.current_context.stop_next = steps
end
end
alias breakpoint debugger unless respond_to?(:breakpoint)
#
# Returns a binding of n-th call frame
#
def binding_n(n = 0)
Debugger.skip do
Debugger.current_context.frame_binding(n+2)
end
end
end
class Exception # :nodoc:
attr_reader :__debug_file, :__debug_line, :__debug_binding, :__debug_context
end
class Module
#
# Wraps the +meth+ method with Debugger.start {...} block.
#
def debug_method(meth)
old_meth = "__debugee_#{meth}"
old_meth = "#{$1}_set" if old_meth =~ /^(.+)=$/
alias_method old_meth.to_sym, meth
class_eval <<-EOD
def #{meth}(*args, &block)
Debugger.start do
debugger 2
#{old_meth}(*args, &block)
end
end
EOD
end
#
# Wraps the +meth+ method with Debugger.post_mortem {...} block.
#
def post_mortem_method(meth)
old_meth = "__postmortem_#{meth}"
old_meth = "#{$1}_set" if old_meth =~ /^(.+)=$/
alias_method old_meth.to_sym, meth
class_eval <<-EOD
def #{meth}(*args, &block)
Debugger.start do |dbg|
dbg.post_mortem do
#{old_meth}(*args, &block)
end
end
end
EOD
end
end