toolbox 0.1.3 → 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 -57
- 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,178 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide explains how to install and use Toolbox for debugging Ruby programs and core dumps with GDB or LLDB.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem:
|
|
8
|
+
|
|
9
|
+
~~~ bash
|
|
10
|
+
$ gem install toolbox
|
|
11
|
+
~~~
|
|
12
|
+
|
|
13
|
+
### Installing GDB Extensions
|
|
14
|
+
|
|
15
|
+
Install the GDB extensions (automatically adds to `~/.gdbinit`):
|
|
16
|
+
|
|
17
|
+
~~~ bash
|
|
18
|
+
$ bake toolbox:gdb:install
|
|
19
|
+
~~~
|
|
20
|
+
|
|
21
|
+
This adds a single line to your `~/.gdbinit` that sources the extensions from the gem's data directory. The extensions will then load automatically every time you start GDB.
|
|
22
|
+
|
|
23
|
+
To install to a custom `.gdbinit` location:
|
|
24
|
+
|
|
25
|
+
~~~ bash
|
|
26
|
+
$ bake toolbox:gdb:install gdbinit=/path/to/custom/gdbinit
|
|
27
|
+
~~~
|
|
28
|
+
|
|
29
|
+
### Installing LLDB Extensions
|
|
30
|
+
|
|
31
|
+
Install the LLDB extensions (automatically adds to `~/.lldbinit`):
|
|
32
|
+
|
|
33
|
+
~~~ bash
|
|
34
|
+
$ bake toolbox:lldb:install
|
|
35
|
+
~~~
|
|
36
|
+
|
|
37
|
+
This adds a command script import line to your `~/.lldbinit` that loads the extensions from the gem's data directory. The extensions will then load automatically every time you start LLDB.
|
|
38
|
+
|
|
39
|
+
To install to a custom `.lldbinit` location:
|
|
40
|
+
|
|
41
|
+
~~~ bash
|
|
42
|
+
$ bake toolbox:lldb:install lldbinit=/path/to/custom/lldbinit
|
|
43
|
+
~~~
|
|
44
|
+
|
|
45
|
+
### Verifying Installation
|
|
46
|
+
|
|
47
|
+
Check GDB installation status:
|
|
48
|
+
|
|
49
|
+
~~~ bash
|
|
50
|
+
$ bake toolbox:gdb:info
|
|
51
|
+
Ruby Toolbox GDB Extensions v0.1.0
|
|
52
|
+
Status: ✓ Installed
|
|
53
|
+
~~~
|
|
54
|
+
|
|
55
|
+
Check LLDB installation status:
|
|
56
|
+
|
|
57
|
+
~~~ bash
|
|
58
|
+
$ bake toolbox:lldb:info
|
|
59
|
+
Ruby Toolbox LLDB Extensions v0.1.0
|
|
60
|
+
Status: ✓ Installed
|
|
61
|
+
~~~
|
|
62
|
+
|
|
63
|
+
Test that extensions load automatically:
|
|
64
|
+
|
|
65
|
+
~~~ bash
|
|
66
|
+
$ gdb --batch -ex "help rb-object-print"
|
|
67
|
+
Recursively print Ruby hash and array structures...
|
|
68
|
+
~~~
|
|
69
|
+
|
|
70
|
+
### Uninstalling
|
|
71
|
+
|
|
72
|
+
To remove the GDB extensions:
|
|
73
|
+
|
|
74
|
+
~~~ bash
|
|
75
|
+
$ bake toolbox:gdb:uninstall
|
|
76
|
+
~~~
|
|
77
|
+
|
|
78
|
+
To remove the LLDB extensions:
|
|
79
|
+
|
|
80
|
+
~~~ bash
|
|
81
|
+
$ bake toolbox:lldb:uninstall
|
|
82
|
+
~~~
|
|
83
|
+
|
|
84
|
+
This removes the source line from your `~/.gdbinit`.
|
|
85
|
+
|
|
86
|
+
## Core Concepts
|
|
87
|
+
|
|
88
|
+
Ruby GDB provides specialized commands for debugging Ruby at multiple levels:
|
|
89
|
+
|
|
90
|
+
- **Context Setup** (`rb-context`) - Get current execution context and set up convenience variables
|
|
91
|
+
- **Object Inspection** (`rb-object-print`) - View Ruby objects, hashes, arrays, and structs with proper formatting
|
|
92
|
+
- **Fiber Debugging** (`rb-fiber-*`) - Scan heap for fibers, inspect state, and switch contexts
|
|
93
|
+
- **Stack Analysis** (`rb-stack-trace`) - Examine combined VM (Ruby) and C (native) stack frames
|
|
94
|
+
- **Heap Navigation** (`rb-heap-scan`) - Scan the Ruby heap to find objects by type
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
### Debugging a Running Process
|
|
99
|
+
|
|
100
|
+
Start your Ruby program under GDB:
|
|
101
|
+
|
|
102
|
+
~~~ bash
|
|
103
|
+
$ gdb --args ruby my_script.rb
|
|
104
|
+
~~~
|
|
105
|
+
|
|
106
|
+
Set a breakpoint and run:
|
|
107
|
+
|
|
108
|
+
~~~
|
|
109
|
+
(gdb) break rb_vm_exec
|
|
110
|
+
(gdb) run
|
|
111
|
+
~~~
|
|
112
|
+
|
|
113
|
+
Once stopped, use Ruby debugging commands:
|
|
114
|
+
|
|
115
|
+
~~~
|
|
116
|
+
(gdb) rb-stack-trace # Show combined Ruby/C backtrace
|
|
117
|
+
(gdb) rb-fiber-scan-heap # Scan heap for fibers
|
|
118
|
+
(gdb) rb-heap-scan --type RUBY_T_STRING --limit 5 # Find strings
|
|
119
|
+
~~~
|
|
120
|
+
|
|
121
|
+
### Debugging a Core Dump
|
|
122
|
+
|
|
123
|
+
When your Ruby program crashes, you can analyze the core dump:
|
|
124
|
+
|
|
125
|
+
~~~ bash
|
|
126
|
+
$ gdb ruby core.dump
|
|
127
|
+
~~~
|
|
128
|
+
|
|
129
|
+
Diagnose the issue (extensions load automatically if installed):
|
|
130
|
+
|
|
131
|
+
~~~
|
|
132
|
+
(gdb) rb-fiber-scan-heap # Scan heap for all fibers
|
|
133
|
+
(gdb) rb-fiber-scan-stack-trace-all # Show backtraces for all fibers
|
|
134
|
+
(gdb) rb-fiber-scan-switch 0 # Switch to main fiber
|
|
135
|
+
(gdb) rb-object-print $errinfo --depth 2 # Print exception (now $errinfo is set)
|
|
136
|
+
(gdb) rb-heap-scan --type RUBY_T_HASH --limit 10 # Find hashes
|
|
137
|
+
~~~
|
|
138
|
+
|
|
139
|
+
## Common Workflows
|
|
140
|
+
|
|
141
|
+
### Inspecting Exception Objects
|
|
142
|
+
|
|
143
|
+
When a Ruby exception occurs, you can inspect it in detail:
|
|
144
|
+
|
|
145
|
+
~~~
|
|
146
|
+
(gdb) break rb_exc_raise
|
|
147
|
+
(gdb) run
|
|
148
|
+
(gdb) rb-context
|
|
149
|
+
(gdb) rb-object-print $errinfo --depth 2
|
|
150
|
+
~~~
|
|
151
|
+
|
|
152
|
+
This shows the exception class, message, and any nested structures. The `rb-context` command displays the current execution context and sets up `$ec`, `$cfp`, and `$errinfo` convenience variables.
|
|
153
|
+
|
|
154
|
+
### Debugging Fiber Issues
|
|
155
|
+
|
|
156
|
+
When working with fibers, you often need to see what each fiber is doing:
|
|
157
|
+
|
|
158
|
+
~~~
|
|
159
|
+
(gdb) rb-fiber-scan-heap # Scan heap for all fibers
|
|
160
|
+
(gdb) rb-fiber-scan-stack-trace-all # Show backtraces for all fibers
|
|
161
|
+
(gdb) rb-fiber-scan-switch 5 # Switch GDB to fiber #5's context
|
|
162
|
+
(gdb) rb-stack-trace # Now shows fiber's combined backtrace
|
|
163
|
+
~~~
|
|
164
|
+
|
|
165
|
+
### Examining Complex Data Structures
|
|
166
|
+
|
|
167
|
+
Ruby hashes and arrays can contain nested structures:
|
|
168
|
+
|
|
169
|
+
~~~
|
|
170
|
+
(gdb) rb-object-print $some_hash --depth 2
|
|
171
|
+
<T_HASH@...>
|
|
172
|
+
[ 0] K: <T_SYMBOL> :name
|
|
173
|
+
V: <T_STRING@...> "Alice"
|
|
174
|
+
[ 1] K: <T_SYMBOL> :age
|
|
175
|
+
V: <T_FIXNUM> 30
|
|
176
|
+
~~~
|
|
177
|
+
|
|
178
|
+
The `--depth` flag controls how deep to recurse into nested objects.
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Heap Debugging
|
|
2
|
+
|
|
3
|
+
This guide explains how to navigate Ruby's heap to find objects, diagnose memory issues, and understand object relationships.
|
|
4
|
+
|
|
5
|
+
## Why Heap Debugging Matters
|
|
6
|
+
|
|
7
|
+
Ruby's garbage collector manages thousands to millions of objects across heap pages. When debugging memory leaks, performance issues, or trying to find specific objects, you need to navigate this heap efficiently. Standard GDB cannot understand Ruby's object layout or page structure, making manual heap inspection tedious and error-prone.
|
|
8
|
+
|
|
9
|
+
Use heap debugging when you need:
|
|
10
|
+
|
|
11
|
+
- **Find leaked objects**: Locate objects that should have been garbage collected
|
|
12
|
+
- **Discover fiber instances**: Find all fibers in a crashed or hung application
|
|
13
|
+
- **Track object relationships**: See what objects reference others
|
|
14
|
+
- **Diagnose memory bloat**: Understand what types of objects consume memory
|
|
15
|
+
|
|
16
|
+
## Heap Structure Overview
|
|
17
|
+
|
|
18
|
+
Ruby's heap is organized as:
|
|
19
|
+
|
|
20
|
+
1. **Heap Pages**: Fixed-size pages allocated from the system
|
|
21
|
+
2. **Slots**: Each page contains fixed-size slots for objects
|
|
22
|
+
3. **Objects**: Ruby objects (RBasic, RHash, RArray, etc.) stored in slots
|
|
23
|
+
4. **Object Space**: The global heap manager (`objspace`)
|
|
24
|
+
|
|
25
|
+
## Scanning the Heap
|
|
26
|
+
|
|
27
|
+
### Finding Objects by Type
|
|
28
|
+
|
|
29
|
+
Scan the heap for specific types of Ruby objects:
|
|
30
|
+
|
|
31
|
+
~~~
|
|
32
|
+
(gdb) rb-heap-scan --type RUBY_T_STRING --limit 10
|
|
33
|
+
Scanning heap for type 0x05, limit=10...
|
|
34
|
+
|
|
35
|
+
Found 10 object(s):
|
|
36
|
+
|
|
37
|
+
[0] $heap0 = <T_STRING@...> "..."
|
|
38
|
+
[1] $heap1 = <T_STRING@...> "..."
|
|
39
|
+
...
|
|
40
|
+
[9] $heap9 = <T_STRING@...> "..."
|
|
41
|
+
|
|
42
|
+
Objects saved in $heap0 through $heap9
|
|
43
|
+
Next scan address saved to $heap: 0x...
|
|
44
|
+
Run 'rb-heap-scan --from $heap ...' for next page
|
|
45
|
+
~~~
|
|
46
|
+
|
|
47
|
+
### Finding Fibers
|
|
48
|
+
|
|
49
|
+
Find all fiber objects:
|
|
50
|
+
|
|
51
|
+
~~~
|
|
52
|
+
(gdb) rb-fiber-scan-heap
|
|
53
|
+
Scanning heap for Fiber objects...
|
|
54
|
+
Checked 45000 objects, found 12 fiber(s)...
|
|
55
|
+
|
|
56
|
+
Found 12 fiber(s):
|
|
57
|
+
|
|
58
|
+
Fiber #0: <T_DATA@...> → <struct rb_fiber_struct@...>
|
|
59
|
+
Status: SUSPENDED
|
|
60
|
+
Stack: <void *@...>
|
|
61
|
+
VM Stack: <VALUE *@...>
|
|
62
|
+
CFP: <rb_control_frame_t@...>
|
|
63
|
+
...
|
|
64
|
+
~~~
|
|
65
|
+
|
|
66
|
+
This uses a specialized scanner optimized for fibers.
|
|
67
|
+
|
|
68
|
+
### How Heap Scanning Works
|
|
69
|
+
|
|
70
|
+
The scanner:
|
|
71
|
+
|
|
72
|
+
1. Accesses `ruby_current_vm_ptr->gc->objspace`
|
|
73
|
+
2. Iterates through `heap_pages->sorted->data[]`
|
|
74
|
+
3. For each page, checks `total_slots` objects
|
|
75
|
+
4. Identifies objects by their `flags` field (bits 0-4 = type)
|
|
76
|
+
5. Filters for specific types if `--type` is specified
|
|
77
|
+
|
|
78
|
+
### Common Types
|
|
79
|
+
|
|
80
|
+
Ruby type constants and their numeric values:
|
|
81
|
+
|
|
82
|
+
~~~
|
|
83
|
+
RUBY_T_STRING = 0x05 # Strings
|
|
84
|
+
RUBY_T_ARRAY = 0x07 # Arrays
|
|
85
|
+
RUBY_T_HASH = 0x08 # Hashes
|
|
86
|
+
RUBY_T_DATA = 0x0c # Data objects (like Fibers)
|
|
87
|
+
RUBY_T_OBJECT = 0x01 # Generic objects
|
|
88
|
+
~~~
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
|
|
92
|
+
~~~
|
|
93
|
+
(gdb) rb-heap-scan --type RUBY_T_STRING --limit 20
|
|
94
|
+
(gdb) rb-heap-scan --type 0x08 --limit 10 # Same as RUBY_T_HASH
|
|
95
|
+
(gdb) rb-heap-scan --limit 100 # All types
|
|
96
|
+
~~~
|
|
97
|
+
|
|
98
|
+
### Pagination
|
|
99
|
+
|
|
100
|
+
Continue scanning from where you left off:
|
|
101
|
+
|
|
102
|
+
~~~
|
|
103
|
+
(gdb) rb-heap-scan --type RUBY_T_STRING --limit 10
|
|
104
|
+
Scanning heap for type 0x05, limit=10...
|
|
105
|
+
|
|
106
|
+
Found 10 object(s):
|
|
107
|
+
[0] $heap0 = <T_STRING@...> "..."
|
|
108
|
+
...
|
|
109
|
+
Objects saved in $heap0 through $heap9
|
|
110
|
+
Next scan address saved to $heap: 0x...
|
|
111
|
+
|
|
112
|
+
(gdb) rb-heap-scan --type RUBY_T_STRING --limit 10 --from $heap
|
|
113
|
+
Scanning heap for type 0x05, limit=10...
|
|
114
|
+
Starting from saved position: 0x...
|
|
115
|
+
|
|
116
|
+
Found 10 object(s):
|
|
117
|
+
[0] $heap0 = <T_STRING@...> "..."
|
|
118
|
+
...
|
|
119
|
+
~~~
|
|
120
|
+
|
|
121
|
+
## Understanding Object Layout
|
|
122
|
+
|
|
123
|
+
### Object Headers (RBasic)
|
|
124
|
+
|
|
125
|
+
Every Ruby object starts with:
|
|
126
|
+
|
|
127
|
+
~~~ruby
|
|
128
|
+
struct RBasic {
|
|
129
|
+
VALUE flags; # Type and flag information
|
|
130
|
+
VALUE klass; # Object's class
|
|
131
|
+
}
|
|
132
|
+
~~~
|
|
133
|
+
|
|
134
|
+
The `flags` field encodes:
|
|
135
|
+
- Object type (bits 0-4): T_STRING, T_HASH, T_ARRAY, etc.
|
|
136
|
+
- GC flags (bit 5-11): Mark bits, frozen status, etc.
|
|
137
|
+
- Type-specific flags (bits 12+): Embedded vs heap, size info, etc.
|
|
138
|
+
|
|
139
|
+
### Type Detection
|
|
140
|
+
|
|
141
|
+
Check an object's type:
|
|
142
|
+
|
|
143
|
+
~~~
|
|
144
|
+
(gdb) set $obj = (VALUE)0x7f8a1c888888
|
|
145
|
+
(gdb) set $basic = (struct RBasic *)$obj
|
|
146
|
+
(gdb) p/x $basic->flags
|
|
147
|
+
$1 = 0x20040005
|
|
148
|
+
|
|
149
|
+
(gdb) p/x $basic->flags & 0x1f
|
|
150
|
+
$2 = 0x5 # T_STRING (0x05)
|
|
151
|
+
~~~
|
|
152
|
+
|
|
153
|
+
Or use Ruby commands:
|
|
154
|
+
|
|
155
|
+
~~~python
|
|
156
|
+
obj_type = flags & 0x1f
|
|
157
|
+
type_names = {
|
|
158
|
+
0x05: "T_STRING",
|
|
159
|
+
0x07: "T_ARRAY",
|
|
160
|
+
0x08: "T_HASH",
|
|
161
|
+
0x0c: "T_DATA",
|
|
162
|
+
...
|
|
163
|
+
}
|
|
164
|
+
~~~
|
|
165
|
+
|
|
166
|
+
## Practical Examples
|
|
167
|
+
|
|
168
|
+
### Finding All Objects of a Type
|
|
169
|
+
|
|
170
|
+
Scan for all string objects (requires custom script):
|
|
171
|
+
|
|
172
|
+
~~~python
|
|
173
|
+
python
|
|
174
|
+
for obj, flags in iterate_heap():
|
|
175
|
+
if (flags & 0x1f) == 0x05: # T_STRING
|
|
176
|
+
print(f"String at {obj}")
|
|
177
|
+
end
|
|
178
|
+
~~~
|
|
179
|
+
|
|
180
|
+
### Locating Large Arrays
|
|
181
|
+
|
|
182
|
+
Find arrays consuming significant memory:
|
|
183
|
+
|
|
184
|
+
~~~python
|
|
185
|
+
python
|
|
186
|
+
for obj, flags in iterate_heap():
|
|
187
|
+
if (flags & 0x1f) == 0x07: # T_ARRAY
|
|
188
|
+
rarray = obj.cast(gdb.lookup_type("struct RArray").pointer())
|
|
189
|
+
if (flags & (1 << 12)) == 0: # Heap array (not embedded)
|
|
190
|
+
length = int(rarray["as"]["heap"]["len"])
|
|
191
|
+
if length > 1000:
|
|
192
|
+
print(f"Large array at {obj}: {length} elements")
|
|
193
|
+
end
|
|
194
|
+
~~~
|
|
195
|
+
|
|
196
|
+
### Finding Objects by Content
|
|
197
|
+
|
|
198
|
+
Search for strings containing specific text:
|
|
199
|
+
|
|
200
|
+
~~~python
|
|
201
|
+
python
|
|
202
|
+
import gdb
|
|
203
|
+
|
|
204
|
+
def find_strings_containing(search_text):
|
|
205
|
+
"""Find all Ruby strings containing specific text"""
|
|
206
|
+
matches = []
|
|
207
|
+
for obj, flags in iterate_heap():
|
|
208
|
+
if (flags & 0x1f) == 0x05: # T_STRING
|
|
209
|
+
try:
|
|
210
|
+
rstring = obj.cast(gdb.lookup_type("struct RString").pointer())
|
|
211
|
+
length = int(rstring["len"])
|
|
212
|
+
|
|
213
|
+
# Read string content (simplified)
|
|
214
|
+
if length < 1000: # Reasonable size
|
|
215
|
+
# ... (read string bytes) ...
|
|
216
|
+
if search_text in string_content:
|
|
217
|
+
matches.append(obj)
|
|
218
|
+
except:
|
|
219
|
+
pass
|
|
220
|
+
return matches
|
|
221
|
+
end
|
|
222
|
+
~~~
|
|
223
|
+
|
|
224
|
+
## Memory Analysis
|
|
225
|
+
|
|
226
|
+
### Heap Page Statistics
|
|
227
|
+
|
|
228
|
+
Understand heap organization:
|
|
229
|
+
|
|
230
|
+
~~~
|
|
231
|
+
(gdb) p ruby_current_vm_ptr->gc->objspace->heap_pages->allocated_pages
|
|
232
|
+
$1 = 1250 # Total pages allocated
|
|
233
|
+
|
|
234
|
+
(gdb) p ruby_current_vm_ptr->gc->objspace->heap_pages->sorted->meta->length
|
|
235
|
+
$2 = 1250 # Accessible pages
|
|
236
|
+
|
|
237
|
+
# Average objects per page:
|
|
238
|
+
(gdb) p 67890 / 1250
|
|
239
|
+
$3 = 54 # ~54 objects per page
|
|
240
|
+
~~~
|
|
241
|
+
|
|
242
|
+
### Object Slot Details
|
|
243
|
+
|
|
244
|
+
Examine a specific heap page:
|
|
245
|
+
|
|
246
|
+
~~~
|
|
247
|
+
(gdb) set $page = ruby_current_vm_ptr->gc->objspace->heap_pages->sorted->data[0]
|
|
248
|
+
(gdb) p $page->start # First object address
|
|
249
|
+
$4 = 0x7f8a1c000000
|
|
250
|
+
|
|
251
|
+
(gdb) p $page->total_slots # Objects in this page
|
|
252
|
+
$5 = 64
|
|
253
|
+
|
|
254
|
+
(gdb) p $page->slot_size # Bytes per slot
|
|
255
|
+
$6 = 40
|
|
256
|
+
~~~
|
|
257
|
+
|
|
258
|
+
### Memory Consumption
|
|
259
|
+
|
|
260
|
+
Calculate memory usage by type:
|
|
261
|
+
|
|
262
|
+
~~~python
|
|
263
|
+
python
|
|
264
|
+
type_counts = {}
|
|
265
|
+
type_memory = {}
|
|
266
|
+
|
|
267
|
+
for obj, flags in iterate_heap():
|
|
268
|
+
obj_type = flags & 0x1f
|
|
269
|
+
type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
|
|
270
|
+
# Each object consumes at minimum one slot
|
|
271
|
+
type_memory[obj_type] = type_memory.get(obj_type, 0) + 40 # slot_size
|
|
272
|
+
|
|
273
|
+
for obj_type in sorted(type_counts.keys()):
|
|
274
|
+
count = type_counts[obj_type]
|
|
275
|
+
memory_kb = type_memory[obj_type] / 1024
|
|
276
|
+
print(f"Type 0x{obj_type:02x}: {count:6} objects, {memory_kb:8.1f} KB")
|
|
277
|
+
end
|
|
278
|
+
~~~
|
|
279
|
+
|
|
280
|
+
## Advanced Techniques
|
|
281
|
+
|
|
282
|
+
### Custom Heap Iterators
|
|
283
|
+
|
|
284
|
+
The fiber scanning code provides a reusable heap iterator:
|
|
285
|
+
|
|
286
|
+
~~~python
|
|
287
|
+
python
|
|
288
|
+
# In your custom GDB script:
|
|
289
|
+
from fiber import RubyFiberDebug
|
|
290
|
+
|
|
291
|
+
debug = RubyFiberDebug()
|
|
292
|
+
if debug.initialize():
|
|
293
|
+
for obj, flags in debug.iterate_heap():
|
|
294
|
+
# Your custom logic here
|
|
295
|
+
pass
|
|
296
|
+
end
|
|
297
|
+
~~~
|
|
298
|
+
|
|
299
|
+
### Finding Objects by Reference
|
|
300
|
+
|
|
301
|
+
Locate what holds a reference to an object:
|
|
302
|
+
|
|
303
|
+
~~~python
|
|
304
|
+
python
|
|
305
|
+
target_address = 0x7f8a1c888888
|
|
306
|
+
|
|
307
|
+
for obj, flags in iterate_heap():
|
|
308
|
+
obj_type = flags & 0x1f
|
|
309
|
+
|
|
310
|
+
# Check if this is a hash
|
|
311
|
+
if obj_type == 0x08:
|
|
312
|
+
rhash = obj.cast(gdb.lookup_type("struct RHash").pointer())
|
|
313
|
+
# ... iterate hash entries ...
|
|
314
|
+
# ... check if any value == target_address ...
|
|
315
|
+
|
|
316
|
+
# Check if this is an array
|
|
317
|
+
elif obj_type == 0x07:
|
|
318
|
+
# ... iterate array elements ...
|
|
319
|
+
end
|
|
320
|
+
~~~
|
|
321
|
+
|
|
322
|
+
This helps track down unexpected object retention.
|
|
323
|
+
|
|
324
|
+
### Heap Fragmentation Analysis
|
|
325
|
+
|
|
326
|
+
Check how objects are distributed across pages:
|
|
327
|
+
|
|
328
|
+
~~~python
|
|
329
|
+
python
|
|
330
|
+
page_utilization = []
|
|
331
|
+
|
|
332
|
+
objspace = gdb.parse_and_eval("ruby_current_vm_ptr->gc->objspace")
|
|
333
|
+
allocated_pages = int(objspace["heap_pages"]["allocated_pages"])
|
|
334
|
+
|
|
335
|
+
for i in range(allocated_pages):
|
|
336
|
+
page = objspace["heap_pages"]["sorted"]["data"][i]
|
|
337
|
+
total_slots = int(page["total_slots"])
|
|
338
|
+
|
|
339
|
+
# Count non-free objects
|
|
340
|
+
used_slots = 0
|
|
341
|
+
# ... iterate and count ...
|
|
342
|
+
|
|
343
|
+
utilization = (used_slots / total_slots) * 100
|
|
344
|
+
page_utilization.append(utilization)
|
|
345
|
+
|
|
346
|
+
average = sum(page_utilization) / len(page_utilization)
|
|
347
|
+
print(f"Average page utilization: {average:.1f}%")
|
|
348
|
+
end
|
|
349
|
+
~~~
|
|
350
|
+
|
|
351
|
+
Low utilization indicates fragmentation.
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
|
+
---
|
|
4
|
+
description: Ruby debugging toolbox for GDB and LLDB
|
|
5
|
+
metadata:
|
|
6
|
+
documentation_uri: https://socketry.github.io/toolbox/
|
|
7
|
+
source_code_uri: https://github.com/socketry/toolbox
|
|
8
|
+
files:
|
|
9
|
+
- path: getting-started.md
|
|
10
|
+
title: Getting Started
|
|
11
|
+
description: This guide explains how to install and use Toolbox for debugging Ruby
|
|
12
|
+
programs and core dumps with GDB or LLDB.
|
|
13
|
+
- path: object-inspection.md
|
|
14
|
+
title: Object Inspection
|
|
15
|
+
description: This guide explains how to use `rb-object-print` to inspect Ruby objects,
|
|
16
|
+
hashes, arrays, and structs in GDB.
|
|
17
|
+
- path: stack-inspection.md
|
|
18
|
+
title: Stack Inspection
|
|
19
|
+
description: This guide explains how to inspect both Ruby VM stacks and native C
|
|
20
|
+
stacks when debugging Ruby programs.
|
|
21
|
+
- path: fiber-debugging.md
|
|
22
|
+
title: Fiber Debugging
|
|
23
|
+
description: This guide explains how to debug Ruby fibers using GDB, including inspecting
|
|
24
|
+
fiber state, backtraces, and switching between fiber contexts.
|
|
25
|
+
- path: heap-debugging.md
|
|
26
|
+
title: Heap Debugging
|
|
27
|
+
description: This guide explains how to navigate Ruby's heap to find objects, diagnose
|
|
28
|
+
memory issues, and understand object relationships.
|