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.
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 +178 -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 +254 -0
  13. data/data/toolbox/constants.py +200 -0
  14. data/data/toolbox/context.py +295 -0
  15. data/data/toolbox/debugger/__init__.py +99 -0
  16. data/data/toolbox/debugger/gdb_backend.py +595 -0
  17. data/data/toolbox/debugger/lldb_backend.py +885 -0
  18. data/data/toolbox/fiber.py +885 -0
  19. data/data/toolbox/format.py +200 -0
  20. data/data/toolbox/heap.py +669 -0
  21. data/data/toolbox/init.py +85 -0
  22. data/data/toolbox/object.py +84 -0
  23. data/data/toolbox/rarray.py +124 -0
  24. data/data/toolbox/rbasic.py +103 -0
  25. data/data/toolbox/rbignum.py +52 -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 +98 -0
  30. data/data/toolbox/rhash.py +159 -0
  31. data/data/toolbox/rstring.py +234 -0
  32. data/data/toolbox/rstruct.py +157 -0
  33. data/data/toolbox/rsymbol.py +302 -0
  34. data/data/toolbox/stack.py +630 -0
  35. data/data/toolbox/value.py +183 -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 +2 -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,200 @@
1
+ import debugger
2
+
3
+ # Global shared cache for Ruby constants
4
+ _CACHE = {}
5
+
6
+ # Global shared cache for debugger type lookups
7
+ _TYPE_CACHE = {}
8
+
9
+ # Default values for Ruby type constants (ruby_value_type enum)
10
+ _TYPE_DEFAULTS = {
11
+ 'RUBY_T_NONE': 0x00,
12
+ 'RUBY_T_OBJECT': 0x01,
13
+ 'RUBY_T_CLASS': 0x02,
14
+ 'RUBY_T_MODULE': 0x03,
15
+ 'RUBY_T_FLOAT': 0x04,
16
+ 'RUBY_T_STRING': 0x05,
17
+ 'RUBY_T_REGEXP': 0x06,
18
+ 'RUBY_T_ARRAY': 0x07,
19
+ 'RUBY_T_HASH': 0x08,
20
+ 'RUBY_T_STRUCT': 0x09,
21
+ 'RUBY_T_BIGNUM': 0x0a,
22
+ 'RUBY_T_FILE': 0x0b,
23
+ 'RUBY_T_DATA': 0x0c,
24
+ 'RUBY_T_MATCH': 0x0d,
25
+ 'RUBY_T_COMPLEX': 0x0e,
26
+ 'RUBY_T_RATIONAL': 0x0f,
27
+ 'RUBY_T_NIL': 0x11,
28
+ 'RUBY_T_TRUE': 0x12,
29
+ 'RUBY_T_FALSE': 0x13,
30
+ 'RUBY_T_SYMBOL': 0x14,
31
+ 'RUBY_T_FIXNUM': 0x15,
32
+ 'RUBY_T_UNDEF': 0x16,
33
+ 'RUBY_T_IMEMO': 0x1a,
34
+ 'RUBY_T_NODE': 0x1b,
35
+ 'RUBY_T_ICLASS': 0x1c,
36
+ 'RUBY_T_ZOMBIE': 0x1d,
37
+ 'RUBY_T_MOVED': 0x1e,
38
+ 'RUBY_T_MASK': 0x1f,
39
+ }
40
+
41
+ # Default values for Ruby flag constants (ruby_fl_type enum)
42
+ _FLAG_DEFAULTS = {
43
+ 'RUBY_FL_USHIFT': 12,
44
+ 'RUBY_FL_USER1': 1 << 13,
45
+ 'RUBY_FL_USER2': 1 << 14,
46
+ 'RUBY_FL_USER3': 1 << 15,
47
+ 'RUBY_FL_USER4': 1 << 16,
48
+ 'RUBY_FL_USER5': 1 << 17,
49
+ 'RUBY_FL_USER6': 1 << 18,
50
+ 'RUBY_FL_USER7': 1 << 19,
51
+ 'RUBY_FL_USER8': 1 << 20,
52
+ 'RUBY_FL_USER9': 1 << 21,
53
+ }
54
+
55
+ def get(name, default=None):
56
+ """Get a Ruby constant value, with caching.
57
+
58
+ Arguments:
59
+ name: The constant name (e.g., 'RUBY_T_STRING', 'RUBY_FL_USER1')
60
+ default: Default value if constant cannot be found
61
+
62
+ Returns:
63
+ The integer value of the constant, or default if not found
64
+
65
+ Raises:
66
+ Exception if constant not found and no default provided
67
+ """
68
+ if name in _CACHE:
69
+ return _CACHE[name]
70
+
71
+ # Try direct evaluation (works in GDB with debug info, LLDB with macros/variables)
72
+ try:
73
+ val = int(debugger.parse_and_eval(name))
74
+ # Only cache if we got a non-zero value or if it's a known zero constant
75
+ if val != 0 or name in ['Qfalse', 'RUBY_Qfalse', 'RUBY_T_NONE']:
76
+ _CACHE[name] = val
77
+ return val
78
+ except Exception:
79
+ pass
80
+
81
+ # Couldn't find the constant
82
+ if default is None:
83
+ raise Exception(f"Constant {name} not found and no default provided")
84
+
85
+ # Return default but don't cache (might be available later with a process)
86
+ return default
87
+
88
+ def get_enum(enum_name, member_name, default=None):
89
+ """Get an enum member value from a specific enum.
90
+
91
+ This is more explicit than get() and works better in LLDB.
92
+
93
+ Arguments:
94
+ enum_name: The enum type name (e.g., 'ruby_value_type')
95
+ member_name: The member name (e.g., 'RUBY_T_STRING')
96
+ default: Default value if enum member cannot be found
97
+
98
+ Returns:
99
+ The integer value of the enum member, or default if not found
100
+
101
+ Raises:
102
+ Exception if member not found and no default provided
103
+
104
+ Examples:
105
+ >>> constants.get_enum('ruby_value_type', 'RUBY_T_STRING', 0x05)
106
+ 5
107
+ """
108
+ cache_key = f"{enum_name}::{member_name}"
109
+
110
+ if cache_key in _CACHE:
111
+ return _CACHE[cache_key]
112
+
113
+ # Use the debugger abstraction (handles GDB vs LLDB differences)
114
+ try:
115
+ val = debugger.get_enum_value(enum_name, member_name)
116
+ _CACHE[cache_key] = val
117
+ return val
118
+ except Exception:
119
+ pass
120
+
121
+ # Couldn't find the enum member
122
+ if default is None:
123
+ raise Exception(f"Enum member {enum_name}::{member_name} not found and no default provided")
124
+
125
+ return default
126
+
127
+ def type_struct(type_name):
128
+ """Get a C struct/type from the debugger, with caching.
129
+
130
+ Arguments:
131
+ type_name: The type name (e.g., 'struct RString', 'struct RArray', 'VALUE')
132
+
133
+ Returns:
134
+ The debugger type object
135
+
136
+ Raises:
137
+ Exception if type cannot be found
138
+
139
+ Examples:
140
+ >>> rbasic_type = constants.type_struct('struct RBasic')
141
+ >>> value_type = constants.type_struct('VALUE')
142
+ """
143
+ if type_name in _TYPE_CACHE:
144
+ return _TYPE_CACHE[type_name]
145
+
146
+ dbg_type = debugger.lookup_type(type_name)
147
+ _TYPE_CACHE[type_name] = dbg_type
148
+ return dbg_type
149
+
150
+ def type(name):
151
+ """Get a Ruby type constant (RUBY_T_*) value.
152
+
153
+ This is a convenience wrapper around get_enum() for ruby_value_type enum.
154
+ Uses built-in defaults for all standard Ruby type constants.
155
+
156
+ Arguments:
157
+ name: The type constant name (e.g., 'RUBY_T_STRING', 'RUBY_T_ARRAY')
158
+
159
+ Returns:
160
+ The integer value of the type constant
161
+
162
+ Raises:
163
+ Exception if constant is not found and not in default table
164
+
165
+ Examples:
166
+ >>> constants.type('RUBY_T_STRING')
167
+ 5
168
+ """
169
+ default = _TYPE_DEFAULTS.get(name)
170
+ return get_enum('ruby_value_type', name, default)
171
+
172
+ def flag(name):
173
+ """Get a Ruby flag constant (RUBY_FL_*) value.
174
+
175
+ This is a convenience wrapper around get_enum() for ruby_fl_type enum.
176
+ Uses built-in defaults for all standard Ruby flag constants.
177
+
178
+ Arguments:
179
+ name: The flag constant name (e.g., 'RUBY_FL_USER1', 'RUBY_FL_USHIFT')
180
+
181
+ Returns:
182
+ The integer value of the flag constant
183
+
184
+ Raises:
185
+ Exception if constant is not found and not in default table
186
+
187
+ Examples:
188
+ >>> constants.flag('RUBY_FL_USER1')
189
+ 8192
190
+ """
191
+ default = _FLAG_DEFAULTS.get(name)
192
+ return get_enum('ruby_fl_type', name, default)
193
+
194
+ def clear():
195
+ """Clear the constants and type caches.
196
+
197
+ Useful when switching between different Ruby processes or versions.
198
+ """
199
+ _CACHE.clear()
200
+ _TYPE_CACHE.clear()
@@ -0,0 +1,295 @@
1
+ """Ruby execution context utilities and commands."""
2
+
3
+ import debugger
4
+ import format
5
+ import value
6
+ import rexception
7
+
8
+
9
+ class RubyContext:
10
+ """Wrapper for Ruby execution context (rb_execution_context_t).
11
+
12
+ Provides a high-level interface for working with Ruby execution contexts,
13
+ including inspection, convenience variable setup, and information display.
14
+
15
+ Example:
16
+ ctx = RubyContext.current()
17
+ if ctx:
18
+ ctx.print_info(terminal)
19
+ ctx.setup_convenience_variables()
20
+ """
21
+
22
+ def __init__(self, ec):
23
+ """Create a RubyContext wrapper.
24
+
25
+ Args:
26
+ ec: Execution context pointer (rb_execution_context_t *)
27
+ """
28
+ self.ec = ec
29
+ self._cfp = None
30
+ self._errinfo = None
31
+ self._vm_stack = None
32
+ self._vm_stack_size = None
33
+
34
+ @classmethod
35
+ def current(cls):
36
+ """Get the current execution context from the running thread.
37
+
38
+ Tries multiple approaches in order of preference:
39
+ 1. ruby_current_ec - TLS variable (works in GDB, some LLDB)
40
+ 2. rb_current_ec_noinline() - function call (works in most cases)
41
+ 3. rb_current_ec() - macOS-specific function
42
+
43
+ Returns:
44
+ RubyContext instance, or None if not available
45
+ """
46
+ # Try ruby_current_ec variable first
47
+ try:
48
+ ec = debugger.parse_and_eval('ruby_current_ec')
49
+ if ec is not None and int(ec) != 0:
50
+ return cls(ec)
51
+ except debugger.Error:
52
+ pass
53
+
54
+ # Fallback to rb_current_ec_noinline() function
55
+ try:
56
+ ec = debugger.parse_and_eval('rb_current_ec_noinline()')
57
+ if ec is not None and int(ec) != 0:
58
+ return cls(ec)
59
+ except debugger.Error:
60
+ pass
61
+
62
+ # Last resort: rb_current_ec() (macOS-specific)
63
+ try:
64
+ ec = debugger.parse_and_eval('rb_current_ec()')
65
+ if ec is not None and int(ec) != 0:
66
+ return cls(ec)
67
+ except debugger.Error:
68
+ pass
69
+
70
+ return None
71
+
72
+ @property
73
+ def cfp(self):
74
+ """Get control frame pointer (lazy load)."""
75
+ if self._cfp is None:
76
+ try:
77
+ self._cfp = self.ec['cfp']
78
+ except Exception:
79
+ pass
80
+ return self._cfp
81
+
82
+ @property
83
+ def errinfo(self):
84
+ """Get exception VALUE (lazy load)."""
85
+ if self._errinfo is None:
86
+ try:
87
+ self._errinfo = self.ec['errinfo']
88
+ except Exception:
89
+ pass
90
+ return self._errinfo
91
+
92
+ @property
93
+ def has_exception(self):
94
+ """Check if there's a real exception (not nil/special value)."""
95
+ if self.errinfo is None:
96
+ return False
97
+ return rexception.is_exception(self.errinfo)
98
+
99
+ @property
100
+ def vm_stack(self):
101
+ """Get VM stack pointer (lazy load)."""
102
+ if self._vm_stack is None:
103
+ try:
104
+ self._vm_stack = self.ec['vm_stack']
105
+ except Exception:
106
+ pass
107
+ return self._vm_stack
108
+
109
+ @property
110
+ def vm_stack_size(self):
111
+ """Get VM stack size (lazy load)."""
112
+ if self._vm_stack_size is None:
113
+ try:
114
+ self._vm_stack_size = int(self.ec['vm_stack_size'])
115
+ except Exception:
116
+ pass
117
+ return self._vm_stack_size
118
+
119
+ def setup_convenience_variables(self):
120
+ """Set up convenience variables for this execution context.
121
+
122
+ Sets:
123
+ $ec - Execution context pointer
124
+ $cfp - Control frame pointer
125
+ $errinfo - Current exception (if any)
126
+
127
+ Returns:
128
+ dict with keys: 'ec', 'cfp', 'errinfo' (values are the set variables)
129
+ """
130
+ result = {}
131
+
132
+ # Set $ec
133
+ debugger.set_convenience_variable('ec', self.ec)
134
+ result['ec'] = self.ec
135
+
136
+ # Set $cfp (control frame pointer)
137
+ if self.cfp is not None:
138
+ debugger.set_convenience_variable('cfp', self.cfp)
139
+ result['cfp'] = self.cfp
140
+ else:
141
+ result['cfp'] = None
142
+
143
+ # Set $errinfo if there's an exception
144
+ if self.has_exception:
145
+ debugger.set_convenience_variable('errinfo', self.errinfo)
146
+ result['errinfo'] = self.errinfo
147
+ else:
148
+ result['errinfo'] = None
149
+
150
+ return result
151
+
152
+ def print_info(self, terminal):
153
+ """Print detailed information about this execution context.
154
+
155
+ Args:
156
+ terminal: Terminal formatter for output
157
+ """
158
+ print("Execution Context:")
159
+ print(f" $ec = ", end='')
160
+ print(terminal.print_type_tag('rb_execution_context_t', int(self.ec), None))
161
+
162
+ # VM Stack info
163
+ if self.vm_stack is not None and self.vm_stack_size is not None:
164
+ print(f" VM Stack: ", end='')
165
+ print(terminal.print_type_tag('VALUE', int(self.vm_stack), f'size={self.vm_stack_size}'))
166
+ else:
167
+ print(f" VM Stack: <unavailable>")
168
+
169
+ # Control Frame info
170
+ if self.cfp is not None:
171
+ print(f" $cfp = ", end='')
172
+ print(terminal.print_type_tag('rb_control_frame_t', int(self.cfp), None))
173
+ else:
174
+ print(f" $cfp = <unavailable>")
175
+
176
+ # Exception info
177
+ if self.has_exception:
178
+ print(f" $errinfo = ", end='')
179
+ print(terminal.print_type_tag('VALUE', int(self.errinfo), None))
180
+ print(" Exception present!")
181
+ else:
182
+ errinfo_int = int(self.errinfo) if self.errinfo else 0
183
+ if errinfo_int == 4: # Qnil
184
+ print(" Exception: None")
185
+ elif errinfo_int == 0: # Qfalse
186
+ print(" Exception: None (false)")
187
+ else:
188
+ print(f" Exception: None")
189
+
190
+ # Tag info (for ensure blocks)
191
+ try:
192
+ tag = self.ec['tag']
193
+ tag_int = int(tag)
194
+ if tag_int != 0:
195
+ print(f" Tag: ", end='')
196
+ print(terminal.print_type_tag('rb_vm_tag', tag_int, None))
197
+ try:
198
+ retval = tag['retval']
199
+ retval_int = int(retval)
200
+ is_retval_special = (retval_int & 0x03) != 0 or retval_int == 0
201
+ if not is_retval_special:
202
+ print(f" $retval available (in ensure block)")
203
+ except Exception:
204
+ pass
205
+ except Exception:
206
+ pass
207
+
208
+
209
+ class RubyContextCommand(debugger.Command):
210
+ """Show current execution context and set convenience variables.
211
+
212
+ This command automatically discovers the current thread's execution context
213
+ and displays detailed information about it, while also setting up convenience
214
+ variables for easy inspection.
215
+
216
+ Usage:
217
+ rb-context
218
+
219
+ Displays:
220
+ - Execution context pointer and details
221
+ - VM stack information
222
+ - Control frame pointer
223
+ - Exception information (if any)
224
+
225
+ Sets these convenience variables:
226
+ $ec - Current execution context (rb_execution_context_t *)
227
+ $cfp - Current control frame pointer
228
+ $errinfo - Current exception (if any)
229
+
230
+ Example:
231
+ (gdb) rb-context
232
+ Execution Context:
233
+ $ec = <rb_execution_context_t *@0x...>
234
+ VM Stack: <VALUE *@0x...> size=1024
235
+ $cfp = <rb_control_frame_t *@0x...>
236
+ Exception: None
237
+
238
+ (gdb) rb-object-print $errinfo
239
+ (gdb) rb-object-print $ec->cfp->sp[-1]
240
+ """
241
+
242
+ def __init__(self):
243
+ super(RubyContextCommand, self).__init__("rb-context", debugger.COMMAND_USER)
244
+
245
+ def invoke(self, arg, from_tty):
246
+ """Execute the rb-context command."""
247
+ try:
248
+ terminal = format.create_terminal(from_tty)
249
+
250
+ # Get current execution context
251
+ ctx = RubyContext.current()
252
+
253
+ if ctx is None:
254
+ print("Error: Could not get current execution context")
255
+ print()
256
+ print("Possible reasons:")
257
+ print(" • Ruby symbols not loaded (compile with debug symbols)")
258
+ print(" • Process not stopped at a Ruby frame")
259
+ print(" • Ruby not fully initialized yet")
260
+ print()
261
+ print("Try:")
262
+ print(" • Break at a Ruby function: break rb_vm_exec")
263
+ print(" • Use rb-fiber-scan-switch to switch to a fiber")
264
+ print(" • Ensure Ruby debug symbols are available")
265
+ return
266
+
267
+ # Print context information
268
+ ctx.print_info(terminal)
269
+
270
+ # Set convenience variables
271
+ vars = ctx.setup_convenience_variables()
272
+
273
+ print()
274
+ print("Convenience variables set:")
275
+ print(f" $ec - Execution context")
276
+ if vars.get('cfp'):
277
+ print(f" $cfp - Control frame pointer")
278
+ if vars.get('errinfo'):
279
+ print(f" $errinfo - Exception object")
280
+
281
+ print()
282
+ print("Now you can use:")
283
+ print(" rb-object-print $errinfo")
284
+ print(" rb-object-print $ec->cfp->sp[-1]")
285
+ print(" rb-stack-trace")
286
+
287
+ except Exception as e:
288
+ print(f"Error: {e}")
289
+ import traceback
290
+ traceback.print_exc()
291
+
292
+
293
+ # Register command
294
+ RubyContextCommand()
295
+
@@ -0,0 +1,99 @@
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
+
74
+ # Constants
75
+ COMMAND_DATA = _backend.COMMAND_DATA
76
+ COMMAND_USER = _backend.COMMAND_USER
77
+
78
+ __all__ = [
79
+ 'DEBUGGER_NAME',
80
+ 'Value',
81
+ 'Type',
82
+ 'Command',
83
+ 'Error',
84
+ 'MemoryError',
85
+ 'parse_and_eval',
86
+ 'lookup_type',
87
+ 'set_convenience_variable',
88
+ 'execute',
89
+ 'lookup_symbol',
90
+ 'invalidate_cached_frames',
91
+ 'get_enum_value',
92
+ 'read_memory',
93
+ 'read_cstring',
94
+ 'create_value',
95
+ 'create_value_from_int',
96
+ 'create_value_from_address',
97
+ 'COMMAND_DATA',
98
+ 'COMMAND_USER',
99
+ ]