Maybe it will be useful for someone.
A beautiful ruby.core file from freshly died ruby process on 32-bit FreeBSD server. I personally dislike freebsd for a bunch of uncomfortable defaults, sometimes it seems for me that freebsd guys tried to make my life worse. Anyway here I am.
If you have a live ruby process with fancy debug symbols, you should be reading this guide.
Okay,
gdb /usr/local/bin/ruby —core ruby.code
(gdb) bt
#0 0×4809c38c in rb_eval () from /usr/local/lib/libruby18.so.18
#1 0×4809dcac in rb_eval () from /usr/local/lib/libruby18.so.18
#2 0×4809ed8a in rb_eval () from /usr/local/lib/libruby18.so.18
…
#4731 0×48099a02 in ruby_exec () from /usr/local/lib/libruby18.so.18
#4732 0×48099a3e in ruby_run () from /usr/local/lib/libruby18.so.18
#4733 0×0804865f in main ()
Woot!, 4733 stack frames. Okay let’s go to ruby’s source:
static VALUE
rb_eval(self, n)
VALUE self;
NODE *n;
Hmm, NODE* interesting.
typedef struct RNode {
unsigned long flags;
char *nd_file;
…
};
#define FL_USHIFT 11
#define NODE_LSHIFT (FL_USHIFT+8)
#define nd_line(n) ((unsigned int)(((RNODE)→flags>>NODE_LSHIFT)&NODE_LMASK))
I need the line number and the file. As you can see, that information are stored in (RNode*)n->nd_file and (RNode *)n->flags>>19.
Now, I need the second argument of rb_eval function. To get it I’m gonna use info frame
(gdb) info frame
Stack level 1, frame at 0xbfa005a0:
eip = 0×4809dcac in rb_eval; saved eip 0×4809ed8a
called by frame at 0xbfa00910, caller of frame at 0xbfa00230
Arglist at 0xbfa00598, args:
Locals at 0xbfa00598, Previous frame’s sp is 0xbfa005a0
Saved registers:
ebx at 0xbfa0058c, ebp at 0xbfa00598, esi at 0xbfa00590, edi at 0xbfa00594, eip at 0xbfa0059c
It says Stack level 1, frame at 0xbfa005a0 which means 0xbfa005a0 is my first argument, and 0xbfa005a4 is the second. All I need now: printf "%s\n", * ( * ( 0xbfa005a4 ) + 4 ), and:
(gdb) printf “%s\n”, * ( * ( 0xbfa005a4 ) + 4)
/usr/local/lib/ruby/gems/1.8/gems/hpricot-0.6.164/lib/hpricot/parse.rb
(gdb) printf “%d\n”, * ( * ( 0xbfa005a4 ) ) >> 19
235
Nice, isn’t it?
I defined a little macro
define rb_current_file
printf “%s:%d\n”, * ( * ($arg0+4) + 4), * ( * ( $arg0 + 4 ) ) >> 19
end
So I can info frame frame_number_here, and rb_current_file addr to get file and line for this stack frame. I guess there’s a way to write the stack trace dumper, but I don’t want to research it.
(gdb) info frame 4573
Stack frame at 0xbfbe9790:
….
(gdb) rb_current_File 0xbfbe9790
myscript.rb:37
Where 37 line is
hpricot = Hpricot(html) unless hpricot
I set it as frame 4573, and went down to #4572 0x480a4ac6 in rb_call () from /usr/local/lib/libruby18.so.18. Okay it’s the actual hpricot call. I need an argument of it. What should I do?. Of course, go to ruby source:
rb_call(klass, recv, mid, argc, argv, scope, self)
As you can see, fifth argument is what I need. I can get it by *(frame+4*4). My frame address is 0xbfbe9400, so I do printf "0x%x\n", *(*((unsigned long*)0xbfbe9400+4)) and see 0xc27c304. It’s a VALUE, so it has type in the first byte: print *0xc27c304 & 0x3f equals 7, which means it’s an RString.
struct RString {
struct RBasic basic;
long len;
char *ptr;
…
};
RBasic is 8 bytes long. So I can get length of the string by print *(0xc27c304+8), and the string by print (char*)*(0xc27c304+12). In my case, all I need to do is dump binary memory file.html 221081600 221301397 and test:
$ irb
irb(main):001:0> require ‘rubygems’
=> true
irb(main):002:0> require ‘hpricot’
=> true
irb(main):003:0> Hpricot(File.read(‘file.html’))
Illegal instruction: 4 (core dumped)
That’s it… Send questions to libc at libc.st