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.
@@ -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