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,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
|
+
]
|