ruby-gdb 0.1.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
- data/bake/ruby/gdb.rb +135 -0
- data/context/fiber-debugging.md +372 -0
- data/context/getting-started.md +167 -0
- data/context/heap-debugging.md +426 -0
- data/context/index.yaml +28 -0
- data/context/object-inspection.md +272 -0
- data/context/stack-inspection.md +357 -0
- data/data/ruby/gdb/command.py +254 -0
- data/data/ruby/gdb/constants.py +59 -0
- data/data/ruby/gdb/fiber.py +825 -0
- data/data/ruby/gdb/format.py +201 -0
- data/data/ruby/gdb/heap.py +563 -0
- data/data/ruby/gdb/init.py +25 -0
- data/data/ruby/gdb/object.py +85 -0
- data/data/ruby/gdb/rarray.py +124 -0
- data/data/ruby/gdb/rbasic.py +103 -0
- data/data/ruby/gdb/rbignum.py +52 -0
- data/data/ruby/gdb/rclass.py +133 -0
- data/data/ruby/gdb/rexception.py +150 -0
- data/data/ruby/gdb/rfloat.py +95 -0
- data/data/ruby/gdb/rhash.py +157 -0
- data/data/ruby/gdb/rstring.py +217 -0
- data/data/ruby/gdb/rstruct.py +157 -0
- data/data/ruby/gdb/rsymbol.py +291 -0
- data/data/ruby/gdb/stack.py +609 -0
- data/data/ruby/gdb/value.py +181 -0
- data/lib/ruby/gdb/version.rb +13 -0
- data/lib/ruby/gdb.rb +23 -0
- data/license.md +21 -0
- data/readme.md +42 -0
- metadata +69 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import gdb
|
|
2
|
+
import rbasic
|
|
3
|
+
import constants
|
|
4
|
+
import format
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RubySymbol:
|
|
8
|
+
"""Ruby symbol from ID (integer identifier)."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, symbol_id):
|
|
11
|
+
"""Initialize from a symbol ID.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
symbol_id: Integer symbol ID
|
|
15
|
+
"""
|
|
16
|
+
self.symbol_id = int(symbol_id)
|
|
17
|
+
self._name = None
|
|
18
|
+
|
|
19
|
+
def to_str(self):
|
|
20
|
+
"""Get symbol name from global symbol table, or None if not found."""
|
|
21
|
+
if self._name is not None:
|
|
22
|
+
return self._name
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
# rb_id_to_serial
|
|
26
|
+
tLAST_OP_ID = constants.get("tLAST_OP_ID", 163)
|
|
27
|
+
ID_ENTRY_UNIT = 512
|
|
28
|
+
ID_ENTRY_SIZE = 2
|
|
29
|
+
|
|
30
|
+
id_val = self.symbol_id
|
|
31
|
+
|
|
32
|
+
if id_val > tLAST_OP_ID:
|
|
33
|
+
try:
|
|
34
|
+
RUBY_ID_SCOPE_SHIFT = int(constants.get("RUBY_ID_SCOPE_SHIFT"))
|
|
35
|
+
except Exception:
|
|
36
|
+
RUBY_ID_SCOPE_SHIFT = 3 # Default value
|
|
37
|
+
serial = id_val >> RUBY_ID_SCOPE_SHIFT
|
|
38
|
+
else:
|
|
39
|
+
serial = id_val
|
|
40
|
+
|
|
41
|
+
# Access ruby_global_symbols
|
|
42
|
+
global_symbols = gdb.parse_and_eval("ruby_global_symbols")
|
|
43
|
+
|
|
44
|
+
# Ruby 3.5+ changed from last_id to next_id
|
|
45
|
+
try:
|
|
46
|
+
last_id = int(global_symbols['last_id'])
|
|
47
|
+
except (gdb.error, KeyError):
|
|
48
|
+
# Ruby 3.5+ uses next_id instead
|
|
49
|
+
last_id = int(global_symbols['next_id']) - 1
|
|
50
|
+
|
|
51
|
+
if not serial or serial > last_id:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
idx = serial // ID_ENTRY_UNIT
|
|
55
|
+
|
|
56
|
+
# Get ids array
|
|
57
|
+
ids = global_symbols['ids'].cast(constants.get_type("struct RArray").pointer())
|
|
58
|
+
|
|
59
|
+
# Import rarray to access the array
|
|
60
|
+
import rarray
|
|
61
|
+
arr = rarray.RArray(global_symbols['ids'])
|
|
62
|
+
if arr is None:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Get the symbols entry
|
|
66
|
+
entry_val = arr[idx]
|
|
67
|
+
|
|
68
|
+
# Get the symbol from the entry
|
|
69
|
+
entry_arr = rarray.RArray(entry_val)
|
|
70
|
+
if entry_arr is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
ids_idx = (serial % ID_ENTRY_UNIT) * ID_ENTRY_SIZE
|
|
74
|
+
sym_val = entry_arr[ids_idx]
|
|
75
|
+
|
|
76
|
+
# Get the string from the symbol
|
|
77
|
+
import rstring
|
|
78
|
+
str_obj = rstring.RString(sym_val)
|
|
79
|
+
if str_obj is None:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
self._name = str_obj.to_str()
|
|
83
|
+
return self._name
|
|
84
|
+
|
|
85
|
+
except Exception:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def __str__(self):
|
|
89
|
+
"""Return symbol representation."""
|
|
90
|
+
name = self.to_str()
|
|
91
|
+
if name:
|
|
92
|
+
return f":{name}"
|
|
93
|
+
else:
|
|
94
|
+
return f":id_0x{self.symbol_id:x}"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RSymbolImmediate:
|
|
98
|
+
"""Ruby immediate symbol (encoded in VALUE)."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, value):
|
|
101
|
+
self.value = value
|
|
102
|
+
self._ruby_symbol = None
|
|
103
|
+
|
|
104
|
+
def id(self):
|
|
105
|
+
"""Get the symbol ID."""
|
|
106
|
+
return get_id(self.value)
|
|
107
|
+
|
|
108
|
+
def ruby_symbol(self):
|
|
109
|
+
"""Get RubySymbol instance for this symbol."""
|
|
110
|
+
if self._ruby_symbol is None:
|
|
111
|
+
self._ruby_symbol = RubySymbol(self.id())
|
|
112
|
+
return self._ruby_symbol
|
|
113
|
+
|
|
114
|
+
def to_str(self):
|
|
115
|
+
"""Get symbol name from global symbol table, or None if not found."""
|
|
116
|
+
return self.ruby_symbol().to_str()
|
|
117
|
+
|
|
118
|
+
def __str__(self):
|
|
119
|
+
"""Return symbol representation."""
|
|
120
|
+
name = self.to_str()
|
|
121
|
+
if name:
|
|
122
|
+
return f"<T_SYMBOL> :{name}"
|
|
123
|
+
else:
|
|
124
|
+
return f"<T_SYMBOL> :id_0x{self.id():x}"
|
|
125
|
+
|
|
126
|
+
def print_to(self, terminal):
|
|
127
|
+
"""Return formatted symbol representation."""
|
|
128
|
+
name = self.to_str()
|
|
129
|
+
if name:
|
|
130
|
+
tag = terminal.print(
|
|
131
|
+
format.metadata, '<',
|
|
132
|
+
format.type, 'T_SYMBOL',
|
|
133
|
+
format.metadata, '>',
|
|
134
|
+
format.reset
|
|
135
|
+
)
|
|
136
|
+
symbol_val = terminal.print(format.symbol, f':{name}', format.reset)
|
|
137
|
+
return f"{tag} {symbol_val}"
|
|
138
|
+
else:
|
|
139
|
+
tag = terminal.print(
|
|
140
|
+
format.metadata, '<',
|
|
141
|
+
format.type, 'T_SYMBOL',
|
|
142
|
+
format.metadata, '>',
|
|
143
|
+
format.reset
|
|
144
|
+
)
|
|
145
|
+
symbol_val = terminal.print(format.symbol, f':id_0x{self.id():x}', format.reset)
|
|
146
|
+
return f"{tag} {symbol_val}"
|
|
147
|
+
|
|
148
|
+
def print_recursive(self, printer, depth):
|
|
149
|
+
"""Print this symbol (no recursion needed)."""
|
|
150
|
+
printer.print(self)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class RSymbolObject:
|
|
154
|
+
"""Ruby T_SYMBOL heap object."""
|
|
155
|
+
|
|
156
|
+
def __init__(self, value):
|
|
157
|
+
self.value = value
|
|
158
|
+
self.rsymbol = value.cast(constants.get_type("struct RSymbol").pointer())
|
|
159
|
+
self._ruby_symbol = None
|
|
160
|
+
|
|
161
|
+
def id(self):
|
|
162
|
+
"""Get the symbol ID."""
|
|
163
|
+
return int(self.rsymbol.dereference()['id'])
|
|
164
|
+
|
|
165
|
+
def ruby_symbol(self):
|
|
166
|
+
"""Get RubySymbol instance for this symbol."""
|
|
167
|
+
if self._ruby_symbol is None:
|
|
168
|
+
self._ruby_symbol = RubySymbol(self.id())
|
|
169
|
+
return self._ruby_symbol
|
|
170
|
+
|
|
171
|
+
def fstr(self):
|
|
172
|
+
"""Get the frozen string containing the symbol name."""
|
|
173
|
+
return self.rsymbol.dereference()['fstr']
|
|
174
|
+
|
|
175
|
+
def to_str(self):
|
|
176
|
+
"""Get symbol name as a string."""
|
|
177
|
+
try:
|
|
178
|
+
import rstring
|
|
179
|
+
fstr_val = self.fstr()
|
|
180
|
+
str_obj = rstring.RString(fstr_val)
|
|
181
|
+
if str_obj:
|
|
182
|
+
return str_obj.to_str()
|
|
183
|
+
return None
|
|
184
|
+
except Exception:
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
def __str__(self):
|
|
188
|
+
"""Return symbol representation."""
|
|
189
|
+
name = self.to_str()
|
|
190
|
+
addr = int(self.value)
|
|
191
|
+
if name:
|
|
192
|
+
return f"<T_SYMBOL@0x{addr:x}> :{name}"
|
|
193
|
+
else:
|
|
194
|
+
fstr_val = self.fstr()
|
|
195
|
+
return f"<T_SYMBOL@0x{addr:x}> :<Symbol:0x{int(fstr_val):x}>"
|
|
196
|
+
|
|
197
|
+
def print_to(self, terminal):
|
|
198
|
+
"""Return formatted symbol representation."""
|
|
199
|
+
name = self.to_str()
|
|
200
|
+
addr = int(self.value)
|
|
201
|
+
if name:
|
|
202
|
+
tag = terminal.print(
|
|
203
|
+
format.metadata, '<',
|
|
204
|
+
format.type, 'T_SYMBOL',
|
|
205
|
+
format.metadata, f'@0x{addr:x}>',
|
|
206
|
+
format.reset
|
|
207
|
+
)
|
|
208
|
+
symbol_val = terminal.print(format.symbol, f':{name}', format.reset)
|
|
209
|
+
return f"{tag} {symbol_val}"
|
|
210
|
+
else:
|
|
211
|
+
fstr_val = self.fstr()
|
|
212
|
+
tag = terminal.print(
|
|
213
|
+
format.metadata, '<',
|
|
214
|
+
format.type, 'T_SYMBOL',
|
|
215
|
+
format.metadata, f'@0x{addr:x}>',
|
|
216
|
+
format.reset
|
|
217
|
+
)
|
|
218
|
+
symbol_val = terminal.print(format.symbol, f':<Symbol:0x{int(fstr_val):x}>', format.reset)
|
|
219
|
+
return f"{tag} {symbol_val}"
|
|
220
|
+
|
|
221
|
+
def print_recursive(self, printer, depth):
|
|
222
|
+
"""Print this symbol (no recursion needed)."""
|
|
223
|
+
printer.print(self)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def is_symbol(value):
|
|
228
|
+
"""
|
|
229
|
+
Check if a VALUE is a symbol using the logic from Ruby's .gdbinit.
|
|
230
|
+
|
|
231
|
+
From .gdbinit:
|
|
232
|
+
if (($arg0) & ~(~(VALUE)0<<RUBY_SPECIAL_SHIFT)) == RUBY_SYMBOL_FLAG
|
|
233
|
+
|
|
234
|
+
This checks if the low bits (after masking RUBY_SPECIAL_SHIFT) match RUBY_SYMBOL_FLAG.
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
val_int = int(value)
|
|
238
|
+
|
|
239
|
+
# Get Ruby constants
|
|
240
|
+
RUBY_SPECIAL_SHIFT = constants.get("RUBY_SPECIAL_SHIFT")
|
|
241
|
+
RUBY_SYMBOL_FLAG = constants.get("RUBY_SYMBOL_FLAG")
|
|
242
|
+
|
|
243
|
+
if RUBY_SPECIAL_SHIFT is None or RUBY_SYMBOL_FLAG is None:
|
|
244
|
+
# Fallback to simple check
|
|
245
|
+
return (val_int & 0xFF) == 0x0C
|
|
246
|
+
|
|
247
|
+
# Create mask for low bits: ~(~0 << RUBY_SPECIAL_SHIFT)
|
|
248
|
+
# This gives us RUBY_SPECIAL_SHIFT low bits set to 1
|
|
249
|
+
mask = ~(~0 << RUBY_SPECIAL_SHIFT)
|
|
250
|
+
|
|
251
|
+
# Check if masked value equals RUBY_SYMBOL_FLAG
|
|
252
|
+
return (val_int & mask) == RUBY_SYMBOL_FLAG
|
|
253
|
+
except Exception:
|
|
254
|
+
# Fallback to simple check
|
|
255
|
+
return (int(value) & 0xFF) == 0x0C
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_id(value):
|
|
259
|
+
"""
|
|
260
|
+
Extract symbol ID from a VALUE using the logic from Ruby's .gdbinit.
|
|
261
|
+
|
|
262
|
+
From .gdbinit:
|
|
263
|
+
set $id = (($arg0) >> RUBY_SPECIAL_SHIFT)
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
RUBY_SPECIAL_SHIFT = constants.get("RUBY_SPECIAL_SHIFT")
|
|
267
|
+
if RUBY_SPECIAL_SHIFT is None:
|
|
268
|
+
# Fallback to shift by 8
|
|
269
|
+
return int(value) >> 8
|
|
270
|
+
return int(value) >> RUBY_SPECIAL_SHIFT
|
|
271
|
+
except Exception:
|
|
272
|
+
# Fallback to shift by 8
|
|
273
|
+
return int(value) >> 8
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def RSymbol(value):
|
|
277
|
+
"""
|
|
278
|
+
Factory function to create the appropriate RSymbol variant.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
RSymbolImmediate, RSymbolObject, or None
|
|
282
|
+
"""
|
|
283
|
+
# Check if it's an immediate symbol
|
|
284
|
+
if is_symbol(value):
|
|
285
|
+
return RSymbolImmediate(value)
|
|
286
|
+
|
|
287
|
+
# Check if it's a T_SYMBOL object
|
|
288
|
+
if rbasic.is_type(value, 'RUBY_T_SYMBOL'):
|
|
289
|
+
return RSymbolObject(value)
|
|
290
|
+
|
|
291
|
+
return None
|