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.
- 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 +178 -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 +254 -0
- data/data/toolbox/constants.py +200 -0
- data/data/toolbox/context.py +295 -0
- data/data/toolbox/debugger/__init__.py +99 -0
- data/data/toolbox/debugger/gdb_backend.py +595 -0
- data/data/toolbox/debugger/lldb_backend.py +885 -0
- data/data/toolbox/fiber.py +885 -0
- data/data/toolbox/format.py +200 -0
- data/data/toolbox/heap.py +669 -0
- data/data/toolbox/init.py +85 -0
- data/data/toolbox/object.py +84 -0
- data/data/toolbox/rarray.py +124 -0
- data/data/toolbox/rbasic.py +103 -0
- data/data/toolbox/rbignum.py +52 -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 +98 -0
- data/data/toolbox/rhash.py +159 -0
- data/data/toolbox/rstring.py +234 -0
- data/data/toolbox/rstruct.py +157 -0
- data/data/toolbox/rsymbol.py +302 -0
- data/data/toolbox/stack.py +630 -0
- data/data/toolbox/value.py +183 -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 +2 -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,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
|
+
|