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.
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 +200 -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 +479 -0
  13. data/data/toolbox/constants.py +200 -0
  14. data/data/toolbox/context.py +371 -0
  15. data/data/toolbox/debugger/__init__.py +101 -0
  16. data/data/toolbox/debugger/gdb_backend.py +664 -0
  17. data/data/toolbox/debugger/lldb_backend.py +986 -0
  18. data/data/toolbox/fiber.py +877 -0
  19. data/data/toolbox/format.py +205 -0
  20. data/data/toolbox/heap.py +679 -0
  21. data/data/toolbox/init.py +89 -0
  22. data/data/toolbox/print.py +79 -0
  23. data/data/toolbox/rarray.py +116 -0
  24. data/data/toolbox/rbasic.py +99 -0
  25. data/data/toolbox/rbignum.py +48 -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 +88 -0
  30. data/data/toolbox/rhash.py +151 -0
  31. data/data/toolbox/rstring.py +230 -0
  32. data/data/toolbox/rstruct.py +149 -0
  33. data/data/toolbox/rsymbol.py +278 -0
  34. data/data/toolbox/rvalue.py +183 -0
  35. data/data/toolbox/stack.py +620 -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 +0 -0
  44. metadata +95 -165
  45. metadata.gz.sig +0 -0
  46. data/Rakefile +0 -61
  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,371 @@
1
+ """Ruby execution context utilities and commands."""
2
+
3
+ import debugger
4
+ import command
5
+ import format
6
+ import rvalue
7
+ import rexception
8
+
9
+
10
+ class RubyContext:
11
+ """Wrapper for Ruby execution context (rb_execution_context_t).
12
+
13
+ Provides a high-level interface for working with Ruby execution contexts,
14
+ including inspection, convenience variable setup, and information display.
15
+
16
+ Example:
17
+ ctx = RubyContext.current()
18
+ if ctx:
19
+ ctx.print_info(terminal)
20
+ ctx.setup_convenience_variables()
21
+ """
22
+
23
+ def __init__(self, ec):
24
+ """Create a RubyContext wrapper.
25
+
26
+ Args:
27
+ ec: Execution context pointer (rb_execution_context_t *)
28
+ """
29
+ self.ec = ec
30
+ self._cfp = None
31
+ self._errinfo = None
32
+ self._vm_stack = None
33
+ self._vm_stack_size = None
34
+
35
+ @classmethod
36
+ def current(cls):
37
+ """Get the current execution context from the running thread.
38
+
39
+ Tries multiple approaches in order of preference:
40
+ 1. ruby_current_ec - TLS variable (works in GDB, some LLDB)
41
+ 2. rb_current_ec_noinline() - function call (works in most cases)
42
+ 3. rb_current_ec() - macOS-specific function
43
+
44
+ Returns:
45
+ RubyContext instance, or None if not available
46
+ """
47
+ # Try ruby_current_ec variable first
48
+ try:
49
+ ec = debugger.parse_and_eval('ruby_current_ec')
50
+ if ec is not None and int(ec) != 0:
51
+ return cls(ec)
52
+ except debugger.Error:
53
+ pass
54
+
55
+ # Fallback to rb_current_ec_noinline() function
56
+ try:
57
+ ec = debugger.parse_and_eval('rb_current_ec_noinline()')
58
+ if ec is not None and int(ec) != 0:
59
+ return cls(ec)
60
+ except debugger.Error:
61
+ pass
62
+
63
+ # Last resort: rb_current_ec() (macOS-specific)
64
+ try:
65
+ ec = debugger.parse_and_eval('rb_current_ec()')
66
+ if ec is not None and int(ec) != 0:
67
+ return cls(ec)
68
+ except debugger.Error:
69
+ pass
70
+
71
+ return None
72
+
73
+ @property
74
+ def cfp(self):
75
+ """Get control frame pointer (lazy load)."""
76
+ if self._cfp is None:
77
+ try:
78
+ self._cfp = self.ec['cfp']
79
+ except Exception:
80
+ pass
81
+ return self._cfp
82
+
83
+ @property
84
+ def errinfo(self):
85
+ """Get exception VALUE (lazy load)."""
86
+ if self._errinfo is None:
87
+ try:
88
+ self._errinfo = self.ec['errinfo']
89
+ except Exception:
90
+ pass
91
+ return self._errinfo
92
+
93
+ @property
94
+ def has_exception(self):
95
+ """Check if there's a real exception (not nil/special value)."""
96
+ if self.errinfo is None:
97
+ return False
98
+ return rexception.is_exception(self.errinfo)
99
+
100
+ @property
101
+ def vm_stack(self):
102
+ """Get VM stack pointer (lazy load)."""
103
+ if self._vm_stack is None:
104
+ try:
105
+ self._vm_stack = self.ec['vm_stack']
106
+ except Exception:
107
+ pass
108
+ return self._vm_stack
109
+
110
+ @property
111
+ def vm_stack_size(self):
112
+ """Get VM stack size (lazy load)."""
113
+ if self._vm_stack_size is None:
114
+ try:
115
+ self._vm_stack_size = int(self.ec['vm_stack_size'])
116
+ except Exception:
117
+ pass
118
+ return self._vm_stack_size
119
+
120
+ @property
121
+ def storage(self):
122
+ """Get fiber storage VALUE."""
123
+ try:
124
+ return self.ec['storage']
125
+ except Exception:
126
+ return None
127
+
128
+ def setup_convenience_variables(self):
129
+ """Set up convenience variables for this execution context.
130
+
131
+ Sets:
132
+ $ec - Execution context pointer
133
+ $cfp - Control frame pointer
134
+ $errinfo - Current exception (if any)
135
+
136
+ Returns:
137
+ dict with keys: 'ec', 'cfp', 'errinfo' (values are the set variables)
138
+ """
139
+ result = {}
140
+
141
+ # Set $ec
142
+ debugger.set_convenience_variable('ec', self.ec)
143
+ result['ec'] = self.ec
144
+
145
+ # Set $cfp (control frame pointer)
146
+ if self.cfp is not None:
147
+ debugger.set_convenience_variable('cfp', self.cfp)
148
+ result['cfp'] = self.cfp
149
+ else:
150
+ result['cfp'] = None
151
+
152
+ # Set $errinfo if there's an exception
153
+ if self.has_exception:
154
+ debugger.set_convenience_variable('errinfo', self.errinfo)
155
+ result['errinfo'] = self.errinfo
156
+ else:
157
+ result['errinfo'] = None
158
+
159
+ return result
160
+
161
+ def print_info(self, terminal):
162
+ """Print detailed information about this execution context.
163
+
164
+ Args:
165
+ terminal: Terminal formatter for output
166
+ """
167
+ # Cache property lookups
168
+ vm_stack = self.vm_stack
169
+ vm_stack_size = self.vm_stack_size
170
+ cfp = self.cfp
171
+ storage = self.storage
172
+ errinfo = self.errinfo
173
+ has_exception = self.has_exception
174
+
175
+ terminal.print("Execution Context:")
176
+ terminal.print(f" $ec = ", end='')
177
+ terminal.print_type_tag('rb_execution_context_t', int(self.ec), None)
178
+ terminal.print()
179
+
180
+ # VM Stack info
181
+ if vm_stack is not None and vm_stack_size is not None:
182
+ terminal.print(f" VM Stack: ", end='')
183
+ terminal.print_type_tag('VALUE', int(vm_stack))
184
+ terminal.print()
185
+ else:
186
+ terminal.print(f" VM Stack: <unavailable>")
187
+
188
+ # Control Frame info
189
+ if cfp is not None:
190
+ terminal.print(f" $cfp = ", end='')
191
+ terminal.print_type_tag('rb_control_frame_t', int(cfp), None)
192
+ terminal.print()
193
+ else:
194
+ terminal.print(f" $cfp = <unavailable>")
195
+
196
+ # Storage info
197
+ if storage is not None and not rvalue.is_nil(storage):
198
+ terminal.print(f" Storage: ", end='')
199
+ terminal.print_type_tag('VALUE', int(storage), None)
200
+ terminal.print()
201
+
202
+ # Exception info
203
+ if has_exception:
204
+ terminal.print(" $errinfo = ", end='')
205
+ terminal.print_type_tag('VALUE', int(errinfo), None)
206
+ terminal.print()
207
+ terminal.print(" Exception present!")
208
+ else:
209
+ errinfo_int = int(errinfo) if errinfo else 0
210
+ if errinfo_int == 4: # Qnil
211
+ terminal.print(" Exception: None")
212
+ elif errinfo_int == 0: # Qfalse
213
+ terminal.print(" Exception: None (false)")
214
+ else:
215
+ terminal.print(" Exception: None")
216
+
217
+ # Tag info (for ensure blocks)
218
+ try:
219
+ tag = self.ec['tag']
220
+ tag_int = int(tag)
221
+ if tag_int != 0:
222
+ terminal.print(" Tag: ", end='')
223
+ terminal.print_type_tag('rb_vm_tag', tag_int, None)
224
+ terminal.print()
225
+ try:
226
+ retval = tag['retval']
227
+ retval_int = int(retval)
228
+ is_retval_special = (retval_int & 0x03) != 0 or retval_int == 0
229
+ if not is_retval_special:
230
+ terminal.print(" $retval available (in ensure block)")
231
+ except Exception:
232
+ pass
233
+ except Exception:
234
+ pass
235
+
236
+
237
+ class RubyContextHandler:
238
+ """Show current execution context and set convenience variables."""
239
+
240
+ USAGE = command.Usage(
241
+ summary="Show current execution context and set convenience variables",
242
+ parameters=[],
243
+ options={},
244
+ flags=[],
245
+ examples=[
246
+ ("rb-context", "Display execution context info"),
247
+ ("rb-context; rb-print $errinfo", "Show context then print exception")
248
+ ]
249
+ )
250
+
251
+ def invoke(self, arguments, terminal):
252
+ """Execute the rb-context command."""
253
+ try:
254
+ # Get current execution context
255
+ ctx = RubyContext.current()
256
+
257
+ if ctx is None:
258
+ print("Error: Could not get current execution context")
259
+ print()
260
+ print("Possible reasons:")
261
+ print(" • Ruby symbols not loaded (compile with debug symbols)")
262
+ print(" • Process not stopped at a Ruby frame")
263
+ print(" • Ruby not fully initialized yet")
264
+ print()
265
+ print("Try:")
266
+ print(" • Break at a Ruby function: break rb_vm_exec")
267
+ print(" • Use rb-fiber-scan-switch to switch to a fiber")
268
+ print(" • Ensure Ruby debug symbols are available")
269
+ return
270
+
271
+ # Print context information
272
+ ctx.print_info(terminal)
273
+
274
+ # Set convenience variables
275
+ vars = ctx.setup_convenience_variables()
276
+
277
+ print()
278
+ print("Convenience variables set:")
279
+ print(f" $ec - Execution context")
280
+ if vars.get('cfp'):
281
+ print(f" $cfp - Control frame pointer")
282
+ if vars.get('errinfo'):
283
+ print(f" $errinfo - Exception object")
284
+
285
+ print()
286
+ print("Now you can use:")
287
+ print(" rb-print $errinfo")
288
+ print(" rb-print $ec->cfp->sp[-1]")
289
+ print(" rb-stack-trace")
290
+
291
+ except Exception as e:
292
+ print(f"Error: {e}")
293
+ import traceback
294
+ traceback.print_exc()
295
+
296
+
297
+ class RubyContextStorageHandler:
298
+ """Print the fiber storage from the current execution context."""
299
+
300
+ USAGE = command.Usage(
301
+ summary="Print fiber storage from current execution context",
302
+ parameters=[],
303
+ options={'depth': (int, 1, 'Recursion depth for nested objects')},
304
+ flags=[('debug', 'Show debug information')],
305
+ examples=[
306
+ ("rb-context-storage", "Print storage with default depth"),
307
+ ("rb-context-storage --depth 3", "Print storage with depth 3")
308
+ ]
309
+ )
310
+
311
+ def invoke(self, arguments, terminal):
312
+ """Execute the rb-context-storage command."""
313
+ try:
314
+ # Get current execution context
315
+ ctx = RubyContext.current()
316
+
317
+ if ctx is None:
318
+ print("Error: Could not get current execution context")
319
+ print("\nTry:")
320
+ print(" • Run rb-context first to set up execution context")
321
+ print(" • Break at a Ruby function")
322
+ print(" • Use rb-fiber-scan-switch to switch to a fiber")
323
+ return
324
+
325
+ # Get storage
326
+ storage_val = ctx.storage
327
+
328
+ if storage_val is None:
329
+ print("Error: Could not access fiber storage")
330
+ return
331
+
332
+ # Check if it's nil
333
+ if rvalue.is_nil(storage_val):
334
+ print("Fiber storage: nil")
335
+ return
336
+
337
+ # Parse arguments (--depth, --debug, etc.)
338
+ arguments = command.parse_arguments(arg if arg else "")
339
+
340
+ # Get depth flag
341
+ depth = 1 # Default depth
342
+ depth_str = arguments.get_option('depth')
343
+ if depth_str:
344
+ try:
345
+ depth = int(depth_str)
346
+ except ValueError:
347
+ print(f"Error: invalid depth '{depth_str}'")
348
+ return
349
+
350
+ # Get debug flag
351
+ debug = arguments.has_flag('debug')
352
+
353
+ # Use print module to print the storage
354
+ import print as print_module
355
+ printer = print_module.RubyObjectPrinter()
356
+
357
+ # Build arguments for the printer
358
+ flags_set = {'debug'} if debug else set()
359
+ args_for_printer = command.Arguments([storage_val], flags_set, {'depth': depth})
360
+
361
+ printer.invoke(args_for_printer, terminal)
362
+
363
+ except Exception as e:
364
+ print(f"Error: {e}")
365
+ import traceback
366
+ traceback.print_exc()
367
+
368
+
369
+ # Register commands
370
+ debugger.register("rb-context", RubyContextHandler, usage=RubyContextHandler.USAGE)
371
+ debugger.register("rb-context-storage", RubyContextStorageHandler, usage=RubyContextStorageHandler.USAGE)
@@ -0,0 +1,101 @@
1
+ """
2
+ Unified debugger abstraction layer for GDB and LLDB.
3
+
4
+ This module provides a common interface for debugger operations,
5
+ automatically loading the appropriate backend (GDB or LLDB) at runtime.
6
+ """
7
+
8
+ import sys
9
+ import os
10
+
11
+ # Detect which debugger we're running under
12
+ _backend = None
13
+ DEBUGGER_NAME = None
14
+
15
+ # Add parent directory to path so we can import debugger.gdb / debugger.lldb
16
+ _toolbox_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ if _toolbox_dir not in sys.path:
18
+ sys.path.insert(0, _toolbox_dir)
19
+
20
+ # Try importing the real GDB or LLDB modules first
21
+ # We need to verify they're the actual debugger modules, not namespace packages
22
+ _gdb_available = False
23
+ _lldb_available = False
24
+
25
+ try:
26
+ import gdb as _gdb_module
27
+ # Verify it's the real GDB module by checking for a key attribute
28
+ if hasattr(_gdb_module, 'COMMAND_DATA'):
29
+ _gdb_available = True
30
+ except ImportError:
31
+ pass
32
+
33
+ try:
34
+ import lldb as _lldb_module
35
+ # Verify it's the real LLDB module by checking for a key class
36
+ if hasattr(_lldb_module, 'SBDebugger'):
37
+ _lldb_available = True
38
+ except ImportError:
39
+ pass
40
+
41
+ # Now load the appropriate backend based on which module is available
42
+ if _gdb_available:
43
+ from debugger import gdb_backend as _backend
44
+ DEBUGGER_NAME = 'gdb'
45
+ elif _lldb_available:
46
+ from debugger import lldb_backend as _backend
47
+ DEBUGGER_NAME = 'lldb'
48
+ else:
49
+ raise RuntimeError("Must be run under GDB or LLDB - neither module found")
50
+
51
+ if _backend is None:
52
+ raise RuntimeError("Failed to load debugger backend")
53
+
54
+ # Export the backend's interface
55
+ Value = _backend.Value
56
+ Type = _backend.Type
57
+ Command = _backend.Command
58
+ Error = _backend.Error
59
+ MemoryError = _backend.MemoryError
60
+
61
+ parse_and_eval = _backend.parse_and_eval
62
+ lookup_type = _backend.lookup_type
63
+ set_convenience_variable = _backend.set_convenience_variable
64
+ execute = _backend.execute
65
+ lookup_symbol = _backend.lookup_symbol
66
+ invalidate_cached_frames = _backend.invalidate_cached_frames
67
+ get_enum_value = _backend.get_enum_value
68
+ read_memory = _backend.read_memory
69
+ read_cstring = _backend.read_cstring
70
+ create_value = _backend.create_value
71
+ create_value_from_int = _backend.create_value_from_int
72
+ create_value_from_address = _backend.create_value_from_address
73
+ register = _backend.register
74
+
75
+ # Constants
76
+ COMMAND_DATA = _backend.COMMAND_DATA
77
+ COMMAND_USER = _backend.COMMAND_USER
78
+
79
+ __all__ = [
80
+ 'DEBUGGER_NAME',
81
+ 'Value',
82
+ 'Type',
83
+ 'Command',
84
+ 'Error',
85
+ 'MemoryError',
86
+ 'parse_and_eval',
87
+ 'lookup_type',
88
+ 'set_convenience_variable',
89
+ 'execute',
90
+ 'lookup_symbol',
91
+ 'invalidate_cached_frames',
92
+ 'get_enum_value',
93
+ 'read_memory',
94
+ 'read_cstring',
95
+ 'create_value',
96
+ 'create_value_from_int',
97
+ 'create_value_from_address',
98
+ 'register',
99
+ 'COMMAND_DATA',
100
+ 'COMMAND_USER',
101
+ ]