-
-
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 |
For example:
(gdb) python
>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, 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']
>
>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")
>
>th = gdb.parse_and_eval('ruby_current_thread')
>
>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, size):
> if cfp['iseq'].dereference().address != 0:
> if cfp['pc'].dereference().address != 0:
> s = "{}:{}:in `{}'".format(get_rstring(cfp['iseq']['location']['path']),
> get_lineno(cfp['iseq'], cfp['pc'] - cfp['iseq']['iseq_encoded']),
> get_rstring(cfp['iseq']['location']['label']))
> call_stack.append(s)
>
> cfp -= 1
>
>for i in reversed(call_stack):
> print i
>
>end
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/input-method.rb:152:in `gets'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:472:in `block (2 levels) in eval_input'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:624:in `signal_status'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:471:in `block in eval_input'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:190:in `buf_input'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:105:in `getc'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/slex.rb:206:in `match_io'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/slex.rb:76:in `match'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:290:in `token'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:266:in `lex'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:237:in `block (2 levels) in each_top_level_statement'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:233:in `block in each_top_level_statement'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb/ruby-lex.rb:232:in `each_top_level_statement'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:488:in `eval_input'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:397:in `block in start'
/usr/lib/shopify-ruby/2.1.1-shopify2/lib/ruby/2.1.0/irb.rb:396:in `start'
/usr/lib/shopify-ruby/2.1.1-shopify2/bin/irb:11:in `<main>'
(gdb)
From @charliesome:
local BT_FILENAME="/tmp/ruby-backtrace-$$.txt"
sudo gdb "$RUBY_BINARY" "$PID" -batch \
-ex "$THREAD" \
-ex 'p (int)dup(1)' \
-ex 'p (int)dup(2)' \
-ex 'p (int)creat("'"$BT_FILENAME"'", 0644)' \
-ex 'p (int)dup2($3, 1)' \
-ex 'p (int)dup2($3, 2)' \
-ex 'call (void)rb_backtrace()' \
-ex 'p (int)dup2($1, 1)' \
-ex 'p (int)dup2($2, 2)' \
-ex 'p (int)close($1)' \
-ex 'p (int)close($2)' \
-ex 'p (int)close($3)' \
&>/dev/null
cat "$BT_FILENAME"
rm "$BT_FILENAME"
(would not work on a coredump)
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 ?
Requires a ruby that is built with symbols. To run from gdb, you can run
python
then paste the script.