A while ago, I found a pair of really useful GDB functions for debugging live Ruby processes on ThoughtBot’s blog:
1 2 3 4 5 6 7
These proved really handy but they have a couple of problems relating to garbage collection and Macs, so I made a few modifications.
NOTE: If you don’t care about the explanation, scroll to the bottom of the post for the modified script.
Problem with garbage collection
In Ruby, when the garbage collector is running, no object allocations are allowed. At all. For any reason. It’s so not allowed, in fact, the process will segfault if you try. Check out this test script:
1 2 3 4 5 6
When we set it running and GDB into it, we can try and run the
helper function from ThoughtBot but will almost certainly see the following
happen in the Ruby process:
test.rb:5: [BUG] object allocation during garbage collection phase ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.5.0] -- Crash Report log information -------------------------------------------- See Crash Report log file under the one of following: ...
And this in the GDB process:
Program received signal SIGABRT, Aborted. 0x00007fff915a5212 in __pthread_kill () The program being debugged was signaled while in a function called from GDB. GDB remains in the frame where the signal was received. To change this behavior use "set unwindonsignal on" Evaluation of the expression containing the function (rb_eval_string) will be abandoned.
And it’s all because of this code around line 1276 in
gc.c of the Ruby core
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Notice line 10. Yup. Computer says no.
Getting around the garbage collector problem
Ruby keeps a variable called
during_gc that should be 0 when the GC is not
running. It also provides access to this variable through the function
rb_during_gc() (which is important seeing as we only have a symbol table to
play with and would not otherwise be able to access the variable directly).
rb_during_gc() in our GDB script to check that we aren’t going to
break anything. The next problem, though…
Unable to call function? Try harder!
If you’ve tried to use the ThoughtBot
.gdbinit script on a Mac machine you may
have noticed that this happens:
Unable to call function "rb_eval_string" at 0x1019e8850: no return type information available. To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)')
Urk. I think this is less a Mac problem and more a
binary-does-not-have-type-information problem, which could be something that’s
specific with how Ruby gets compiled on a Mac (I do it through ruby-install and
gcc binary is actually
clang, so this doesn’t sound unreasonable).
To get around this, we need to specifically cast our function calls to their proper return types (it’s important we get this right, otherwise weird things might happen).
The call to
rb_during_gc() returns an
int and the
can be cast to
void seeing as we don’t care much about their return values.
tl;dr: The modified script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
This was tested on Ruby 1.9.3 and Ruby 2.1.0 on a Mac OSX 10.8 machine and an Arch Linux virtual machine. Hopefully now there will be no more tears shed when an important process is accidentally segfaulted because you forgot to check if the GC was running :)