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