Last active
June 25, 2024 19:12
-
-
Save csfrancis/11376304 to your computer and use it in GitHub Desktop.
Dump an MRI call stack from gdb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
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 ?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The above script just prints the current thread. This gdb script will a ruby backtrace for all ruby threads to /tmp/ruby-backtrace.txt