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