toolbox 0.1.4 → 0.3.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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/ruby/gdb.rb +135 -0
  4. data/bake/toolbox/gdb.rb +137 -0
  5. data/bake/toolbox/lldb.rb +137 -0
  6. data/context/fiber-debugging.md +171 -0
  7. data/context/getting-started.md +200 -0
  8. data/context/heap-debugging.md +351 -0
  9. data/context/index.yaml +28 -0
  10. data/context/object-inspection.md +208 -0
  11. data/context/stack-inspection.md +188 -0
  12. data/data/toolbox/command.py +479 -0
  13. data/data/toolbox/constants.py +200 -0
  14. data/data/toolbox/context.py +371 -0
  15. data/data/toolbox/debugger/__init__.py +101 -0
  16. data/data/toolbox/debugger/gdb_backend.py +664 -0
  17. data/data/toolbox/debugger/lldb_backend.py +986 -0
  18. data/data/toolbox/fiber.py +877 -0
  19. data/data/toolbox/format.py +205 -0
  20. data/data/toolbox/heap.py +679 -0
  21. data/data/toolbox/init.py +89 -0
  22. data/data/toolbox/print.py +79 -0
  23. data/data/toolbox/rarray.py +116 -0
  24. data/data/toolbox/rbasic.py +99 -0
  25. data/data/toolbox/rbignum.py +48 -0
  26. data/data/toolbox/rclass.py +136 -0
  27. data/data/toolbox/readme.md +214 -0
  28. data/data/toolbox/rexception.py +150 -0
  29. data/data/toolbox/rfloat.py +88 -0
  30. data/data/toolbox/rhash.py +151 -0
  31. data/data/toolbox/rstring.py +230 -0
  32. data/data/toolbox/rstruct.py +149 -0
  33. data/data/toolbox/rsymbol.py +278 -0
  34. data/data/toolbox/rvalue.py +183 -0
  35. data/data/toolbox/stack.py +620 -0
  36. data/lib/toolbox/gdb.rb +21 -0
  37. data/lib/toolbox/lldb.rb +21 -0
  38. data/lib/toolbox/version.rb +7 -1
  39. data/lib/toolbox.rb +9 -24
  40. data/license.md +21 -0
  41. data/readme.md +64 -0
  42. data/releases.md +9 -0
  43. data.tar.gz.sig +0 -0
  44. metadata +95 -165
  45. metadata.gz.sig +0 -0
  46. data/Rakefile +0 -61
  47. data/lib/dirs.rb +0 -9
  48. data/lib/toolbox/config.rb +0 -211
  49. data/lib/toolbox/default_controller.rb +0 -393
  50. data/lib/toolbox/helpers.rb +0 -11
  51. data/lib/toolbox/rendering.rb +0 -413
  52. data/lib/toolbox/searching.rb +0 -85
  53. data/lib/toolbox/session_params.rb +0 -63
  54. data/lib/toolbox/sorting.rb +0 -74
  55. data/locale/de/LC_MESSAGES/toolbox.mo +0 -0
  56. data/public/images/add.png +0 -0
  57. data/public/images/arrow_down.gif +0 -0
  58. data/public/images/arrow_up.gif +0 -0
  59. data/public/images/close.png +0 -0
  60. data/public/images/edit.gif +0 -0
  61. data/public/images/email.png +0 -0
  62. data/public/images/page.png +0 -0
  63. data/public/images/page_acrobat.png +0 -0
  64. data/public/images/page_add.png +0 -0
  65. data/public/images/page_copy.png +0 -0
  66. data/public/images/page_delete.png +0 -0
  67. data/public/images/page_edit.png +0 -0
  68. data/public/images/page_excel.png +0 -0
  69. data/public/images/page_list.png +0 -0
  70. data/public/images/page_save.png +0 -0
  71. data/public/images/page_word.png +0 -0
  72. data/public/images/remove.png +0 -0
  73. data/public/images/show.gif +0 -0
  74. data/public/images/spinner.gif +0 -0
  75. data/public/javascripts/popup.js +0 -498
  76. data/public/javascripts/toolbox.js +0 -18
  77. data/public/stylesheets/context_menu.css +0 -168
  78. data/public/stylesheets/popup.css +0 -30
  79. data/public/stylesheets/toolbox.css +0 -107
  80. data/view/toolbox/_collection.html.erb +0 -24
  81. data/view/toolbox/_collection_header.html.erb +0 -7
  82. data/view/toolbox/_context_menu.html.erb +0 -17
  83. data/view/toolbox/_dialogs.html.erb +0 -6
  84. data/view/toolbox/_form.html.erb +0 -30
  85. data/view/toolbox/_form_collection_row.html.erb +0 -18
  86. data/view/toolbox/_form_fieldset.html.erb +0 -30
  87. data/view/toolbox/_form_fieldset_row.html.erb +0 -19
  88. data/view/toolbox/_list.html.erb +0 -25
  89. data/view/toolbox/_list_row.html.erb +0 -10
  90. data/view/toolbox/_menu.html.erb +0 -7
  91. data/view/toolbox/_search_field.html.erb +0 -8
  92. data/view/toolbox/_show.html.erb +0 -12
  93. data/view/toolbox/_show_collection_row.html.erb +0 -6
  94. data/view/toolbox/_show_fieldset.html.erb +0 -21
  95. data/view/toolbox/edit.html.erb +0 -5
  96. data/view/toolbox/index.html.erb +0 -3
  97. data/view/toolbox/new.html.erb +0 -9
  98. data/view/toolbox/show.html.erb +0 -39
@@ -0,0 +1,664 @@
1
+ """
2
+ GDB backend implementation for unified debugger interface.
3
+ """
4
+
5
+ import gdb
6
+ import format
7
+
8
+ # Command categories
9
+ COMMAND_DATA = gdb.COMMAND_DATA
10
+ COMMAND_USER = gdb.COMMAND_USER
11
+
12
+ # Exception types
13
+ Error = gdb.error
14
+ MemoryError = gdb.MemoryError
15
+
16
+
17
+ class Value:
18
+ """Wrapper for GDB values providing unified interface."""
19
+
20
+ def __init__(self, gdb_value):
21
+ """Initialize with a GDB value.
22
+
23
+ Args:
24
+ gdb_value: Native gdb.Value object
25
+ """
26
+ self._value = gdb_value
27
+
28
+ def __int__(self):
29
+ """Convert value to integer."""
30
+ return int(self._value)
31
+
32
+ def __str__(self):
33
+ """Convert value to string."""
34
+ return str(self._value)
35
+
36
+ def __eq__(self, other):
37
+ """Compare values for equality.
38
+
39
+ Compares by address/integer value to avoid GDB conversion issues.
40
+
41
+ Args:
42
+ other: Another Value, gdb.Value, or integer
43
+
44
+ Returns:
45
+ True if values are equal
46
+ """
47
+ if isinstance(other, Value):
48
+ return int(self._value) == int(other._value)
49
+ elif isinstance(other, gdb.Value):
50
+ return int(self._value) == int(other)
51
+ else:
52
+ return int(self._value) == int(other)
53
+
54
+ def __hash__(self):
55
+ """Return hash of value for use in sets/dicts.
56
+
57
+ Returns:
58
+ Hash of the integer value
59
+ """
60
+ return hash(int(self._value))
61
+
62
+ def __lt__(self, other):
63
+ """Less than comparison for pointer ordering.
64
+
65
+ Args:
66
+ other: Another Value, gdb.Value, or integer
67
+
68
+ Returns:
69
+ True if this value is less than other
70
+ """
71
+ if isinstance(other, Value):
72
+ return int(self._value) < int(other._value)
73
+ elif isinstance(other, gdb.Value):
74
+ return int(self._value) < int(other)
75
+ else:
76
+ return int(self._value) < int(other)
77
+
78
+ def __le__(self, other):
79
+ """Less than or equal comparison for pointer ordering.
80
+
81
+ Args:
82
+ other: Another Value, gdb.Value, or integer
83
+
84
+ Returns:
85
+ True if this value is less than or equal to other
86
+ """
87
+ if isinstance(other, Value):
88
+ return int(self._value) <= int(other._value)
89
+ elif isinstance(other, gdb.Value):
90
+ return int(self._value) <= int(other)
91
+ else:
92
+ return int(self._value) <= int(other)
93
+
94
+ def __gt__(self, other):
95
+ """Greater than comparison for pointer ordering.
96
+
97
+ Args:
98
+ other: Another Value, gdb.Value, or integer
99
+
100
+ Returns:
101
+ True if this value is greater than other
102
+ """
103
+ if isinstance(other, Value):
104
+ return int(self._value) > int(other._value)
105
+ elif isinstance(other, gdb.Value):
106
+ return int(self._value) > int(other)
107
+ else:
108
+ return int(self._value) > int(other)
109
+
110
+ def __ge__(self, other):
111
+ """Greater than or equal comparison for pointer ordering.
112
+
113
+ Args:
114
+ other: Another Value, gdb.Value, or integer
115
+
116
+ Returns:
117
+ True if this value is greater than or equal to other
118
+ """
119
+ if isinstance(other, Value):
120
+ return int(self._value) >= int(other._value)
121
+ elif isinstance(other, gdb.Value):
122
+ return int(self._value) >= int(other)
123
+ else:
124
+ return int(self._value) >= int(other)
125
+
126
+ def cast(self, type_obj):
127
+ """Cast this value to a different type.
128
+
129
+ Args:
130
+ type_obj: Type object to cast to
131
+
132
+ Returns:
133
+ New Value with cast type
134
+ """
135
+ if isinstance(type_obj, Type):
136
+ return Value(self._value.cast(type_obj._type))
137
+ else:
138
+ # Assume it's a native GDB type
139
+ return Value(self._value.cast(type_obj))
140
+
141
+ def dereference(self):
142
+ """Dereference this pointer value.
143
+
144
+ Returns:
145
+ Value at the address
146
+ """
147
+ return Value(self._value.dereference())
148
+
149
+ @property
150
+ def address(self):
151
+ """Get the address of this value.
152
+
153
+ Returns:
154
+ Value representing the address
155
+ """
156
+ return Value(self._value.address)
157
+
158
+ def __getitem__(self, key):
159
+ """Access struct field or array element.
160
+
161
+ Args:
162
+ key: Field name (str) or array index (int)
163
+
164
+ Returns:
165
+ Value of the field/element, or None if field doesn't exist or is invalid
166
+ """
167
+ try:
168
+ result = self._value[key]
169
+ return Value(result)
170
+ except (gdb.error, KeyError, AttributeError):
171
+ return None
172
+
173
+ def __add__(self, other):
174
+ """Add to this value (pointer arithmetic).
175
+
176
+ Args:
177
+ other: Value, gdb.Value, or integer to add
178
+
179
+ Returns:
180
+ Value result of addition
181
+ """
182
+ if isinstance(other, Value):
183
+ return Value(self._value + other._value)
184
+ elif isinstance(other, gdb.Value):
185
+ return Value(self._value + other)
186
+ else:
187
+ return Value(self._value + other)
188
+
189
+ def __radd__(self, other):
190
+ """Reverse add - when Value is on the right side of +.
191
+
192
+ Args:
193
+ other: Value, gdb.Value, or integer to add
194
+
195
+ Returns:
196
+ Integer result of addition
197
+ """
198
+ return self.__add__(other)
199
+
200
+ def __sub__(self, other):
201
+ """Subtract from this value (pointer arithmetic).
202
+
203
+ Args:
204
+ other: Value, gdb.Value, or integer to subtract
205
+
206
+ Returns:
207
+ Value result of subtraction
208
+ """
209
+ if isinstance(other, Value):
210
+ return Value(self._value - other._value)
211
+ elif isinstance(other, gdb.Value):
212
+ return Value(self._value - other)
213
+ else:
214
+ return Value(self._value - other)
215
+
216
+ def __rsub__(self, other):
217
+ """Reverse subtract - when Value is on the right side of -.
218
+
219
+ Args:
220
+ other: Value, gdb.Value, or integer to subtract from
221
+
222
+ Returns:
223
+ Integer result of subtraction
224
+ """
225
+ if isinstance(other, Value):
226
+ return int(other._value) - int(self._value)
227
+ elif isinstance(other, gdb.Value):
228
+ return int(other) - int(self._value)
229
+ else:
230
+ return int(other) - int(self._value)
231
+
232
+
233
+ @property
234
+ def type(self):
235
+ """Get the type of this value.
236
+
237
+ Returns:
238
+ Type object
239
+ """
240
+ return Type(self._value.type)
241
+
242
+ @property
243
+ def native(self):
244
+ """Get the underlying native GDB value.
245
+
246
+ Returns:
247
+ gdb.Value object
248
+ """
249
+ return self._value
250
+
251
+
252
+ class Type:
253
+ """Wrapper for GDB types providing unified interface."""
254
+
255
+ def __init__(self, gdb_type):
256
+ """Initialize with a GDB type.
257
+
258
+ Args:
259
+ gdb_type: Native gdb.Type object
260
+ """
261
+ self._type = gdb_type
262
+
263
+ def __str__(self):
264
+ """Convert type to string."""
265
+ return str(self._type)
266
+
267
+ def pointer(self):
268
+ """Get pointer type to this type.
269
+
270
+ Returns:
271
+ Type representing pointer to this type
272
+ """
273
+ return Type(self._type.pointer())
274
+
275
+ def array(self, count):
276
+ """Get array type of this type.
277
+
278
+ Args:
279
+ count: Number of elements in the array
280
+
281
+ Returns:
282
+ Type representing array of this type
283
+ """
284
+ return Type(self._type.array(count))
285
+
286
+ @property
287
+ def native(self):
288
+ """Get the underlying native GDB type.
289
+
290
+ Returns:
291
+ gdb.Type object
292
+ """
293
+ return self._type
294
+
295
+ @property
296
+ def sizeof(self):
297
+ """Get the size of this type in bytes.
298
+
299
+ Returns:
300
+ Size in bytes as integer
301
+ """
302
+ return self._type.sizeof
303
+
304
+
305
+ class Command:
306
+ """Base class for debugger commands.
307
+
308
+ Subclass this and implement invoke() to create custom commands.
309
+ """
310
+
311
+ def __init__(self, name, category=COMMAND_DATA):
312
+ """Initialize and register a command.
313
+
314
+ Args:
315
+ name: Command name (e.g., "rb-print")
316
+ category: Command category (COMMAND_DATA or COMMAND_USER)
317
+ """
318
+ self.name = name
319
+ self.category = category
320
+
321
+ # Create a GDB command that delegates to our invoke method
322
+ class GDBCommandWrapper(gdb.Command):
323
+ def __init__(wrapper_self, wrapped):
324
+ super(GDBCommandWrapper, wrapper_self).__init__(name, category)
325
+ wrapper_self.wrapped = wrapped
326
+
327
+ def invoke(wrapper_self, arg, from_tty):
328
+ wrapper_self.wrapped.invoke(arg, from_tty)
329
+
330
+ # Register the wrapper
331
+ self._wrapper = GDBCommandWrapper(self)
332
+
333
+ def invoke(self, arg, from_tty):
334
+ """Handle command invocation.
335
+
336
+ Override this method in subclasses.
337
+
338
+ Args:
339
+ arg: Command arguments as string
340
+ from_tty: True if command invoked from terminal
341
+ """
342
+ raise NotImplementedError("Subclasses must implement invoke()")
343
+
344
+
345
+ def parse_and_eval(expression):
346
+ """Evaluate an expression in the debugger.
347
+
348
+ Args:
349
+ expression: Expression string (e.g., "$var", "ruby_current_vm_ptr")
350
+
351
+ Returns:
352
+ Value object representing the result
353
+ """
354
+ return Value(gdb.parse_and_eval(expression))
355
+
356
+
357
+ def lookup_type(type_name):
358
+ """Look up a type by name.
359
+
360
+ Args:
361
+ type_name: Type name (e.g., "struct RString", "VALUE")
362
+
363
+ Returns:
364
+ Type object
365
+ """
366
+ return Type(gdb.lookup_type(type_name))
367
+
368
+
369
+ def set_convenience_variable(name, value):
370
+ """Set a GDB convenience variable.
371
+
372
+ Args:
373
+ name: Variable name (without $ prefix)
374
+ value: Value to set (can be Value wrapper or native value)
375
+ """
376
+ if isinstance(value, Value):
377
+ gdb.set_convenience_variable(name, value._value)
378
+ else:
379
+ gdb.set_convenience_variable(name, value)
380
+
381
+
382
+ def execute(command, from_tty=False, to_string=False):
383
+ """Execute a debugger command.
384
+
385
+ Args:
386
+ command: Command string to execute
387
+ from_tty: Whether command is from terminal
388
+ to_string: If True, return command output as string
389
+
390
+ Returns:
391
+ String output if to_string=True, None otherwise
392
+ """
393
+ return gdb.execute(command, from_tty=from_tty, to_string=to_string)
394
+
395
+
396
+ def lookup_symbol(address):
397
+ """Look up symbol name for an address.
398
+
399
+ Args:
400
+ address: Memory address (as integer)
401
+
402
+ Returns:
403
+ Symbol name string, or None if no symbol found
404
+ """
405
+ try:
406
+ symbol_info = execute(f"info symbol 0x{address:x}", to_string=True)
407
+ return symbol_info.split()[0]
408
+ except:
409
+ return None
410
+
411
+
412
+ def invalidate_cached_frames():
413
+ """Invalidate cached frame information.
414
+
415
+ Call this when switching contexts (e.g., fiber switching).
416
+ """
417
+ gdb.invalidate_cached_frames()
418
+
419
+
420
+ def get_enum_value(enum_name, member_name):
421
+ """Get an enum member value.
422
+
423
+ Args:
424
+ enum_name: The enum type name (e.g., 'ruby_value_type')
425
+ member_name: The member name (e.g., 'RUBY_T_STRING')
426
+
427
+ Returns:
428
+ Integer value of the enum member
429
+
430
+ Raises:
431
+ Error if enum member cannot be found
432
+
433
+ Note: In GDB, enum members are imported into the global namespace,
434
+ so we can just evaluate the member name directly.
435
+ """
436
+ # GDB imports enum members globally, so just evaluate the name
437
+ return int(gdb.parse_and_eval(member_name))
438
+
439
+
440
+ def read_memory(address, size):
441
+ """Read memory from the debugged process.
442
+
443
+ Args:
444
+ address: Memory address (as integer or pointer value)
445
+ size: Number of bytes to read
446
+
447
+ Returns:
448
+ bytes object containing the memory contents
449
+
450
+ Raises:
451
+ MemoryError: If memory cannot be read
452
+ """
453
+ # Convert to integer address if needed
454
+ if hasattr(address, '__int__'):
455
+ address = int(address)
456
+
457
+ try:
458
+ inferior = gdb.selected_inferior()
459
+ return inferior.read_memory(address, size).tobytes()
460
+ except gdb.MemoryError as e:
461
+ raise MemoryError(f"Cannot read {size} bytes at 0x{address:x}: {e}")
462
+
463
+
464
+ def read_cstring(address, max_length=256):
465
+ """Read a NUL-terminated C string from memory.
466
+
467
+ Args:
468
+ address: Memory address (as integer or pointer value)
469
+ max_length: Maximum bytes to read before giving up
470
+
471
+ Returns:
472
+ Tuple of (bytes, actual_length) where actual_length is the string
473
+ length not including the NUL terminator
474
+
475
+ Raises:
476
+ MemoryError: If memory cannot be read
477
+ """
478
+ # Convert to integer address if needed
479
+ if hasattr(address, '__int__'):
480
+ address = int(address)
481
+
482
+ try:
483
+ inferior = gdb.selected_inferior()
484
+ buffer = inferior.read_memory(address, max_length).tobytes()
485
+ n = buffer.find(b'\x00')
486
+ if n == -1:
487
+ n = max_length
488
+ return (buffer[:n], n)
489
+ except gdb.MemoryError as e:
490
+ raise MemoryError(f"Cannot read memory at 0x{address:x}: {e}")
491
+
492
+
493
+ def create_value(address, value_type):
494
+ """Create a typed Value from a memory address.
495
+
496
+ Args:
497
+ address: Memory address (as integer, pointer value, or gdb.Value)
498
+ value_type: Type object (or native gdb.Type) to cast to
499
+
500
+ Returns:
501
+ Value object representing the typed value at that address
502
+
503
+ Examples:
504
+ >>> rbasic_type = debugger.lookup_type('struct RBasic').pointer()
505
+ >>> obj = debugger.create_value(0x7fff12345678, rbasic_type)
506
+ """
507
+ # Unwrap Type if needed
508
+ if isinstance(value_type, Type):
509
+ value_type = value_type._type
510
+
511
+ # Handle different address types
512
+ if isinstance(address, Value):
513
+ # It's already a wrapped Value, get the native value
514
+ address = address._value
515
+ elif isinstance(address, gdb.Value):
516
+ # It's a native gdb.Value, use it directly
517
+ pass
518
+ else:
519
+ # Convert to integer address if needed
520
+ if hasattr(address, '__int__'):
521
+ address = int(address)
522
+ # Create a gdb.Value from the integer
523
+ address = gdb.Value(address)
524
+
525
+
526
+ return Value(address.cast(value_type))
527
+
528
+
529
+ def create_value_from_int(int_value, value_type):
530
+ """Create a typed Value from an integer (not a memory address to read from).
531
+
532
+ This is used when the integer itself IS the value (like VALUE which is a pointer).
533
+
534
+ Args:
535
+ int_value: Integer value, or Value object that will be converted to int
536
+ value_type: Type object (or native gdb.Type) to cast to
537
+
538
+ Returns:
539
+ Value object with the integer value
540
+
541
+ Examples:
542
+ >>> value_type = debugger.lookup_type('VALUE')
543
+ >>> obj_address = page['start'] # Value object
544
+ >>> obj = debugger.create_value_from_int(obj_address, value_type)
545
+ """
546
+ # Convert to integer if needed (handles Value objects via __int__)
547
+ if hasattr(int_value, '__int__'):
548
+ int_value = int(int_value)
549
+
550
+ # Unwrap Type if needed
551
+ if isinstance(value_type, Type):
552
+ value_type = value_type._type
553
+
554
+ # Create a gdb.Value from the integer
555
+ int_val = gdb.Value(int_value)
556
+ return Value(int_val.cast(value_type))
557
+
558
+
559
+ def create_value_from_address(address, value_type):
560
+ """Create a typed Value from a memory address.
561
+
562
+ In GDB, this is equivalent to casting the address to a pointer type
563
+ and dereferencing it.
564
+
565
+ Args:
566
+ address: Memory address (as integer, or Value object representing a pointer)
567
+ value_type: Type object (or native gdb.Type) representing the type
568
+
569
+ Returns:
570
+ Value object representing the data at that address
571
+
572
+ Examples:
573
+ >>> rbasic_type = debugger.lookup_type('struct RBasic')
574
+ >>> array_type = rbasic_type.array(100)
575
+ >>> page_start = page['start'] # Value object
576
+ >>> page_array = debugger.create_value_from_address(page_start, array_type)
577
+ """
578
+ # Convert to integer if needed (handles Value objects via __int__)
579
+ if hasattr(address, '__int__'):
580
+ address = int(address)
581
+
582
+ # Unwrap Type if needed
583
+ if isinstance(value_type, Type):
584
+ value_type = value_type._type
585
+
586
+ # Create a pointer to the type and dereference it
587
+ # This is GDB's way of saying "interpret this address as this type"
588
+ ptr_type = value_type.pointer()
589
+ addr_val = gdb.Value(address).cast(ptr_type)
590
+ return Value(addr_val.dereference())
591
+
592
+
593
+ def register(name, handler_class, usage=None, category=COMMAND_USER):
594
+ """Register a command with GDB using a handler class.
595
+
596
+ This creates a wrapper Command that handles parsing, terminal setup,
597
+ and delegates to the handler class for actual command logic.
598
+
599
+ Args:
600
+ name: Command name (e.g., "rb-print")
601
+ handler_class: Class to instantiate for handling the command
602
+ usage: Optional command.Usage specification for validation/help
603
+ category: Command category (COMMAND_USER, etc.)
604
+
605
+ Example:
606
+ class PrintHandler:
607
+ def invoke(self, arguments, terminal):
608
+ depth = arguments.get_option('depth', 1)
609
+ print(f"Depth: {depth}")
610
+
611
+ usage = command.Usage(
612
+ summary="Print something",
613
+ options={'depth': (int, 1)},
614
+ flags=['debug']
615
+ )
616
+ debugger.register("my-print", PrintHandler, usage=usage)
617
+
618
+ Returns:
619
+ The registered Command instance
620
+ """
621
+ class RegisteredCommand(Command):
622
+ def __init__(self):
623
+ super(RegisteredCommand, self).__init__(name, category)
624
+ self.usage_spec = usage
625
+ self.handler_class = handler_class
626
+
627
+ def invoke(self, arg, from_tty):
628
+ """GDB entry point - parses arguments and delegates to handler."""
629
+ # Create terminal first (needed for help text)
630
+ import format
631
+ terminal = format.create_terminal(from_tty)
632
+
633
+ try:
634
+ # Parse and validate arguments
635
+ if self.usage_spec:
636
+ arguments = self.usage_spec.parse(arg if arg else "")
637
+ else:
638
+ # Fallback to basic parsing without validation
639
+ import command
640
+ arguments = command.parse_arguments(arg if arg else "")
641
+
642
+ # Instantiate handler and invoke
643
+ handler = self.handler_class()
644
+ handler.invoke(arguments, terminal)
645
+
646
+ except ValueError as e:
647
+ # Validation error - show colored help
648
+ print(f"Error: {e}")
649
+ if self.usage_spec:
650
+ print()
651
+ self.usage_spec.print_to(terminal, name)
652
+ except Exception as e:
653
+ print(f"Error: {e}")
654
+ import traceback
655
+ traceback.print_exc()
656
+
657
+ # Instantiate and register the command with GDB
658
+ return RegisteredCommand()
659
+
660
+
661
+
662
+
663
+
664
+