WARNING
This is an ADVANCED presentation. While you do
not need to be an expert on the inner workings of
C Ruby, this talk does presume you are familiar
with concepts such as pointers, memory
allocation, and pages (no, not book pages,
memory pages).
If you are prone to motion sickness or become
nauseous at the sight of hexadecimal memory
addresses, this talk may not be for you.
Reproduce It!
• Can you make the bug occur every time?
most of the time? some of the time?
• .irbrc, .gemrc, Other rc files
• Environment and Environment Variables
• rvm/rbenv
• Ask a friend
Report It!
• Ruby Bug Tracker: http://bugs.ruby-lang.org/
• All steps to reproduce, bonus points for
attachments
• Ruby Version
• Crash log (hint: look in Console.app)
Reduce It!
• Eliminate libraries
• Simple scripts are best
• Shrink code (5 or fewer lines is ideal)
Regress It!
• Try different Ruby versions
• Better: try different Ruby releases
• Best: git bisect!
• Bonus: try different build settings
A long time ago in a
faraway land...
for full backstory, see: http://blogs.burnsidedigital.com/
Disaster Strikes!
> ruby delegation_bench.rb
Call one method
user system total real
Pre-delegate 4.940000 0.020000 4.960000 ( 4.954945)
Post-delegate 0.060000 0.000000 0.060000 ( 0.060528)
On Demand 0.010000 0.000000 0.010000 ( 0.010604)
Reversible 0.110000 0.000000 0.110000 ( 0.101494)
Call one method 100 times
...
Call three methods 100 times
user system total real
Pre-delegate 5.640000 0.000000 5.640000 ( 5.640255)
Post-delegate 0.980000 0.000000 0.980000 ( 0.975919)
On Demand 1.210000 0.000000 1.210000 ( 1.205214)
Reversible 1.020000 0.000000 1.020000 ( 1.027759)
Call three methods 10,000 times
user system total real
Pre-delegate ruby(5919,0x7fff732a5180) malloc: *** error for object 0x7fb18f80d750:
incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
zsh: abort ruby delegation_bench.rb
Pack Your Bags
• Download Ruby Source
(https://github.com/ruby/ruby)
• Build
./configure && make
(see ./configure --help for more)
Study the Map
• ./include/ruby/ruby.h
• ID => symbols
• VALUE => everything else
• rb_id2name => dump symbols in GDB
• rb_string_value_cstr => #to_s for GDB
• vm_call_method
Bring Your Gardener
miniruby
• like ruby, only smaller
• lexer, parser, interpreter,VM/runtime
• core library classes
• make miniruby to build
Weapon of Choice
gdb
• gdb ./miniruby
• Set pre-run breakpoints
• Pass arguments to miniruby with “run”
• Watch it crash!
> gdb ./miniruby
...
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001100397198
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
> gdb ./miniruby
...
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001100397198
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
> gdb ./miniruby
...
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001100397198
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
(gdb) bt
#0 0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
#1 0x00007fff8e60e76d in szone_free_definite_size ()
#2 0x00007fff8e608898 in free ()
#3 0x000000010012f5dc in rb_free_method_entry [inlined] () at vm_method.c:155
#4 0x000000010012f5dc in rb_sweep_method_entry (pvm=0xb592caed) at...
#5 0x000000010004a00a in gc_lazy_sweep [inlined] () at gc.c:2107
#6 0x000000010004a00a in rb_newobj () at gc.c:1183
#7 0x000000010000caf4 in ary_alloc [inlined] () at array.c:301
#8 0x000000010000caf4 in ary_new (klass=4303833400, capa=3046355020) at...
#9 0x000000010000d165 in rb_ary_new2 [inlined] () at array.c:334
#10 0x000000010000d165 in rb_ary_new4 (n=0, elts=0x7fff5fbfee90) at array.c...
#11 0x000000010013288a in vm_yield_with_cfunc (th=0x100303e90, block=...
#12 0x000000010013dbb1 in rb_vm_invoke_proc (th=0x100303e90, proc=...
#13 0x000000010013bd90 in vm_call_bmethod [inlined] () at vm_insnhelper.c:433
#14 0x000000010013bd90 in vm_call_method (th=0x7fff5fbfef60,
cfp=0x7fff5fbfef60, num=8766224, blockptr=0x7fff5fbfef60,
flag=140734799802208, id=4298129040, me=0x10038ec90, recv=4303733520) at
vm_insnhelper.c:566
...
(gdb) p rb_string_value_cstr(4303733520)
(gdb) bt
#0 0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
#1 0x00007fff8e60e76d in szone_free_definite_size ()
#2 0x00007fff8e608898 in free ()
#3 0x000000010012f5dc in rb_free_method_entry [inlined] () at vm_method.c:155
#4 0x000000010012f5dc in rb_sweep_method_entry (pvm=0xb592caed) at...
#5 0x000000010004a00a in gc_lazy_sweep [inlined] () at gc.c:2107
#6 0x000000010004a00a in rb_newobj () at gc.c:1183
#7 0x000000010000caf4 in ary_alloc [inlined] () at array.c:301
#8 0x000000010000caf4 in ary_new (klass=4303833400, capa=3046355020) at...
#9 0x000000010000d165 in rb_ary_new2 [inlined] () at array.c:334
#10 0x000000010000d165 in rb_ary_new4 (n=0, elts=0x7fff5fbfee90) at array.c...
#11 0x000000010013288a in vm_yield_with_cfunc (th=0x100303e90, block=...
#12 0x000000010013dbb1 in rb_vm_invoke_proc (th=0x100303e90, proc=...
#13 0x000000010013bd90 in vm_call_bmethod [inlined] () at vm_insnhelper.c:433
#14 0x000000010013bd90 in vm_call_method (th=0x7fff5fbfef60,
cfp=0x7fff5fbfef60, num=8766224, blockptr=0x7fff5fbfef60,
flag=140734799802208, id=4298129040, me=0x10038ec90, recv=4303733520) at
vm_insnhelper.c:566
...
(gdb) p rb_string_value_cstr(4303733520)
(gdb) bt
#0 0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
#1 0x00007fff8e60e76d in szone_free_definite_size ()
#2 0x00007fff8e608898 in free ()
#3 0x000000010012f5dc in rb_free_method_entry [inlined] () at vm_method.c:155
#4 0x000000010012f5dc in rb_sweep_method_entry (pvm=0xb592caed) at...
#5 0x000000010004a00a in gc_lazy_sweep [inlined] () at gc.c:2107
#6 0x000000010004a00a in rb_newobj () at gc.c:1183
#7 0x000000010000caf4 in ary_alloc [inlined] () at array.c:301
#8 0x000000010000caf4 in ary_new (klass=4303833400, capa=3046355020) at...
#9 0x000000010000d165 in rb_ary_new2 [inlined] () at array.c:334
#10 0x000000010000d165 in rb_ary_new4 (n=0, elts=0x7fff5fbfee90) at array.c...
#11 0x000000010013288a in vm_yield_with_cfunc (th=0x100303e90, block=...
#12 0x000000010013dbb1 in rb_vm_invoke_proc (th=0x100303e90, proc=...
#13 0x000000010013bd90 in vm_call_bmethod [inlined] () at vm_insnhelper.c:433
#14 0x000000010013bd90 in vm_call_method (th=0x7fff5fbfef60,
cfp=0x7fff5fbfef60, num=8766224, blockptr=0x7fff5fbfef60,
flag=140734799802208, id=4298129040, me=0x10038ec90, recv=4303733520) at
vm_insnhelper.c:566
...
(gdb) p rb_string_value_cstr(4303733520)
(gdb) bt
#0 0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
#1 0x00007fff8e60e76d in szone_free_definite_size ()
#2 0x00007fff8e608898 in free ()
#3 0x000000010012f5dc in rb_free_method_entry [inlined] () at vm_method.c:155
#4 0x000000010012f5dc in rb_sweep_method_entry (pvm=0xb592caed) at...
#5 0x000000010004a00a in gc_lazy_sweep [inlined] () at gc.c:2107
#6 0x000000010004a00a in rb_newobj () at gc.c:1183
#7 0x000000010000caf4 in ary_alloc [inlined] () at array.c:301
#8 0x000000010000caf4 in ary_new (klass=4303833400, capa=3046355020) at...
#9 0x000000010000d165 in rb_ary_new2 [inlined] () at array.c:334
#10 0x000000010000d165 in rb_ary_new4 (n=0, elts=0x7fff5fbfee90) at array.c...
#11 0x000000010013288a in vm_yield_with_cfunc (th=0x100303e90, block=...
#12 0x000000010013dbb1 in rb_vm_invoke_proc (th=0x100303e90, proc=...
#13 0x000000010013bd90 in vm_call_bmethod [inlined] () at vm_insnhelper.c:433
#14 0x000000010013bd90 in vm_call_method (th=0x7fff5fbfef60,
cfp=0x7fff5fbfef60, num=8766224, blockptr=0x7fff5fbfef60,
flag=140734799802208, id=4298129040, me=0x10038ec90, recv=4303733520) at
vm_insnhelper.c:566
...
Call three methods 10,000 times
user system total real
Pre-delegate ruby(5919,0x7fff732a5180) malloc: *** error for object
0x7fb18f80d750: incorrect checksum for freed object - object was probably
modified after being freed.
*** set a breakpoint in malloc_error_break to debug
> gdb ./miniruby
...
(gdb) break malloc_error_break
Function "malloc_error_break" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (malloc_error_break) pending.
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Breakpoint 1 at 0x7fff8e607558
Pending breakpoint 1 - "malloc_error_break" resolved
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000110060c908
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
> gdb ./miniruby
...
(gdb) break malloc_error_break
Function "malloc_error_break" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (malloc_error_break) pending.
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Breakpoint 1 at 0x7fff8e607558
Pending breakpoint 1 - "malloc_error_break" resolved
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000110060c908
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
> gdb ./miniruby
...
(gdb) break malloc_error_break
Function "malloc_error_break" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (malloc_error_break) pending.
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Breakpoint 1 at 0x7fff8e607558
Pending breakpoint 1 - "malloc_error_break" resolved
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000110060c908
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
> gdb ./miniruby
...
(gdb) break malloc_error_break
Function "malloc_error_break" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (malloc_error_break) pending.
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
Reading symbols for shared libraries ++............................ done
Breakpoint 1 at 0x7fff8e607558
Pending breakpoint 1 - "malloc_error_break" resolved
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x000000110060c908
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb)
Advanced Weaponry
MallocStackLoggingNoCompact
• man malloc
• Set environment variables to log the stack
during allocations
• Run in GDB, use malloc_history to view
stacks
• CFLAGS=”-O0 -g” ./configure
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
zsh(49630) malloc: recording malloc stacks to disk using standard recorder
zsh(49630) malloc: stack logging compaction turned off; size of log files on
disk can increase rapidly
...
Reading symbols for shared libraries ++............................ done
miniruby(49630) malloc: recording malloc stacks to disk using standard recorder
miniruby(49630) malloc: stack logging compaction turned off; size of log files
on disk can increase rapidly
miniruby(49630) malloc: stack logs deleted from /tmp/stack-logs.49630.arch.
2JF4tB.index
miniruby(49630) malloc: stack logs being written into /tmp/stack-logs.
49630.miniruby.lZtwm8.index
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001000000008
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
zsh(49630) malloc: recording malloc stacks to disk using standard recorder
zsh(49630) malloc: stack logging compaction turned off; size of log files on
disk can increase rapidly
...
Reading symbols for shared libraries ++............................ done
miniruby(49630) malloc: recording malloc stacks to disk using standard recorder
miniruby(49630) malloc: stack logging compaction turned off; size of log files
on disk can increase rapidly
miniruby(49630) malloc: stack logs deleted from /tmp/stack-logs.49630.arch.
2JF4tB.index
miniruby(49630) malloc: stack logs being written into /tmp/stack-logs.
49630.miniruby.lZtwm8.index
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001000000008
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
zsh(49630) malloc: recording malloc stacks to disk using standard recorder
zsh(49630) malloc: stack logging compaction turned off; size of log files on
disk can increase rapidly
...
Reading symbols for shared libraries ++............................ done
miniruby(49630) malloc: recording malloc stacks to disk using standard recorder
miniruby(49630) malloc: stack logging compaction turned off; size of log files
on disk can increase rapidly
miniruby(49630) malloc: stack logs deleted from /tmp/stack-logs.49630.arch.
2JF4tB.index
miniruby(49630) malloc: stack logs being written into /tmp/stack-logs.
49630.miniruby.lZtwm8.index
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001000000008
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
zsh(49630) malloc: recording malloc stacks to disk using standard recorder
zsh(49630) malloc: stack logging compaction turned off; size of log files on
disk can increase rapidly
...
Reading symbols for shared libraries ++............................ done
miniruby(49630) malloc: recording malloc stacks to disk using standard recorder
miniruby(49630) malloc: stack logging compaction turned off; size of log files
on disk can increase rapidly
miniruby(49630) malloc: stack logs deleted from /tmp/stack-logs.49630.arch.
2JF4tB.index
miniruby(49630) malloc: stack logs being written into /tmp/stack-logs.
49630.miniruby.lZtwm8.index
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000001000000008
0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
(gdb) bt
#0 0x00007fff8e611cfb in tiny_free_list_remove_ptr ()
#1 0x00007fff8e60e76d in szone_free_definite_size ()
#2 0x00007fff8e608898 in free ()
#3 0x000000010006f6cc in vm_xfree (objspace=0x10081c400, ptr=0x1004918b0)
at gc.c:830
#4 0x000000010006fa76 in ruby_xfree (x=0x1004918b0) at gc.c:894
...
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) set environment DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
GuardMalloc[zsh-50147]: recording malloc stacks to disk using standard recorder
GuardMalloc[zsh-50147]: stack logging compaction turned off; size of log files
on disk can increase rapidly
...
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) set environment DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
GuardMalloc[zsh-50147]: recording malloc stacks to disk using standard recorder
GuardMalloc[zsh-50147]: stack logging compaction turned off; size of log files
on disk can increase rapidly
...
(gdb) set environment MallocStackLoggingNoCompact=true
(gdb) set environment DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
(gdb) run ./bug.rb
Starting program: /Users/jballanc/Source/ruby/miniruby ./bug.rb
GuardMalloc[zsh-50147]: recording malloc stacks to disk using standard recorder
GuardMalloc[zsh-50147]: stack logging compaction turned off; size of log files
on disk can increase rapidly
...
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000000107203fe4
0x0000000100208b3c in rb_thread_mark (ptr=0x10085fce0) at vm.c:1733
1733! ! ! if (cfp->me) ((rb_method_entry_t *)cfp->me)->mark = 1;
(gdb) p cfp->me
$1 = (rb_method_entry_t *) 0x107203fe0
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000000107203fe4
0x0000000100208b3c in rb_thread_mark (ptr=0x10085fce0) at vm.c:1733
1733! ! ! if (cfp->me) ((rb_method_entry_t *)cfp->me)->mark = 1;
(gdb) p cfp->me
$1 = (rb_method_entry_t *) 0x107203fe0