mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
1819652578
* Fix debug documents to match Thread#to_s change (Feature #16412 ticket) * TracePoint#inspect returns "... file:line" (Feature #16513) * Guard older version of Ruby in Tracepoint inspection tests * Focus on current thread only when running TracePoint inspection test
1106 lines
30 KiB
Ruby
1106 lines
30 KiB
Ruby
# frozen_string_literal: true
|
|
# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
|
|
# Copyright (C) 2000 Information-technology Promotion Agency, Japan
|
|
# Copyright (C) 2000-2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org>
|
|
|
|
require 'continuation'
|
|
|
|
require 'tracer'
|
|
require 'pp'
|
|
|
|
class Tracer # :nodoc:
|
|
def Tracer.trace_func(*vars)
|
|
Single.trace_func(*vars)
|
|
end
|
|
end
|
|
|
|
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ # :nodoc:
|
|
|
|
##
|
|
# This library provides debugging functionality to Ruby.
|
|
#
|
|
# To add a debugger to your code, start by requiring +debug+ in your
|
|
# program:
|
|
#
|
|
# def say(word)
|
|
# require 'debug'
|
|
# puts word
|
|
# end
|
|
#
|
|
# This will cause Ruby to interrupt execution and show a prompt when the +say+
|
|
# method is run.
|
|
#
|
|
# Once you're inside the prompt, you can start debugging your program.
|
|
#
|
|
# (rdb:1) p word
|
|
# "hello"
|
|
#
|
|
# == Getting help
|
|
#
|
|
# You can get help at any time by pressing +h+.
|
|
#
|
|
# (rdb:1) h
|
|
# Debugger help v.-0.002b
|
|
# Commands
|
|
# b[reak] [file:|class:]<line|method>
|
|
# b[reak] [class.]<line|method>
|
|
# set breakpoint to some position
|
|
# wat[ch] <expression> set watchpoint to some expression
|
|
# cat[ch] (<exception>|off) set catchpoint to an exception
|
|
# b[reak] list breakpoints
|
|
# cat[ch] show catchpoint
|
|
# del[ete][ nnn] delete some or all breakpoints
|
|
# disp[lay] <expression> add expression into display expression list
|
|
# undisp[lay][ nnn] delete one particular or all display expressions
|
|
# c[ont] run until program ends or hit breakpoint
|
|
# s[tep][ nnn] step (into methods) one line or till line nnn
|
|
# n[ext][ nnn] go over one line or till line nnn
|
|
# w[here] display frames
|
|
# f[rame] alias for where
|
|
# l[ist][ (-|nn-mm)] list program, - lists backwards
|
|
# nn-mm lists given lines
|
|
# up[ nn] move to higher frame
|
|
# down[ nn] move to lower frame
|
|
# fin[ish] return to outer frame
|
|
# tr[ace] (on|off) set trace mode of current thread
|
|
# tr[ace] (on|off) all set trace mode of all threads
|
|
# q[uit] exit from debugger
|
|
# v[ar] g[lobal] show global variables
|
|
# v[ar] l[ocal] show local variables
|
|
# v[ar] i[nstance] <object> show instance variables of object
|
|
# v[ar] c[onst] <object> show constants of object
|
|
# m[ethod] i[nstance] <obj> show methods of object
|
|
# m[ethod] <class|module> show instance methods of class or module
|
|
# th[read] l[ist] list all threads
|
|
# th[read] c[ur[rent]] show current thread
|
|
# th[read] [sw[itch]] <nnn> switch thread context to nnn
|
|
# th[read] stop <nnn> stop thread nnn
|
|
# th[read] resume <nnn> resume thread nnn
|
|
# p expression evaluate expression and print its value
|
|
# h[elp] print this help
|
|
# <everything else> evaluate
|
|
#
|
|
# == Usage
|
|
#
|
|
# The following is a list of common functionalities that the debugger
|
|
# provides.
|
|
#
|
|
# === Navigating through your code
|
|
#
|
|
# In general, a debugger is used to find bugs in your program, which
|
|
# often means pausing execution and inspecting variables at some point
|
|
# in time.
|
|
#
|
|
# Let's look at an example:
|
|
#
|
|
# def my_method(foo)
|
|
# require 'debug'
|
|
# foo = get_foo if foo.nil?
|
|
# raise if foo.nil?
|
|
# end
|
|
#
|
|
# When you run this program, the debugger will kick in just before the
|
|
# +foo+ assignment.
|
|
#
|
|
# (rdb:1) p foo
|
|
# nil
|
|
#
|
|
# In this example, it'd be interesting to move to the next line and
|
|
# inspect the value of +foo+ again. You can do that by pressing +n+:
|
|
#
|
|
# (rdb:1) n # goes to next line
|
|
# (rdb:1) p foo
|
|
# nil
|
|
#
|
|
# You now know that the original value of +foo+ was nil, and that it
|
|
# still was nil after calling +get_foo+.
|
|
#
|
|
# Other useful commands for navigating through your code are:
|
|
#
|
|
# +c+::
|
|
# Runs the program until it either exists or encounters another breakpoint.
|
|
# You usually press +c+ when you are finished debugging your program and
|
|
# want to resume its execution.
|
|
# +s+::
|
|
# Steps into method definition. In the previous example, +s+ would take you
|
|
# inside the method definition of +get_foo+.
|
|
# +r+::
|
|
# Restart the program.
|
|
# +q+::
|
|
# Quit the program.
|
|
#
|
|
# === Inspecting variables
|
|
#
|
|
# You can use the debugger to easily inspect both local and global variables.
|
|
# We've seen how to inspect local variables before:
|
|
#
|
|
# (rdb:1) p my_arg
|
|
# 42
|
|
#
|
|
# You can also pretty print the result of variables or expressions:
|
|
#
|
|
# (rdb:1) pp %w{a very long long array containing many words}
|
|
# ["a",
|
|
# "very",
|
|
# "long",
|
|
# ...
|
|
# ]
|
|
#
|
|
# You can list all local variables with +v l+:
|
|
#
|
|
# (rdb:1) v l
|
|
# foo => "hello"
|
|
#
|
|
# Similarly, you can show all global variables with +v g+:
|
|
#
|
|
# (rdb:1) v g
|
|
# all global variables
|
|
#
|
|
# Finally, you can omit +p+ if you simply want to evaluate a variable or
|
|
# expression
|
|
#
|
|
# (rdb:1) 5**2
|
|
# 25
|
|
#
|
|
# === Going beyond basics
|
|
#
|
|
# Ruby Debug provides more advanced functionalities like switching
|
|
# between threads, setting breakpoints and watch expressions, and more.
|
|
# The full list of commands is available at any time by pressing +h+.
|
|
#
|
|
# == Staying out of trouble
|
|
#
|
|
# Make sure you remove every instance of +require 'debug'+ before
|
|
# shipping your code. Failing to do so may result in your program
|
|
# hanging unpredictably.
|
|
#
|
|
# Debug is not available in safe mode.
|
|
|
|
class DEBUGGER__
|
|
MUTEX = Thread::Mutex.new # :nodoc:
|
|
|
|
class Context # :nodoc:
|
|
DEBUG_LAST_CMD = []
|
|
|
|
begin
|
|
require 'readline'
|
|
def readline(prompt, hist)
|
|
Readline::readline(prompt, hist)
|
|
end
|
|
rescue LoadError
|
|
def readline(prompt, hist)
|
|
STDOUT.print prompt
|
|
STDOUT.flush
|
|
line = STDIN.gets
|
|
exit unless line
|
|
line.chomp!
|
|
line
|
|
end
|
|
USE_READLINE = false
|
|
end
|
|
|
|
def initialize
|
|
if Thread.current == Thread.main
|
|
@stop_next = 1
|
|
else
|
|
@stop_next = 0
|
|
end
|
|
@last_file = nil
|
|
@file = nil
|
|
@line = nil
|
|
@no_step = nil
|
|
@frames = []
|
|
@finish_pos = 0
|
|
@trace = false
|
|
@catch = "StandardError"
|
|
@suspend_next = false
|
|
end
|
|
|
|
def stop_next(n=1)
|
|
@stop_next = n
|
|
end
|
|
|
|
def set_suspend
|
|
@suspend_next = true
|
|
end
|
|
|
|
def clear_suspend
|
|
@suspend_next = false
|
|
end
|
|
|
|
def suspend_all
|
|
DEBUGGER__.suspend
|
|
end
|
|
|
|
def resume_all
|
|
DEBUGGER__.resume
|
|
end
|
|
|
|
def check_suspend
|
|
while MUTEX.synchronize {
|
|
if @suspend_next
|
|
DEBUGGER__.waiting.push Thread.current
|
|
@suspend_next = false
|
|
true
|
|
end
|
|
}
|
|
end
|
|
end
|
|
|
|
def trace?
|
|
@trace
|
|
end
|
|
|
|
def set_trace(arg)
|
|
@trace = arg
|
|
end
|
|
|
|
def stdout
|
|
DEBUGGER__.stdout
|
|
end
|
|
|
|
def break_points
|
|
DEBUGGER__.break_points
|
|
end
|
|
|
|
def display
|
|
DEBUGGER__.display
|
|
end
|
|
|
|
def context(th)
|
|
DEBUGGER__.context(th)
|
|
end
|
|
|
|
def set_trace_all(arg)
|
|
DEBUGGER__.set_trace(arg)
|
|
end
|
|
|
|
def set_last_thread(th)
|
|
DEBUGGER__.set_last_thread(th)
|
|
end
|
|
|
|
def debug_eval(str, binding)
|
|
begin
|
|
eval(str, binding)
|
|
rescue StandardError, ScriptError => e
|
|
at = eval("caller(1)", binding)
|
|
stdout.printf "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '')
|
|
for i in at
|
|
stdout.printf "\tfrom %s\n", i
|
|
end
|
|
throw :debug_error
|
|
end
|
|
end
|
|
|
|
def debug_silent_eval(str, binding)
|
|
begin
|
|
eval(str, binding)
|
|
rescue StandardError, ScriptError
|
|
nil
|
|
end
|
|
end
|
|
|
|
def var_list(ary, binding)
|
|
ary.sort!
|
|
for v in ary
|
|
stdout.printf " %s => %s\n", v, eval(v.to_s, binding).inspect
|
|
end
|
|
end
|
|
|
|
def debug_variable_info(input, binding)
|
|
case input
|
|
when /^\s*g(?:lobal)?\s*$/
|
|
var_list(global_variables, binding)
|
|
|
|
when /^\s*l(?:ocal)?\s*$/
|
|
var_list(eval("local_variables", binding), binding)
|
|
|
|
when /^\s*i(?:nstance)?\s+/
|
|
obj = debug_eval($', binding)
|
|
var_list(obj.instance_variables, obj.instance_eval{binding()})
|
|
|
|
when /^\s*c(?:onst(?:ant)?)?\s+/
|
|
obj = debug_eval($', binding)
|
|
unless obj.kind_of? Module
|
|
stdout.print "Should be Class/Module: ", $', "\n"
|
|
else
|
|
var_list(obj.constants, obj.module_eval{binding()})
|
|
end
|
|
end
|
|
end
|
|
|
|
def debug_method_info(input, binding)
|
|
case input
|
|
when /^i(:?nstance)?\s+/
|
|
obj = debug_eval($', binding)
|
|
|
|
len = 0
|
|
for v in obj.methods.sort
|
|
len += v.size + 1
|
|
if len > 70
|
|
len = v.size + 1
|
|
stdout.print "\n"
|
|
end
|
|
stdout.print v, " "
|
|
end
|
|
stdout.print "\n"
|
|
|
|
else
|
|
obj = debug_eval(input, binding)
|
|
unless obj.kind_of? Module
|
|
stdout.print "Should be Class/Module: ", input, "\n"
|
|
else
|
|
len = 0
|
|
for v in obj.instance_methods(false).sort
|
|
len += v.size + 1
|
|
if len > 70
|
|
len = v.size + 1
|
|
stdout.print "\n"
|
|
end
|
|
stdout.print v, " "
|
|
end
|
|
stdout.print "\n"
|
|
end
|
|
end
|
|
end
|
|
|
|
def thnum
|
|
num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
|
|
unless num
|
|
DEBUGGER__.make_thread_list
|
|
num = DEBUGGER__.instance_eval{@thread_list[Thread.current]}
|
|
end
|
|
num
|
|
end
|
|
|
|
def debug_command(file, line, id, binding)
|
|
MUTEX.lock
|
|
unless defined?($debugger_restart) and $debugger_restart
|
|
callcc{|c| $debugger_restart = c}
|
|
end
|
|
set_last_thread(Thread.current)
|
|
frame_pos = 0
|
|
binding_file = file
|
|
binding_line = line
|
|
previous_line = nil
|
|
if ENV['EMACS']
|
|
stdout.printf "\032\032%s:%d:\n", binding_file, binding_line
|
|
else
|
|
stdout.printf "%s:%d:%s", binding_file, binding_line,
|
|
line_at(binding_file, binding_line)
|
|
end
|
|
@frames[0] = [binding, file, line, id]
|
|
display_expressions(binding)
|
|
prompt = true
|
|
while prompt and input = readline("(rdb:%d) "%thnum(), true)
|
|
catch(:debug_error) do
|
|
if input == ""
|
|
next unless DEBUG_LAST_CMD[0]
|
|
input = DEBUG_LAST_CMD[0]
|
|
stdout.print input, "\n"
|
|
else
|
|
DEBUG_LAST_CMD[0] = input
|
|
end
|
|
|
|
case input
|
|
when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/
|
|
if defined?( $2 )
|
|
if $1 == 'on'
|
|
set_trace_all true
|
|
else
|
|
set_trace_all false
|
|
end
|
|
elsif defined?( $1 )
|
|
if $1 == 'on'
|
|
set_trace true
|
|
else
|
|
set_trace false
|
|
end
|
|
end
|
|
if trace?
|
|
stdout.print "Trace on.\n"
|
|
else
|
|
stdout.print "Trace off.\n"
|
|
end
|
|
|
|
when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/
|
|
pos = $2
|
|
if $1
|
|
klass = debug_silent_eval($1, binding)
|
|
file = File.expand_path($1)
|
|
end
|
|
if pos =~ /^\d+$/
|
|
pname = pos
|
|
pos = pos.to_i
|
|
else
|
|
pname = pos = pos.intern.id2name
|
|
end
|
|
break_points.push [true, 0, klass || file, pos]
|
|
stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, klass || file, pname
|
|
|
|
when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/
|
|
pos = $2.intern.id2name
|
|
klass = debug_eval($1, binding)
|
|
break_points.push [true, 0, klass, pos]
|
|
stdout.printf "Set breakpoint %d at %s.%s\n", break_points.size, klass, pos
|
|
|
|
when /^\s*wat(?:ch)?\s+(.+)$/
|
|
exp = $1
|
|
break_points.push [true, 1, exp]
|
|
stdout.printf "Set watchpoint %d:%s\n", break_points.size, exp
|
|
|
|
when /^\s*b(?:reak)?$/
|
|
if break_points.find{|b| b[1] == 0}
|
|
n = 1
|
|
stdout.print "Breakpoints:\n"
|
|
break_points.each do |b|
|
|
if b[0] and b[1] == 0
|
|
stdout.printf " %d %s:%s\n", n, b[2], b[3]
|
|
end
|
|
n += 1
|
|
end
|
|
end
|
|
if break_points.find{|b| b[1] == 1}
|
|
n = 1
|
|
stdout.print "\n"
|
|
stdout.print "Watchpoints:\n"
|
|
for b in break_points
|
|
if b[0] and b[1] == 1
|
|
stdout.printf " %d %s\n", n, b[2]
|
|
end
|
|
n += 1
|
|
end
|
|
end
|
|
if break_points.size == 0
|
|
stdout.print "No breakpoints\n"
|
|
else
|
|
stdout.print "\n"
|
|
end
|
|
|
|
when /^\s*del(?:ete)?(?:\s+(\d+))?$/
|
|
pos = $1
|
|
unless pos
|
|
input = readline("Clear all breakpoints? (y/n) ", false)
|
|
if input == "y"
|
|
for b in break_points
|
|
b[0] = false
|
|
end
|
|
end
|
|
else
|
|
pos = pos.to_i
|
|
if break_points[pos-1]
|
|
break_points[pos-1][0] = false
|
|
else
|
|
stdout.printf "Breakpoint %d is not defined\n", pos
|
|
end
|
|
end
|
|
|
|
when /^\s*disp(?:lay)?\s+(.+)$/
|
|
exp = $1
|
|
display.push [true, exp]
|
|
stdout.printf "%d: ", display.size
|
|
display_expression(exp, binding)
|
|
|
|
when /^\s*disp(?:lay)?$/
|
|
display_expressions(binding)
|
|
|
|
when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/
|
|
pos = $1
|
|
unless pos
|
|
input = readline("Clear all expressions? (y/n) ", false)
|
|
if input == "y"
|
|
for d in display
|
|
d[0] = false
|
|
end
|
|
end
|
|
else
|
|
pos = pos.to_i
|
|
if display[pos-1]
|
|
display[pos-1][0] = false
|
|
else
|
|
stdout.printf "Display expression %d is not defined\n", pos
|
|
end
|
|
end
|
|
|
|
when /^\s*c(?:ont)?$/
|
|
prompt = false
|
|
|
|
when /^\s*s(?:tep)?(?:\s+(\d+))?$/
|
|
if $1
|
|
lev = $1.to_i
|
|
else
|
|
lev = 1
|
|
end
|
|
@stop_next = lev
|
|
prompt = false
|
|
|
|
when /^\s*n(?:ext)?(?:\s+(\d+))?$/
|
|
if $1
|
|
lev = $1.to_i
|
|
else
|
|
lev = 1
|
|
end
|
|
@stop_next = lev
|
|
@no_step = @frames.size - frame_pos
|
|
prompt = false
|
|
|
|
when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/
|
|
display_frames(frame_pos)
|
|
|
|
when /^\s*l(?:ist)?(?:\s+(.+))?$/
|
|
if not $1
|
|
b = previous_line ? previous_line + 10 : binding_line - 5
|
|
e = b + 9
|
|
elsif $1 == '-'
|
|
b = previous_line ? previous_line - 10 : binding_line - 5
|
|
e = b + 9
|
|
else
|
|
b, e = $1.split(/[-,]/)
|
|
if e
|
|
b = b.to_i
|
|
e = e.to_i
|
|
else
|
|
b = b.to_i - 5
|
|
e = b + 9
|
|
end
|
|
end
|
|
previous_line = b
|
|
display_list(b, e, binding_file, binding_line)
|
|
|
|
when /^\s*up(?:\s+(\d+))?$/
|
|
previous_line = nil
|
|
if $1
|
|
lev = $1.to_i
|
|
else
|
|
lev = 1
|
|
end
|
|
frame_pos += lev
|
|
if frame_pos >= @frames.size
|
|
frame_pos = @frames.size - 1
|
|
stdout.print "At toplevel\n"
|
|
end
|
|
binding, binding_file, binding_line = @frames[frame_pos]
|
|
stdout.print format_frame(frame_pos)
|
|
|
|
when /^\s*down(?:\s+(\d+))?$/
|
|
previous_line = nil
|
|
if $1
|
|
lev = $1.to_i
|
|
else
|
|
lev = 1
|
|
end
|
|
frame_pos -= lev
|
|
if frame_pos < 0
|
|
frame_pos = 0
|
|
stdout.print "At stack bottom\n"
|
|
end
|
|
binding, binding_file, binding_line = @frames[frame_pos]
|
|
stdout.print format_frame(frame_pos)
|
|
|
|
when /^\s*fin(?:ish)?$/
|
|
if frame_pos == @frames.size
|
|
stdout.print "\"finish\" not meaningful in the outermost frame.\n"
|
|
else
|
|
@finish_pos = @frames.size - frame_pos
|
|
frame_pos = 0
|
|
prompt = false
|
|
end
|
|
|
|
when /^\s*cat(?:ch)?(?:\s+(.+))?$/
|
|
if $1
|
|
excn = $1
|
|
if excn == 'off'
|
|
@catch = nil
|
|
stdout.print "Clear catchpoint.\n"
|
|
else
|
|
@catch = excn
|
|
stdout.printf "Set catchpoint %s.\n", @catch
|
|
end
|
|
else
|
|
if @catch
|
|
stdout.printf "Catchpoint %s.\n", @catch
|
|
else
|
|
stdout.print "No catchpoint.\n"
|
|
end
|
|
end
|
|
|
|
when /^\s*q(?:uit)?$/
|
|
input = readline("Really quit? (y/n) ", false)
|
|
if input == "y"
|
|
exit! # exit -> exit!: No graceful way to stop threads...
|
|
end
|
|
|
|
when /^\s*v(?:ar)?\s+/
|
|
debug_variable_info($', binding)
|
|
|
|
when /^\s*m(?:ethod)?\s+/
|
|
debug_method_info($', binding)
|
|
|
|
when /^\s*th(?:read)?\s+/
|
|
if DEBUGGER__.debug_thread_info($', binding) == :cont
|
|
prompt = false
|
|
end
|
|
|
|
when /^\s*pp\s+/
|
|
PP.pp(debug_eval($', binding), stdout)
|
|
|
|
when /^\s*p\s+/
|
|
stdout.printf "%s\n", debug_eval($', binding).inspect
|
|
|
|
when /^\s*r(?:estart)?$/
|
|
$debugger_restart.call
|
|
|
|
when /^\s*h(?:elp)?$/
|
|
debug_print_help()
|
|
|
|
else
|
|
v = debug_eval(input, binding)
|
|
stdout.printf "%s\n", v.inspect
|
|
end
|
|
end
|
|
end
|
|
MUTEX.unlock
|
|
resume_all
|
|
end
|
|
|
|
def debug_print_help
|
|
stdout.print <<EOHELP
|
|
Debugger help v.-0.002b
|
|
Commands
|
|
b[reak] [file:|class:]<line|method>
|
|
b[reak] [class.]<line|method>
|
|
set breakpoint to some position
|
|
wat[ch] <expression> set watchpoint to some expression
|
|
cat[ch] (<exception>|off) set catchpoint to an exception
|
|
b[reak] list breakpoints
|
|
cat[ch] show catchpoint
|
|
del[ete][ nnn] delete some or all breakpoints
|
|
disp[lay] <expression> add expression into display expression list
|
|
undisp[lay][ nnn] delete one particular or all display expressions
|
|
c[ont] run until program ends or hit breakpoint
|
|
s[tep][ nnn] step (into methods) one line or till line nnn
|
|
n[ext][ nnn] go over one line or till line nnn
|
|
w[here] display frames
|
|
f[rame] alias for where
|
|
l[ist][ (-|nn-mm)] list program, - lists backwards
|
|
nn-mm lists given lines
|
|
up[ nn] move to higher frame
|
|
down[ nn] move to lower frame
|
|
fin[ish] return to outer frame
|
|
tr[ace] (on|off) set trace mode of current thread
|
|
tr[ace] (on|off) all set trace mode of all threads
|
|
q[uit] exit from debugger
|
|
v[ar] g[lobal] show global variables
|
|
v[ar] l[ocal] show local variables
|
|
v[ar] i[nstance] <object> show instance variables of object
|
|
v[ar] c[onst] <object> show constants of object
|
|
m[ethod] i[nstance] <obj> show methods of object
|
|
m[ethod] <class|module> show instance methods of class or module
|
|
th[read] l[ist] list all threads
|
|
th[read] c[ur[rent]] show current thread
|
|
th[read] [sw[itch]] <nnn> switch thread context to nnn
|
|
th[read] stop <nnn> stop thread nnn
|
|
th[read] resume <nnn> resume thread nnn
|
|
pp expression evaluate expression and pretty_print its value
|
|
p expression evaluate expression and print its value
|
|
r[estart] restart program
|
|
h[elp] print this help
|
|
<everything else> evaluate
|
|
EOHELP
|
|
end
|
|
|
|
def display_expressions(binding)
|
|
n = 1
|
|
for d in display
|
|
if d[0]
|
|
stdout.printf "%d: ", n
|
|
display_expression(d[1], binding)
|
|
end
|
|
n += 1
|
|
end
|
|
end
|
|
|
|
def display_expression(exp, binding)
|
|
stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s
|
|
end
|
|
|
|
def frame_set_pos(file, line)
|
|
if @frames[0]
|
|
@frames[0][1] = file
|
|
@frames[0][2] = line
|
|
end
|
|
end
|
|
|
|
def display_frames(pos)
|
|
0.upto(@frames.size - 1) do |n|
|
|
if n == pos
|
|
stdout.print "--> "
|
|
else
|
|
stdout.print " "
|
|
end
|
|
stdout.print format_frame(n)
|
|
end
|
|
end
|
|
|
|
def format_frame(pos)
|
|
_, file, line, id = @frames[pos]
|
|
sprintf "#%d %s:%s%s\n", pos + 1, file, line,
|
|
(id ? ":in `#{id.id2name}'" : "")
|
|
end
|
|
|
|
def script_lines(file, line)
|
|
unless (lines = SCRIPT_LINES__[file]) and lines != true
|
|
Tracer::Single.get_line(file, line) if File.exist?(file)
|
|
lines = SCRIPT_LINES__[file]
|
|
lines = nil if lines == true
|
|
end
|
|
lines
|
|
end
|
|
|
|
def display_list(b, e, file, line)
|
|
if lines = script_lines(file, line)
|
|
stdout.printf "[%d, %d] in %s\n", b, e, file
|
|
b.upto(e) do |n|
|
|
if n > 0 && lines[n-1]
|
|
if n == line
|
|
stdout.printf "=> %d %s\n", n, lines[n-1].chomp
|
|
else
|
|
stdout.printf " %d %s\n", n, lines[n-1].chomp
|
|
end
|
|
end
|
|
end
|
|
else
|
|
stdout.printf "No sourcefile available for %s\n", file
|
|
end
|
|
end
|
|
|
|
def line_at(file, line)
|
|
lines = script_lines(file, line)
|
|
if lines and line = lines[line-1]
|
|
return line
|
|
end
|
|
return "\n"
|
|
end
|
|
|
|
def debug_funcname(id)
|
|
if id.nil?
|
|
"toplevel"
|
|
else
|
|
id.id2name
|
|
end
|
|
end
|
|
|
|
def check_break_points(file, klass, pos, binding, id)
|
|
return false if break_points.empty?
|
|
n = 1
|
|
for b in break_points
|
|
if b[0] # valid
|
|
if b[1] == 0 # breakpoint
|
|
if (b[2] == file and b[3] == pos) or
|
|
(klass and b[2] == klass and b[3] == pos)
|
|
stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
|
|
return true
|
|
end
|
|
elsif b[1] == 1 # watchpoint
|
|
if debug_silent_eval(b[2], binding)
|
|
stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
n += 1
|
|
end
|
|
return false
|
|
end
|
|
|
|
def excn_handle(file, line, id, binding)
|
|
if $!.class <= SystemExit
|
|
set_trace_func nil
|
|
exit
|
|
end
|
|
|
|
if @catch and ($!.class.ancestors.find { |e| e.to_s == @catch })
|
|
stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.class
|
|
fs = @frames.size
|
|
tb = caller(0)[-fs..-1]
|
|
if tb
|
|
for i in tb
|
|
stdout.printf "\tfrom %s\n", i
|
|
end
|
|
end
|
|
suspend_all
|
|
debug_command(file, line, id, binding)
|
|
end
|
|
end
|
|
|
|
def trace_func(event, file, line, id, binding, klass)
|
|
Tracer.trace_func(event, file, line, id, binding, klass) if trace?
|
|
context(Thread.current).check_suspend
|
|
@file = file
|
|
@line = line
|
|
case event
|
|
when 'line'
|
|
frame_set_pos(file, line)
|
|
if !@no_step or @frames.size == @no_step
|
|
@stop_next -= 1
|
|
@stop_next = -1 if @stop_next < 0
|
|
elsif @frames.size < @no_step
|
|
@stop_next = 0 # break here before leaving...
|
|
else
|
|
# nothing to do. skipped.
|
|
end
|
|
if @stop_next == 0 or check_break_points(file, nil, line, binding, id)
|
|
@no_step = nil
|
|
suspend_all
|
|
debug_command(file, line, id, binding)
|
|
end
|
|
|
|
when 'call'
|
|
@frames.unshift [binding, file, line, id]
|
|
if check_break_points(file, klass, id.id2name, binding, id)
|
|
suspend_all
|
|
debug_command(file, line, id, binding)
|
|
end
|
|
|
|
when 'c-call'
|
|
frame_set_pos(file, line)
|
|
|
|
when 'class'
|
|
@frames.unshift [binding, file, line, id]
|
|
|
|
when 'return', 'end'
|
|
if @frames.size == @finish_pos
|
|
@stop_next = 1
|
|
@finish_pos = 0
|
|
end
|
|
@frames.shift
|
|
|
|
when 'raise'
|
|
excn_handle(file, line, id, binding)
|
|
|
|
end
|
|
@last_file = file
|
|
end
|
|
end
|
|
|
|
trap("INT") { DEBUGGER__.interrupt }
|
|
@last_thread = Thread::main
|
|
@max_thread = 1
|
|
@thread_list = {Thread::main => 1}
|
|
@break_points = []
|
|
@display = []
|
|
@waiting = []
|
|
@stdout = STDOUT
|
|
|
|
class << DEBUGGER__
|
|
# Returns the IO used as stdout. Defaults to STDOUT
|
|
def stdout
|
|
@stdout
|
|
end
|
|
|
|
# Sets the IO used as stdout. Defaults to STDOUT
|
|
def stdout=(s)
|
|
@stdout = s
|
|
end
|
|
|
|
# Returns the display expression list
|
|
#
|
|
# See DEBUGGER__ for more usage
|
|
def display
|
|
@display
|
|
end
|
|
|
|
# Returns the list of break points where execution will be stopped.
|
|
#
|
|
# See DEBUGGER__ for more usage
|
|
def break_points
|
|
@break_points
|
|
end
|
|
|
|
# Returns the list of waiting threads.
|
|
#
|
|
# When stepping through the traces of a function, thread gets suspended, to
|
|
# be resumed later.
|
|
def waiting
|
|
@waiting
|
|
end
|
|
|
|
def set_trace( arg )
|
|
MUTEX.synchronize do
|
|
make_thread_list
|
|
for th, in @thread_list
|
|
context(th).set_trace arg
|
|
end
|
|
end
|
|
arg
|
|
end
|
|
|
|
def set_last_thread(th)
|
|
@last_thread = th
|
|
end
|
|
|
|
def suspend
|
|
MUTEX.synchronize do
|
|
make_thread_list
|
|
for th, in @thread_list
|
|
next if th == Thread.current
|
|
context(th).set_suspend
|
|
end
|
|
end
|
|
# Schedule other threads to suspend as soon as possible.
|
|
Thread.pass
|
|
end
|
|
|
|
def resume
|
|
MUTEX.synchronize do
|
|
make_thread_list
|
|
@thread_list.each do |th,|
|
|
next if th == Thread.current
|
|
context(th).clear_suspend
|
|
end
|
|
waiting.each do |th|
|
|
th.run
|
|
end
|
|
waiting.clear
|
|
end
|
|
# Schedule other threads to restart as soon as possible.
|
|
Thread.pass
|
|
end
|
|
|
|
def context(thread=Thread.current)
|
|
c = thread[:__debugger_data__]
|
|
unless c
|
|
thread[:__debugger_data__] = c = Context.new
|
|
end
|
|
c
|
|
end
|
|
|
|
def interrupt
|
|
context(@last_thread).stop_next
|
|
end
|
|
|
|
def get_thread(num)
|
|
th = @thread_list.key(num)
|
|
unless th
|
|
@stdout.print "No thread ##{num}\n"
|
|
throw :debug_error
|
|
end
|
|
th
|
|
end
|
|
|
|
def thread_list(num)
|
|
th = get_thread(num)
|
|
if th == Thread.current
|
|
@stdout.print "+"
|
|
else
|
|
@stdout.print " "
|
|
end
|
|
@stdout.printf "%d ", num
|
|
@stdout.print th.inspect, "\t"
|
|
file = context(th).instance_eval{@file}
|
|
if file
|
|
@stdout.print file,":",context(th).instance_eval{@line}
|
|
end
|
|
@stdout.print "\n"
|
|
end
|
|
|
|
# Prints all threads in @thread_list to @stdout. Returns a sorted array of
|
|
# values from the @thread_list hash.
|
|
#
|
|
# While in the debugger you can list all of
|
|
# the threads with: <b>DEBUGGER__.thread_list_all</b>
|
|
#
|
|
# (rdb:1) DEBUGGER__.thread_list_all
|
|
# +1 #<Thread:0x007fb2320c03f0 run> debug_me.rb.rb:3
|
|
# 2 #<Thread:0x007fb23218a538 debug_me.rb.rb:3 sleep>
|
|
# 3 #<Thread:0x007fb23218b0f0 debug_me.rb.rb:3 sleep>
|
|
# [1, 2, 3]
|
|
#
|
|
# Your current thread is indicated by a <b>+</b>
|
|
#
|
|
# Additionally you can list all threads with <b>th l</b>
|
|
#
|
|
# (rdb:1) th l
|
|
# +1 #<Thread:0x007f99328c0410 run> debug_me.rb:3
|
|
# 2 #<Thread:0x007f9932938230 debug_me.rb:3 sleep> debug_me.rb:3
|
|
# 3 #<Thread:0x007f9932938e10 debug_me.rb:3 sleep> debug_me.rb:3
|
|
#
|
|
# See DEBUGGER__ for more usage.
|
|
|
|
def thread_list_all
|
|
for th in @thread_list.values.sort
|
|
thread_list(th)
|
|
end
|
|
end
|
|
|
|
def make_thread_list
|
|
hash = {}
|
|
for th in Thread::list
|
|
if @thread_list.key? th
|
|
hash[th] = @thread_list[th]
|
|
else
|
|
@max_thread += 1
|
|
hash[th] = @max_thread
|
|
end
|
|
end
|
|
@thread_list = hash
|
|
end
|
|
|
|
def debug_thread_info(input, binding)
|
|
case input
|
|
when /^l(?:ist)?/
|
|
make_thread_list
|
|
thread_list_all
|
|
|
|
when /^c(?:ur(?:rent)?)?$/
|
|
make_thread_list
|
|
thread_list(@thread_list[Thread.current])
|
|
|
|
when /^(?:sw(?:itch)?\s+)?(\d+)/
|
|
make_thread_list
|
|
th = get_thread($1.to_i)
|
|
if th == Thread.current
|
|
@stdout.print "It's the current thread.\n"
|
|
else
|
|
thread_list(@thread_list[th])
|
|
context(th).stop_next
|
|
th.run
|
|
return :cont
|
|
end
|
|
|
|
when /^stop\s+(\d+)/
|
|
make_thread_list
|
|
th = get_thread($1.to_i)
|
|
if th == Thread.current
|
|
@stdout.print "It's the current thread.\n"
|
|
elsif th.stop?
|
|
@stdout.print "Already stopped.\n"
|
|
else
|
|
thread_list(@thread_list[th])
|
|
context(th).suspend
|
|
end
|
|
|
|
when /^resume\s+(\d+)/
|
|
make_thread_list
|
|
th = get_thread($1.to_i)
|
|
if th == Thread.current
|
|
@stdout.print "It's the current thread.\n"
|
|
elsif !th.stop?
|
|
@stdout.print "Already running."
|
|
else
|
|
thread_list(@thread_list[th])
|
|
th.run
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
stdout.printf "Debug.rb\n"
|
|
stdout.printf "Emacs support available.\n\n"
|
|
RubyVM::InstructionSequence.compile_option = {
|
|
trace_instruction: true
|
|
}
|
|
set_trace_func proc { |event, file, line, id, binding, klass, *rest|
|
|
DEBUGGER__.context.trace_func event, file, line, id, binding, klass
|
|
}
|
|
end
|