ruby/ruby
Debugging
Tools and idioms for diagnosing crashes, miscompiles, GC bugs, and slow code in the Ruby implementation.
The bundled gdb config
.gdbinit (35 KB) at the repository root defines dozens of helper commands for inspecting Ruby state from gdb. Examples:
| Command | What it does |
|---|---|
rp obj |
Pretty-prints a VALUE (calls rb_inspect internally) |
rb_classname |
Print the class name of a VALUE |
rb_ps |
Print the running threads with their backtraces |
rb_iseq |
Inspect an iseq's instruction sequence |
print_ec |
Dump the current execution context |
bt_ruby |
Show only the Ruby-level backtrace |
Just gdb ./ruby at the repo root and the .gdbinit is auto-loaded (you may need to whitelist it in ~/.gdbinit: add-auto-load-safe-path /path/to/ruby/.gdbinit).
When the interpreter crashes
CRuby installs a SEGV handler (signal.c, vm_dump.c) that prints:
- The C-level backtrace (with function names if
addr2line.ccan resolve them). - The Ruby-level backtrace.
- The current iseq and its disassembly.
- Object-space statistics.
- Loaded extensions.
This output ends up on stderr. If you can't catch it, set RUBY_CRASH_REPORT=core or RUBY_CRASH_REPORT=/tmp/ruby-crash-%p.log to redirect.
If the crash report itself crashes (the process is too damaged), run under gdb instead:
gdb --args ./ruby some_script.rb
(gdb) run
(gdb) bt full
(gdb) rb_psTracing the VM
Several environment variables make the running interpreter chatty:
| Variable | Effect |
|---|---|
RUBY_DEBUG=verbose |
Generic debug output |
RUBY_DEBUG_COUNTER_DISABLE=1 |
Skip dumping counters (otherwise they print at exit) |
RUBYOPT=--dump=insns_without_opt --dump=insns |
Print disassembled iseqs as they're compiled |
RUBY_GC_DEBUG=1 |
Verbose GC; see gc.c gc_*_internal_log macros |
RUBY_THREAD_TIMESLICE=... |
Tweak GVL handoff cadence |
RUBY_FREE_AT_EXIT=1 |
Free everything at exit (catches use-after-free) |
RUBY_MN_THREADS=1 |
Enable M:N thread scheduler (thread_pthread_mn.c) |
debug_counter.h defines named counters that the interpreter ticks during execution. Build with --enable-debug-env and they'll print at exit; very useful for "how often does X happen" investigations.
Tracing JIT compilation
./ruby --yjit --yjit-stats some_script.rb
./ruby --yjit --yjit-trace-exits some_script.rb
./ruby --zjit --zjit-stats some_script.rb
./ruby --zjit --zjit-dump-disasm=foo some_script.rb
./ruby --zjit --zjit-dump-hir-opt=foo some_script.rb--yjit-stats prints a counter table (call site polymorphism, side exits by reason, code memory used). --yjit-trace-exits records every side exit and prints the top sources at exit.
ZJIT dumps high-level IR (HIR), low-level IR (LIR), and disassembly per method. tool/zjit_iongraph.rb renders the IR graph in tool/zjit_iongraph.html (open it in a browser).
Memory and GC bugs
Heap walking
ext/objspace/ exposes ObjectSpace::dump, dump_all, count_objects, memsize_of, etc. From a running Ruby:
require 'objspace'
ObjectSpace.dump_all(output: File.open('/tmp/heap.json', 'w'))The output is a JSON dump suitable for tools like heapy.
GC stress modes
RUBY_GC_STRESS=1 ./ruby foo.rb # GC on every allocation; finds use-after-free
RUBY_GC_STRESS_COMPACT=1 ... # Compact on every chance
RUBY_GC_HEAP_INIT_SLOTS=... # Tweak initial heap sizeRUBY_GC_STRESS=1 is roughly 100x slower but turns latent bugs into immediate crashes.
MMTk GC
Build with --with-gc=mmtk to use the MMTk-backed allocator. Useful for cross-checking the in-tree GC's correctness; bugs that reproduce only under MMTk are usually missing write barriers.
Address Sanitizer
./configure CC="clang" optflags="-O0 -fsanitize=address -fno-omit-frame-pointer"
make
make test-allASan-clean builds are part of CI; tool/lib/asan.rb and the *_asan*.yml workflows track them.
Disassembling Ruby code
./ruby --dump=insns_without_opt -e 'def foo; 1+2; end; p RubyVM::InstructionSequence.of(method(:foo))'
./ruby -e 'puts RubyVM::InstructionSequence.compile("1+2").disasm'Or interactively:
iseq = RubyVM::InstructionSequence.compile("1.times{|i| p i}")
puts iseq.disasmThe mnemonics are defined in insns.def and printed by iseq.c::iseq_disasm.
Tracing method calls
TracePoint (the user-facing API) and vm_trace.c (its implementation) provide:
TracePoint.new(:call, :return) { |tp| p [tp.event, tp.path, tp.lineno, tp.method_id] }.enableFor C-level tracing, make produces DTrace-compatible probes when built with --enable-dtrace. Probe definitions are in probes.d.
Common pitfalls
- Forgetting write barriers. After
RB_OBJ_WRITE(parent, &slot, child), the GC knows about the new pointer; without it, the generational GC may freechildwhile it's still reachable. - Allocations during GC marking. Many GC routines run with allocations forbidden; allocating triggers an assertion. Use
rb_gc_markcarefully. - Mutating frozen objects. Most core class methods short-circuit with a
FrozenErrorfor frozen receivers. New ones must too. - Blocking syscalls without releasing the GVL. Every blocking syscall in C must use
rb_thread_call_without_gvl(orrb_io_wait) so other threads can run. - Encoding bugs. String operations need to handle ASCII-compatible vs not, valid vs broken byte sequences, and the
Encoding::ASCII_8BIT("binary") special case. Seestring.cfor the patterns.
Useful documents in this tree
doc/extension.rdoc— writing a C extension.doc/maintainers.md— official owners by subsystem (used by auto-review).doc/contributing/— additional contributor docs (build, reporting issues, etc.).KNOWNBUGS.rb— currently-known interpreter bugs.NEWS.md— release-in-progress changelog.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.