toolbox 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8bc49b89f83b1baa5270a63e16df4c63dfc3de61fa98e8c6da8fabbcde66e87
4
- data.tar.gz: cd5b34b044e80e8aa2b278a67fa138cd5230829da83aa9710b04b8b77478b8ff
3
+ metadata.gz: 47056303f26efc916f64b03caf6c67278818e8c2bfb79b00b23b9ed752ce393f
4
+ data.tar.gz: 3a3860fadb6e0a7e4f5d591344801d459480da891d95c87ecc2b1cb2044c9ce8
5
5
  SHA512:
6
- metadata.gz: 891326e75355c9a42cbed524b63345f7b47e6ee8ed29f5c08ee96a5c8a40cb495ad1913bfeb0043b2479f1e08ff4c763d93b4f8231a30f5b958a356eabc0ae46
7
- data.tar.gz: c3edb1db9689d4b399c96a112876d8b8ba3f3b4754903d61e2fafb999d2913409509f4ee2a4964096a4ed5b30ccec0647deee5843a31d77b8700de0ef62ee9b9
6
+ metadata.gz: 272ee139aec5ae2ce7ba7b08ae57d329e64dfd4f370cfa99649621978c9d588266de01e902d5f88a26ad90981bbc062f270015d8422d60dbc7ed1f8421f5df5c
7
+ data.tar.gz: b96fc5c29410655d1f3538df655eaa865237f549c553250ef4ea3586ac0b0d01d0902d27505e9d17cf31a70cf2b3822352a0e59b6f9215a1d40ec1907909931e
checksums.yaml.gz.sig CHANGED
Binary file
data/data/toolbox/heap.py CHANGED
@@ -531,8 +531,18 @@ class RubyHeapScanHandler:
531
531
  """
532
532
  import constants
533
533
 
534
- # Try as a constant name first
535
- type_value = constants.get(type_arg)
534
+ # Try as a Ruby type constant (RUBY_T_*) first, using defaults table
535
+ if type_arg.startswith('RUBY_T_'):
536
+ try:
537
+ return constants.type(type_arg)
538
+ except Exception:
539
+ pass
540
+
541
+ # Try as a general constant name
542
+ try:
543
+ type_value = constants.get(type_arg)
544
+ except Exception:
545
+ type_value = None
536
546
 
537
547
  if type_value is None:
538
548
  # Try parsing as a number (hex or decimal)
@@ -675,5 +685,176 @@ class RubyHeapScanHandler:
675
685
  traceback.print_exc()
676
686
 
677
687
 
688
+ class RubyHeapDumpHandler:
689
+ """Dump the Ruby heap to a JSON file.
690
+
691
+ Usage: rb-heap-dump --output FILE [--type TYPE] [--pretty]
692
+
693
+ Iterates the entire Ruby heap and writes each live object as a JSON record.
694
+ Each record includes the object's address, type, and type-specific metadata.
695
+
696
+ Options:
697
+ --output FILE Path to the output JSON file (required)
698
+ --type TYPE Filter by Ruby type (e.g., RUBY_T_STRING, 0x05); omit for all types
699
+ --pretty Pretty-print the JSON output (default: compact)
700
+
701
+ JSON format:
702
+ {
703
+ "total": N,
704
+ "objects": [
705
+ { "address": "0x...", "type": "T_STRING", "properties": { ... } },
706
+ ...
707
+ ]
708
+ }
709
+
710
+ Examples:
711
+ rb-heap-dump --output /tmp/heap.json
712
+ rb-heap-dump --output /tmp/strings.json --type RUBY_T_STRING --pretty
713
+ """
714
+
715
+ USAGE = command.Usage(
716
+ summary="Dump the Ruby heap to a JSON file",
717
+ parameters=[],
718
+ options={
719
+ 'output': (str, None, 'Output file path (required)'),
720
+ 'type': (str, None, 'Filter by Ruby type (e.g., RUBY_T_STRING, or 0x05)'),
721
+ 'limit': (int, None, 'Stop after dumping N objects'),
722
+ },
723
+ flags=[('pretty', 'Pretty-print the JSON output')],
724
+ examples=[
725
+ ("rb-heap-dump --output /tmp/heap.json", "Dump the entire heap"),
726
+ ("rb-heap-dump --output /tmp/strings.json --type RUBY_T_STRING --pretty", "Dump all strings, pretty-printed"),
727
+ ("rb-heap-dump --output /tmp/strings.json --type RUBY_T_STRING --limit 1 --pretty", "Dump first string, pretty-printed"),
728
+ ]
729
+ )
730
+
731
+ def _parse_type(self, type_arg):
732
+ """Parse a type argument and return the type value (reuse scan handler logic)."""
733
+ import constants
734
+
735
+ if type_arg.startswith('RUBY_T_'):
736
+ try:
737
+ return constants.type(type_arg)
738
+ except Exception:
739
+ pass
740
+
741
+ try:
742
+ type_value = constants.get(type_arg)
743
+ except Exception:
744
+ type_value = None
745
+
746
+ if type_value is None:
747
+ try:
748
+ if type_arg.startswith('0x') or type_arg.startswith('0X'):
749
+ type_value = int(type_arg, 16)
750
+ else:
751
+ type_value = int(type_arg)
752
+ except ValueError:
753
+ print(f"Error: Unknown type constant '{type_arg}'")
754
+ print("Use a constant like RUBY_T_STRING or a numeric value like 0x05")
755
+ return None
756
+
757
+ return type_value
758
+
759
+ def _object_to_dict(self, obj, flags):
760
+ """Serialize a heap object to a JSON-compatible dict.
761
+
762
+ Args:
763
+ obj: GDB VALUE for the heap object
764
+ flags: Integer flags value read from the object's RBasic header
765
+
766
+ Returns:
767
+ Dict with 'address', 'type', and 'data' keys
768
+ """
769
+ import rvalue
770
+ import rbasic as rbasic_mod
771
+
772
+ addr = int(obj)
773
+ type_name = rbasic_mod.type_name(obj)
774
+
775
+ record = {
776
+ "address": f"0x{addr:x}",
777
+ "type": type_name,
778
+ }
779
+
780
+ try:
781
+ interpreted = rvalue.interpret(obj)
782
+ properties = interpreted.as_json()
783
+ if properties:
784
+ record["properties"] = properties
785
+ except Exception:
786
+ pass
787
+
788
+ return record
789
+
790
+ def invoke(self, arguments, terminal):
791
+ """Execute the heap dump command."""
792
+ import json
793
+
794
+ output_path = arguments.get_option('output')
795
+ if output_path is None:
796
+ print("Error: --output FILE is required")
797
+ return
798
+
799
+ pretty = arguments.has_flag('pretty')
800
+
801
+ limit = None
802
+ limit_option = arguments.get_option('limit')
803
+ if limit_option is not None:
804
+ try:
805
+ limit = int(limit_option)
806
+ except ValueError:
807
+ print("Error: --limit must be a number")
808
+ return
809
+
810
+ type_value = None
811
+ type_option = arguments.get_option('type')
812
+ if type_option is not None:
813
+ type_value = self._parse_type(type_option)
814
+ if type_value is None:
815
+ return
816
+
817
+ heap = RubyHeap()
818
+ if not heap.initialize():
819
+ return
820
+
821
+ if type_value is not None:
822
+ type_desc = f"type 0x{type_value:02x}"
823
+ else:
824
+ type_desc = "all types"
825
+
826
+ print(f"Scanning heap ({type_desc}), writing to {output_path}...")
827
+
828
+ objects = []
829
+ count = 0
830
+
831
+ for obj, flags, address in heap.iterate_heap():
832
+ if type_value is not None and (flags & RBASIC_FLAGS_TYPE_MASK) != type_value:
833
+ continue
834
+
835
+ count += 1
836
+ if count % 10000 == 0:
837
+ print(f" Processed {count} objects...", file=sys.stderr)
838
+
839
+ record = self._object_to_dict(obj, flags)
840
+ objects.append(record)
841
+
842
+ if limit is not None and len(objects) >= limit:
843
+ break
844
+
845
+ result = {"total": len(objects), "objects": objects}
846
+
847
+ try:
848
+ with open(output_path, 'w', encoding='utf-8') as f:
849
+ if pretty:
850
+ json.dump(result, f, indent=2, ensure_ascii=False)
851
+ else:
852
+ json.dump(result, f, ensure_ascii=False)
853
+ print(f"Wrote {len(objects)} object(s) to {output_path}")
854
+ except OSError as e:
855
+ print(f"Error writing {output_path}: {e}")
856
+
857
+
678
858
  # Register commands
679
859
  debugger.register("rb-heap-scan", RubyHeapScanHandler, usage=RubyHeapScanHandler.USAGE)
860
+ debugger.register("rb-heap-dump", RubyHeapDumpHandler, usage=RubyHeapDumpHandler.USAGE)
@@ -15,6 +15,7 @@ import rstruct
15
15
  import rfloat
16
16
  import rbignum
17
17
  import rbasic
18
+ import robject
18
19
  import format
19
20
 
20
21
 
@@ -56,6 +56,10 @@ class RArrayBase:
56
56
  except Exception as e:
57
57
  print(f"Error accessing element {i}: {e}")
58
58
 
59
+ def as_json(self):
60
+ """Return a JSON-serializable dict of this array's data."""
61
+ return {"length": self.length()}
62
+
59
63
  class RArrayEmbedded(RArrayBase):
60
64
  """Embedded array (small arrays stored directly in struct)."""
61
65
 
@@ -24,7 +24,7 @@ def is_type(value, ruby_type_constant):
24
24
  True if the value is of the specified type, False otherwise
25
25
  """
26
26
  type_flag = type_of(value)
27
- expected_type = constants.get(ruby_type_constant)
27
+ expected_type = constants.type(ruby_type_constant)
28
28
  return type_flag == expected_type
29
29
 
30
30
  # Map of type constants to their names for display
@@ -67,7 +67,7 @@ def type_name(value):
67
67
 
68
68
  # Try to find matching type name
69
69
  for const_name, display_name in TYPE_NAMES.items():
70
- if constants.get(const_name) == type_flag:
70
+ if constants.type(const_name) == type_flag:
71
71
  return display_name
72
72
 
73
73
  return f'Unknown(0x{type_flag:x})'
@@ -94,6 +94,10 @@ class RBasic:
94
94
  # Use print_type_tag for consistency with other types
95
95
  terminal.print_type_tag(type_str, addr)
96
96
 
97
+ def as_json(self):
98
+ """Return a JSON-serializable dict of this object's data."""
99
+ return {}
100
+
97
101
  def print_recursive(self, printer, depth):
98
102
  """Print this basic object (no recursion)."""
99
103
  printer.print(self)
@@ -37,6 +37,10 @@ class RBignumObject:
37
37
  details = f"{storage} length={len(self)}"
38
38
  terminal.print_type_tag('T_BIGNUM', addr, details)
39
39
 
40
+ def as_json(self):
41
+ """Return a JSON-serializable dict of this bignum's data."""
42
+ return {"embedded": self.is_embedded(), "digits": len(self)}
43
+
40
44
  def print_recursive(self, printer, depth):
41
45
  """Print this bignum (no recursion needed)."""
42
46
  printer.print(self)
@@ -37,6 +37,13 @@ class RFloatImmediate:
37
37
  terminal.print(' ', end='')
38
38
  terminal.print(format.number, str(self.float_value()), format.reset, end='')
39
39
 
40
+ def as_json(self):
41
+ """Return a JSON-serializable dict of this float's data."""
42
+ try:
43
+ return {"value": self.float_value()}
44
+ except Exception:
45
+ return {"value": None}
46
+
40
47
  def print_recursive(self, printer, depth):
41
48
  """Print this float (no recursion needed)."""
42
49
  printer.print(self)
@@ -60,6 +67,13 @@ class RFloatObject:
60
67
  terminal.print(' ', end='')
61
68
  terminal.print(format.number, str(self.float_value()), format.reset, end='')
62
69
 
70
+ def as_json(self):
71
+ """Return a JSON-serializable dict of this float's data."""
72
+ try:
73
+ return {"value": self.float_value()}
74
+ except Exception:
75
+ return {"value": None}
76
+
63
77
  def print_recursive(self, printer, depth):
64
78
  """Print this float (no recursion needed)."""
65
79
  printer.print(self)
@@ -70,6 +70,10 @@ class RHashSTTable(RHashBase):
70
70
  printer.print_value_label(printer.max_depth - depth)
71
71
  printer.print_value(value, depth - 1)
72
72
 
73
+ def as_json(self):
74
+ """Return a JSON-serializable dict of this hash's data."""
75
+ return {"size": self.size()}
76
+
73
77
  class RHashARTable(RHashBase):
74
78
  """Hash using AR table structure (newer, smaller hashes)."""
75
79
 
@@ -0,0 +1,219 @@
1
+ import debugger
2
+ import constants
3
+ import format
4
+ import rclass
5
+
6
+
7
+ class RObjectBase:
8
+ """Base class for Ruby T_OBJECT instances (regular class instances like User.new)."""
9
+
10
+ def __init__(self, value):
11
+ """value is a VALUE pointing to a T_OBJECT."""
12
+ self.value = value
13
+ self.basic = value.cast(constants.type_struct('struct RBasic').pointer())
14
+ self.flags = int(self.basic.dereference()['flags'])
15
+ self._class_name = None
16
+
17
+ @property
18
+ def class_name(self):
19
+ if self._class_name is None:
20
+ try:
21
+ klass = self.basic['klass']
22
+ self._class_name = rclass.get_class_name(klass)
23
+ except Exception:
24
+ self._class_name = f"#<Class:0x{int(self.value):x}>"
25
+ return self._class_name
26
+
27
+ def numiv(self):
28
+ """Get the number of instance variables. Subclasses must implement."""
29
+ raise NotImplementedError
30
+
31
+ def ivptr(self):
32
+ """Get pointer to instance variable values. Subclasses must implement."""
33
+ raise NotImplementedError
34
+
35
+ def __str__(self):
36
+ addr = int(self.value)
37
+ n = self.numiv()
38
+ if n > 0:
39
+ return f"<{self.class_name}@0x{addr:x} ivars={n}>"
40
+ return f"<{self.class_name}@0x{addr:x}>"
41
+
42
+ def print_to(self, terminal):
43
+ addr = int(self.value)
44
+ n = self.numiv()
45
+ details = f"ivars={n}" if n > 0 else None
46
+ terminal.print_type_tag(self.class_name, addr, details)
47
+
48
+ def as_json(self):
49
+ """Return a JSON-serializable dict of this object's data."""
50
+ data = {"class": self.class_name}
51
+ try:
52
+ data["ivars"] = self.numiv()
53
+ except Exception:
54
+ data["ivars"] = None
55
+ return data
56
+
57
+ def print_recursive(self, printer, depth):
58
+ printer.print(self)
59
+
60
+ n = self.numiv()
61
+ if depth <= 0 or n <= 0:
62
+ return
63
+
64
+ ptr = self.ivptr()
65
+ if ptr is None:
66
+ return
67
+
68
+ for i in range(n):
69
+ try:
70
+ printer.print_item_label(printer.max_depth - depth, i)
71
+ printer.print_value(ptr[i], depth - 1)
72
+ except Exception:
73
+ break
74
+
75
+
76
+ class RObjectEmbedded(RObjectBase):
77
+ """T_OBJECT with instance variables stored inline (small objects)."""
78
+
79
+ def __init__(self, value, robject):
80
+ super().__init__(value)
81
+ self.robject = robject
82
+ self._numiv = None
83
+
84
+ def numiv(self):
85
+ if self._numiv is not None:
86
+ return self._numiv
87
+
88
+ try:
89
+ # Try shape-based numiv (Ruby 3.4+)
90
+ self._numiv = self._numiv_from_shape()
91
+ if self._numiv is not None:
92
+ return self._numiv
93
+ except Exception:
94
+ pass
95
+
96
+ # Fallback: count non-zero slots in the embedded array
97
+ self._numiv = self._count_embedded_slots()
98
+ return self._numiv
99
+
100
+ def _numiv_from_shape(self):
101
+ """Try to get ivar count from the object's shape (Ruby 3.4+)."""
102
+ try:
103
+ shape_id_mask = 0xFFFF # shape_id is in bits 16..31 of flags typically
104
+ # In Ruby 3.4+, shape_id_t is stored in flags
105
+ # SHAPE_FLAG_SHIFT is typically 16
106
+ shape_id = (self.flags >> 16) & 0xFFFF
107
+ if shape_id == 0:
108
+ return 0
109
+
110
+ # Try to read from shape table
111
+ shape_list = debugger.parse_and_eval('rb_shape_tree.shape_list')
112
+ shape = shape_list[shape_id]
113
+ next_iv = int(shape['next_iv'])
114
+ return next_iv
115
+ except Exception:
116
+ return None
117
+
118
+ def _count_embedded_slots(self):
119
+ """Count non-Qundef/non-zero slots in the embedded array."""
120
+ try:
121
+ ary = self.robject.dereference()['as']['ary']
122
+ count = 0
123
+ # ROBJECT_EMBED_LEN_MAX is typically 3 (Ruby 3.2) or varies by slot size
124
+ for i in range(12):
125
+ try:
126
+ val = int(ary[i])
127
+ if val == 0 or val == 0x34: # Qundef
128
+ break
129
+ count += 1
130
+ except Exception:
131
+ break
132
+ return count
133
+ except Exception:
134
+ return 0
135
+
136
+ def ivptr(self):
137
+ try:
138
+ return self.robject.dereference()['as']['ary']
139
+ except Exception:
140
+ return None
141
+
142
+
143
+ class RObjectHeap(RObjectBase):
144
+ """T_OBJECT with instance variables stored on the heap (many ivars)."""
145
+
146
+ def __init__(self, value, robject):
147
+ super().__init__(value)
148
+ self.robject = robject
149
+ self._numiv = None
150
+
151
+ def numiv(self):
152
+ if self._numiv is not None:
153
+ return self._numiv
154
+
155
+ try:
156
+ self._numiv = int(self.robject.dereference()['as']['heap']['numiv'])
157
+ except Exception:
158
+ self._numiv = 0
159
+ return self._numiv
160
+
161
+ def ivptr(self):
162
+ try:
163
+ return self.robject.dereference()['as']['heap']['ivptr']
164
+ except Exception:
165
+ return None
166
+
167
+
168
+ class RObjectGeneric(RObjectBase):
169
+ """Fallback T_OBJECT when struct RObject is unavailable or has unknown layout."""
170
+
171
+ def numiv(self):
172
+ return 0
173
+
174
+ def ivptr(self):
175
+ return None
176
+
177
+
178
+ def RObject(value):
179
+ """Factory function that detects the RObject variant and returns the appropriate instance.
180
+
181
+ Caller should ensure value is a RUBY_T_OBJECT before calling.
182
+ """
183
+ try:
184
+ robject = value.cast(constants.type_struct('struct RObject').pointer())
185
+ except Exception:
186
+ return RObjectGeneric(value)
187
+
188
+ # Detect embedded vs heap storage
189
+ try:
190
+ FL_USER1 = constants.flag('RUBY_FL_USER1')
191
+ basic = value.cast(constants.type_struct('struct RBasic').pointer())
192
+ flags = int(basic.dereference()['flags'])
193
+
194
+ if flags & FL_USER1:
195
+ # ROBJECT_EMBED flag set — ivars are inline
196
+ return RObjectEmbedded(value, robject)
197
+ else:
198
+ # Heap-allocated ivars
199
+ return RObjectHeap(value, robject)
200
+ except Exception:
201
+ pass
202
+
203
+ # Feature detection: check what fields exist
204
+ try:
205
+ as_union = robject.dereference()['as']
206
+ if as_union is not None:
207
+ heap = as_union['heap']
208
+ if heap is not None:
209
+ numiv_field = heap['numiv']
210
+ if numiv_field is not None:
211
+ return RObjectHeap(value, robject)
212
+
213
+ ary = as_union['ary']
214
+ if ary is not None:
215
+ return RObjectEmbedded(value, robject)
216
+ except Exception:
217
+ pass
218
+
219
+ return RObjectGeneric(value)
@@ -77,6 +77,15 @@ class RStringBase:
77
77
  # Use repr() to properly escape quotes, newlines, etc.
78
78
  terminal.print(format.string, repr(content), format.reset, end='')
79
79
 
80
+ def as_json(self):
81
+ """Return a JSON-serializable dict of this string's data."""
82
+ data = {"length": self.length(), "embedded": self._is_embedded()}
83
+ try:
84
+ data["value"] = self.to_str()
85
+ except Exception:
86
+ data["value"] = None
87
+ return data
88
+
80
89
  def print_recursive(self, printer, depth):
81
90
  """Print this string (no recursion needed for strings)."""
82
91
  printer.print(self)
@@ -11,6 +11,7 @@ import rarray
11
11
  import rhash
12
12
  import rstruct
13
13
  import rbignum
14
+ import robject
14
15
 
15
16
  class RImmediate:
16
17
  """Wrapper for Ruby immediate values (fixnum, nil, true, false)."""
@@ -50,6 +51,18 @@ class RImmediate:
50
51
  # Unknown immediate
51
52
  terminal.print_type_tag('Immediate', self.val_int)
52
53
 
54
+ def as_json(self):
55
+ """Return a JSON-serializable dict of this immediate value's data."""
56
+ if self.val_int == 0:
57
+ return {"value": False}
58
+ elif self.val_int == 0x04 or self.val_int == 0x08:
59
+ return {"value": None}
60
+ elif self.val_int == 0x14:
61
+ return {"value": True}
62
+ elif (self.val_int & 0x01) != 0:
63
+ return {"value": self.val_int >> 1}
64
+ return {}
65
+
53
66
  def print_recursive(self, printer, depth):
54
67
  """Print this immediate value (no recursion needed)."""
55
68
  printer.print(self)
@@ -174,6 +187,8 @@ def interpret(value):
174
187
  return rfloat.RFloat(value)
175
188
  elif type_flag == constants.type("RUBY_T_BIGNUM"):
176
189
  return rbignum.RBignum(value)
190
+ elif type_flag == constants.type("RUBY_T_OBJECT"):
191
+ return robject.RObject(value)
177
192
  else:
178
193
  # Unknown type - return generic RBasic
179
194
  return rbasic.RBasic(value)
@@ -5,6 +5,6 @@
5
5
 
6
6
  # @namespace
7
7
  module Toolbox
8
- VERSION = "0.3.0"
8
+ VERSION = "0.4.0"
9
9
  end
10
10
 
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2025, by Samuel Williams.
3
+ Copyright, 2025-2026, by Samuel Williams.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -31,6 +31,11 @@ Please see the [project documentation](https://socketry.github.io/toolbox/) for
31
31
 
32
32
  Please see the [project releases](https://socketry.github.io/toolbox/releases/index) for all releases.
33
33
 
34
+ ### v0.4.0
35
+
36
+ - Better support for printing `T_OBJECT` values, including class name.
37
+ - Introduce `rb-heap-dump` command to dump the Ruby heap to a JSON file, with optional type filtering and limit.
38
+
34
39
  ### v0.2.0
35
40
 
36
41
  - Introduce support for `lldb` using debugger shims.
@@ -55,6 +60,22 @@ We welcome contributions to this project.
55
60
  4. Push to the branch (`git push origin my-new-feature`).
56
61
  5. Create new Pull Request.
57
62
 
63
+ ### Running Tests
64
+
65
+ To run the test suite:
66
+
67
+ ``` shell
68
+ bundle exec sus
69
+ ```
70
+
71
+ ### Making Releases
72
+
73
+ To make a new release:
74
+
75
+ ``` shell
76
+ bundle exec bake gem:release:patch # or minor or major
77
+ ```
78
+
58
79
  ### Developer Certificate of Origin
59
80
 
60
81
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Releases
2
2
 
3
+ ## v0.4.0
4
+
5
+ - Better support for printing `T_OBJECT` values, including class name.
6
+ - Introduce `rb-heap-dump` command to dump the Ruby heap to a JSON file, with optional type filtering and limit.
7
+
3
8
  ## v0.2.0
4
9
 
5
10
  - Introduce support for `lldb` using debugger shims.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toolbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -70,6 +70,7 @@ files:
70
70
  - data/toolbox/rexception.py
71
71
  - data/toolbox/rfloat.py
72
72
  - data/toolbox/rhash.py
73
+ - data/toolbox/robject.py
73
74
  - data/toolbox/rstring.py
74
75
  - data/toolbox/rstruct.py
75
76
  - data/toolbox/rsymbol.py
@@ -95,14 +96,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
96
  requirements:
96
97
  - - ">="
97
98
  - !ruby/object:Gem::Version
98
- version: '3.2'
99
+ version: '3.3'
99
100
  required_rubygems_version: !ruby/object:Gem::Requirement
100
101
  requirements:
101
102
  - - ">="
102
103
  - !ruby/object:Gem::Version
103
104
  version: '0'
104
105
  requirements: []
105
- rubygems_version: 3.6.9
106
+ rubygems_version: 4.0.6
106
107
  specification_version: 4
107
108
  summary: Ruby debugging toolbox for GDB and LLDB
108
109
  test_files: []
metadata.gz.sig CHANGED
Binary file