toolbox 0.1.3 → 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 -57
  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,885 @@
1
+ """
2
+ LLDB backend implementation for unified debugger interface.
3
+
4
+ Note: This is a proof-of-concept implementation. Full LLDB support
5
+ would require more extensive testing and edge case handling.
6
+ """
7
+
8
+ import lldb
9
+
10
+ # Command categories (LLDB doesn't have exact equivalents, using symbolic constants)
11
+ COMMAND_DATA = 0
12
+ COMMAND_USER = 1
13
+
14
+ # Exception types
15
+ Error = RuntimeError # LLDB doesn't have a specific error type
16
+ MemoryError = RuntimeError # Map to RuntimeError for now
17
+
18
+
19
+ class Value:
20
+ """Wrapper for LLDB values providing unified interface."""
21
+
22
+ def __init__(self, lldb_value):
23
+ """Initialize with an LLDB value.
24
+
25
+ Args:
26
+ lldb_value: Native lldb.SBValue object
27
+ """
28
+ self._value = lldb_value
29
+
30
+ def __int__(self):
31
+ """Convert value to integer."""
32
+ return self._value.GetValueAsUnsigned()
33
+
34
+ def __str__(self):
35
+ """Convert value to string."""
36
+ return self._value.GetValue() or str(self._value.GetValueAsUnsigned())
37
+
38
+ def __eq__(self, other):
39
+ """Compare values for equality.
40
+
41
+ Compares by address/integer value to avoid debugger conversion issues.
42
+
43
+ Args:
44
+ other: Another Value, lldb.SBValue, or integer
45
+
46
+ Returns:
47
+ True if values are equal
48
+ """
49
+ if isinstance(other, Value):
50
+ return self._value.GetValueAsUnsigned() == other._value.GetValueAsUnsigned()
51
+ elif isinstance(other, lldb.SBValue):
52
+ return self._value.GetValueAsUnsigned() == other.GetValueAsUnsigned()
53
+ else:
54
+ return self._value.GetValueAsUnsigned() == int(other)
55
+
56
+ def __hash__(self):
57
+ """Return hash of value for use in sets/dicts.
58
+
59
+ Returns:
60
+ Hash of the integer value
61
+ """
62
+ return hash(self._value.GetValueAsUnsigned())
63
+
64
+ def __lt__(self, other):
65
+ """Less than comparison for pointer ordering.
66
+
67
+ Args:
68
+ other: Another Value, lldb.SBValue, or integer
69
+
70
+ Returns:
71
+ True if this value is less than other
72
+ """
73
+ if isinstance(other, Value):
74
+ return self._value.GetValueAsUnsigned() < other._value.GetValueAsUnsigned()
75
+ elif isinstance(other, lldb.SBValue):
76
+ return self._value.GetValueAsUnsigned() < other.GetValueAsUnsigned()
77
+ else:
78
+ return self._value.GetValueAsUnsigned() < int(other)
79
+
80
+ def __le__(self, other):
81
+ """Less than or equal comparison for pointer ordering.
82
+
83
+ Args:
84
+ other: Another Value, lldb.SBValue, or integer
85
+
86
+ Returns:
87
+ True if this value is less than or equal to other
88
+ """
89
+ if isinstance(other, Value):
90
+ return self._value.GetValueAsUnsigned() <= other._value.GetValueAsUnsigned()
91
+ elif isinstance(other, lldb.SBValue):
92
+ return self._value.GetValueAsUnsigned() <= other.GetValueAsUnsigned()
93
+ else:
94
+ return self._value.GetValueAsUnsigned() <= int(other)
95
+
96
+ def __gt__(self, other):
97
+ """Greater than comparison for pointer ordering.
98
+
99
+ Args:
100
+ other: Another Value, lldb.SBValue, or integer
101
+
102
+ Returns:
103
+ True if this value is greater than other
104
+ """
105
+ if isinstance(other, Value):
106
+ return self._value.GetValueAsUnsigned() > other._value.GetValueAsUnsigned()
107
+ elif isinstance(other, lldb.SBValue):
108
+ return self._value.GetValueAsUnsigned() > other.GetValueAsUnsigned()
109
+ else:
110
+ return self._value.GetValueAsUnsigned() > int(other)
111
+
112
+ def __ge__(self, other):
113
+ """Greater than or equal comparison for pointer ordering.
114
+
115
+ Args:
116
+ other: Another Value, lldb.SBValue, or integer
117
+
118
+ Returns:
119
+ True if this value is greater than or equal to other
120
+ """
121
+ if isinstance(other, Value):
122
+ return self._value.GetValueAsUnsigned() >= other._value.GetValueAsUnsigned()
123
+ elif isinstance(other, lldb.SBValue):
124
+ return self._value.GetValueAsUnsigned() >= other.GetValueAsUnsigned()
125
+ else:
126
+ return self._value.GetValueAsUnsigned() >= int(other)
127
+
128
+ def __add__(self, other):
129
+ """Add to this value.
130
+
131
+ Args:
132
+ other: Value, SBValue, or integer to add
133
+
134
+ Returns:
135
+ Integer result of addition
136
+ """
137
+ if isinstance(other, Value):
138
+ return self._value.GetValueAsUnsigned() + other._value.GetValueAsUnsigned()
139
+ elif isinstance(other, lldb.SBValue):
140
+ return self._value.GetValueAsUnsigned() + other.GetValueAsUnsigned()
141
+ else:
142
+ return self._value.GetValueAsUnsigned() + int(other)
143
+
144
+ def __radd__(self, other):
145
+ """Reverse add - when Value is on the right side of +.
146
+
147
+ Args:
148
+ other: Value, SBValue, or integer to add
149
+
150
+ Returns:
151
+ Integer result of addition
152
+ """
153
+ return self.__add__(other)
154
+
155
+ def __sub__(self, other):
156
+ """Subtract from this value.
157
+
158
+ Args:
159
+ other: Value, SBValue, or integer to subtract
160
+
161
+ Returns:
162
+ Integer result of subtraction
163
+ """
164
+ if isinstance(other, Value):
165
+ return self._value.GetValueAsUnsigned() - other._value.GetValueAsUnsigned()
166
+ elif isinstance(other, lldb.SBValue):
167
+ return self._value.GetValueAsUnsigned() - other.GetValueAsUnsigned()
168
+ else:
169
+ return self._value.GetValueAsUnsigned() - int(other)
170
+
171
+ def __rsub__(self, other):
172
+ """Reverse subtract - when Value is on the right side of -.
173
+
174
+ Args:
175
+ other: Value, SBValue, or integer to subtract from
176
+
177
+ Returns:
178
+ Integer result of subtraction
179
+ """
180
+ if isinstance(other, Value):
181
+ return other._value.GetValueAsUnsigned() - self._value.GetValueAsUnsigned()
182
+ elif isinstance(other, lldb.SBValue):
183
+ return other.GetValueAsUnsigned() - self._value.GetValueAsUnsigned()
184
+ else:
185
+ return int(other) - self._value.GetValueAsUnsigned()
186
+
187
+ def cast(self, type_obj):
188
+ """Cast this value to a different type.
189
+
190
+ Args:
191
+ type_obj: Type object to cast to
192
+
193
+ Returns:
194
+ New Value with cast type
195
+ """
196
+ if isinstance(type_obj, Type):
197
+ return Value(self._value.Cast(type_obj._type))
198
+ else:
199
+ # Assume it's a native LLDB type
200
+ return Value(self._value.Cast(type_obj))
201
+
202
+ def dereference(self):
203
+ """Dereference this pointer value.
204
+
205
+ Returns:
206
+ Value at the address
207
+ """
208
+ return Value(self._value.Dereference())
209
+
210
+ @property
211
+ def address(self):
212
+ """Get the address of this value.
213
+
214
+ Returns:
215
+ Value representing the address
216
+ """
217
+ return Value(self._value.AddressOf())
218
+
219
+ def __getitem__(self, key):
220
+ """Access struct field or array element.
221
+
222
+ Args:
223
+ key: Field name (str) or array index (int)
224
+
225
+ Returns:
226
+ Value of the field/element, or None if field doesn't exist or is invalid
227
+ """
228
+ if isinstance(key, str):
229
+ result = self._value.GetChildMemberWithName(key)
230
+ # Check if the result is valid
231
+ if result.IsValid() and not result.GetError().Fail():
232
+ return Value(result)
233
+ else:
234
+ return None
235
+ else:
236
+ # For integer index: check if this is a pointer type or array type
237
+ # Both need pointer-style arithmetic (arrays for flexible array members)
238
+ type_obj = self._value.GetType()
239
+ if type_obj.IsPointerType() or type_obj.IsArrayType():
240
+ # For arrays, treat the array address as a pointer to the element type
241
+ if type_obj.IsArrayType():
242
+ # Get the element type from the array
243
+ element_type = type_obj.GetArrayElementType()
244
+ # The address of the array IS the address of the first element
245
+ base_addr = self._value.GetLoadAddress()
246
+ else:
247
+ # For pointers, get the pointee type and dereference
248
+ element_type = type_obj.GetPointeeType()
249
+ base_addr = self._value.GetValueAsUnsigned()
250
+
251
+ type_size = element_type.GetByteSize()
252
+ new_addr = base_addr + (key * type_size)
253
+
254
+ # Optimization: if element type is a pointer, we can use CreateValueFromAddress
255
+ # which is much faster than reading memory and creating SBData
256
+ target = lldb.debugger.GetSelectedTarget()
257
+ if element_type.IsPointerType():
258
+ # Fast path for pointer arrays: use CreateValueFromAddress
259
+ # CreateValueFromAddress needs an SBAddress
260
+ sb_addr = lldb.SBAddress(new_addr, target)
261
+ result = target.CreateValueFromAddress(
262
+ f"element_{key}",
263
+ sb_addr,
264
+ element_type
265
+ )
266
+ return Value(result)
267
+ else:
268
+ # Slow path for struct/primitive arrays: read memory
269
+ process = target.GetProcess()
270
+ error = lldb.SBError()
271
+
272
+ # Read the value from memory
273
+ data = process.ReadMemory(new_addr, type_size, error)
274
+ if error.Fail():
275
+ raise MemoryError(f"Failed to read memory at 0x{new_addr:x}: {error.GetCString()}")
276
+
277
+ # Create an SBData from the bytes
278
+ sb_data = lldb.SBData()
279
+ sb_data.SetData(error, data, target.GetByteOrder(), element_type.GetByteSize())
280
+
281
+ # Create a value from the data
282
+ result = target.CreateValueFromData(
283
+ f"element_{key}",
284
+ sb_data,
285
+ element_type
286
+ )
287
+ return Value(result)
288
+ else:
289
+ # For structs, use GetChildAtIndex
290
+ return Value(self._value.GetChildAtIndex(key))
291
+
292
+ def __add__(self, offset):
293
+ """Pointer arithmetic: add offset.
294
+
295
+ Args:
296
+ offset: Integer offset to add
297
+
298
+ Returns:
299
+ New Value with adjusted pointer
300
+ """
301
+ # LLDB requires more explicit pointer arithmetic
302
+ # Get the type size and calculate new address
303
+ type_size = self._value.GetType().GetPointeeType().GetByteSize()
304
+ new_addr = self._value.GetValueAsUnsigned() + (offset * type_size)
305
+
306
+ # Create a new pointer value using CreateValueFromExpression
307
+ target = lldb.debugger.GetSelectedTarget()
308
+ addr_value = target.CreateValueFromExpression(
309
+ "temp",
310
+ f"({self._value.GetType().GetName()})0x{new_addr:x}"
311
+ )
312
+ return Value(addr_value)
313
+
314
+ def __sub__(self, offset):
315
+ """Pointer arithmetic: subtract offset.
316
+
317
+ Args:
318
+ offset: Integer offset to subtract (or another Value for pointer difference)
319
+
320
+ Returns:
321
+ New Value with adjusted pointer, or integer difference if subtracting pointers
322
+ """
323
+ if isinstance(offset, (Value, type(self._value))):
324
+ # Subtracting two pointers - return the difference in elements
325
+ other_value = offset if isinstance(offset, Value) else Value(offset)
326
+ type_size = self._value.GetType().GetPointeeType().GetByteSize()
327
+ addr_diff = self._value.GetValueAsUnsigned() - other_value._value.GetValueAsUnsigned()
328
+ return addr_diff // type_size
329
+ else:
330
+ # Subtracting integer offset from pointer
331
+ type_size = self._value.GetType().GetPointeeType().GetByteSize()
332
+ new_addr = self._value.GetValueAsUnsigned() - (offset * type_size)
333
+
334
+ # Create a new pointer value using CreateValueFromExpression
335
+ target = lldb.debugger.GetSelectedTarget()
336
+ addr_value = target.CreateValueFromExpression(
337
+ "temp",
338
+ f"({self._value.GetType().GetName()})0x{new_addr:x}"
339
+ )
340
+ return Value(addr_value)
341
+
342
+ @property
343
+ def type(self):
344
+ """Get the type of this value.
345
+
346
+ Returns:
347
+ Type object
348
+ """
349
+ return Type(self._value.GetType())
350
+
351
+ @property
352
+ def native(self):
353
+ """Get the underlying native LLDB value.
354
+
355
+ Returns:
356
+ lldb.SBValue object
357
+ """
358
+ return self._value
359
+
360
+
361
+ class Type:
362
+ """Wrapper for LLDB types providing unified interface."""
363
+
364
+ def __init__(self, lldb_type):
365
+ """Initialize with an LLDB type.
366
+
367
+ Args:
368
+ lldb_type: Native lldb.SBType object
369
+ """
370
+ self._type = lldb_type
371
+
372
+ def __str__(self):
373
+ """Convert type to string."""
374
+ return self._type.GetName()
375
+
376
+ def pointer(self):
377
+ """Get pointer type to this type.
378
+
379
+ Returns:
380
+ Type representing pointer to this type
381
+ """
382
+ return Type(self._type.GetPointerType())
383
+
384
+ def array(self, count):
385
+ """Get array type of this type.
386
+
387
+ Args:
388
+ count: Number of elements in the array (count - 1 for LLDB)
389
+
390
+ Returns:
391
+ Type representing array of this type
392
+ """
393
+ return Type(self._type.GetArrayType(count + 1))
394
+
395
+ @property
396
+ def native(self):
397
+ """Get the underlying native LLDB type.
398
+
399
+ Returns:
400
+ lldb.SBType object
401
+ """
402
+ return self._type
403
+
404
+ @property
405
+ def sizeof(self):
406
+ """Get the size of this type in bytes.
407
+
408
+ Returns:
409
+ Size in bytes as integer
410
+ """
411
+ return self._type.GetByteSize()
412
+
413
+
414
+ # Global registry of LLDB command wrappers
415
+ _lldb_command_wrappers = {}
416
+
417
+ class Command:
418
+ """Base class for debugger commands.
419
+
420
+ Subclass this and implement invoke() to create custom commands.
421
+ """
422
+
423
+ # Registry of commands for LLDB
424
+ _commands = {}
425
+
426
+ def __init__(self, name, category=COMMAND_DATA):
427
+ """Initialize and register a command.
428
+
429
+ Args:
430
+ name: Command name (e.g., "rb-object-print")
431
+ category: Command category (not used in LLDB)
432
+ """
433
+ self.name = name
434
+ self.category = category
435
+
436
+ # Register in our command registry
437
+ Command._commands[name] = self
438
+
439
+ # Note: LLDB command registration happens in init.py after all commands are loaded
440
+ # This is because LLDB needs the wrapper functions to be in a module's namespace,
441
+ # and that's easier to manage from the init script
442
+
443
+ def invoke(self, arg, from_tty):
444
+ """Handle command invocation.
445
+
446
+ Override this method in subclasses.
447
+
448
+ Args:
449
+ arg: Command arguments as string
450
+ from_tty: True if command invoked from terminal
451
+ """
452
+ raise NotImplementedError("Subclasses must implement invoke()")
453
+
454
+ @classmethod
455
+ def get_command(cls, name):
456
+ """Get a registered command by name.
457
+
458
+ Args:
459
+ name: Command name
460
+
461
+ Returns:
462
+ Command instance or None
463
+ """
464
+ return cls._commands.get(name)
465
+
466
+
467
+ def parse_and_eval(expression):
468
+ """Evaluate an expression in the debugger.
469
+
470
+ Args:
471
+ expression: Expression string (e.g., "$var", "ruby_current_vm_ptr", "42")
472
+
473
+ Returns:
474
+ Value object representing the result
475
+ """
476
+ target = lldb.debugger.GetSelectedTarget()
477
+
478
+ # If no target is selected, use the dummy target for constant evaluation
479
+ if not target.IsValid():
480
+ target = lldb.debugger.GetDummyTarget()
481
+
482
+ # Try to evaluate with a frame context if available (for variables, memory access, etc.)
483
+ process = target.GetProcess()
484
+ if process.IsValid():
485
+ thread = process.GetSelectedThread()
486
+ if thread.IsValid():
487
+ frame = thread.GetSelectedFrame()
488
+ if frame.IsValid():
489
+ # We have a full context - use frame evaluation
490
+ result = frame.EvaluateExpression(expression)
491
+ if result.IsValid():
492
+ return Value(result)
493
+
494
+ # Fallback to target-level evaluation (works for constants, globals, type casts)
495
+ # This works even with core dumps or dummy targets
496
+ result = target.EvaluateExpression(expression)
497
+
498
+ if not result.IsValid():
499
+ raise Error(f"Failed to evaluate expression: {expression}")
500
+
501
+ # Check if the expression evaluation had an error (even if result is "valid")
502
+ # LLDB returns valid=True with value=0 for undefined symbols
503
+ error = result.GetError()
504
+ if error.Fail():
505
+ raise Error(f"Failed to evaluate expression '{expression}': {error.GetCString()}")
506
+
507
+ return Value(result)
508
+
509
+
510
+ def lookup_type(type_name):
511
+ """Look up a type by name.
512
+
513
+ Args:
514
+ type_name: Type name (e.g., "struct RString", "VALUE")
515
+
516
+ Returns:
517
+ Type object
518
+ """
519
+ target = lldb.debugger.GetSelectedTarget()
520
+
521
+ # LLDB's FindFirstType searches the type system
522
+ lldb_type = target.FindFirstType(type_name)
523
+
524
+ if not lldb_type.IsValid():
525
+ raise Error(f"Failed to find type: {type_name}")
526
+
527
+ return Type(lldb_type)
528
+
529
+
530
+ def set_convenience_variable(name, value):
531
+ """Set an LLDB convenience variable.
532
+
533
+ Args:
534
+ name: Variable name (without $ prefix)
535
+ value: Value to set (can be Value wrapper or native value)
536
+
537
+ Note: LLDB doesn't have direct convenience variables like GDB.
538
+ This implementation uses expression evaluation to set variables.
539
+ """
540
+ if isinstance(value, Value):
541
+ native_value = value._value
542
+ else:
543
+ native_value = value
544
+
545
+ # LLDB approach: use expression to create a persistent variable
546
+ # Variables in LLDB are prefixed with $
547
+ target = lldb.debugger.GetSelectedTarget()
548
+ frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
549
+
550
+ # Create a persistent variable by evaluating an expression
551
+ if hasattr(native_value, 'GetValue'):
552
+ # It's an SBValue
553
+ addr = native_value.GetValueAsUnsigned()
554
+ type_name = native_value.GetType().GetName()
555
+ frame.EvaluateExpression(f"({type_name})0x{addr:x}", lldb.SBExpressionOptions())
556
+ # For now, simplified implementation
557
+ # Full implementation would require more complex value handling
558
+
559
+
560
+ def execute(command, from_tty=False, to_string=False):
561
+ """Execute a debugger command.
562
+
563
+ Args:
564
+ command: Command string to execute
565
+ from_tty: Whether command is from terminal (unused in LLDB)
566
+ to_string: If True, return command output as string
567
+
568
+ Returns:
569
+ String output if to_string=True, None otherwise
570
+ """
571
+ debugger = lldb.debugger
572
+ interpreter = debugger.GetCommandInterpreter()
573
+
574
+ result = lldb.SBCommandReturnObject()
575
+ interpreter.HandleCommand(command, result)
576
+
577
+ if not result.Succeeded():
578
+ raise Error(f"Command failed: {result.GetError()}")
579
+
580
+ if to_string:
581
+ return result.GetOutput()
582
+ return None
583
+
584
+
585
+ def lookup_symbol(address):
586
+ """Look up symbol name for an address.
587
+
588
+ Args:
589
+ address: Memory address (as integer)
590
+
591
+ Returns:
592
+ Symbol name string, or None if no symbol found
593
+ """
594
+ try:
595
+ symbol_info = execute(f"image lookup -a 0x{address:x}", to_string=True)
596
+ # LLDB output format: "Summary: module`symbol_name at file:line"
597
+ # or "Symbol: ... name = "symbol_name""
598
+ for line in symbol_info.split('\n'):
599
+ if 'Summary:' in line:
600
+ # Extract symbol from "Summary: ruby`rb_f_puts at io.c:8997:1"
601
+ if '`' in line:
602
+ start = line.index('`') + 1
603
+ # Symbol ends at ' at ' or end of line
604
+ if ' at ' in line[start:]:
605
+ end = start + line[start:].index(' at ')
606
+ else:
607
+ end = len(line)
608
+ return line[start:end].strip()
609
+ elif 'Symbol:' in line and 'name = "' in line:
610
+ # Alternative format
611
+ start = line.index('name = "') + 8
612
+ end = line.index('"', start)
613
+ return line[start:end]
614
+ return None
615
+ except:
616
+ return None
617
+
618
+
619
+ def invalidate_cached_frames():
620
+ """Invalidate cached frame information.
621
+
622
+ Note: LLDB handles frame caching differently than GDB.
623
+ This is a no-op for now.
624
+ """
625
+ # LLDB typically invalidates frames automatically when the
626
+ # process state changes. Manual invalidation is rarely needed.
627
+ pass
628
+
629
+
630
+ def get_enum_value(enum_name, member_name):
631
+ """Get an enum member value.
632
+
633
+ Args:
634
+ enum_name: The enum type name (e.g., 'ruby_value_type')
635
+ member_name: The member name (e.g., 'RUBY_T_STRING')
636
+
637
+ Returns:
638
+ Integer value of the enum member
639
+
640
+ Raises:
641
+ Error if enum member cannot be found
642
+
643
+ Note: In LLDB, enum members must be accessed with the enum prefix:
644
+ (int)enum_name::member_name
645
+ """
646
+ enum_expr = f"(int){enum_name}::{member_name}"
647
+ return int(parse_and_eval(enum_expr))
648
+
649
+
650
+ def read_memory(address, size):
651
+ """Read memory from the debugged process.
652
+
653
+ Args:
654
+ address: Memory address (as integer or pointer value)
655
+ size: Number of bytes to read
656
+
657
+ Returns:
658
+ bytes object containing the memory contents
659
+
660
+ Raises:
661
+ MemoryError: If memory cannot be read
662
+ """
663
+ # Convert to integer address if needed
664
+ if hasattr(address, '__int__'):
665
+ address = int(address)
666
+
667
+ process = lldb.debugger.GetSelectedTarget().GetProcess()
668
+ error = lldb.SBError()
669
+ data = process.ReadMemory(address, size, error)
670
+
671
+ if not error.Success():
672
+ raise MemoryError(f"Cannot read {size} bytes at 0x{address:x}: {error}")
673
+
674
+ return bytes(data)
675
+
676
+
677
+ def read_cstring(address, max_length=256):
678
+ """Read a NUL-terminated C string from memory.
679
+
680
+ Args:
681
+ address: Memory address (as integer or pointer value)
682
+ max_length: Maximum bytes to read before giving up
683
+
684
+ Returns:
685
+ Tuple of (bytes, actual_length) where actual_length is the string
686
+ length not including the NUL terminator
687
+
688
+ Raises:
689
+ MemoryError: If memory cannot be read
690
+ """
691
+ # Convert to integer address if needed
692
+ if hasattr(address, '__int__'):
693
+ address = int(address)
694
+
695
+ process = lldb.debugger.GetSelectedTarget().GetProcess()
696
+ error = lldb.SBError()
697
+ data = process.ReadMemory(address, max_length, error)
698
+
699
+ if not error.Success():
700
+ raise MemoryError(f"Cannot read memory at 0x{address:x}: {error}")
701
+
702
+ buffer = bytes(data)
703
+ n = buffer.find(b'\x00')
704
+ if n == -1:
705
+ n = max_length
706
+ return (buffer[:n], n)
707
+
708
+
709
+ def create_value(address, value_type):
710
+ """Create a typed Value from a memory address.
711
+
712
+ Args:
713
+ address: Memory address (as integer, pointer value, or lldb.SBValue)
714
+ value_type: Type object (or native lldb.SBType) to cast to
715
+
716
+ Returns:
717
+ Value object representing the typed value at that address
718
+
719
+ Examples:
720
+ >>> rbasic_type = debugger.lookup_type('struct RBasic').pointer()
721
+ >>> obj = debugger.create_value(0x7fff12345678, rbasic_type)
722
+ """
723
+ # Unwrap Type if needed
724
+ if isinstance(value_type, Type):
725
+ value_type = value_type._type
726
+
727
+ # Handle different address types
728
+ if isinstance(address, Value):
729
+ # It's already a wrapped Value, just cast it
730
+ return Value(address._value.Cast(value_type))
731
+ elif isinstance(address, lldb.SBValue):
732
+ # It's a native lldb.SBValue, cast it directly
733
+ return Value(address.Cast(value_type))
734
+ else:
735
+ # Convert to integer address if needed
736
+ if hasattr(address, '__int__'):
737
+ address = int(address)
738
+
739
+ target = lldb.debugger.GetSelectedTarget()
740
+
741
+ # For array types, use expression evaluation (CreateValueFromData doesn't work for large arrays)
742
+ if value_type.IsArrayType():
743
+ type_name = value_type.GetName()
744
+ expr = f"*({type_name}*)0x{address:x}"
745
+ addr_value = target.CreateValueFromExpression(
746
+ f"array_0x{address:x}",
747
+ expr
748
+ )
749
+ return Value(addr_value)
750
+
751
+ # OPTIMIZATION: For simple types like VALUE (unsigned long), use CreateValueFromData
752
+ # which avoids expression evaluation and is much faster for bulk operations
753
+ process = target.GetProcess()
754
+
755
+ # For scalar types (integers/pointers), we can read and create directly
756
+ type_size = value_type.GetByteSize()
757
+ error = lldb.SBError()
758
+ data_bytes = process.ReadMemory(address, type_size, error)
759
+
760
+ if error.Fail():
761
+ # Fall back to expression-based approach if memory read fails
762
+ type_name = value_type.GetName()
763
+ expr = f"({type_name})0x{address:x}"
764
+ addr_value = target.CreateValueFromExpression(
765
+ f"addr_0x{address:x}",
766
+ expr
767
+ )
768
+ return Value(addr_value)
769
+
770
+ # Create SBData from the memory bytes
771
+ sb_data = lldb.SBData()
772
+ sb_data.SetData(error, data_bytes, target.GetByteOrder(), type_size)
773
+
774
+ # Create value from the data (no expression evaluation!)
775
+ # Note: This creates a value-by-value, but for VALUE (unsigned long) that's correct
776
+ addr_value = target.CreateValueFromData(
777
+ f"val_0x{address:x}",
778
+ sb_data,
779
+ value_type
780
+ )
781
+
782
+ # Store the original address as metadata so we can use it for debugging
783
+ # The value itself contains the VALUE integer read from that address
784
+ return Value(addr_value)
785
+
786
+
787
+ def create_value_from_address(address, value_type):
788
+ """Create a typed Value from a memory address.
789
+
790
+ This uses CreateValueFromAddress to create a value of the given type at the
791
+ specified address. This is more efficient than CreateValueFromExpression
792
+ and supports array types directly.
793
+
794
+ Args:
795
+ address: Memory address (as integer, or Value object representing a pointer)
796
+ value_type: Type object (or native lldb.SBType) representing the type
797
+
798
+ Returns:
799
+ Value object representing the data at that address
800
+
801
+ Examples:
802
+ >>> rbasic_type = debugger.lookup_type('struct RBasic')
803
+ >>> array_type = rbasic_type.array(100)
804
+ >>> page_start = page['start'] # Value object
805
+ >>> page_array = debugger.create_value_from_address(page_start, array_type)
806
+ """
807
+ # Convert to integer address if needed (handles Value objects via __int__)
808
+ if hasattr(address, '__int__'):
809
+ address = int(address)
810
+
811
+ # Unwrap Type if needed
812
+ if isinstance(value_type, Type):
813
+ value_type = value_type._type
814
+
815
+ target = lldb.debugger.GetSelectedTarget()
816
+
817
+ # CreateValueFromAddress takes an SBAddress, not an integer
818
+ # We need to create an SBAddress from the load address
819
+ sb_addr = target.ResolveLoadAddress(address)
820
+ if not sb_addr.IsValid():
821
+ raise MemoryError(f"Invalid address: 0x{address:x}")
822
+
823
+ # CreateValueFromAddress takes an address and creates a value of the given type
824
+ # reading from that memory location
825
+ addr_value = target.CreateValueFromAddress(
826
+ f"val_at_0x{address:x}",
827
+ sb_addr,
828
+ value_type
829
+ )
830
+
831
+ if not addr_value.IsValid():
832
+ raise MemoryError(f"Failed to create value from address 0x{address:x}")
833
+
834
+ return Value(addr_value)
835
+
836
+
837
+ def create_value_from_int(int_value, value_type):
838
+ """Create a typed Value from an integer (not a memory address to read from).
839
+
840
+ This is used when the integer itself IS the value (like VALUE which is a pointer).
841
+ Unlike create_value(), this doesn't read from memory - it creates a value containing
842
+ the integer itself.
843
+
844
+ Args:
845
+ int_value: Integer value, or Value object that will be converted to int
846
+ value_type: Type object (or native lldb.SBType) to cast to
847
+
848
+ Returns:
849
+ Value object with the integer value
850
+
851
+ Examples:
852
+ >>> value_type = debugger.lookup_type('VALUE')
853
+ >>> obj_address = page['start'] # Value object
854
+ >>> obj = debugger.create_value_from_int(obj_address, value_type)
855
+ """
856
+ # Convert to integer if needed (handles Value objects via __int__)
857
+ if hasattr(int_value, '__int__'):
858
+ int_value = int(int_value)
859
+
860
+ # Unwrap Type if needed
861
+ if isinstance(value_type, Type):
862
+ value_type = value_type._type
863
+
864
+ # Create SBData with the integer value
865
+ target = lldb.debugger.GetSelectedTarget()
866
+ type_size = value_type.GetByteSize()
867
+
868
+ # Convert integer to bytes (little-endian for x86_64)
869
+ int_bytes = int_value.to_bytes(type_size, byteorder='little', signed=False)
870
+
871
+ # Create SBData from bytes
872
+ error = lldb.SBError()
873
+ sb_data = lldb.SBData()
874
+ sb_data.SetData(error, int_bytes, target.GetByteOrder(), type_size)
875
+
876
+ # Create value from data
877
+ result = target.CreateValueFromData(
878
+ f"int_0x{int_value:x}",
879
+ sb_data,
880
+ value_type
881
+ )
882
+
883
+ return Value(result)
884
+
885
+