Menu

Call Frame

Rocky Bernstein

Note: we describe the Frame object for used in 2.1.5; this is a little different form object in 1.9.3.

A big change I've made, is access to the call stack, or call frame. This is done via the object RubyVM::Frame.

Information here overlaps with Thread::Backtrace::Location, but frame objects can access dynamic information. Thread::Backtrace::Location object is confined to static information around a backtrace at a given point of time.

For example, one method on the frame object is binding() which returns a Binding object. Over the course of execution, values available to the frame may change and new objects may get added; the binding() method will track those changes, and can all you to set variables in the context of the frame as well.

Since a frame tracks dynamic call-stack information, it is meaningful only while that frame is active. I have put in checks in methods using frames to test that the frame is still valid. However these checks aren't foolprool. You shouldn't save frames in variables which have a lifetime longer than the lifetime of the frame it refers to. If you do, either the check will raise an error that the frame is invalid, or you will get garbage information back, or possibly a system crash.

Frame Singleton Methods

stack_size()

Get the frame stack size for the current thread.

get() -> RubyVM::Frame object

RubyVM::Frame::get(thread)     -> RubyVM::Frame object
RubyVM::Frame::get(thread, n)  -> RubyVM::Frame object
RubyVM::Frame::get             -> RubyVM::Frame object
RubyVM::Frame::get(n)          -> RubyVM::Frame object



In the first form, get(thread), we return the current RubyVM::Frame
for the Thread object
passed. This is the current frame, Thread::current.

In the second form, get(thread, n) we try to go back that Fixnum n frames.

In the the third form, get(), the current thread is assumed, and like the
first form, but we use get the current frame.

The fourth form, get(n), like the third form, we assume the current
thread. And like the first form we go back we try to back a
Fixnum n, number of frame.

When count n is given, 1 is synonymous with the previous frame. If n is negative, we count from the bottom of the frame stack.

In all cases we return a RubyVM::Frame or nil if we can't go back (or forward for a negative n) that many frames.

FrameError can be raised if the frame object is no longer valid.

All of the following are equivalent

RubyVM::Frame::get(Thread::current) #     -> RubyVM::Frame object
RubyVM::Frame::get(Thread::current, 0)a
RubyVM::Frame::get
RubyVM::Frame::get(0)


Frame Instrance Methods

#argc() -> Fixnum

Returns the number of arguments that were actually passed in the call to this frame. This often the same value as arity(), but differs when arity can take optional or "splat"ted parameters.

FrameError can be raised if the frame object is no longer valid.

irb$ proc { || RubyVM::Frame::get.argc }.call
=> 0
irb$ proc { |a| RubyVM::Frame::get.argc }.call
=> 1
irb$ proc { |a,*b| RubyVM::Frame::get.argc }.call
=> 1
irb$ proc { |a,b| RubyVM::Frame::get.argc }.call
=> 2


#arity() -> Fixnum

Returns the number of arguments that would not be ignored. See Ruby 2.1 proc#arity

FrameError can be raised if the frame object is no longer valid.

irb$ proc { || RubyVM::Frame::get.arity }.call
=> 0
irb$ proc { |a| RubyVM::Frame::get.arity }.call
=> 1
irb$ proc { |a,*b| RubyVM::Frame::get.arity }.call
=> -2
irb$ proc { |a,b| RubyVM::Frame::get.argc }.call
=> 2


#binding() -> Binding

Returns a Binding object for
the frame.

FrameError can be raised if the frame object is no longer valid.

irb$ proc { | a=1 | eval("a+2", RubyVM::Frame::get.binding ) }.call
=> 3


#getlocal(index) => Object

Returns the local variable at the given stack index. The values of local variables in the Ruby MRI indexed by number. This method retrieves the value of a local variable by number.

FrameError can be raised if the frame object is no longer valid.

irb$ proc { || a = 'ab'; RubyVM::Frame::get.getlocal(2)  }.call
=> "ab"


#iseq()

Returns an Ruby VM instruction sequence object create from the Frame object or nil if there is none. Note that frames associated with calls to C functions do not Ruby VM instructions sequences.

FrameError can be raised if the frame object is no longer valid.

irb$ puts proc { || a = 'ab'; RubyVM::Frame::get.iseq.disassemble }.call
<{ || a = 'ab'; RubyVM::Frame::get.iseq.disassemble  }.call
== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: redo   st: 0002 ed: 0025 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0025 sp: 0000 cont: 0025
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] a
0000 trace            4096 (   1)
0002 trace            1
0004 putstring        "ab"
0006 setlocal_OP__WC__0 2
0008 trace            1
0010 getinlinecache   19, <is:0>
0013 getconstant      :RubyVM
0015 getconstant      :Frame
0017 setinlinecache   <is:0>
0019 opt_send_simple  <callinfo!mid:get, argc:0, ARGS_SKIP>
0021 opt_send_simple  <callinfo!mid:iseq, argc:0, ARGS_SKIP>
0023 opt_send_simple  <callinfo!mid:disassemble, argc:0, ARGS_SKIP>
0025 trace            512
0027 leave


#klass()

Returns the class (if any) associated with the frame.

FrameError can be raised if the frame object is no longer valid.


#label() -> String or nil

Returns the name of the frame.

FrameError can be raised if the frame object is no longer valid.

irb$ proc { || a = 'ab'; RubyVM::Frame::get.label  }.call()
=> "block in irb_binding"
irb(main):005:0> def foo; RubyVM::Frame::get.label ; end; foo
=> "foo"


#method() -> String or nil

Returns the method associated with the frame or nil of none.

FrameError can be raised if the frame object is no longer valid.


#pc_offset() -> Fixnum

Returns the offset inside the instruction sequence or -1 if invalid. Note that C frames do not have VM instruction sequences associated with them.

FrameError can be raised if the frame object is no longer valid.

 proc { || f = RubyVM::Frame::get; [f.pc_offset, f.pc_offset] }.call
 => [23, 27]


#pc_offset=(Fixnum)

Sets Ruby VM PC to the offset given. This is pretty dangerous.
It is the way a debugger can implement skipping over statements.

You need to make sure set this to a valid instruction offset since
little checking is done.

Note that C frames do not have VM instruction sequences
associated with them.

FrameError can be raised if the frame object is no longer valid.


#prev(n=1) -> Frame

Returns a RubyVM::Frame object for the frame prior to the Frame object or +nil+ if there is none. Setting n to 0 just returns the object passed. A negative number starts from the end. So prev(-1) is the top frame. Counts outside of the range of [-stack_size* .. stack_size-1] exceed the the range of the stack and return nil See also get()

FrameError can be raised if the frame object is no longer valid.


#setlocal

Sets the local variable at the given stack index. The values of local variables in the Ruby MRI indexed by number.
FrameError can be raised if the frame object is no longer valid.


#source_container() -> [String, Object, [,String]]

Returns a tuple representing

  • a kind of container
  • a name of thing inside the container
  • an optional absolute path if the container is a file.

A container represents where where the code that is getting run comes from. Most Ruby programs of course come from reading in a Ruby source-code file. In that case, the container would be "file", and the second entry in the tuple is the name of a file. The 3rd and last parameter here is the absolute name of the file. Note that even though the type may be "file" and the second parameter is a file name, sometimes it is not Ruby source code. The trepanning debugger performs and additional test to make sure a filename given pack is a Ruby file.

However somtimes, code comes from eval'ing a string. In that container is "string". If that's the case, the value of the string is probably somewhere on the call stack. The trepanning debugger picks this value out from the stack. What is stored as the filename may be somewhat arbitrary.

Finally, it could be the case that the code running comes from some Dynamically linked code. Here, the container type is "binary". Here the second parameter is the machine address, and one can use this inside say gdb to get the function name.

FrameError can be raised if the frame object is no longer valid.


#source_location() -> Array

Returns an array of source location positions that match
tf.instruction_offset. A source location here is a line number, a Fixnum. In the future we may want to change that to a pair of line and column numbers or byte offset in the file, or interval of start end locations.

But why do we return an array? Underneath you are stopped at an instruction. In the presence of compiler optimization and common-subexpression elimination, an instruction may map to several different positions in the Ruby source code. In the MRI VM specifically, I don't believe this can happen. But it doesn't hurt to handle the general case.

FrameError can be raised if the frame object is no longer valid.

proc { || f = RubyVM::Frame::get.source_location }.call
=> [1]


#sp()

RubyVM::Frame#sp(n)  -> Object

The MRI VM stores interediate values on a stack; sp() will retrieve the object given position n; 0 is the top object, 1 the next-to-top object and so on.

FrameError can be raised if the frame object is no longer valid.

irb $ proc { || 10 + RubyVM::Frame::get.sp(1)  }.call()
=> 20


#sp_set(n, new_value) -> Object

Sets VM stack position nto new_value. The top object is position 0. 1 is the next object.

FrameError can be raised if the frame object is no longer valid.


#sp_size() -> Fixnum

Returns the number of stack or sp entries in the current frame. This is the number values that have been pushed onto the stack since the current call. This is different thanRubyVM::Frame#stack_size() which counts the number of frames in the call stack. nil is returned if there is an error.

FrameError can be raised if the frame object is no longer valid.

#step_out()

Assists in a debugger implementing a "step out" command by turning off any tracing for this call frame and any call frames that are called from this frame.

FrameError can be raised if the frame object is no longer valid.

#step_over()

Assists in a debugger implementing a "step oover" command by turning off any tracing for ny call frames that are called from this frame.

FrameError can be raised if the frame object is no longer valid.

#thread()

Returns the Thread object for the call frame.

FrameError can be raised if the frame object is no longer valid.

#valid? -> boolean

Returns true if the frame is no longer valid. On the other hand, since the test we use is weak, returning false might not mean the frame is valid, just that we can't disprove that it is not invalid.

You shouldn't save frames in variables which have a lifetime longer than the lifetime of the frame it refers to. If you do, either the check will raise an error that the frame is invalid, or you will get garbage information back, or possibly a system crash.


Related

Wiki: Home

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.