You'll miss Carp in Ruby

jjore on 2009-07-11T05:08:15

In Perl, you have a nice built in library called Carp which gives you stack traces including the parameters on the stack. If you desire, you can dump the entire contents of any variable in any argument in your backtrace. If you really desire, you can modify your caller's variables.

This isn't possible in ruby-1.8.x due to mistakes or oversights in the design.

In perl, here's a sample of what you can do easily:

use Carp qw( cluck );
sub a { cluck('Message') }
sub b { a( 1 .. 2 ) }
sub c { b( 'a' .. 'c' ) }
c();


which prints your stack:

Message at - line 2
	main::a(1, 2) called at - line 3
	main::b('a', 'b', 'c') called at - line 4
	main::c() called at - line 5


There's other options like Carp::confess() to throw an exception with a stack trace, Carp::longmess() to make the string you see above, the module Carp::Always (http://search.cpan.org/dist/Carp-Always>) to make Carp be your default behavior.
Carp is based on the caller() function which returns many pieces of information: class, filename, line, function, has args?, context, eval() text, is require?, compilation/pragma info. If you read perldebguts, you learn to also get the arguments:

sub b {
    package DB;
    () = caller 1;
    say "caller's args=@DB::args";
    $DB::args[0] = 'Zappo';
    return;
}
sub a {
    say "args=@_";
    b();
    say "args=@_";
}
a( 'flash' );

# args=flash # caller's args=flash # args=Zappo


I tried, I really tried to add this into Ruby-1.8.6. The problem is that for each ruby function, it uses actual C recursion to go deeper and it passes the arguments as an Array. For something like this:

def c(x);      end
def b(x); c(3); end
def a(x); b(2); end
a(1)


something like this happens in C:

rb_call(a,[1]) {
  rb_call(b,[2]) {
    rb_call(c,[3]) {
    }
  }
}


The problem being... how do you access the arguments without a pointer? You don't. Perl works because it is "stackless" in the sense of nested perl functions don't usually make the C stack go deeper and "stack based" in the sense that all arguments are pushed onto an application level stack.

Ruby's caller() function is like a particularly stupid perl version. You get the filenames, line numbers, and method names but that's it.

As is, it is nearly impossible to implement a useful stack trace in ruby-1.8.6. I've got a vague idea I might go learn how gdb extracts the backtrace, then walk that to find the argument arrays but OMG, that is such an ugly idea. I've got some x86 assembly books I might drag out to help with this.


Some help maybe

djberg96 on 2009-07-15T00:27:18

This post may be of interest to you:

http://weblog.jamisbuck.org/2006/9/22/inspecting-a-live-ruby-process

Be sure to follow the comments, too.

Re:Some help maybe

jjore on 2009-07-20T02:26:56

Ah, gleaning the information from backtrace() is nicer than using rb_eval() which would be my normal first pass trick. That is, in any language like Perl, Ruby, or I assume Python, if there's a C API that exposes an eval() function, you can get enormous powers just by driving eval() from gdb.

FWIW and because I the weblog.jamisbuck.org blog software comments aren't working for me, the clean bit of fu to glark the call stack is packaged as a GDB function thusly:

define ruby_backtrace
  set $ary = (struct RArray)backtrace(-1)
  set $count = (long)($ary->len)
  set $index = (long)0
  while    $index < $count
    set    $str = (struct RString)rb_ary_entry($ary, $index)
    set    $ptr = (char*)($str->ptr)
    x/1s *$ptr
    set    $index = (long)($index + 1)
  end
end

HOWEVER.

This still misses the point since what I originally complained of, the arguments to the functions are *still* missing and for exactly the same reasons.