Rubykaigi 2010 present part1 - rocky/rb-threadframe GitHub Wiki
Here I will demonstrate some patches to Ruby 1.9 that allow better tracing, profiling, debugging, and call frame introspection. I use these in a new Ruby debugger that I am working on.
But before showing the debugger, first let me demonstrate the call frame object.
I go into irb, and let me find something interesting from the current call stack using the caller() method
$ irb
>>> puts caller(0)[0..3]
(irb):1:in `irb_binding'
/usr/local/lib/ruby/1.9.1/irb/workspace.rb:80:in `eval'
/usr/local/lib/ruby/1.9.1/irb/workspace.rb:80:in `evaluate'
/usr/local/lib/ruby/1.9.1/irb/context.rb:254:in `evaluate'
=> nil
I want the 3rd item down. Let me pick that up with the C extension that I have been working on.
>> require 'thread_frame'
=> true
>> tf = RubyVM::ThreadFrame.current.prev(3)
RubyVM::ThreadFrame.current is a singleton method that gives me the current stack frame, and prev(3) goes back 3 entries from that. From this object, I can get some of the same information shown previously by the caller() method:
>> puts %w(arity type source_location source_container).map{|f| tf.send(f).inspect}
2
"METHOD"
[254]
["file", "/usr/local/lib/ruby/1.9.1/irb/context.rb"]
=> nil
>>
But note I have access some other information. I can get like arity(): 2 here, or the type of the frame: “METHOD” here.
Also note the “file” in the source container. This is in contrast to “string” which would occur in a string evaluation. We will see that later.
The container type could also be a member of a package archive. Or perhaps the method was created from some sort of S-expression. That would be indicated instead of “file”.
Here are all the methods of the frame:
>> ps tf.methods
ps tf.methods
! instance_eval public_methods
!= instance_exec public_send
!~ instance_of? respond_to?
<=> instance_variable_defined? respond_to_missing?
== instance_variable_get return_stop=
=== instance_variable_set return_stop?
=~ instance_variables self
__id__ invalid? send
__send__ is_a? singleton_class
argc iseq singleton_methods
arity kind_of? source_container
binding lfp source_location
class method sp
clone methods sp_set
define_singleton_method next sp_size
dfp nil? stack_size
display object_id taint
dup pc_offset tainted?
enum_for pc_offset= tap
eql? pretty_inspect thread
equal? pretty_print to_enum
extend pretty_print_cycle to_s
flag pretty_print_inspect trace_off=
freeze pretty_print_instance_variables trace_off?
frozen? prev trust
hash private_methods type
initialize_clone proc untaint
initialize_dup protected_methods untrust
inspect public_method untrusted?
=> nil
>>
An important method used by debuggers is binding(). Let me show that. Using the location given, let’s look in the source for something interesting. The stack showed _context.rb _at line 254
M-x switch-to-buffer context.rb
Looks like line is something I might be interested in.
>> eval('line # hi there', tf.binding)
=> "eval('line # hi there', tf.binding)\n"
>>
And I get back the line that I just typed.
I can get the instruction sequence for that frame. This frame has an instruction sequence.
>> puts tf.iseq.disasm
== disasm: <RubyVM::InstructionSequence:evaluate@/usr/local/lib/ruby/1.9.1/irb/context.rb>
local table (size: 3, argc: 2 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 3] line<Arg> [ 2] line_no<Arg>
0000 trace 8 ( 252)
0002 trace 1 ( 253)
0004 getlocal line_no
0006 setinstancevariable :@line_no, <ic:0>
0009 trace 1 ( 254)
0011 putnil
0012 getinstancevariable :@workspace, <ic:1>
0015 putself
0016 getlocal line
0018 putnil
0019 send :irb_path, 0, nil, 24, <ic:2>
0025 getlocal line_no
0027 send :evaluate, 4, nil, 0, <ic:3>
0033 send :set_last_value, 1, nil, 8, <ic:4>
0039 trace 16 ( 257)
0041 leave ( 254)
=> nil
>>
Again that’s for the source code shown previously.
And I can get the program-counter offset:
>> puts tf.pc_offset
=> 33
>>
So that would put me at the send before trace 16.