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.

⚠️ **GitHub.com Fallback** ⚠️