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 +7 -0
- data/bake/ruby/gdb.rb +135 -0
- data/context/fiber-debugging.md +372 -0
- data/context/getting-started.md +167 -0
- data/context/heap-debugging.md +426 -0
- data/context/index.yaml +28 -0
- data/context/object-inspection.md +272 -0
- data/context/stack-inspection.md +357 -0
- data/data/ruby/gdb/command.py +254 -0
- data/data/ruby/gdb/constants.py +59 -0
- data/data/ruby/gdb/fiber.py +825 -0
- data/data/ruby/gdb/format.py +201 -0
- data/data/ruby/gdb/heap.py +563 -0
- data/data/ruby/gdb/init.py +25 -0
- data/data/ruby/gdb/object.py +85 -0
- data/data/ruby/gdb/rarray.py +124 -0
- data/data/ruby/gdb/rbasic.py +103 -0
- data/data/ruby/gdb/rbignum.py +52 -0
- data/data/ruby/gdb/rclass.py +133 -0
- data/data/ruby/gdb/rexception.py +150 -0
- data/data/ruby/gdb/rfloat.py +95 -0
- data/data/ruby/gdb/rhash.py +157 -0
- data/data/ruby/gdb/rstring.py +217 -0
- data/data/ruby/gdb/rstruct.py +157 -0
- data/data/ruby/gdb/rsymbol.py +291 -0
- data/data/ruby/gdb/stack.py +609 -0
- data/data/ruby/gdb/value.py +181 -0
- data/lib/ruby/gdb/version.rb +13 -0
- data/lib/ruby/gdb.rb +23 -0
- data/license.md +21 -0
- data/readme.md +42 -0
- metadata +69 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Heap Debugging
|
|
2
|
+
|
|
3
|
+
This guide explains how to navigate Ruby's heap to find objects, diagnose memory issues, and understand object relationships.
|
|
4
|
+
|
|
5
|
+
## Why Heap Debugging Matters
|
|
6
|
+
|
|
7
|
+
Ruby's garbage collector manages thousands to millions of objects across heap pages. When debugging memory leaks, performance issues, or trying to find specific objects, you need to navigate this heap efficiently. Standard GDB cannot understand Ruby's object layout or page structure, making manual heap inspection tedious and error-prone.
|
|
8
|
+
|
|
9
|
+
Use heap debugging when you need:
|
|
10
|
+
|
|
11
|
+
- **Find leaked objects**: Locate objects that should have been garbage collected
|
|
12
|
+
- **Discover fiber instances**: Find all fibers in a crashed or hung application
|
|
13
|
+
- **Track object relationships**: See what objects reference others
|
|
14
|
+
- **Diagnose memory bloat**: Understand what types of objects consume memory
|
|
15
|
+
|
|
16
|
+
## Heap Structure Overview
|
|
17
|
+
|
|
18
|
+
Ruby's heap is organized as:
|
|
19
|
+
|
|
20
|
+
1. **Heap Pages**: Fixed-size pages allocated from the system
|
|
21
|
+
2. **Slots**: Each page contains fixed-size slots for objects
|
|
22
|
+
3. **Objects**: Ruby objects (RBasic, RHash, RArray, etc.) stored in slots
|
|
23
|
+
4. **Object Space**: The global heap manager (`objspace`)
|
|
24
|
+
|
|
25
|
+
## Scanning the Heap
|
|
26
|
+
|
|
27
|
+
### Finding All Fibers
|
|
28
|
+
|
|
29
|
+
The most common use case - find every fiber in the application:
|
|
30
|
+
|
|
31
|
+
~~~
|
|
32
|
+
(gdb) rb-scan-fibers
|
|
33
|
+
Scanning 1250 heap pages...
|
|
34
|
+
Checked 45000 objects, found 5 fiber(s)...
|
|
35
|
+
Found fiber #1 at 0x7f8a1c800000
|
|
36
|
+
Found fiber #2 at 0x7f8a1c800100
|
|
37
|
+
...
|
|
38
|
+
Scan complete: checked 67890 objects
|
|
39
|
+
|
|
40
|
+
Found 12 fiber(s):
|
|
41
|
+
...
|
|
42
|
+
~~~
|
|
43
|
+
|
|
44
|
+
This iterates through all heap pages and identifies fiber objects by their type metadata.
|
|
45
|
+
|
|
46
|
+
### How Heap Scanning Works
|
|
47
|
+
|
|
48
|
+
The scanner:
|
|
49
|
+
|
|
50
|
+
1. Accesses `ruby_current_vm_ptr->gc->objspace`
|
|
51
|
+
2. Iterates through `heap_pages->sorted->data[]`
|
|
52
|
+
3. For each page, checks `total_slots` objects
|
|
53
|
+
4. Identifies objects by their `flags` field
|
|
54
|
+
5. Filters for specific types (e.g., T_DATA with `fiber_data_type`)
|
|
55
|
+
|
|
56
|
+
### Performance Considerations
|
|
57
|
+
|
|
58
|
+
Heap scanning can be slow in large applications:
|
|
59
|
+
|
|
60
|
+
~~~
|
|
61
|
+
Scanning 5000 heap pages...
|
|
62
|
+
Checked 100000 objects, found 50 fiber(s)...
|
|
63
|
+
Checked 200000 objects, found 125 fiber(s)...
|
|
64
|
+
...
|
|
65
|
+
~~~
|
|
66
|
+
|
|
67
|
+
Progress updates appear every 10,000 objects. For faster subsequent access, use caching:
|
|
68
|
+
|
|
69
|
+
~~~
|
|
70
|
+
(gdb) rb-scan-fibers --cache
|
|
71
|
+
... (scan happens) ...
|
|
72
|
+
Saved 125 fiber address(es) to fibers.json
|
|
73
|
+
|
|
74
|
+
# Later (even in a new GDB session):
|
|
75
|
+
(gdb) rb-scan-fibers --cache
|
|
76
|
+
Loaded 125 fiber address(es) from fibers.json
|
|
77
|
+
Successfully reconstructed 125 fiber object(s)
|
|
78
|
+
~~~
|
|
79
|
+
|
|
80
|
+
## Understanding Object Layout
|
|
81
|
+
|
|
82
|
+
### Object Headers (RBasic)
|
|
83
|
+
|
|
84
|
+
Every Ruby object starts with:
|
|
85
|
+
|
|
86
|
+
~~~ruby
|
|
87
|
+
struct RBasic {
|
|
88
|
+
VALUE flags; # Type and flag information
|
|
89
|
+
VALUE klass; # Object's class
|
|
90
|
+
}
|
|
91
|
+
~~~
|
|
92
|
+
|
|
93
|
+
The `flags` field encodes:
|
|
94
|
+
- Object type (bits 0-4): T_STRING, T_HASH, T_ARRAY, etc.
|
|
95
|
+
- GC flags (bit 5-11): Mark bits, frozen status, etc.
|
|
96
|
+
- Type-specific flags (bits 12+): Embedded vs heap, size info, etc.
|
|
97
|
+
|
|
98
|
+
### Type Detection
|
|
99
|
+
|
|
100
|
+
Check an object's type:
|
|
101
|
+
|
|
102
|
+
~~~
|
|
103
|
+
(gdb) set $obj = (VALUE)0x7f8a1c888888
|
|
104
|
+
(gdb) set $basic = (struct RBasic *)$obj
|
|
105
|
+
(gdb) p/x $basic->flags
|
|
106
|
+
$1 = 0x20040005
|
|
107
|
+
|
|
108
|
+
(gdb) p/x $basic->flags & 0x1f
|
|
109
|
+
$2 = 0x5 # T_STRING (0x05)
|
|
110
|
+
~~~
|
|
111
|
+
|
|
112
|
+
Or use Ruby commands:
|
|
113
|
+
|
|
114
|
+
~~~python
|
|
115
|
+
obj_type = flags & 0x1f
|
|
116
|
+
type_names = {
|
|
117
|
+
0x05: "T_STRING",
|
|
118
|
+
0x07: "T_ARRAY",
|
|
119
|
+
0x08: "T_HASH",
|
|
120
|
+
0x0c: "T_DATA",
|
|
121
|
+
...
|
|
122
|
+
}
|
|
123
|
+
~~~
|
|
124
|
+
|
|
125
|
+
## Practical Examples
|
|
126
|
+
|
|
127
|
+
### Finding All Objects of a Type
|
|
128
|
+
|
|
129
|
+
Scan for all string objects (requires custom script):
|
|
130
|
+
|
|
131
|
+
~~~python
|
|
132
|
+
python
|
|
133
|
+
for obj, flags in iterate_heap():
|
|
134
|
+
if (flags & 0x1f) == 0x05: # T_STRING
|
|
135
|
+
print(f"String at {obj}")
|
|
136
|
+
end
|
|
137
|
+
~~~
|
|
138
|
+
|
|
139
|
+
### Locating Large Arrays
|
|
140
|
+
|
|
141
|
+
Find arrays consuming significant memory:
|
|
142
|
+
|
|
143
|
+
~~~python
|
|
144
|
+
python
|
|
145
|
+
for obj, flags in iterate_heap():
|
|
146
|
+
if (flags & 0x1f) == 0x07: # T_ARRAY
|
|
147
|
+
rarray = obj.cast(gdb.lookup_type("struct RArray").pointer())
|
|
148
|
+
if (flags & (1 << 12)) == 0: # Heap array (not embedded)
|
|
149
|
+
length = int(rarray["as"]["heap"]["len"])
|
|
150
|
+
if length > 1000:
|
|
151
|
+
print(f"Large array at {obj}: {length} elements")
|
|
152
|
+
end
|
|
153
|
+
~~~
|
|
154
|
+
|
|
155
|
+
### Finding Objects by Content
|
|
156
|
+
|
|
157
|
+
Search for strings containing specific text:
|
|
158
|
+
|
|
159
|
+
~~~python
|
|
160
|
+
python
|
|
161
|
+
import gdb
|
|
162
|
+
|
|
163
|
+
def find_strings_containing(search_text):
|
|
164
|
+
"""Find all Ruby strings containing specific text"""
|
|
165
|
+
matches = []
|
|
166
|
+
for obj, flags in iterate_heap():
|
|
167
|
+
if (flags & 0x1f) == 0x05: # T_STRING
|
|
168
|
+
try:
|
|
169
|
+
rstring = obj.cast(gdb.lookup_type("struct RString").pointer())
|
|
170
|
+
length = int(rstring["len"])
|
|
171
|
+
|
|
172
|
+
# Read string content (simplified)
|
|
173
|
+
if length < 1000: # Reasonable size
|
|
174
|
+
# ... (read string bytes) ...
|
|
175
|
+
if search_text in string_content:
|
|
176
|
+
matches.append(obj)
|
|
177
|
+
except:
|
|
178
|
+
pass
|
|
179
|
+
return matches
|
|
180
|
+
end
|
|
181
|
+
~~~
|
|
182
|
+
|
|
183
|
+
## Memory Analysis
|
|
184
|
+
|
|
185
|
+
### Heap Page Statistics
|
|
186
|
+
|
|
187
|
+
Understand heap organization:
|
|
188
|
+
|
|
189
|
+
~~~
|
|
190
|
+
(gdb) p ruby_current_vm_ptr->gc->objspace->heap_pages->allocated_pages
|
|
191
|
+
$1 = 1250 # Total pages allocated
|
|
192
|
+
|
|
193
|
+
(gdb) p ruby_current_vm_ptr->gc->objspace->heap_pages->sorted->meta->length
|
|
194
|
+
$2 = 1250 # Accessible pages
|
|
195
|
+
|
|
196
|
+
# Average objects per page:
|
|
197
|
+
(gdb) p 67890 / 1250
|
|
198
|
+
$3 = 54 # ~54 objects per page
|
|
199
|
+
~~~
|
|
200
|
+
|
|
201
|
+
### Object Slot Details
|
|
202
|
+
|
|
203
|
+
Examine a specific heap page:
|
|
204
|
+
|
|
205
|
+
~~~
|
|
206
|
+
(gdb) set $page = ruby_current_vm_ptr->gc->objspace->heap_pages->sorted->data[0]
|
|
207
|
+
(gdb) p $page->start # First object address
|
|
208
|
+
$4 = 0x7f8a1c000000
|
|
209
|
+
|
|
210
|
+
(gdb) p $page->total_slots # Objects in this page
|
|
211
|
+
$5 = 64
|
|
212
|
+
|
|
213
|
+
(gdb) p $page->slot_size # Bytes per slot
|
|
214
|
+
$6 = 40
|
|
215
|
+
~~~
|
|
216
|
+
|
|
217
|
+
### Memory Consumption
|
|
218
|
+
|
|
219
|
+
Calculate memory usage by type:
|
|
220
|
+
|
|
221
|
+
~~~python
|
|
222
|
+
python
|
|
223
|
+
type_counts = {}
|
|
224
|
+
type_memory = {}
|
|
225
|
+
|
|
226
|
+
for obj, flags in iterate_heap():
|
|
227
|
+
obj_type = flags & 0x1f
|
|
228
|
+
type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
|
|
229
|
+
# Each object consumes at minimum one slot
|
|
230
|
+
type_memory[obj_type] = type_memory.get(obj_type, 0) + 40 # slot_size
|
|
231
|
+
|
|
232
|
+
for obj_type in sorted(type_counts.keys()):
|
|
233
|
+
count = type_counts[obj_type]
|
|
234
|
+
memory_kb = type_memory[obj_type] / 1024
|
|
235
|
+
print(f"Type 0x{obj_type:02x}: {count:6} objects, {memory_kb:8.1f} KB")
|
|
236
|
+
end
|
|
237
|
+
~~~
|
|
238
|
+
|
|
239
|
+
## Advanced Techniques
|
|
240
|
+
|
|
241
|
+
### Custom Heap Iterators
|
|
242
|
+
|
|
243
|
+
The fiber scanning code provides a reusable heap iterator:
|
|
244
|
+
|
|
245
|
+
~~~python
|
|
246
|
+
python
|
|
247
|
+
# In your custom GDB script:
|
|
248
|
+
from fiber import RubyFiberDebug
|
|
249
|
+
|
|
250
|
+
debug = RubyFiberDebug()
|
|
251
|
+
if debug.initialize():
|
|
252
|
+
for obj, flags in debug.iterate_heap():
|
|
253
|
+
# Your custom logic here
|
|
254
|
+
pass
|
|
255
|
+
end
|
|
256
|
+
~~~
|
|
257
|
+
|
|
258
|
+
### Finding Objects by Reference
|
|
259
|
+
|
|
260
|
+
Locate what holds a reference to an object:
|
|
261
|
+
|
|
262
|
+
~~~python
|
|
263
|
+
python
|
|
264
|
+
target_address = 0x7f8a1c888888
|
|
265
|
+
|
|
266
|
+
for obj, flags in iterate_heap():
|
|
267
|
+
obj_type = flags & 0x1f
|
|
268
|
+
|
|
269
|
+
# Check if this is a hash
|
|
270
|
+
if obj_type == 0x08:
|
|
271
|
+
rhash = obj.cast(gdb.lookup_type("struct RHash").pointer())
|
|
272
|
+
# ... iterate hash entries ...
|
|
273
|
+
# ... check if any value == target_address ...
|
|
274
|
+
|
|
275
|
+
# Check if this is an array
|
|
276
|
+
elif obj_type == 0x07:
|
|
277
|
+
# ... iterate array elements ...
|
|
278
|
+
end
|
|
279
|
+
~~~
|
|
280
|
+
|
|
281
|
+
This helps track down unexpected object retention.
|
|
282
|
+
|
|
283
|
+
### Heap Fragmentation Analysis
|
|
284
|
+
|
|
285
|
+
Check how objects are distributed across pages:
|
|
286
|
+
|
|
287
|
+
~~~python
|
|
288
|
+
python
|
|
289
|
+
page_utilization = []
|
|
290
|
+
|
|
291
|
+
objspace = gdb.parse_and_eval("ruby_current_vm_ptr->gc->objspace")
|
|
292
|
+
allocated_pages = int(objspace["heap_pages"]["allocated_pages"])
|
|
293
|
+
|
|
294
|
+
for i in range(allocated_pages):
|
|
295
|
+
page = objspace["heap_pages"]["sorted"]["data"][i]
|
|
296
|
+
total_slots = int(page["total_slots"])
|
|
297
|
+
|
|
298
|
+
# Count non-free objects
|
|
299
|
+
used_slots = 0
|
|
300
|
+
# ... iterate and count ...
|
|
301
|
+
|
|
302
|
+
utilization = (used_slots / total_slots) * 100
|
|
303
|
+
page_utilization.append(utilization)
|
|
304
|
+
|
|
305
|
+
average = sum(page_utilization) / len(page_utilization)
|
|
306
|
+
print(f"Average page utilization: {average:.1f}%")
|
|
307
|
+
end
|
|
308
|
+
~~~
|
|
309
|
+
|
|
310
|
+
Low utilization indicates fragmentation.
|
|
311
|
+
|
|
312
|
+
## Best Practices
|
|
313
|
+
|
|
314
|
+
### Cache Fiber Results
|
|
315
|
+
|
|
316
|
+
Don't scan repeatedly in the same session:
|
|
317
|
+
|
|
318
|
+
~~~
|
|
319
|
+
(gdb) rb-scan-fibers --cache fibers.json # Scan once
|
|
320
|
+
(gdb) rb-fiber 5 # Use cached results
|
|
321
|
+
(gdb) rb-fiber-bt 5
|
|
322
|
+
...
|
|
323
|
+
# Later in session:
|
|
324
|
+
(gdb) rb-fiber 10 # Still using cache
|
|
325
|
+
~~~
|
|
326
|
+
|
|
327
|
+
### Limit Scans in Production
|
|
328
|
+
|
|
329
|
+
For production core dumps with millions of objects:
|
|
330
|
+
|
|
331
|
+
~~~
|
|
332
|
+
(gdb) rb-scan-fibers 20 # Find first 20 fibers only
|
|
333
|
+
~~~
|
|
334
|
+
|
|
335
|
+
Often you only need a few fibers to diagnose issues.
|
|
336
|
+
|
|
337
|
+
### Use Object Inspection Together
|
|
338
|
+
|
|
339
|
+
Combine heap scanning with object inspection:
|
|
340
|
+
|
|
341
|
+
~~~
|
|
342
|
+
(gdb) rb-scan-fibers
|
|
343
|
+
Fiber #5 has exception: IOError
|
|
344
|
+
|
|
345
|
+
(gdb) rb-fiber 5
|
|
346
|
+
(gdb) set $ec = ... # (shown in output)
|
|
347
|
+
(gdb) rb-object-print $ec->errinfo --depth 3
|
|
348
|
+
~~~
|
|
349
|
+
|
|
350
|
+
## Common Pitfalls
|
|
351
|
+
|
|
352
|
+
### Stale Cache Files
|
|
353
|
+
|
|
354
|
+
If you load a cache from a different core dump:
|
|
355
|
+
|
|
356
|
+
~~~
|
|
357
|
+
(gdb) rb-scan-fibers --cache
|
|
358
|
+
Loaded 125 fiber address(es) from fibers.json
|
|
359
|
+
Warning: Could not access fiber at 0x7f8a1c800500
|
|
360
|
+
Warning: Could not access fiber at 0x7f8a1c800600
|
|
361
|
+
...
|
|
362
|
+
~~~
|
|
363
|
+
|
|
364
|
+
Delete the cache and rescan:
|
|
365
|
+
|
|
366
|
+
~~~ bash
|
|
367
|
+
$ rm fibers.json
|
|
368
|
+
~~~
|
|
369
|
+
|
|
370
|
+
### Scanning Uninitialized VM
|
|
371
|
+
|
|
372
|
+
If you scan too early during Ruby initialization:
|
|
373
|
+
|
|
374
|
+
~~~
|
|
375
|
+
(gdb) rb-scan-fibers
|
|
376
|
+
Error: ruby_current_vm_ptr is NULL
|
|
377
|
+
Make sure Ruby is fully initialized and the process is running.
|
|
378
|
+
~~~
|
|
379
|
+
|
|
380
|
+
Set a breakpoint after VM initialization:
|
|
381
|
+
|
|
382
|
+
~~~
|
|
383
|
+
(gdb) break rb_vm_exec
|
|
384
|
+
(gdb) run
|
|
385
|
+
(gdb) rb-scan-fibers # Now works
|
|
386
|
+
~~~
|
|
387
|
+
|
|
388
|
+
### Memory Errors in Core Dumps
|
|
389
|
+
|
|
390
|
+
Some heap pages may be unmapped in core dumps:
|
|
391
|
+
|
|
392
|
+
~~~
|
|
393
|
+
Scanning 5000 heap pages...
|
|
394
|
+
Error reading page 1234: Cannot access memory at address 0x...
|
|
395
|
+
~~~
|
|
396
|
+
|
|
397
|
+
This is normal - continue with accessible pages.
|
|
398
|
+
|
|
399
|
+
## Performance Optimization
|
|
400
|
+
|
|
401
|
+
### Targeted Scanning
|
|
402
|
+
|
|
403
|
+
Instead of scanning the entire heap, use known addresses:
|
|
404
|
+
|
|
405
|
+
~~~
|
|
406
|
+
(gdb) rb-fiber-from-stack 0x7f8a1e000000
|
|
407
|
+
Searching for fiber with stack base 0x7f8a1e000000...
|
|
408
|
+
Found fiber: 0x7f8a1c800300
|
|
409
|
+
~~~
|
|
410
|
+
|
|
411
|
+
### Heap Iteration Caching
|
|
412
|
+
|
|
413
|
+
The scanner caches GDB type lookups for performance:
|
|
414
|
+
|
|
415
|
+
- `struct RBasic`
|
|
416
|
+
- `struct RTypedData`
|
|
417
|
+
- `struct rb_fiber_struct`
|
|
418
|
+
|
|
419
|
+
This makes subsequent iterations much faster.
|
|
420
|
+
|
|
421
|
+
## See Also
|
|
422
|
+
|
|
423
|
+
- {ruby Ruby::GDB::fiber-debugging Fiber debugging} for working with found fibers
|
|
424
|
+
- {ruby Ruby::GDB::object-inspection Object inspection} for examining heap objects
|
|
425
|
+
- {ruby Ruby::GDB::stack-inspection Stack inspection} for understanding object references
|
|
426
|
+
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
|
+
---
|
|
4
|
+
description: Ruby debugging extensions for GDB
|
|
5
|
+
metadata:
|
|
6
|
+
documentation_uri: https://socketry.github.io/ruby-gdb/
|
|
7
|
+
source_code_uri: https://github.com/socketry/ruby-gdb
|
|
8
|
+
files:
|
|
9
|
+
- path: getting-started.md
|
|
10
|
+
title: Getting Started
|
|
11
|
+
description: This guide explains how to install and use Ruby GDB extensions for
|
|
12
|
+
debugging Ruby programs and core dumps.
|
|
13
|
+
- path: object-inspection.md
|
|
14
|
+
title: Object Inspection
|
|
15
|
+
description: This guide explains how to use `rb-object-print` to inspect Ruby objects,
|
|
16
|
+
hashes, arrays, and structs in GDB.
|
|
17
|
+
- path: stack-inspection.md
|
|
18
|
+
title: Stack Inspection
|
|
19
|
+
description: This guide explains how to inspect both Ruby VM stacks and native C
|
|
20
|
+
stacks when debugging Ruby programs.
|
|
21
|
+
- path: fiber-debugging.md
|
|
22
|
+
title: Fiber Debugging
|
|
23
|
+
description: This guide explains how to debug Ruby fibers using GDB, including inspecting
|
|
24
|
+
fiber state, backtraces, and switching between fiber contexts.
|
|
25
|
+
- path: heap-debugging.md
|
|
26
|
+
title: Heap Debugging
|
|
27
|
+
description: This guide explains how to navigate Ruby's heap to find objects, diagnose
|
|
28
|
+
memory issues, and understand object relationships.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Object Inspection
|
|
2
|
+
|
|
3
|
+
This guide explains how to use `rb-object-print` to inspect Ruby objects, hashes, arrays, and structs in GDB.
|
|
4
|
+
|
|
5
|
+
## Why Object Inspection Matters
|
|
6
|
+
|
|
7
|
+
When debugging Ruby programs or analyzing core dumps, you often need to inspect complex data structures that are difficult to read in their raw memory representation. Standard GDB commands show pointer addresses and raw memory, but not the logical structure of Ruby objects.
|
|
8
|
+
|
|
9
|
+
Use `rb-object-print` when you need:
|
|
10
|
+
|
|
11
|
+
- **Understand exception objects**: See the full exception hierarchy, message, and backtrace data
|
|
12
|
+
- **Inspect fiber storage**: View thread-local data and fiber-specific variables
|
|
13
|
+
- **Debug data corruption**: Check the contents of hashes and arrays for unexpected values
|
|
14
|
+
- **Analyze VM state**: Examine objects on the VM stack without manual pointer arithmetic
|
|
15
|
+
|
|
16
|
+
## Basic Usage
|
|
17
|
+
|
|
18
|
+
The `rb-object-print` command recursively prints Ruby objects in a human-readable format.
|
|
19
|
+
|
|
20
|
+
### Syntax
|
|
21
|
+
|
|
22
|
+
~~~
|
|
23
|
+
rb-object-print <expression> [--depth N] [--debug]
|
|
24
|
+
~~~
|
|
25
|
+
|
|
26
|
+
Where:
|
|
27
|
+
- `<expression>`: Any GDB expression that evaluates to a Ruby VALUE
|
|
28
|
+
- `--depth N`: Maximum recursion depth (default: 1)
|
|
29
|
+
- `--debug`: Enable diagnostic output for troubleshooting
|
|
30
|
+
|
|
31
|
+
### Simple Values
|
|
32
|
+
|
|
33
|
+
Print immediate values and special constants:
|
|
34
|
+
|
|
35
|
+
~~~
|
|
36
|
+
(gdb) rb-object-print 0 # Qfalse
|
|
37
|
+
(gdb) rb-object-print 8 # Qnil
|
|
38
|
+
(gdb) rb-object-print 20 # Qtrue
|
|
39
|
+
(gdb) rb-object-print 85 # Fixnum: 42
|
|
40
|
+
~~~
|
|
41
|
+
|
|
42
|
+
These work without any Ruby process running, making them useful for learning and testing.
|
|
43
|
+
|
|
44
|
+
### Expressions
|
|
45
|
+
|
|
46
|
+
Use any valid GDB expression:
|
|
47
|
+
|
|
48
|
+
~~~
|
|
49
|
+
(gdb) rb-object-print $ec->errinfo # Exception object
|
|
50
|
+
(gdb) rb-object-print $ec->cfp->sp[-1] # Top of VM stack
|
|
51
|
+
(gdb) rb-object-print $ec->storage # Fiber storage hash
|
|
52
|
+
(gdb) rb-object-print (VALUE)0x00007f8a12345678 # Object at specific address
|
|
53
|
+
~~~
|
|
54
|
+
|
|
55
|
+
## Inspecting Hashes
|
|
56
|
+
|
|
57
|
+
Ruby hashes have two internal representations (ST table and AR table). The command automatically detects and displays both:
|
|
58
|
+
|
|
59
|
+
### Small Hashes (AR Table)
|
|
60
|
+
|
|
61
|
+
For hashes with fewer than 8 entries, Ruby uses an array-based implementation:
|
|
62
|
+
|
|
63
|
+
~~~
|
|
64
|
+
(gdb) rb-object-print $some_hash
|
|
65
|
+
AR Table at 0x7f8a1c123456 (size=4, bound=3):
|
|
66
|
+
[ 0] K: :name
|
|
67
|
+
V: "Alice"
|
|
68
|
+
[ 1] K: :age
|
|
69
|
+
V: Fixnum: 30
|
|
70
|
+
[ 2] K: :active
|
|
71
|
+
V: Qtrue
|
|
72
|
+
~~~
|
|
73
|
+
|
|
74
|
+
### Large Hashes (ST Table)
|
|
75
|
+
|
|
76
|
+
For larger hashes, Ruby uses a hash table:
|
|
77
|
+
|
|
78
|
+
~~~
|
|
79
|
+
(gdb) rb-object-print $large_hash
|
|
80
|
+
ST Table at 0x7f8a1c789abc (15 entries):
|
|
81
|
+
[ 0] K: :user_id
|
|
82
|
+
V: Fixnum: 12345
|
|
83
|
+
[ 1] K: :session_data
|
|
84
|
+
V: <RString>
|
|
85
|
+
...
|
|
86
|
+
~~~
|
|
87
|
+
|
|
88
|
+
### Controlling Depth
|
|
89
|
+
|
|
90
|
+
Prevent overwhelming output from deeply nested structures:
|
|
91
|
+
|
|
92
|
+
~~~
|
|
93
|
+
(gdb) rb-object-print $nested_hash --depth 1 # Only top level
|
|
94
|
+
(gdb) rb-object-print $nested_hash --depth 2 # One level of nesting
|
|
95
|
+
(gdb) rb-object-print $nested_hash --depth 5 # Deep inspection
|
|
96
|
+
~~~
|
|
97
|
+
|
|
98
|
+
At depth 1, nested hashes/arrays show as `<RHash>` or `<RArray>`. Increase depth to expand them.
|
|
99
|
+
|
|
100
|
+
## Inspecting Arrays
|
|
101
|
+
|
|
102
|
+
Arrays also have two representations based on size:
|
|
103
|
+
|
|
104
|
+
### Embedded Arrays
|
|
105
|
+
|
|
106
|
+
Small arrays (up to 3 elements on 64-bit) are stored inline:
|
|
107
|
+
|
|
108
|
+
~~~
|
|
109
|
+
(gdb) rb-object-print $small_array
|
|
110
|
+
Embedded Array at 0x7f8a1c234567 (length 3)
|
|
111
|
+
[ 0] I: Fixnum: 1
|
|
112
|
+
[ 1] I: Fixnum: 2
|
|
113
|
+
[ 2] I: Fixnum: 3
|
|
114
|
+
~~~
|
|
115
|
+
|
|
116
|
+
### Heap Arrays
|
|
117
|
+
|
|
118
|
+
Larger arrays allocate separate memory:
|
|
119
|
+
|
|
120
|
+
~~~
|
|
121
|
+
(gdb) rb-object-print $big_array --depth 2
|
|
122
|
+
Heap Array at 0x7f8a1c345678 (length 100)
|
|
123
|
+
[ 0] I: Fixnum: 1
|
|
124
|
+
[ 1] I: "first item"
|
|
125
|
+
...
|
|
126
|
+
~~~
|
|
127
|
+
|
|
128
|
+
## Inspecting Structs
|
|
129
|
+
|
|
130
|
+
Ruby Struct objects work similarly to arrays:
|
|
131
|
+
|
|
132
|
+
~~~
|
|
133
|
+
(gdb) rb-object-print $struct_instance
|
|
134
|
+
Heap Struct at 0x7f8a1c456789 (length 4)
|
|
135
|
+
[ 0] I: "John"
|
|
136
|
+
[ 1] I: Fixnum: 25
|
|
137
|
+
[ 2] I: "Engineer"
|
|
138
|
+
[ 3] I: Qtrue
|
|
139
|
+
~~~
|
|
140
|
+
|
|
141
|
+
## Practical Examples
|
|
142
|
+
|
|
143
|
+
### Debugging Exception in Fiber
|
|
144
|
+
|
|
145
|
+
When a fiber has an exception, inspect it:
|
|
146
|
+
|
|
147
|
+
~~~
|
|
148
|
+
(gdb) rb-scan-fibers
|
|
149
|
+
(gdb) rb-fiber 5 # Shows fiber with exception
|
|
150
|
+
(gdb) set $ec = ... # (shown in output)
|
|
151
|
+
(gdb) rb-object-print $ec->errinfo --depth 3
|
|
152
|
+
~~~
|
|
153
|
+
|
|
154
|
+
This reveals the full exception structure including any nested causes.
|
|
155
|
+
|
|
156
|
+
### Inspecting Method Arguments
|
|
157
|
+
|
|
158
|
+
Break at a method and examine arguments on the stack:
|
|
159
|
+
|
|
160
|
+
~~~
|
|
161
|
+
(gdb) break some_method
|
|
162
|
+
(gdb) run
|
|
163
|
+
(gdb) rb-object-print $ec->cfp->sp[-1] # Last argument
|
|
164
|
+
(gdb) rb-object-print $ec->cfp->sp[-2] # Second-to-last argument
|
|
165
|
+
~~~
|
|
166
|
+
|
|
167
|
+
### Examining Fiber Storage
|
|
168
|
+
|
|
169
|
+
Thread-local variables are stored in fiber storage:
|
|
170
|
+
|
|
171
|
+
~~~
|
|
172
|
+
(gdb) rb-scan-fibers
|
|
173
|
+
(gdb) rb-fiber 0
|
|
174
|
+
(gdb) rb-object-print $ec->storage --depth 2
|
|
175
|
+
~~~
|
|
176
|
+
|
|
177
|
+
This shows all thread-local variables and their values.
|
|
178
|
+
|
|
179
|
+
## Debugging with --debug Flag
|
|
180
|
+
|
|
181
|
+
When `rb-object-print` doesn't show what you expect, use `--debug`:
|
|
182
|
+
|
|
183
|
+
~~~
|
|
184
|
+
(gdb) rb-object-print $suspicious_value --debug
|
|
185
|
+
DEBUG: Evaluated '$suspicious_value' to 0x7f8a1c567890
|
|
186
|
+
DEBUG: Loaded constant RUBY_T_MASK = 31
|
|
187
|
+
DEBUG: Object at 0x7f8a1c567890 with flags=0x20040005, type=0x5
|
|
188
|
+
...
|
|
189
|
+
~~~
|
|
190
|
+
|
|
191
|
+
This shows:
|
|
192
|
+
- How the expression was evaluated
|
|
193
|
+
- What constants were loaded
|
|
194
|
+
- Object type detection logic
|
|
195
|
+
- Any errors encountered
|
|
196
|
+
|
|
197
|
+
Use this to troubleshoot:
|
|
198
|
+
- Unexpected output format
|
|
199
|
+
- Missing nested structures
|
|
200
|
+
- Type detection issues
|
|
201
|
+
- Access errors
|
|
202
|
+
|
|
203
|
+
## Best Practices
|
|
204
|
+
|
|
205
|
+
### Choose Appropriate Depth
|
|
206
|
+
|
|
207
|
+
- **Depth 1**: Quick overview, minimal output (default)
|
|
208
|
+
- **Depth 2-3**: Common for most debugging tasks
|
|
209
|
+
- **Depth 5+**: Only for deep investigation, can be verbose
|
|
210
|
+
|
|
211
|
+
### Work with Core Dumps
|
|
212
|
+
|
|
213
|
+
The command works perfectly with core dumps since it only reads memory:
|
|
214
|
+
|
|
215
|
+
~~~
|
|
216
|
+
$ gdb ruby core.12345
|
|
217
|
+
(gdb) source ~/.local/share/gdb/ruby/init.gdb
|
|
218
|
+
(gdb) rb-object-print $ec->errinfo
|
|
219
|
+
~~~
|
|
220
|
+
|
|
221
|
+
No running process needed!
|
|
222
|
+
|
|
223
|
+
### Combine with Standard GDB
|
|
224
|
+
|
|
225
|
+
Use standard GDB commands alongside Ruby extensions:
|
|
226
|
+
|
|
227
|
+
~~~
|
|
228
|
+
(gdb) info locals # See C variables
|
|
229
|
+
(gdb) rb-object-print $val # Interpret as Ruby object
|
|
230
|
+
(gdb) x/10gx $ec->cfp->sp # Raw memory view
|
|
231
|
+
~~~
|
|
232
|
+
|
|
233
|
+
## Common Pitfalls
|
|
234
|
+
|
|
235
|
+
### Accessing Deallocated Objects
|
|
236
|
+
|
|
237
|
+
If an object address is invalid, you'll see an error:
|
|
238
|
+
|
|
239
|
+
~~~
|
|
240
|
+
(gdb) rb-object-print (VALUE)0xdeadbeef
|
|
241
|
+
[Error printing object: Cannot access memory at address 0xdeadbeef]
|
|
242
|
+
~~~
|
|
243
|
+
|
|
244
|
+
Always verify the address is valid before inspecting.
|
|
245
|
+
|
|
246
|
+
### Depth Too Low
|
|
247
|
+
|
|
248
|
+
If you see `<RHash>` or `<RArray>` where you expected expanded content:
|
|
249
|
+
|
|
250
|
+
~~~
|
|
251
|
+
(gdb) rb-object-print $hash # Shows: <RHash>
|
|
252
|
+
(gdb) rb-object-print $hash --depth 2 # Shows actual content
|
|
253
|
+
~~~
|
|
254
|
+
|
|
255
|
+
Increase `--depth` to see nested structures.
|
|
256
|
+
|
|
257
|
+
### Missing Debug Symbols
|
|
258
|
+
|
|
259
|
+
Without debug symbols, some type information may be unavailable:
|
|
260
|
+
|
|
261
|
+
~~~
|
|
262
|
+
Python Exception <class 'gdb.error'>: No type named RBasic
|
|
263
|
+
~~~
|
|
264
|
+
|
|
265
|
+
Solution: Install Ruby with debug symbols or use a debug build.
|
|
266
|
+
|
|
267
|
+
## See Also
|
|
268
|
+
|
|
269
|
+
- {ruby Ruby::GDB::fiber-debugging Fiber debugging} for inspecting fiber-specific data
|
|
270
|
+
- {ruby Ruby::GDB::stack-inspection Stack inspection} for examining call frames
|
|
271
|
+
- {ruby Ruby::GDB::heap-debugging Heap debugging} for scanning objects
|
|
272
|
+
|