Skip to content

Instantly share code, notes, and snippets.

@wr0ngway
Last active January 16, 2025 04:42
Show Gist options
  • Save wr0ngway/3001d76b4e5bf312e912 to your computer and use it in GitHub Desktop.
Save wr0ngway/3001d76b4e5bf312e912 to your computer and use it in GitHub Desktop.
Uses gdb to dump ruby backtraces for all threads, and displays them in an easily readable format
#!/bin/bash
PID=$1
RUBY_BINARY=`which ruby`
BT_FILENAME="/tmp/ruby-backtrace-$PID-$(date +%s).txt"
cmdfile=$(mktemp)
cat <<EOF > $cmdfile
set \$old_stdout = dup(1)
set \$old_stderr = dup(2)
set \$fd = creat("$BT_FILENAME", 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)
EOF
gdb "$RUBY_BINARY" "$PID" -batch -x $cmdfile &> /dev/null
ruby -e 'ARGF.each {|l| val = eval(l); puts val ? val.join("\n") : "nil thread"; puts "\n\n\n\n" }' $BT_FILENAME
@guymaliar
Copy link

@wr0ngway, this is awesome!
I changed RUBY_BINARY to rbenv which ruby and it worked well.

@hyperair
Copy link

On Linux, changing RUBY_BINARY to /proc/$PID/exe may yield more accurate results if you have many ruby binaries floating around.

@johncoates-st
Copy link

johncoates-st commented Jan 16, 2025

LLDB version:

#!/bin/bash

PID=$1
BT_FILENAME="/tmp/ruby-backtrace-$PID-$(date +%s).txt"
cmdfile=$(mktemp)

echo "Writing backtrace to $BT_FILENAME"

cat <<EOF > "$cmdfile"
expr int \$old_stdout = (int)dup(1)
expr int \$old_stderr = (int)dup(2)
expr int \$fd = (int)creat("$BT_FILENAME", 0644)
expr (int) dup2(\$fd, 1)
expr (int) dup2(\$fd, 2)

expr void* \$thread_list = (void*)rb_thread_list()
expr long \$num_threads = (long)rb_num2long((uintptr_t)rb_ary_length((void*)\$thread_list))

expr --
for (int i = 0; i < \$num_threads; i++) {
  (void)rb_p((uintptr_t)rb_thread_backtrace_m(0, 0, (uintptr_t)rb_ary_entry(\$thread_list, i)));
}


expr (int) dup2(\$old_stdout, 1)
expr (int) dup2(\$old_stderr, 2)
expr (int) close(\$old_stdout)
expr (int) close(\$old_stderr)
expr (int) close(\$fd)

detach
quit
EOF

lldb -b -s "$cmdfile" -p "$PID" &> /dev/null
ruby -e 'ARGF.each {|l| val = eval(l); puts val ? val.join("\n") : "nil thread"; puts "\n\n\n\n" }' "$BT_FILENAME"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment