-
Notifications
You must be signed in to change notification settings - Fork 4
Cool things
See also:
- Method returns: stopping, setting, and showing the return value
- Step until and Step to
- Debug from irb
- Tracing Events: What Did I Miss?
- Nested Debugging
- Debugger Aliases and Macros
##Stopping on an exception before it gets raised
Ruby provides for a "raise" event and trepan can stop on that kind of event. (set events
determines which events to stop at). To force an exception to be raised in the program use the debugger "raise" command.
trepan will also show you the exception object that will passed back on a raise event. No, I don't have a way to clear the exception. For that, come back later.
trepan /tmp/ex1.rb
-- (/tmp/ex1.rb:1)
1/0
(trepan): step! # step until the next "exception" event
#<ZeroDivisionError: divided by 0>
!! (/tmp/ex1.rb:1)
1/0
(trepan): where
--> #0 CFUNC Fixnum#/(0) in file /tmp/ex1.rb at line 1
#1 TOP Object#<top /tmp/ex1.rb> in file /tmp/ex1.rb at line 1
(trepan):
In fact if you step along at a low-enough level of granularity, you can even see the exception object as it gets created:
trepan /tmp/ex1.rb
-- (/tmp/ex1.rb:1)
1/0
(trepan): set trace on
trace is on.
(trepan): step until false
CFUNC Fixnum#/(0)
C> (/tmp/ex1.rb:1)
1/0
CFUNC Class#new(#<RubyVM::...)
C> (/tmp/ex1.rb:1)
1/0
CFUNC ZeroDivisionError#initialize(#<RubyVM::...)
C> (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
1/0
CFUNC ZeroDivisionError#exception()
C> (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
1/0
CFUNC ZeroDivisionError#backtrace()
C> (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
1/0
CFUNC ZeroDivisionError#set_backtrace(#<RubyVM::...)
C> (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
1/0
#<ZeroDivisionError: divided by 0>
!! (/tmp/ex1.rb:1)
1/0
<C (/tmp/ex1.rb:1)
Above, trace on
shows events without stopping. "step until false" is just one way of making sure the debugger sees events. "finish" would also work. But "continue" causes the debugger to get out of the way and full-speed execution to continue until there is a previously-set VM breakpoint.
ruby-debug can also stop at an exception event, but the exception has to be mentioned beforehand using the "catch" command. In trepan all exceptions are handled by default. To disable, this use "set events" and omit the "raise" event.
##internal prelude (via an implicit set substitute file)
In Ruby, "require_relative" is a method which is defined in a C string which is essentially passed to "eval". In the debugger though, we put the lines of that string into a file, and then tell the debugger to use that whenever it wants to list the source for that method.
$ trepan /tmp/rr.rb
-- (/tmp/rr.rb:1)
require_relative 'ev'
(trepan): step
-> (<internal:prelude>:28 remapped /src/external-vcs/trepan/data/prelude.rb:2)
def require_relative(relative_feature)
(trepan): list
23 end
24 end
25
26 module Kernel
27 module_function
28 -> def require_relative(relative_feature)
29 c = caller.first
30 e = c.rindex(/:\d+:in /)
31 file = $`
32 if /\A\((.*)\)/ =~ file # eval, etc.
(trepan):
##set string substitute
More generally, if you have a variable that gets eval'd, there is a way to tell the debugger to create a file from that string and use this temporary file for reference for listing:
$ trepan /tmp/ev.rb
-- (/tmp/ev.rb:1)
x = '
(trepan): list
1 -> x = '
2 class Foo
3 def bar
4 5
5 end
6 end
7 '
8 eval(x)
9 f = Foo.new
10 f.bar
(trepan): c 10
xx (/tmp/ev.rb:10)
f.bar
(trepan): set substitute string (eval) x
(trepan): s
-- (/tmp/ev.rb:10)
f.bar
(trepan): s
.. (/tmp/(eval)-x-20091023-7525-tqcd7l.rb:3)
def bar
(trepan): list
1
2 class Foo
3 def bar
4 5
5 end
6 end
(trepan): where
--> #0 METHOD Foo#bar() in string (eval)
#1 TOP Object#<top /tmp/ev.rb> file /tmp/ev.rb at line 10
Note: there may ultimately be lots of strings called "(eval)" so distinguishing between them is a problem. Eventually we will address this..
The instruction sequence name "<top /tmp/ev.rb>" is due to a patch to Ruby 1.9. In an unpatched Ruby 1.9 the name would be the unhelpful and cryptic "<top (required)>". Adding the file name in the instruction sequence name also allows us to disambiguate between several source files compiled would would otherwise have the same name.
In cases where the debugger the evaluated string is still on the call stack, the debugger will detect that and automatically do a string-to-file substition:
$ trepan /tmp/ev.rb
-- (/tmp/ev.rb:1)
x = '
(trepan): step to eval
CFUNC Kernel#eval("\nclass "...)
C> (/tmp/ev.rb:8)
eval(x)
(trepan): s+
-- (/tmp/eval-20100331-17221-12r49n9.rb:2) # sees eval string on call stack here.
class Foo
(trepan): list
1
2 class Foo
3 def bar
4 5
5 end
6 end
(trepan):
[vm]: ##VM hacking
You can use the debugger to understand more at the VM-level what's going on. The need for this is probably not of interest to most Ruby programmers. However if you are interested in hacking the VM say to find a bug in code generation, understand the performance issues, or figure out how to improve generated code, the debugger may be able to help.
When a breakpoint is set, we report exactly where in the VM instruction sequence you are stopping at:
$ trepan /tmp/gcd.rb 3 5
-- (/tmp/gcd.rb:4)
def gcd(a, b)
(trepan): list
1 #!/usr/bin/env ruby
2
3 # GCD. We assume positive numbers
4 -> def gcd(a, b)
5 # Make: a <= b
6 if a > b
7 a, b = [b, a]
8 end
9
10 return nil if a <= 0
(trepan): b 6
Breakpoint 1 set in file /tmp/gcd.rb,
VM offset 4 of instruction sequence gcd.
(trepan): c
xx (/tmp/gcd.rb:6)
if a > b
(trepan): s+
-- (/tmp/gcd.rb:6)
if a > b
(trepan): s+
.. (/tmp/gcd.rb:6)
if a > b
(trepan): disassemble
== disasm: <RubyVM::InstructionSequence:gcd@/tmp/gcd.rb>
local table (size: 3, argc: 2 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 3] a<Arg> [ 2] b<Arg>
0000 trace 8 ( 4)
0002 trace 1 ( 6)
B 0004 getlocal a
0006 getlocal b
--> 0008 opt_gt <ic>
0010 branchunless 22
0012 trace 1 ( 7)
0014 getlocal b
0016 getlocal a
0018 setlocal b
0020 setlocal a
0022 trace 1 ( 10)
0024 getlocal a
0026 putobject 0
0028 opt_le <ic>
0030 branchunless 38
0032 jump 34
0034 putnil
0035 trace 16
0037 leave
0038 trace 1 ( 12)
0040 getlocal a
0042 putobject 1
0044 opt_eq <ic>
0046 branchif 60
0048 getlocal b
0050 getlocal a
0052 opt_minus <ic>
0054 putobject 0
0056 opt_eq <ic>
...
One can inspect VM stack locations and registers:
(trepan): info reg sp 1
VM sp(1) = 5
(trepan): info reg sp 2
VM sp(2) = 3
(trepan): info reg lfp 0
VM lfp(0) = 3
local_name(0)=a
(trepan): info reg lfp 1
VM lfp(1) = 5
local_name(0)=b
(trepan): info reg pc
VM pc = 8
Finally, we allow you to specify a VM instruction to stop at. Below we will "continue" to offset 52. Offset are represented by prefacing a number with 'o';
(trepan): c o52
xx (/tmp/gcd.rb:12)
if a == 1 or b-a == 0
(trepan): info reg pc
VM pc = 52
(trepan):
##Debugger hacking
One of the sad things about working on a debugger, is that the debugger writer is often handicapped in his/her ability to use this nice tool that is being provided to everyone else.
By making the debugger modular, Ruby's dynamic behvior comes to the rescue!
Below I show fixing a bug in the "set event" command:
$ trepan /tmp/gcd.rb 3 5
-- (/tmp/gcd.rb:4)
def gcd(a, b)
(trepan): where
--> #0 TOP Object#<top /tmp/ev.rb> in file /tmp/gcd.rb at line 4
A little bit of a digression here. The above stack listing is a bit of a fake. Recall I issued trepan from the outset. The debugger is in fact hiding the upper levels from causal perusal. However if you really want the down-and-dirty details, that's available:
(trepan): set debug stack on
debugstack is on.
(trepan): where
--> #0 TOP Object#<top /tmp/ev.rb> in file /tmp/gcd.rb at line 4
#1 CFUNC Module#load() in file /src/external-vcs/trepan/lib/run.rb at line 39
#2 BLOCK Object#block in debug_program in file /src/external-vcs/trepan/lib/run.rb at line 39
#3 CFUNC Proc#call() in file /src/external-vcs/trepan/trepan.rb at line 68
...
Okay now to the little bug I had. The bug is that in the "set events" command the word "events" was treated as an event name like "c_call" rather than a word that needed to be skipped over.
(trepan): set events c_call, c_return
*** Event names unrecognized/ignored: events
Magic happens now while I fix the code in file "set_subcmd/events.rb". I then reload that file, and presto!
(trepan): load '/src/external-vcs/trepan/processor/command/set_subcmd/events.rb'
(trepan): set event c_call, c_return
Trace events we may stop on:
c_call, c_return
Finally, there is complete access to the debugger innerds. The "-d" option on irb has a global variable $trepan_frame set to a RubyVM::ThreadFrame for the point where the debugged program has stopped.
(trepan): irb -d
You are in a trepan session. You should have access to program scope.
'dbgr', 'step', 'n', 'q', 'cont' commands have been added.
You should have access to debugger state via global variable $trepan
You should have access to the program frame via global variable $trepan_frame
You should have access to the command processor via global variable $trepan_cmdproc
trepan >> $trepan.settings
=> {:cmdproc_opts=>{}, :core_opts=>{}, :restart_argv=>["/usr/local/bin/ruby", "/src/external-vcs/trepan/bin/trepan", "./tmp/gcd.rb", "3", "5"]}
trepan >> $trepan.core.settings
=> {:cmdproc_opts=>{}, :hook_name=>:event_processor, :step_count=>0, :async_events=>196736, :step_events=>927}
trepan >> $trepan.core.processor.settings
=> {:autoeval=>true, :autoirb=>false, :autolist=>false, :basename=>false, :different=>true, :debugexcept=>true, :debugskip=>false, :debugstack=>true, :listsize=>10, :maxstring=>150, :prompt=>"(trepan): ", :width=>80}
trepan >> fr = $trepan.core.processor.frame
=> #<RubyVM::ThreadFrame:0x00000001e008e8>