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.
@@ -0,0 +1,563 @@
1
+ import gdb
2
+ import sys
3
+
4
+ # Constants
5
+ RBASIC_FLAGS_TYPE_MASK = 0x1f
6
+
7
+ class RubyHeap:
8
+ """Ruby heap scanning infrastructure.
9
+
10
+ Provides methods to iterate through the Ruby heap and find objects
11
+ by type. Returns VALUEs (not extracted pointers) for maximum flexibility.
12
+ """
13
+
14
+ def __init__(self):
15
+ """Initialize heap scanner (call initialize() to set up VM pointers)."""
16
+ self.vm_ptr = None
17
+ self.objspace = None
18
+
19
+ # Cached type lookups
20
+ self._rbasic_type = None
21
+ self._value_type = None
22
+ self._char_ptr_type = None
23
+
24
+ def initialize(self):
25
+ """Initialize VM and objspace pointers.
26
+
27
+ Returns:
28
+ True if initialization successful, False otherwise
29
+ """
30
+ try:
31
+ self.vm_ptr = gdb.parse_and_eval('ruby_current_vm_ptr')
32
+ if int(self.vm_ptr) == 0:
33
+ print("Error: ruby_current_vm_ptr is NULL")
34
+ print("Make sure Ruby is fully initialized and the process is running.")
35
+ return False
36
+
37
+ # Ruby 3.3+ moved objspace into a gc struct
38
+ try:
39
+ self.objspace = self.vm_ptr['gc']['objspace']
40
+ except (gdb.error, KeyError):
41
+ # Ruby 3.2 and earlier have objspace directly in VM
42
+ self.objspace = self.vm_ptr['objspace']
43
+
44
+ if int(self.objspace) == 0:
45
+ print("Error: objspace is NULL")
46
+ print("Make sure the Ruby GC has been initialized.")
47
+ return False
48
+
49
+ # Cache commonly used type lookups
50
+ self._rbasic_type = gdb.lookup_type('struct RBasic').pointer()
51
+ self._value_type = gdb.lookup_type('VALUE')
52
+ self._char_ptr_type = gdb.lookup_type('char').pointer()
53
+
54
+ return True
55
+ except gdb.error as e:
56
+ print(f"Error initializing: {e}")
57
+ print("Make sure you're debugging a Ruby process with debug symbols.")
58
+ return False
59
+ except gdb.MemoryError as e:
60
+ print(f"Memory error during initialization: {e}")
61
+ print("The Ruby VM may not be fully initialized yet.")
62
+ print("Try breaking at a point where Ruby is running (e.g., after rb_vm_exec).")
63
+ return False
64
+
65
+ def _get_page(self, page_index):
66
+ """Get a heap page by index, handling Ruby version differences.
67
+
68
+ Args:
69
+ page_index: Index of the page to retrieve
70
+
71
+ Returns:
72
+ Page object, or None on error
73
+ """
74
+ try:
75
+ # Ruby 3.3+ uses rb_darray with 'data' field, Ruby 3.2- uses direct pointer
76
+ try:
77
+ return self.objspace['heap_pages']['sorted']['data'][page_index]
78
+ except (gdb.error, KeyError):
79
+ # Ruby 3.2 and earlier: sorted is a direct pointer array
80
+ return self.objspace['heap_pages']['sorted'][page_index]
81
+ except (gdb.MemoryError, gdb.error):
82
+ return None
83
+
84
+ def iterate_heap(self):
85
+ """Yield all objects from the Ruby heap.
86
+
87
+ Yields:
88
+ Tuple of (VALUE, flags, address) for each object on the heap
89
+ """
90
+ for obj, flags, address in self.iterate_heap_from(None):
91
+ yield obj, flags, address
92
+
93
+ def scan(self, type_flag=None, limit=None, from_address=None):
94
+ """Scan heap for objects matching a specific Ruby type flag.
95
+
96
+ Args:
97
+ type_flag: Ruby type constant (e.g., RUBY_T_STRING, RUBY_T_DATA), or None for all types
98
+ limit: Maximum number of objects to find (None for no limit)
99
+ from_address: Address to continue from (for pagination)
100
+
101
+ Returns:
102
+ Tuple of (objects, next_address) where:
103
+ - objects: List of VALUEs matching the type
104
+ - next_address: The next address to scan from (for pagination), or None if no more objects
105
+ """
106
+ if from_address:
107
+ print(f"DEBUG: scan() called with from_address=0x{from_address:x}", file=sys.stderr)
108
+ else:
109
+ print(f"DEBUG: scan() called with from_address=None", file=sys.stderr)
110
+ objects = []
111
+ next_address = None
112
+
113
+ # Iterate heap, starting from the address if specified
114
+ for obj, flags, obj_address in self.iterate_heap_from(from_address):
115
+ # Check type (lower 5 bits of flags) if type_flag is specified
116
+ if type_flag is not None:
117
+ if (flags & RBASIC_FLAGS_TYPE_MASK) != type_flag:
118
+ continue
119
+
120
+ # If we've already hit the limit, this is the next address to continue from
121
+ if limit and len(objects) >= limit:
122
+ next_address = obj_address
123
+ break
124
+
125
+ objects.append(obj)
126
+
127
+ # Return the next address to scan from (the first object we didn't include)
128
+ return objects, next_address
129
+
130
+ def _find_page_for_address(self, address):
131
+ """Find which heap page contains the given address.
132
+
133
+ Args:
134
+ address: Memory address to search for
135
+
136
+ Returns:
137
+ Page index if found, None otherwise
138
+ """
139
+ if not self.objspace:
140
+ return None
141
+
142
+ try:
143
+ allocated_pages = int(self.objspace['heap_pages']['allocated_pages'])
144
+ except (gdb.MemoryError, gdb.error):
145
+ return None
146
+
147
+ # Linear search through pages
148
+ # TODO: Could use binary search since pages are sorted
149
+ for i in range(allocated_pages):
150
+ page = self._get_page(i)
151
+ if page is None:
152
+ continue
153
+
154
+ try:
155
+ start = int(page['start'])
156
+ total_slots = int(page['total_slots'])
157
+ slot_size = int(page['slot_size'])
158
+
159
+ # Check if address falls within this page's range
160
+ page_end = start + (total_slots * slot_size)
161
+ if start <= address < page_end:
162
+ return i
163
+ except (gdb.MemoryError, gdb.error):
164
+ continue
165
+
166
+ return None
167
+
168
+ def iterate_heap_from(self, from_address=None):
169
+ """Yield all objects from the Ruby heap, optionally starting from a specific address.
170
+
171
+ Args:
172
+ from_address: If specified, finds the page containing this address and starts from there.
173
+ If None, starts from the beginning of the heap.
174
+
175
+ Yields:
176
+ Tuple of (VALUE, flags, address) for each object on the heap
177
+ """
178
+ # If we have a from_address, find which page contains it
179
+ start_page = 0
180
+ start_address = None
181
+ if from_address is not None:
182
+ print(f"DEBUG: iterate_heap_from called with from_address=0x{from_address:x}", file=sys.stderr)
183
+ start_page = self._find_page_for_address(from_address)
184
+ print(f"DEBUG: _find_page_for_address returned {start_page}", file=sys.stderr)
185
+ if start_page is None:
186
+ # Address not found in any page, start from beginning
187
+ print(f"Warning: Address 0x{from_address:x} not found in heap, starting from beginning", file=sys.stderr)
188
+ start_page = 0
189
+ else:
190
+ # Remember to skip within the page to this address
191
+ start_address = from_address
192
+ print(f"DEBUG: Will start from page {start_page}, address 0x{start_address:x}", file=sys.stderr)
193
+
194
+ # Delegate to the page-based iterator
195
+ for obj, flags, obj_address in self._iterate_heap_from_page(start_page, start_address):
196
+ yield obj, flags, obj_address
197
+
198
+ def _iterate_heap_from_page(self, start_page=0, skip_until_address=None):
199
+ """Yield all objects from the Ruby heap, starting from a specific page.
200
+
201
+ Args:
202
+ start_page: Page index to start from (default: 0)
203
+ skip_until_address: If specified, calculate the slot index and start from there (for first page only)
204
+
205
+ Yields:
206
+ Tuple of (VALUE, flags, address) for each object on the heap
207
+ """
208
+ if not self.objspace:
209
+ return
210
+
211
+ try:
212
+ allocated_pages = int(self.objspace['heap_pages']['allocated_pages'])
213
+ except gdb.MemoryError as e:
214
+ print(f"Error reading heap_pages: {e}")
215
+ print("The heap may not be initialized yet.")
216
+ return
217
+
218
+ for i in range(start_page, allocated_pages):
219
+ page = self._get_page(i)
220
+ if page is None:
221
+ continue
222
+
223
+ try:
224
+ start = int(page['start'])
225
+ total_slots = int(page['total_slots'])
226
+ slot_size = int(page['slot_size'])
227
+ except (gdb.MemoryError, gdb.error) as e:
228
+ print(f"Error reading page {i}: {e}", file=sys.stderr)
229
+ continue
230
+
231
+ # OPTIMIZATION: Create base pointer once per page
232
+ try:
233
+ base_ptr = gdb.Value(start).cast(self._rbasic_type)
234
+ except (gdb.error, RuntimeError):
235
+ continue
236
+
237
+ # For the first page, calculate which slot to start from
238
+ start_slot = 0
239
+ if i == start_page and skip_until_address is not None:
240
+ # Calculate slot index from address
241
+ offset_from_page_start = skip_until_address - start
242
+ start_slot = offset_from_page_start // slot_size
243
+
244
+ # DEBUG
245
+ print(f"DEBUG: Resuming from address 0x{skip_until_address:x}", file=sys.stderr)
246
+ print(f"DEBUG: Page {i} starts at 0x{start:x}, slot_size={slot_size}", file=sys.stderr)
247
+ print(f"DEBUG: Starting at slot {start_slot}", file=sys.stderr)
248
+
249
+ # Ensure we don't go out of bounds
250
+ if start_slot >= total_slots:
251
+ continue # Skip this entire page
252
+ if start_slot < 0:
253
+ start_slot = 0
254
+
255
+ # Iterate through objects using pointer arithmetic (much faster!)
256
+ for j in range(start_slot, total_slots):
257
+ try:
258
+ # Calculate byte offset and address
259
+ byte_offset = j * slot_size
260
+ obj_address = start + byte_offset
261
+
262
+ # Use pointer arithmetic (much faster than creating new Value)
263
+ obj_ptr = (base_ptr.cast(self._char_ptr_type) + byte_offset).cast(self._rbasic_type)
264
+
265
+ # Read the flags
266
+ flags = int(obj_ptr['flags'])
267
+
268
+ # Skip free objects
269
+ if flags == 0:
270
+ continue
271
+
272
+ # Yield the VALUE, flags, and address
273
+ obj = obj_ptr.cast(self._value_type)
274
+ yield obj, flags, obj_address
275
+ except (gdb.error, RuntimeError):
276
+ continue
277
+
278
+ def find_typed_data(self, data_type, limit=None, progress=False):
279
+ """Find RTypedData objects matching a specific type.
280
+
281
+ Args:
282
+ data_type: Pointer to rb_data_type_struct to match
283
+ limit: Maximum number of objects to find (None for no limit)
284
+ progress: If True, print progress to stderr
285
+
286
+ Returns:
287
+ List of VALUEs (not extracted data pointers) matching the type
288
+ """
289
+ objects = []
290
+
291
+ # T_DATA constant
292
+ T_DATA = 0x0c
293
+
294
+ # Get RTypedData type for casting
295
+ rtypeddata_type = gdb.lookup_type('struct RTypedData').pointer()
296
+
297
+ try:
298
+ if progress:
299
+ allocated_pages = int(self.objspace['heap_pages']['allocated_pages'])
300
+ print(f"Scanning {allocated_pages} heap pages...", file=sys.stderr)
301
+ except (gdb.MemoryError, gdb.error):
302
+ pass
303
+
304
+ objects_checked = 0
305
+
306
+ for obj, flags, address in self.iterate_heap():
307
+ # Check if we've reached the limit
308
+ if limit and len(objects) >= limit:
309
+ if progress:
310
+ print(f"Reached limit of {limit} object(s), stopping scan", file=sys.stderr)
311
+ break
312
+
313
+ objects_checked += 1
314
+
315
+ # Print progress every 10000 objects
316
+ if progress and objects_checked % 10000 == 0:
317
+ print(f" Checked {objects_checked} objects, found {len(objects)} match(es)...", file=sys.stderr)
318
+
319
+ # Check if it's T_DATA
320
+ if (flags & RBASIC_FLAGS_TYPE_MASK) != T_DATA:
321
+ continue
322
+
323
+ # Cast to RTypedData and check type
324
+ try:
325
+ typed_data = obj.cast(rtypeddata_type)
326
+
327
+ if typed_data['type'] == data_type:
328
+ # Return the VALUE, not the extracted data pointer
329
+ objects.append(obj)
330
+ if progress:
331
+ print(f" Found object #{len(objects)} at VALUE 0x{int(obj):x}", file=sys.stderr)
332
+ except (gdb.error, RuntimeError):
333
+ continue
334
+
335
+ if progress:
336
+ if limit and len(objects) >= limit:
337
+ print(f"Scan complete: checked {objects_checked} objects (stopped at limit)", file=sys.stderr)
338
+ else:
339
+ print(f"Scan complete: checked {objects_checked} objects", file=sys.stderr)
340
+
341
+ return objects
342
+
343
+
344
+ class RubyHeapScanCommand(gdb.Command):
345
+ """Scan the Ruby heap for objects, optionally filtered by type.
346
+
347
+ Usage: rb-heap-scan [--type TYPE] [--limit N] [--from $heap]
348
+
349
+ TYPE can be:
350
+ - A Ruby type constant like RUBY_T_STRING, RUBY_T_ARRAY, RUBY_T_HASH
351
+ - A numeric value (e.g., 0x05 for T_STRING)
352
+ - Omit --type to scan all objects
353
+
354
+ Options:
355
+ --type TYPE Filter by Ruby type (omit to scan all objects)
356
+ --limit N Stop after finding N objects (default: 10)
357
+ --from ADDR Start scanning from the given address (for pagination)
358
+
359
+ Pagination:
360
+ The address of the last found object is saved to $heap, allowing you to paginate:
361
+ rb-heap-scan --type RUBY_T_STRING --limit 10 # First page
362
+ rb-heap-scan --type RUBY_T_STRING --limit 10 --from $heap # Next page
363
+
364
+ The $heap variable contains the address of the last scanned object.
365
+
366
+ Examples:
367
+ rb-heap-scan --type RUBY_T_STRING
368
+ rb-heap-scan --type RUBY_T_ARRAY --limit 20
369
+ rb-heap-scan --type 0x05 # T_STRING
370
+ rb-heap-scan --limit 100 # All objects
371
+ rb-heap-scan --from $heap # Continue from last scan
372
+ """
373
+
374
+ def __init__(self):
375
+ super(RubyHeapScanCommand, self).__init__("rb-heap-scan", gdb.COMMAND_USER)
376
+
377
+ def usage(self):
378
+ """Print usage information."""
379
+ print("Usage: rb-heap-scan [--type TYPE] [--limit N] [--from $heap]")
380
+ print("Examples:")
381
+ print(" rb-heap-scan --type RUBY_T_STRING # Find up to 10 strings")
382
+ print(" rb-heap-scan --type RUBY_T_ARRAY --limit 5 # Find up to 5 arrays")
383
+ print(" rb-heap-scan --type 0x05 --limit 100 # Find up to 100 T_STRING objects")
384
+ print(" rb-heap-scan --limit 20 # Scan 20 objects (any type)")
385
+ print(" rb-heap-scan --type RUBY_T_STRING --from $heap # Continue from last scan")
386
+ print()
387
+ print("Pagination:")
388
+ print(" The address of the last object is saved to $heap for pagination:")
389
+ print(" rb-heap-scan --type RUBY_T_STRING --limit 10 # First page")
390
+ print(" rb-heap-scan --type RUBY_T_STRING --from $heap # Next page")
391
+
392
+ def _parse_type(self, type_arg):
393
+ """Parse a type argument and return the type value.
394
+
395
+ Args:
396
+ type_arg: String type argument (constant name or numeric value)
397
+
398
+ Returns:
399
+ Integer type value, or None on error
400
+ """
401
+ import constants
402
+
403
+ # Try as a constant name first
404
+ type_value = constants.get(type_arg)
405
+
406
+ if type_value is None:
407
+ # Try parsing as a number (hex or decimal)
408
+ try:
409
+ if type_arg.startswith('0x') or type_arg.startswith('0X'):
410
+ type_value = int(type_arg, 16)
411
+ else:
412
+ type_value = int(type_arg)
413
+ except ValueError:
414
+ print(f"Error: Unknown type constant '{type_arg}'")
415
+ print("Use a constant like RUBY_T_STRING or a numeric value like 0x05")
416
+ return None
417
+
418
+ # Validate type value is reasonable (0-31 for the 5-bit type field)
419
+ if not (0 <= type_value <= 31):
420
+ print(f"Warning: Type value {type_value} (0x{type_value:x}) is outside valid range 0-31")
421
+
422
+ return type_value
423
+
424
+ def invoke(self, arg, from_tty):
425
+ """Execute the heap scan command."""
426
+ try:
427
+ # Parse arguments
428
+ import command
429
+ arguments = command.parse_arguments(arg if arg else "")
430
+
431
+ print(f"DEBUG: Raw arg string: '{arg}'", file=sys.stderr)
432
+
433
+ # Check if we're continuing from a previous scan
434
+ from_option = arguments.get_option('from')
435
+ print(f"DEBUG: from_option = {from_option}", file=sys.stderr)
436
+ if from_option is not None:
437
+ try:
438
+ # $heap should be an address (pointer value)
439
+ from_address = int(gdb.parse_and_eval(from_option))
440
+ print(f"DEBUG: Parsed from_address = 0x{from_address:x}", file=sys.stderr)
441
+ except (gdb.error, ValueError, TypeError) as e:
442
+ # If $heap doesn't exist or is void/invalid, start from the beginning
443
+ print(f"Note: {from_option} is not set or invalid, wrapping around to start of heap", file=sys.stderr)
444
+ from_address = None
445
+ else:
446
+ # New scan
447
+ from_address = None
448
+
449
+ # Get limit (default 10)
450
+ limit = 10
451
+ limit_value = arguments.get_option('limit')
452
+ if limit_value is not None:
453
+ try:
454
+ limit = int(limit_value)
455
+ except (ValueError, TypeError):
456
+ print("Error: --limit must be a number")
457
+ return
458
+
459
+ # Get type (optional)
460
+ type_value = None
461
+ type_option = arguments.get_option('type')
462
+ if type_option is not None:
463
+ type_value = self._parse_type(type_option)
464
+ if type_value is None:
465
+ return
466
+
467
+ # Initialize heap
468
+ heap = RubyHeap()
469
+ if not heap.initialize():
470
+ return
471
+
472
+ # Print search description
473
+ if type_value is not None:
474
+ type_desc = f"type 0x{type_value:02x}"
475
+ else:
476
+ type_desc = "all types"
477
+
478
+ if from_address:
479
+ print(f"Scanning heap for {type_desc}, limit={limit}, continuing from address 0x{from_address:x}...")
480
+ else:
481
+ print(f"Scanning heap for {type_desc}, limit={limit}...")
482
+ print()
483
+
484
+ # Find objects
485
+ objects, next_address = heap.scan(type_value, limit=limit, from_address=from_address)
486
+
487
+ if not objects:
488
+ print("No objects found")
489
+ if from_address:
490
+ print("(You may have reached the end of the heap)")
491
+ return
492
+
493
+ # Import format for terminal output
494
+ import format
495
+ terminal = format.create_terminal(from_tty)
496
+
497
+ # Import value module for interpretation
498
+ import value as value_module
499
+
500
+ print(f"Found {len(objects)} object(s):")
501
+ print()
502
+
503
+ for i, obj in enumerate(objects):
504
+ obj_int = int(obj)
505
+
506
+ # Set as convenience variable
507
+ var_name = f"heap{i}"
508
+ gdb.set_convenience_variable(var_name, obj)
509
+
510
+ # Try to interpret and display the object
511
+ try:
512
+ interpreted = value_module.interpret(obj)
513
+
514
+ print(terminal.print(
515
+ format.metadata, f" [{i}] ",
516
+ format.dim, f"${var_name} = ",
517
+ format.reset, interpreted
518
+ ))
519
+ except Exception as e:
520
+ print(terminal.print(
521
+ format.metadata, f" [{i}] ",
522
+ format.dim, f"${var_name} = ",
523
+ format.error, f"<error: {e}>"
524
+ ))
525
+
526
+ print()
527
+ print(terminal.print(
528
+ format.dim,
529
+ f"Objects saved in $heap0 through $heap{len(objects)-1}",
530
+ format.reset
531
+ ))
532
+
533
+ # Save next address to $heap for pagination
534
+ if next_address is not None:
535
+ # Save the next address to continue from
536
+ gdb.set_convenience_variable('heap', gdb.Value(next_address).cast(gdb.lookup_type('void').pointer()))
537
+ print(terminal.print(
538
+ format.dim,
539
+ f"Next scan address saved to $heap: 0x{next_address:016x}",
540
+ format.reset
541
+ ))
542
+ print(terminal.print(
543
+ format.dim,
544
+ f"Run 'rb-heap-scan --type {type_option if type_option else '...'} --from $heap' for next page",
545
+ format.reset
546
+ ))
547
+ else:
548
+ # Reached the end of the heap - unset $heap so next scan starts fresh
549
+ gdb.set_convenience_variable('heap', None)
550
+ print(terminal.print(
551
+ format.dim,
552
+ f"Reached end of heap (no more objects to scan)",
553
+ format.reset
554
+ ))
555
+
556
+ except Exception as e:
557
+ print(f"Error: {e}")
558
+ import traceback
559
+ traceback.print_exc()
560
+
561
+
562
+ # Register commands
563
+ RubyHeapScanCommand()
@@ -0,0 +1,25 @@
1
+ """
2
+ Ruby GDB Extensions Initialization
3
+
4
+ This module loads all Ruby debugging extensions for GDB.
5
+ """
6
+
7
+ import gdb
8
+ import os
9
+ import sys
10
+
11
+ # Get the directory containing this file
12
+ ruby_gdb_dir = os.path.dirname(os.path.abspath(__file__))
13
+
14
+ # Add to Python path for imports
15
+ if ruby_gdb_dir not in sys.path:
16
+ sys.path.insert(0, ruby_gdb_dir)
17
+
18
+ # Load object inspection extensions:
19
+ import object
20
+
21
+ # Load fiber debugging extensions:
22
+ import fiber
23
+
24
+ # Load stack inspection extensions:
25
+ import stack
@@ -0,0 +1,85 @@
1
+ import gdb
2
+ import sys
3
+
4
+ # Import utilities
5
+ import command
6
+ import constants
7
+ import value
8
+ import rstring
9
+ import rarray
10
+ import rhash
11
+ import rsymbol
12
+ import rstruct
13
+ import rfloat
14
+ import rbignum
15
+ import rbasic
16
+ import format
17
+
18
+ class RubyObjectPrintCommand(gdb.Command):
19
+ """Recursively print Ruby hash and array structures.
20
+ Usage: rb-object-print <expression> [max_depth] [--debug]
21
+ Examples:
22
+ rb-object-print $errinfo # Print exception object
23
+ rb-object-print $ec->storage # Print fiber storage
24
+ rb-object-print 0x7f7a12345678 # Print object at address
25
+ rb-object-print $var 2 # Print with max depth 2
26
+
27
+ Default max_depth is 1 if not specified.
28
+ Add --debug flag to enable debug output."""
29
+
30
+ def __init__(self):
31
+ super(RubyObjectPrintCommand, self).__init__("rb-object-print", gdb.COMMAND_DATA)
32
+
33
+ def usage(self):
34
+ """Print usage information."""
35
+ print("Usage: rb-object-print <expression> [--depth N] [--debug]")
36
+ print("Examples:")
37
+ print(" rb-object-print $errinfo")
38
+ print(" rb-object-print $ec->storage --depth 2")
39
+ print(" rb-object-print foo + 10")
40
+ print(" rb-object-print $ec->cfp->sp[-1] --depth 3 --debug")
41
+
42
+ def invoke(self, argument, from_tty):
43
+ # Parse arguments using the robust parser
44
+ arguments = command.parse_arguments(argument if argument else "")
45
+
46
+ # Validate that we have at least one expression
47
+ if not arguments.expressions:
48
+ self.usage()
49
+ return
50
+
51
+ # Apply flags
52
+ debug_mode = arguments.has_flag('debug')
53
+
54
+ # Apply options
55
+ max_depth = arguments.get_option('depth', 1)
56
+
57
+ # Validate depth
58
+ if max_depth < 1:
59
+ print("Error: --depth must be >= 1")
60
+ return
61
+
62
+ # Create terminal and printer
63
+ terminal = format.create_terminal(from_tty)
64
+ printer = format.Printer(terminal, max_depth, debug_mode)
65
+
66
+ # Process each expression
67
+ for expression in arguments.expressions:
68
+ try:
69
+ # Evaluate the expression
70
+ ruby_value = gdb.parse_and_eval(expression)
71
+ printer.debug(f"Evaluated '{expression}' to 0x{int(ruby_value):x}")
72
+
73
+ # Interpret the value and let it print itself recursively
74
+ ruby_object = value.interpret(ruby_value)
75
+ ruby_object.print_recursive(printer, max_depth)
76
+ except gdb.error as e:
77
+ print(f"Error evaluating expression '{expression}': {e}")
78
+ except Exception as e:
79
+ print(f"Error processing '{expression}': {type(e).__name__}: {e}")
80
+ if debug_mode:
81
+ import traceback
82
+ traceback.print_exc(file=sys.stderr)
83
+
84
+ # Register command
85
+ RubyObjectPrintCommand()