44import struct
55import json
66import os
7+ import sys
78
89# Import command parser
910import command
11+ import value
12+ import format
1013
1114# Global cache of fibers
1215_fiber_cache = []
@@ -74,7 +77,13 @@ def initialize(self):
7477 print ("Make sure Ruby is fully initialized and the process is running." )
7578 return False
7679
77- self .objspace = self .vm_ptr ['gc' ]['objspace' ]
80+ # Ruby 3.3+ moved objspace into a gc struct
81+ try :
82+ self .objspace = self .vm_ptr ['gc' ]['objspace' ]
83+ except (gdb .error , KeyError ):
84+ # Ruby 3.2 and earlier have objspace directly in VM
85+ self .objspace = self .vm_ptr ['objspace' ]
86+
7887 if int (self .objspace ) == 0 :
7988 print ("Error: objspace is NULL" )
8089 print ("Make sure the Ruby GC has been initialized." )
@@ -118,8 +127,13 @@ def iterate_heap(self):
118127
119128 for i in range (allocated_pages ):
120129 try :
121- # rb_darray is a struct with 'meta' and 'data[]' fields
122- page = self .objspace ['heap_pages' ]['sorted' ]['data' ][i ]
130+ # Ruby 3.3+ uses rb_darray with 'data' field, Ruby 3.2- uses direct pointer
131+ try :
132+ page = self .objspace ['heap_pages' ]['sorted' ]['data' ][i ]
133+ except (gdb .error , KeyError ):
134+ # Ruby 3.2 and earlier: sorted is a direct pointer array
135+ page = self .objspace ['heap_pages' ]['sorted' ][i ]
136+
123137 start = int (page ['start' ])
124138 total_slots = int (page ['total_slots' ])
125139 slot_size = int (page ['slot_size' ])
@@ -165,7 +179,7 @@ def find_all_fibers(self, limit=None):
165179
166180 try :
167181 allocated_pages = int (self .objspace ['heap_pages' ]['allocated_pages' ])
168- print (f"Scanning { allocated_pages } heap pages..." )
182+ print (f"Scanning { allocated_pages } heap pages..." , file = sys . stderr )
169183 except (gdb .MemoryError , gdb .error ) as e :
170184 print (f"Error accessing heap pages: { e } " )
171185 return fibers
@@ -183,7 +197,7 @@ def find_all_fibers(self, limit=None):
183197
184198 # Print progress every 10000 objects
185199 if objects_checked % 10000 == 0 :
186- print (f" Checked { objects_checked } objects, found { len (fibers )} fiber(s)..." )
200+ print (f" Checked { objects_checked } objects, found { len (fibers )} fiber(s)..." , file = sys . stderr )
187201
188202 # Check if it's T_DATA
189203 if (flags & 0x1f ) != self .T_DATA :
@@ -196,14 +210,14 @@ def find_all_fibers(self, limit=None):
196210 if typed_data ['type' ] == self .fiber_data_type :
197211 fiber_ptr = typed_data ['data' ].cast (self ._rb_fiber_struct_type )
198212 fibers .append (fiber_ptr )
199- print (f" Found fiber #{ len (fibers )} at { fiber_ptr } " )
213+ print (f" Found fiber #{ len (fibers )} at { fiber_ptr } " , file = sys . stderr )
200214 except (gdb .error , RuntimeError ):
201215 continue
202216
203217 if limit and len (fibers ) >= limit :
204- print (f"Scan complete: checked { objects_checked } objects (stopped at limit)" )
218+ print (f"Scan complete: checked { objects_checked } objects (stopped at limit)" , file = sys . stderr )
205219 else :
206- print (f"Scan complete: checked { objects_checked } objects" )
220+ print (f"Scan complete: checked { objects_checked } objects" , file = sys . stderr )
207221 return fibers
208222
209223 def find_fiber_by_stack (self , stack_base ):
@@ -235,18 +249,9 @@ def get_fiber_info(self, fiber):
235249 errinfo_str = None
236250 try :
237251 errinfo_val = ec ['errinfo' ]
238- errinfo_int = int (errinfo_val )
239-
240- # Check if errinfo is a real object (not a special constant)
241- # Special constants: Qfalse(0x0/0x0), Qnil(0x2/0x4/0x8), Qtrue(0x6/0x14), fixnums, etc.
242- # RB_SPECIAL_CONST_P checks: (obj == Qfalse) || RB_IMMEDIATE_P(obj)
243- # RB_IMMEDIATE_P checks: obj & 0x03 (fixnum or special)
244-
245- # Simple check: if low bits indicate immediate/special, skip
246- # If it's a real object, low bits should be 0 (aligned pointer)
247- is_special = (errinfo_int & 0x03 ) != 0 or errinfo_int == 0
248-
249- if not is_special :
252+
253+ # Only process if it's a real object (not nil or other immediate value)
254+ if value .is_object (errinfo_val ) and not value .is_nil (errinfo_val ):
250255 errinfo = errinfo_val
251256 # Try to get exception class and message
252257 try :
@@ -256,9 +261,10 @@ def get_fiber_info(self, fiber):
256261 errinfo_str = f"{ exc_class } : { exc_msg } "
257262 else :
258263 errinfo_str = exc_class
259- except :
260- errinfo_str = f"<exception:0x{ errinfo_int :x} >"
261- except :
264+ except Exception :
265+ # If we can't decode the exception, don't show it
266+ pass
267+ except Exception :
262268 pass
263269
264270 return {
@@ -301,8 +307,9 @@ def get_exception_class(self, exc_value):
301307 pass
302308
303309 return f"Exception(klass=0x{ int (klass ):x} )"
304- except :
305- return "Exception"
310+ except Exception :
311+ # Don't return a fallback - let the caller handle it
312+ raise
306313
307314 def get_exception_message (self , exc_value ):
308315 """Get the message from an exception object.
@@ -597,7 +604,7 @@ def value_to_string(self, value):
597604 return f"<error:{ e } >"
598605
599606
600- class RubyScanFibersCommand (gdb .Command ):
607+ class RubyFiberScanHeapCommand (gdb .Command ):
601608 """Scan heap and list all Ruby fibers.
602609
603610 Usage: rb-fiber-scan-heap [limit] [--cache [filename]]
@@ -608,7 +615,7 @@ class RubyScanFibersCommand(gdb.Command):
608615 """
609616
610617 def __init__ (self ):
611- super (RubyScanFibersCommand , self ).__init__ ("rb-fiber-scan-heap" , gdb .COMMAND_USER )
618+ super (RubyFiberScanHeapCommand , self ).__init__ ("rb-fiber-scan-heap" , gdb .COMMAND_USER )
612619 self .debug = RubyFiberDebug ()
613620
614621 def save_cache (self , fibers , filename ):
@@ -664,6 +671,9 @@ def load_cache(self, filename):
664671 def invoke (self , arg , from_tty ):
665672 global _fiber_cache
666673
674+ # Create terminal for formatting
675+ terminal = format .create_terminal (from_tty )
676+
667677 # Parse arguments using the robust parser
668678 arguments = command .parse_arguments (arg if arg else "" )
669679
@@ -696,14 +706,11 @@ def invoke(self, arg, from_tty):
696706 for i , fiber in enumerate (loaded_fibers ):
697707 try :
698708 info = self .debug .get_fiber_info (fiber )
699- print (f"Fiber #{ i } : { info ['address' ]} " )
700- print (f" Status: { info ['status' ]} " )
701- print (f" Stack: base={ info ['stack_base' ]} , size={ info ['stack_size' ]} " )
702- print (f" VM Stack: { info ['vm_stack' ]} , size={ info ['vm_stack_size' ]} " )
703- print (f" CFP: { info ['cfp' ]} " )
704- print ()
709+ self ._print_fiber_info (terminal , i , info )
705710 except :
706- print (f"Fiber #{ i } : 0x{ int (fiber ):x} (error reading info)" )
711+ print (f"Fiber #{ i } : " , end = '' )
712+ print (terminal .print (format .metadata , '<' , format .type , '0x' , format .metadata , f'{ int (fiber ):x} >' , format .reset ))
713+ print (f" (error reading info)" )
707714 print ()
708715
709716 print (f"Fibers cached. Use 'rb-fiber <index>' to inspect a specific fiber." )
@@ -716,9 +723,9 @@ def invoke(self, arg, from_tty):
716723 return
717724
718725 if limit :
719- print (f"Scanning heap for first { limit } Fiber object(s)..." )
726+ print (f"Scanning heap for first { limit } Fiber object(s)..." , file = sys . stderr )
720727 else :
721- print ("Scanning heap for Fiber objects..." )
728+ print ("Scanning heap for Fiber objects..." , file = sys . stderr )
722729
723730 fibers = self .debug .find_all_fibers (limit = limit )
724731
@@ -732,14 +739,7 @@ def invoke(self, arg, from_tty):
732739
733740 for i , fiber in enumerate (fibers ):
734741 info = self .debug .get_fiber_info (fiber )
735- print (f"Fiber #{ i } : { info ['address' ]} " )
736- print (f" Status: { info ['status' ]} " )
737- if info .get ('errinfo_str' ):
738- print (f" Exception: { info ['errinfo_str' ]} " )
739- print (f" Stack: base={ info ['stack_base' ]} , size={ info ['stack_size' ]} " )
740- print (f" VM Stack: { info ['vm_stack' ]} , size={ info ['vm_stack_size' ]} " )
741- print (f" CFP: { info ['cfp' ]} " )
742- print ()
742+ self ._print_fiber_info (terminal , i , info )
743743
744744 # Save to cache if requested
745745 if use_cache and fibers :
@@ -748,6 +748,36 @@ def invoke(self, arg, from_tty):
748748
749749 print (f"Fibers cached. Use 'rb-fiber <index>' to inspect a specific fiber." )
750750
751+ def _print_fiber_info (self , terminal , index , info ):
752+ """Print formatted fiber information."""
753+ # Print fiber index and address
754+ print (f"Fiber #{ index } : " , end = '' )
755+ print (terminal .print (format .metadata , '<' , format .type , '0x' , format .metadata , f'{ int (info ["address" ]):x} >' , format .reset ))
756+
757+ # Print status
758+ print (f" Status: { info ['status' ]} " )
759+
760+ # Print exception if present
761+ if info .get ('errinfo_str' ):
762+ print (f" Exception: { info ['errinfo_str' ]} " )
763+
764+ # Print Stack with formatted pointer
765+ stack_base_val = info ['stack_base' ]
766+ stack_type = str (stack_base_val .type )
767+ print (f" Stack: " , end = '' )
768+ print (terminal .print_type_tag (stack_type , int (stack_base_val ), f' size={ info ["stack_size" ]} ' ))
769+
770+ # Print VM Stack with formatted pointer
771+ vm_stack_val = info ['vm_stack' ]
772+ vm_stack_type = str (vm_stack_val .type )
773+ print (f" VM Stack: " , end = '' )
774+ print (terminal .print_type_tag (vm_stack_type , int (vm_stack_val ), f' size={ info ["vm_stack_size" ]} ' ))
775+
776+ # Print CFP
777+ print (f" CFP: " , end = '' )
778+ print (terminal .print (format .metadata , '<' , format .type , '0x' , format .metadata , f'{ int (info ["cfp" ]):x} >' , format .reset ))
779+ print ()
780+
751781
752782class RubyFiberBacktraceCommand (gdb .Command ):
753783 """Print backtrace for a Ruby fiber.
@@ -1505,7 +1535,7 @@ def invoke(self, arg, from_tty):
15051535 if not self .debug .initialize ():
15061536 return
15071537
1508- print ("Scanning heap for Fiber objects..." )
1538+ print ("Scanning heap for Fiber objects..." , file = sys . stderr )
15091539 fibers = self .debug .find_all_fibers ()
15101540
15111541 print (f"Found { len (fibers )} fiber(s)\n " )
@@ -1602,7 +1632,7 @@ def invoke(self, arg, from_tty):
16021632
16031633 print (f"VM Stack for Fiber #{ index } :" )
16041634 print (f" Base: { info ['vm_stack' ]} " )
1605- print (f" Size: { info ['vm_stack_size' ]} VALUEs ({ info ['vm_stack_size' ] * 8 } bytes)" )
1635+ print (f" Size: < { info ['vm_stack_size' ]} > VALUEs (< { info ['vm_stack_size' ] * 8 } > bytes)" )
16061636 print (f" CFP: { info ['cfp' ]} " )
16071637 print ()
16081638
@@ -2108,7 +2138,7 @@ def invoke(self, arg, from_tty):
21082138 search_addr = None
21092139
21102140 matches = []
2111- print (f"\n Scanning { len (_fiber_cache )} fiber(s)...\n " )
2141+ print (f"\n Scanning { len (_fiber_cache )} fiber(s)...\n " , file = sys . stderr )
21122142
21132143 for i , fiber in enumerate (_fiber_cache ):
21142144 try :
@@ -2222,7 +2252,7 @@ def invoke(self, arg, from_tty):
22222252 print (f"C/Machine Stack for Fiber #{ index } :" )
22232253 print (f" Fiber: { fiber } " )
22242254 print (f" Stack Base: { info ['stack_base' ]} " )
2225- print (f" Stack Size: { info ['stack_size' ]} bytes" )
2255+ print (f" Stack Size: < { info ['stack_size' ]} > bytes" )
22262256
22272257 try :
22282258 stack_start = ec ['machine' ]['stack_start' ]
@@ -2231,7 +2261,7 @@ def invoke(self, arg, from_tty):
22312261
22322262 print (f" Stack Start: { stack_start } " )
22332263 print (f" Stack End: { stack_end } " )
2234- print (f" Stack Maxsize: { stack_maxsize } " )
2264+ print (f" Stack Maxsize: < { stack_maxsize } > " )
22352265
22362266 if int (stack_start ) > int (stack_end ):
22372267 stack_used = int (stack_start ) - int (stack_end )
@@ -2337,7 +2367,7 @@ def invoke(self, arg, from_tty):
23372367
23382368
23392369# Register commands
2340- RubyScanFibersCommand ()
2370+ RubyFiberScanHeapCommand ()
23412371RubyFiberCommand ()
23422372RubyFiberIndexBacktraceCommand ()
23432373RubyFiberVMStackCommand ()
0 commit comments