ruby-gdb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 631791caaf97095f10517f1dd754921143a8f6384cfabea6cd68e24ce71f15fa
4
+ data.tar.gz: 6049e59f42b890adc927bacfe16b7319e288f93d0c7e2b1454d9971fb2f0d299
5
+ SHA512:
6
+ metadata.gz: e550b09ea2c326b506c18bc96a619c1af2dccef8e19cb068addb5cff0985409c271f83b7ba34eb68532e845dda1c8abf30c2c4ec29fa3f65b9fc3c29b49e147b
7
+ data.tar.gz: 55517867200278ccb455091b08339594b4b5611ce20fe84ecc66df68da61e4acc05999c6dbc221253d31ae5144afdbf14ad35f512cdb347725f929e0ee448d4a
data/bake/ruby/gdb.rb ADDED
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "ruby/gdb"
7
+ require "fileutils"
8
+
9
+ # Install GDB extensions by adding source line to ~/.gdbinit
10
+ # @parameter gdbinit [String] Optional path to .gdbinit (defaults to ~/.gdbinit)
11
+ def install(gdbinit: nil)
12
+ gdbinit_path = gdbinit || File.join(Dir.home, ".gdbinit")
13
+ init_py_path = Ruby::GDB.init_script_path
14
+ source_line = "source #{init_py_path}"
15
+ marker_comment = "# Ruby GDB Extensions (ruby-gdb gem)"
16
+
17
+ puts "Installing Ruby GDB extensions..."
18
+ puts " Extensions: #{File.dirname(init_py_path)}"
19
+ puts " Config: #{gdbinit_path}"
20
+
21
+ # Read existing .gdbinit or create empty array
22
+ lines = File.exist?(gdbinit_path) ? File.readlines(gdbinit_path) : []
23
+
24
+ # Check if already installed (look for marker comment)
25
+ marker_index = lines.index{|line| line.strip == marker_comment}
26
+
27
+ if marker_index
28
+ # Already installed - update the source line in case path changed
29
+ source_index = marker_index + 1
30
+ if source_index < lines.size && lines[source_index].strip.start_with?("source")
31
+ old_source = lines[source_index].strip
32
+ if old_source == source_line
33
+ puts "\n✓ Already installed in #{gdbinit_path}"
34
+ puts " #{source_line}"
35
+ return
36
+ else
37
+ # Path changed - update it
38
+ lines[source_index] = "#{source_line}\n"
39
+ File.write(gdbinit_path, lines.join)
40
+ puts "\n✓ Updated installation in #{gdbinit_path}"
41
+ puts " Old: #{old_source}"
42
+ puts " New: #{source_line}"
43
+ return
44
+ end
45
+ end
46
+ end
47
+
48
+ # Not installed - add it
49
+ File.open(gdbinit_path, "a") do |f|
50
+ f.puts unless lines.last&.strip&.empty?
51
+ f.puts marker_comment
52
+ f.puts source_line
53
+ end
54
+
55
+ puts "\n✓ Installation complete!"
56
+ puts "\nAdded to #{gdbinit_path}:"
57
+ puts " #{source_line}"
58
+ puts "\nExtensions will load automatically when you start GDB."
59
+ end
60
+
61
+ # Uninstall GDB extensions by removing source line from ~/.gdbinit
62
+ # @parameter gdbinit [String] Optional path to .gdbinit (defaults to ~/.gdbinit)
63
+ def uninstall(gdbinit: nil)
64
+ gdbinit_path = gdbinit || File.join(Dir.home, ".gdbinit")
65
+ marker_comment = "# Ruby GDB Extensions (ruby-gdb gem)"
66
+
67
+ puts "Uninstalling Ruby GDB extensions..."
68
+
69
+ unless File.exist?(gdbinit_path)
70
+ puts "No ~/.gdbinit file found - nothing to uninstall."
71
+ return
72
+ end
73
+
74
+ lines = File.readlines(gdbinit_path)
75
+ marker_index = lines.index{|line| line.strip == marker_comment}
76
+
77
+ unless marker_index
78
+ puts "Extensions were not found in #{gdbinit_path}"
79
+ return
80
+ end
81
+
82
+ # Remove the marker comment and the source line after it
83
+ lines.delete_at(marker_index) # Remove comment
84
+ if marker_index < lines.size && lines[marker_index].strip.start_with?("source")
85
+ removed_line = lines.delete_at(marker_index).strip # Remove source line
86
+ puts " Removed: #{removed_line}"
87
+ end
88
+
89
+ # Clean up empty line before marker if it exists
90
+ if marker_index > 0 && lines[marker_index - 1].strip.empty?
91
+ lines.delete_at(marker_index - 1)
92
+ end
93
+
94
+ File.write(gdbinit_path, lines.join)
95
+ puts "✓ Removed Ruby GDB extensions from #{gdbinit_path}"
96
+ end
97
+
98
+ # Show installation information
99
+ # @parameter gdbinit [String] Optional path to .gdbinit (defaults to ~/.gdbinit)
100
+ def info(gdbinit: nil)
101
+ gdbinit_path = gdbinit || File.join(Dir.home, ".gdbinit")
102
+ init_py_path = Ruby::GDB.init_script_path
103
+ marker_comment = "# Ruby GDB Extensions (ruby-gdb gem)"
104
+
105
+ puts "Ruby GDB Extensions v#{Ruby::GDB::VERSION}"
106
+ puts "\nGem data directory: #{Ruby::GDB.data_path}"
107
+ puts "Init script: #{init_py_path}"
108
+
109
+ # Check installation status by looking for marker comment
110
+ installed = false
111
+ current_source = nil
112
+
113
+ if File.exist?(gdbinit_path)
114
+ lines = File.readlines(gdbinit_path)
115
+ marker_index = lines.index{|line| line.strip == marker_comment}
116
+ if marker_index
117
+ installed = true
118
+ source_index = marker_index + 1
119
+ if source_index < lines.size
120
+ current_source = lines[source_index].strip
121
+ end
122
+ end
123
+ end
124
+
125
+ puts "\nGDB config: #{gdbinit_path}"
126
+ if installed
127
+ puts "Status: ✓ Installed"
128
+ if current_source
129
+ puts " #{current_source}"
130
+ end
131
+ else
132
+ puts "Status: ✗ Not installed"
133
+ puts "\nRun: bake ruby:gdb:install"
134
+ end
135
+ end
@@ -0,0 +1,372 @@
1
+ # Fiber Debugging
2
+
3
+ This guide explains how to debug Ruby fibers using GDB, including inspecting fiber state, backtraces, and switching between fiber contexts.
4
+
5
+ ## Why Fiber Debugging is Critical
6
+
7
+ When debugging concurrent Ruby applications, fibers can be in various states - running, suspended, or terminated. Unlike traditional debugging where you see one call stack, fiber-based programs have multiple execution contexts simultaneously. Understanding what each fiber is doing and why it stopped is essential for diagnosing deadlocks, exceptions, and state corruption.
8
+
9
+ Use fiber debugging when you need:
10
+
11
+ - **Diagnose deadlocks**: See which fibers are waiting and on what
12
+ - **Find hidden exceptions**: Discover exceptions in suspended fibers that haven't propagated yet
13
+ - **Understand concurrency**: Visualize what all fibers are doing at a point in time
14
+ - **Debug async code**: Navigate between fiber contexts to trace execution flow
15
+
16
+ ## Scanning for Fibers
17
+
18
+ The first step in fiber debugging is finding all fibers in the heap.
19
+
20
+ ### Basic Scan
21
+
22
+ Scan the entire Ruby heap for fiber objects:
23
+
24
+ ~~~
25
+ (gdb) rb-scan-fibers
26
+ Scanning 1250 heap pages...
27
+ Checked 45000 objects, found 12 fiber(s)...
28
+ Scan complete: checked 67890 objects
29
+
30
+ Found 12 fiber(s):
31
+
32
+ Fiber #0: 0x7f8a1c800000
33
+ Status: RESUMED
34
+ Stack: base=0x7f8a1d000000, size=1048576
35
+ VM Stack: 0x7f8a1c900000, size=4096
36
+ CFP: 0x7f8a1c901000
37
+
38
+ Fiber #1: 0x7f8a1c800100
39
+ Status: SUSPENDED
40
+ Exception: RuntimeError: Connection failed
41
+ ...
42
+ ~~~
43
+
44
+ ### Limiting Results
45
+
46
+ For large applications, limit the scan:
47
+
48
+ ~~~
49
+ (gdb) rb-scan-fibers 10 # Find first 10 fibers only
50
+ ~~~
51
+
52
+ ### Caching Results
53
+
54
+ Cache fiber addresses for faster subsequent access:
55
+
56
+ ~~~
57
+ (gdb) rb-scan-fibers --cache # Save to fibers.json
58
+ (gdb) rb-scan-fibers --cache my.json # Custom cache file
59
+ ~~~
60
+
61
+ Later, load from cache instantly:
62
+
63
+ ~~~
64
+ (gdb) rb-scan-fibers --cache
65
+ Loaded 12 fiber address(es) from fibers.json
66
+ ~~~
67
+
68
+ This is especially useful with core dumps where heap scanning is slow.
69
+
70
+ ## Inspecting Specific Fibers
71
+
72
+ After scanning, inspect fibers by index:
73
+
74
+ ### Fiber Overview
75
+
76
+ ~~~
77
+ (gdb) rb-fiber 5
78
+ Fiber #5: 0x7f8a1c800500
79
+ Status: SUSPENDED
80
+ Exception: IOError: Connection reset
81
+ Stack: base=0x7f8a1e000000, size=1048576
82
+ VM Stack: 0x7f8a1c950000, size=4096
83
+ CFP: 0x7f8a1c951000
84
+ EC: 0x7f8a1c800600
85
+
86
+ Available commands:
87
+ rb-fiber-bt 5 - Show backtrace
88
+ rb-fiber-vm-stack 5 - Show VM stack
89
+ rb-fiber-c-stack 5 - Show C/machine stack info
90
+ ~~~
91
+
92
+ This shows at a glance whether the fiber has an exception and what its state is.
93
+
94
+ ### Fiber Backtraces
95
+
96
+ See the Ruby-level call stack:
97
+
98
+ ~~~
99
+ (gdb) rb-fiber-bt 5
100
+ Backtrace for fiber 0x7f8a1c800500:
101
+ 45: /app/lib/connection.rb:123:in `read'
102
+ 44: /app/lib/connection.rb:89:in `receive'
103
+ 43: /app/lib/server.rb:56:in `handle_client'
104
+ 42: /app/lib/server.rb:34:in `block in run'
105
+ 41: /gems/async-2.0/lib/async/task.rb:45:in `run'
106
+ ...
107
+ ~~~
108
+
109
+ Show backtraces for all non-terminated fibers:
110
+
111
+ ~~~
112
+ (gdb) rb-all-fiber-bt
113
+ ~~~
114
+
115
+ ## Switching Fiber Context
116
+
117
+ The most powerful feature: switch GDB's view to a fiber's stack (even in core dumps!).
118
+
119
+ ### Basic Usage
120
+
121
+ ~~~
122
+ (gdb) rb-scan-fibers
123
+ (gdb) rb-fiber-switch 5
124
+ Switched to Fiber #5: 0x7f8a1c800500
125
+ Status: SUSPENDED
126
+ Exception: IOError: Connection reset
127
+
128
+ Convenience variables set:
129
+ $fiber = Current fiber (struct rb_fiber_struct *)
130
+ $ec = Execution context (rb_execution_context_t *)
131
+ $errinfo = Exception being handled (VALUE)
132
+
133
+ Now try:
134
+ bt # Show C backtrace of fiber
135
+ frame <n> # Switch to frame N
136
+ info locals # Show local variables
137
+ rp $errinfo # Pretty print exception
138
+ ~~~
139
+
140
+ After switching, all standard GDB commands work with the fiber's context:
141
+
142
+ ~~~
143
+ (gdb) bt # C backtrace of fiber
144
+ #0 0x00007f8a1c567890 in fiber_setcontext
145
+ #1 0x00007f8a1c567900 in rb_fiber_yield
146
+ #2 0x00007f8a1c234567 in rb_io_wait_readable
147
+ ...
148
+
149
+ (gdb) frame 2
150
+ (gdb) info locals # Local C variables in that frame
151
+ (gdb) rb-object-print $ec->cfp->sp[-1] # Ruby values on VM stack
152
+ ~~~
153
+
154
+ ### Switching Back
155
+
156
+ Return to normal stack view:
157
+
158
+ ~~~
159
+ (gdb) rb-fiber-switch off
160
+ Fiber unwinder deactivated. Switched back to normal stack view.
161
+ ~~~
162
+
163
+ ## Analyzing Fiber State
164
+
165
+ ### VM Stack Inspection
166
+
167
+ View the Ruby VM stack for a fiber:
168
+
169
+ ~~~
170
+ (gdb) rb-fiber-vm-stack 5
171
+ VM Stack for Fiber #5:
172
+ Base: 0x7f8a1c950000
173
+ Size: 4096 VALUEs (32768 bytes)
174
+ CFP: 0x7f8a1c951000
175
+ ~~~
176
+
177
+ ### VM Control Frames
178
+
179
+ Walk through each Ruby method frame:
180
+
181
+ ~~~
182
+ (gdb) rb-fiber-vm-frames 5
183
+ VM Control Frames for Fiber #5:
184
+ ...
185
+
186
+ Frame #0 (depth 45):
187
+ CFP Address: 0x7f8a1c951000
188
+ PC: 0x7f8a1c234500
189
+ SP: 0x7f8a1c950100
190
+ Location: /app/lib/connection.rb:123
191
+ Method: read
192
+ Frame Type: VM_FRAME_MAGIC_METHOD
193
+ Stack Depth: 256 slots
194
+ ~~~
195
+
196
+ ### Stack Top Values
197
+
198
+ See what's on top of the VM stack:
199
+
200
+ ~~~
201
+ (gdb) rb-fiber-stack-top 5 20
202
+ VM Stack Top for Fiber #5:
203
+
204
+ Top 20 VALUE(s) on stack (newest first):
205
+
206
+ [ -1] 0x00007f8a1c888888 T_STRING "Hello"
207
+ [ -2] 0x0000000000000015 Fixnum(10) Fixnum: 10
208
+ [ -3] 0x00007f8a1c999999 T_HASH <hash:0x7f8a1c999999>
209
+ ...
210
+ ~~~
211
+
212
+ ## Diagnosing Crashes
213
+
214
+ When Ruby crashes, find out why:
215
+
216
+ ~~~
217
+ (gdb) rb-diagnose-exit
218
+ ================================================================================
219
+ DIAGNOSING RUBY EXIT/CRASH
220
+ ================================================================================
221
+
222
+ [1] Main Thread Exception:
223
+ --------------------------------------------------------------------------------
224
+ Main thread has exception: NoMethodError
225
+ VALUE: 0x7f8a1c777777
226
+ Use: rb-object-print (VALUE)0x7f8a1c777777
227
+
228
+ [2] Fibers with Exceptions:
229
+ --------------------------------------------------------------------------------
230
+ Fiber #5 (SUSPENDED): RuntimeError
231
+ Fiber: 0x7f8a1c800500, errinfo: 0x7f8a1c666666
232
+ Fiber #8 (SUSPENDED): IOError
233
+ Fiber: 0x7f8a1c800800, errinfo: 0x7f8a1c555555
234
+
235
+ [3] Interrupt Flags:
236
+ --------------------------------------------------------------------------------
237
+ interrupt_flag: 0x00000002
238
+ interrupt_mask: 0x00000000
239
+ WARNING: Interrupts pending!
240
+ - TRAP
241
+
242
+ [4] Signal Information (from core dump):
243
+ --------------------------------------------------------------------------------
244
+ Program terminated with signal SIGSEGV, Segmentation fault.
245
+ ...
246
+ ~~~
247
+
248
+ This comprehensive overview helps quickly identify the root cause.
249
+
250
+ ## Advanced Techniques
251
+
252
+ ### Finding Fibers by Stack Address
253
+
254
+ If you know a stack address, find the owning fiber:
255
+
256
+ ~~~
257
+ (gdb) info frame
258
+ ... rsp = 0x7f8a1e000500 ...
259
+
260
+ (gdb) rb-fiber-from-stack 0x7f8a1e000000
261
+ Searching for fiber with stack base 0x7f8a1e000000...
262
+
263
+ Found fiber: 0x7f8a1c800300
264
+ Status: SUSPENDED
265
+ Stack: base=0x7f8a1e000000, size=1048576
266
+ ~~~
267
+
268
+ ### Searching Fibers by Function
269
+
270
+ Find which fibers are blocked in a specific C function:
271
+
272
+ ~~~
273
+ (gdb) rb-fiber-c-stack-search pthread_cond_wait
274
+ Scanning 12 fiber(s)...
275
+
276
+ Match: Fiber #3 - found at 0x7f8a1e000450
277
+ Match: Fiber #7 - found at 0x7f8a1e100780
278
+
279
+ Search complete: 2 fiber(s) matched.
280
+ ~~~
281
+
282
+ Use this to find all fibers waiting on locks or I/O.
283
+
284
+ ### Debug Unwinder Issues
285
+
286
+ If fiber switching doesn't work as expected:
287
+
288
+ ~~~
289
+ (gdb) rb-fiber-debug-unwind 5
290
+ Debug unwinding for Fiber #5: 0x7f8a1c800500
291
+
292
+ Coroutine context:
293
+ fiber->context.stack_pointer = 0x7f8a1e000480
294
+
295
+ Saved registers on coroutine stack:
296
+ [0x7f8a1e000480+0] = R15: 0x0000000000000000
297
+ [0x7f8a1e000480+8] = R14: 0x00007f8a1c567890
298
+ ...
299
+ [0x7f8a1e000480+48] = RIP: 0x00007f8a1ab12345
300
+
301
+ Validation:
302
+ ✓ RIP looks like a valid code address
303
+ Symbol: fiber_setcontext + 123
304
+ ✓ RSP is within fiber's stack range
305
+ ~~~
306
+
307
+ ## Best Practices
308
+
309
+ ### Scan Once, Use Indices
310
+
311
+ After scanning fibers, use indices for all operations:
312
+
313
+ ~~~
314
+ (gdb) rb-scan-fibers # Scan once
315
+ (gdb) rb-fiber 5 # Use index thereafter
316
+ (gdb) rb-fiber-bt 5
317
+ (gdb) rb-fiber-switch 5
318
+ ~~~
319
+
320
+ The cache persists throughout your GDB session.
321
+
322
+ ### Check Fiber Status First
323
+
324
+ Before inspecting, check the fiber's status:
325
+
326
+ ~~~
327
+ (gdb) rb-fiber 5
328
+ Status: TERMINATED # Won't have useful context
329
+
330
+ (gdb) rb-fiber 3
331
+ Status: SUSPENDED # Good candidate for inspection
332
+ ~~~
333
+
334
+ CREATED and TERMINATED fibers may not have valid saved contexts.
335
+
336
+ ### Use Convenience Variables
337
+
338
+ After switching to a fiber, use the set convenience variables:
339
+
340
+ ~~~
341
+ (gdb) rb-fiber-switch 5
342
+ (gdb) rb-object-print $errinfo # Pre-set to fiber's exception
343
+ (gdb) rb-object-print $ec->storage # Fiber-local storage
344
+ ~~~
345
+
346
+ ## Common Pitfalls
347
+
348
+ ### Fiber Not Suspended
349
+
350
+ Only SUSPENDED fibers have fully saved context:
351
+
352
+ ~~~
353
+ Fiber status: CREATED (0)
354
+ Note: CREATED fibers may not have been suspended yet
355
+ ~~~
356
+
357
+ CREATED fibers haven't yielded yet, so they don't have saved register state.
358
+
359
+ ### Core Dump Limitations
360
+
361
+ Core dumps capture memory but not CPU registers. Switching to the current fiber may show incomplete stacks. For complete debugging, use live process debugging when possible.
362
+
363
+ ### macOS Code Signing
364
+
365
+ On macOS, GDB may not be able to attach to running Ruby processes due to code signing restrictions. Core dump analysis works without issues.
366
+
367
+ ## See Also
368
+
369
+ - {ruby Ruby::GDB::object-inspection Object inspection} for examining fiber storage and exception objects
370
+ - {ruby Ruby::GDB::stack-inspection Stack inspection} for understanding VM frames
371
+ - {ruby Ruby::GDB::heap-debugging Heap debugging} for finding fiber objects
372
+
@@ -0,0 +1,167 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to install and use Ruby GDB extensions for debugging Ruby programs and core dumps.
4
+
5
+ ## Installation
6
+
7
+ Install the gem:
8
+
9
+ ~~~ bash
10
+ $ gem install ruby-gdb
11
+ ~~~
12
+
13
+ ### Installing GDB Extensions
14
+
15
+ Install the extensions (automatically adds to `~/.gdbinit`):
16
+
17
+ ~~~ bash
18
+ $ bake ruby:gdb:install
19
+ ~~~
20
+
21
+ This adds a single line to your `~/.gdbinit` that sources the extensions from the gem's data directory. The extensions will then load automatically every time you start GDB.
22
+
23
+ To install to a custom `.gdbinit` location:
24
+
25
+ ~~~ bash
26
+ $ bake ruby:gdb:install gdbinit=/path/to/custom/gdbinit
27
+ ~~~
28
+
29
+ ### Verifying Installation
30
+
31
+ Check installation status:
32
+
33
+ ~~~ bash
34
+ $ bake ruby:gdb:info
35
+ Ruby GDB Extensions v0.1.0
36
+ Status: ✓ Installed
37
+ ~~~
38
+
39
+ Test that extensions load automatically:
40
+
41
+ ~~~ bash
42
+ $ gdb --batch -ex "help rb-object-print"
43
+ Recursively print Ruby hash and array structures...
44
+ ~~~
45
+
46
+ ### Uninstalling
47
+
48
+ To remove the extensions:
49
+
50
+ ~~~ bash
51
+ $ bake ruby:gdb:uninstall
52
+ ~~~
53
+
54
+ This removes the source line from your `~/.gdbinit`.
55
+
56
+ ## Core Concepts
57
+
58
+ Ruby GDB provides specialized commands for debugging Ruby at multiple levels:
59
+
60
+ - **Object Inspection** - View Ruby objects, hashes, arrays, and structs with {ruby Ruby::GDB::object-inspection proper formatting}
61
+ - **Fiber Debugging** - Navigate and inspect fiber state, backtraces, and exceptions
62
+ - **Stack Analysis** - Examine both VM (Ruby) and C (native) stack frames
63
+ - **Heap Navigation** - Scan the Ruby heap to find objects and understand memory usage
64
+
65
+ ## Quick Start
66
+
67
+ ### Debugging a Running Process
68
+
69
+ Start your Ruby program under GDB:
70
+
71
+ ~~~ bash
72
+ $ gdb --args ruby my_script.rb
73
+ ~~~
74
+
75
+ Set a breakpoint and run:
76
+
77
+ ~~~
78
+ (gdb) break rb_vm_exec
79
+ (gdb) run
80
+ ~~~
81
+
82
+ Once stopped, use Ruby debugging commands:
83
+
84
+ ~~~
85
+ (gdb) rb-object-print $ec->cfp->sp[-1] # Print top of VM stack
86
+ (gdb) rb-scan-fibers # List all fibers
87
+ (gdb) rb-fiber-bt 0 # Show fiber backtrace
88
+ ~~~
89
+
90
+ ### Debugging a Core Dump
91
+
92
+ When your Ruby program crashes, you can analyze the core dump:
93
+
94
+ ~~~ bash
95
+ $ gdb ruby core.dump
96
+ ~~~
97
+
98
+ Load the Ruby extensions and diagnose the issue:
99
+
100
+ ~~~
101
+ (gdb) source ~/.local/share/gdb/ruby/init.gdb
102
+ (gdb) rb-diagnose-exit # Check for exceptions and signals
103
+ (gdb) rb-scan-fibers # List all fibers
104
+ (gdb) rb-fiber-bt 0 # Show fiber backtraces
105
+ (gdb) rb-object-print $errinfo # Print exception objects
106
+ ~~~
107
+
108
+ ## Common Workflows
109
+
110
+ ### Inspecting Exception Objects
111
+
112
+ When a Ruby exception occurs, you can inspect it in detail:
113
+
114
+ ~~~
115
+ (gdb) break rb_exc_raise
116
+ (gdb) run
117
+ (gdb) rb-object-print $ec->errinfo --depth 2
118
+ ~~~
119
+
120
+ This shows the exception class, message, and any nested structures.
121
+
122
+ ### Debugging Fiber Issues
123
+
124
+ When working with fibers, you often need to see what each fiber is doing:
125
+
126
+ ~~~
127
+ (gdb) rb-scan-fibers # Scan heap for all fibers
128
+ (gdb) rb-all-fiber-bt # Show backtraces for all fibers
129
+ (gdb) rb-fiber 5 # Inspect specific fiber
130
+ (gdb) rb-fiber-switch 5 # Switch GDB to fiber's stack
131
+ (gdb) bt # Now shows fiber's C backtrace
132
+ ~~~
133
+
134
+ ### Examining Complex Data Structures
135
+
136
+ Ruby hashes and arrays can contain nested structures:
137
+
138
+ ~~~
139
+ (gdb) rb-object-print $some_hash --depth 3
140
+ (gdb) rb-object-print $ec->storage --depth 2
141
+ ~~~
142
+
143
+ The `--depth` flag controls how deep to recurse into nested objects.
144
+
145
+ ## Next Steps
146
+
147
+ - Learn about {ruby Ruby::GDB::object-inspection inspecting Ruby objects} in detail
148
+ - Explore {ruby Ruby::GDB::fiber-debugging fiber debugging} capabilities
149
+ - Understand {ruby Ruby::GDB::stack-inspection stack analysis} techniques
150
+ - Master {ruby Ruby::GDB::heap-debugging heap navigation} for memory issues
151
+
152
+ ## Requirements
153
+
154
+ - GDB with Python support (GDB 7.0+)
155
+ - Ruby 3.0+ recommended (works with 2.x with limitations)
156
+ - For best results: Ruby built with debug symbols (`--with-debug-symbols` or install `ruby-debug` package)
157
+
158
+ ## Platform Support
159
+
160
+ These extensions work on:
161
+
162
+ - **Linux**: Full support with all features
163
+ - **macOS**: Viewing core dumps works; running processes may require code signing or disabling SIP
164
+ - **BSD**: Should work similar to Linux (untested)
165
+
166
+ For production debugging, Linux with Ruby debug symbols provides the best experience.
167
+