-
-
Save csfrancis/11376304 to your computer and use it in GitHub Desktop.
# Updated for Ruby 2.3 | |
string_t = None | |
def get_rstring(addr): | |
s = addr.cast(string_t.pointer()) | |
if s['basic']['flags'] & (1 << 13): | |
return s['as']['heap']['ptr'].string() | |
else: | |
return s['as']['ary'].string() | |
def get_lineno(iseq, pos): | |
if pos != 0: | |
pos -= 1 | |
t = iseq['line_info_table'] | |
t_size = iseq['line_info_size'] | |
if t_size == 0: | |
return 0 | |
elif t_size == 1: | |
return t[0]['line_no'] | |
for i in range(0, int(t_size)): | |
if pos == t[i]['position']: | |
return t[i]['line_no'] | |
elif t[i]['position'] > pos: | |
return t[i-1]['line_no'] | |
return t[t_size-1]['line_no'] | |
def get_ruby_stacktrace(th=None): | |
global string_t | |
try: | |
control_frame_t = gdb.lookup_type('rb_control_frame_t') | |
string_t = gdb.lookup_type('struct RString') | |
except gdb.error: | |
raise gdb.GdbError ("ruby extension requires symbols") | |
if th == None: | |
th = gdb.parse_and_eval('ruby_current_thread') | |
else: | |
th = gdb.parse_and_eval('(rb_thread_t *) %s' % th) | |
last_cfp = th['cfp'] | |
start_cfp = (th['stack'] + th['stack_size']).cast(control_frame_t.pointer()) - 2 | |
size = start_cfp - last_cfp + 1 | |
cfp = start_cfp | |
call_stack = [] | |
for i in range(0, int(size)): | |
if cfp['iseq'].dereference().address != 0: | |
if cfp['pc'].dereference().address != 0: | |
s = "{}:{}:in `{}'".format(get_rstring(cfp['iseq']['body']['location']['path']), | |
get_lineno(cfp['iseq']['body'], cfp['pc'] - cfp['iseq']['body']['iseq_encoded']), | |
get_rstring(cfp['iseq']['body']['location']['label'])) | |
call_stack.append(s) | |
cfp -= 1 | |
for i in reversed(call_stack): | |
print(i) | |
end |
The above script just prints the current thread. This gdb script will a ruby backtrace for all ruby threads to /tmp/ruby-backtrace.txt
set $old_stdout = dup(1)
set $old_stderr = dup(2)
set $fd = creat("/tmp/ruby-backtrace.txt", 0644)
call dup2($fd, 1)
call dup2($fd, 2)
set $thread_list = rb_thread_list()
set $num_threads = rb_num2long(rb_ary_length($thread_list))
set $i = 0
while $i < $num_threads
call rb_p(rb_thread_backtrace_m(0, 0, rb_ary_entry($thread_list, $i++)))
end
call dup2($old_stdout, 1)
call dup2($old_stderr, 2)
call close($old_stdout)
call close($old_stderr)
call close($fd)
This snippet now supports multiple threads.
python get_ruby_stacktrace()
By default this will return the stack from the main thread.
python get_ruby_stacktrace("0x7fed6cd81800")
This would return a stack by casting 0x7fed6cd81800
to an rb_thread_t
. How do you determine the address of the rb_thread_t
? You can find it in the C call stack for the given thread:
(gdb) info threads
Id Target Id Frame
5 Thread 0x7fed64ca6700 (LWP 32573) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
4 Thread 0x7fed5f9a7700 (LWP 32574) "ruby" pthread_cond_timedwait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_timedwait.S:238
3 Thread 0x7fed5f8a6700 (LWP 32575) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
2 Thread 0x7fed64cbc700 (LWP 32577) "ruby-timer-thr" 0x00007fed63c1912d in poll () at ../sysdeps/unix/syscall-template.S:81
* 1 Thread 0x7fed64ca8780 (LWP 32531) "ruby" 0x00007fed63c1dda3 in select () at ../sysdeps/unix/syscall-template.S:81
(gdb) thread 3
[Switching to thread 3 (Thread 0x7fed5f8a6700 (LWP 32575))]
#0 pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
185 ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S: No such file or directory.
(gdb) where
#0 pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
#1 0x00007fed64ec7093 in native_cond_wait (mutex=0x7fed6c8fcd80, cond=0x7fed6c8fcda8) at thread_pthread.c:334
#2 lock_func (timeout_ms=0, mutex=0x7fed6c8fcd80, th=0x7fed6cd81800) at thread.c:4343
#3 rb_mutex_lock (self=140657681410960) at thread.c:4417
#4 0x00007fed64ea3a65 in vm_call_cfunc_with_frame (th=0x7fed6cd81800, reg_cfp=0x7fed6ce85d30, ci=<optimized out>) at vm_insnhelper.c:1510
#5 0x00007fed64eaa9b4 in vm_exec_core (th=th@entry=0x7fed6cd81800, initial=initial@entry=0) at insns.def:1028
#6 0x00007fed64eaf1ed in vm_exec (th=th@entry=0x7fed6cd81800) at vm.c:1398
#7 0x00007fed64ea4c4a in invoke_block_from_c (th=th@entry=0x7fed6cd81800, block=block@entry=0x7fed6cc480d0, self=self@entry=140657682652720, argc=argc@entry=0, argv=argv@entry=0x7fed6b35f7a0, blockptr=blockptr@entry=0x0,
cref=cref@entry=0x0, defined_class=defined_class@entry=140657681351360) at vm.c:817
#8 0x00007fed64ea57bb in vm_invoke_proc (th=th@entry=0x7fed6cd81800, proc=proc@entry=0x7fed6cc480d0, self=140657682652720, defined_class=140657681351360, argc=0, argv=0x7fed6b35f7a0, blockptr=blockptr@entry=0x0) at vm.c:881
#9 0x00007fed64ea586a in rb_vm_invoke_proc (th=th@entry=0x7fed6cd81800, proc=proc@entry=0x7fed6cc480d0, argc=<optimized out>, argv=<optimized out>, blockptr=blockptr@entry=0x0) at vm.c:900
#10 0x00007fed64ec950a in thread_start_func_2 (th=th@entry=0x7fed6cd81800, stack_start=<optimized out>) at thread.c:535
#11 0x00007fed64ec991b in thread_start_func_1 (th_ptr=0x7fed6cd81800) at thread_pthread.c:840 <-- RIGHT HERE
#12 0x00007fed6463c182 in start_thread (arg=0x7fed5f8a6700) at pthread_create.c:312
#13 0x00007fed63c2647d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111
You can find the rb_thread_t
pointer passed as the parameter to thread_start_func_1
.
If placed in your gdb data-directory, I assume it will then be automatically found by gdb and can be called - and no need to paste into gdb?
If you're in ruby 2.3 you'll need to replace all instances of cfp['iseq']
with cfp['iseq']['body']
(except for the dereference null check).
@csfrancis On a core dump from a ruby process, is it possible to get the Thread.current[:foo] values ?
From @charliesome:
(would not work on a coredump)