toolbox 0.2.0 → 0.3.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.
@@ -70,6 +70,7 @@ read_cstring = _backend.read_cstring
70
70
  create_value = _backend.create_value
71
71
  create_value_from_int = _backend.create_value_from_int
72
72
  create_value_from_address = _backend.create_value_from_address
73
+ register = _backend.register
73
74
 
74
75
  # Constants
75
76
  COMMAND_DATA = _backend.COMMAND_DATA
@@ -94,6 +95,7 @@ __all__ = [
94
95
  'create_value',
95
96
  'create_value_from_int',
96
97
  'create_value_from_address',
98
+ 'register',
97
99
  'COMMAND_DATA',
98
100
  'COMMAND_USER',
99
101
  ]
@@ -3,6 +3,7 @@ GDB backend implementation for unified debugger interface.
3
3
  """
4
4
 
5
5
  import gdb
6
+ import format
6
7
 
7
8
  # Command categories
8
9
  COMMAND_DATA = gdb.COMMAND_DATA
@@ -311,7 +312,7 @@ class Command:
311
312
  """Initialize and register a command.
312
313
 
313
314
  Args:
314
- name: Command name (e.g., "rb-object-print")
315
+ name: Command name (e.g., "rb-print")
315
316
  category: Command category (COMMAND_DATA or COMMAND_USER)
316
317
  """
317
318
  self.name = name
@@ -589,6 +590,74 @@ def create_value_from_address(address, value_type):
589
590
  return Value(addr_val.dereference())
590
591
 
591
592
 
593
+ def register(name, handler_class, usage=None, category=COMMAND_USER):
594
+ """Register a command with GDB using a handler class.
595
+
596
+ This creates a wrapper Command that handles parsing, terminal setup,
597
+ and delegates to the handler class for actual command logic.
598
+
599
+ Args:
600
+ name: Command name (e.g., "rb-print")
601
+ handler_class: Class to instantiate for handling the command
602
+ usage: Optional command.Usage specification for validation/help
603
+ category: Command category (COMMAND_USER, etc.)
604
+
605
+ Example:
606
+ class PrintHandler:
607
+ def invoke(self, arguments, terminal):
608
+ depth = arguments.get_option('depth', 1)
609
+ print(f"Depth: {depth}")
610
+
611
+ usage = command.Usage(
612
+ summary="Print something",
613
+ options={'depth': (int, 1)},
614
+ flags=['debug']
615
+ )
616
+ debugger.register("my-print", PrintHandler, usage=usage)
617
+
618
+ Returns:
619
+ The registered Command instance
620
+ """
621
+ class RegisteredCommand(Command):
622
+ def __init__(self):
623
+ super(RegisteredCommand, self).__init__(name, category)
624
+ self.usage_spec = usage
625
+ self.handler_class = handler_class
626
+
627
+ def invoke(self, arg, from_tty):
628
+ """GDB entry point - parses arguments and delegates to handler."""
629
+ # Create terminal first (needed for help text)
630
+ import format
631
+ terminal = format.create_terminal(from_tty)
632
+
633
+ try:
634
+ # Parse and validate arguments
635
+ if self.usage_spec:
636
+ arguments = self.usage_spec.parse(arg if arg else "")
637
+ else:
638
+ # Fallback to basic parsing without validation
639
+ import command
640
+ arguments = command.parse_arguments(arg if arg else "")
641
+
642
+ # Instantiate handler and invoke
643
+ handler = self.handler_class()
644
+ handler.invoke(arguments, terminal)
645
+
646
+ except ValueError as e:
647
+ # Validation error - show colored help
648
+ print(f"Error: {e}")
649
+ if self.usage_spec:
650
+ print()
651
+ self.usage_spec.print_to(terminal, name)
652
+ except Exception as e:
653
+ print(f"Error: {e}")
654
+ import traceback
655
+ traceback.print_exc()
656
+
657
+ # Instantiate and register the command with GDB
658
+ return RegisteredCommand()
659
+
660
+
592
661
 
593
662
 
594
663
 
@@ -6,6 +6,7 @@ would require more extensive testing and edge case handling.
6
6
  """
7
7
 
8
8
  import lldb
9
+ import format
9
10
 
10
11
  # Command categories (LLDB doesn't have exact equivalents, using symbolic constants)
11
12
  COMMAND_DATA = 0
@@ -427,7 +428,7 @@ class Command:
427
428
  """Initialize and register a command.
428
429
 
429
430
  Args:
430
- name: Command name (e.g., "rb-object-print")
431
+ name: Command name (e.g., "rb-print")
431
432
  category: Command category (not used in LLDB)
432
433
  """
433
434
  self.name = name
@@ -515,10 +516,13 @@ def lookup_type(type_name):
515
516
 
516
517
  Returns:
517
518
  Type object
519
+
520
+ Raises:
521
+ Error: If type cannot be found in debug symbols
518
522
  """
519
523
  target = lldb.debugger.GetSelectedTarget()
520
524
 
521
- # LLDB's FindFirstType searches the type system
525
+ # LLDB's FindFirstType searches loaded debug symbols
522
526
  lldb_type = target.FindFirstType(type_name)
523
527
 
524
528
  if not lldb_type.IsValid():
@@ -534,8 +538,8 @@ def set_convenience_variable(name, value):
534
538
  name: Variable name (without $ prefix)
535
539
  value: Value to set (can be Value wrapper or native value)
536
540
 
537
- Note: LLDB doesn't have direct convenience variables like GDB.
538
- This implementation uses expression evaluation to set variables.
541
+ Note: LLDB convenience variables are created using expression evaluation.
542
+ The variable name will be prefixed with $ automatically.
539
543
  """
540
544
  if isinstance(value, Value):
541
545
  native_value = value._value
@@ -545,16 +549,45 @@ def set_convenience_variable(name, value):
545
549
  # LLDB approach: use expression to create a persistent variable
546
550
  # Variables in LLDB are prefixed with $
547
551
  target = lldb.debugger.GetSelectedTarget()
548
- frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
552
+ if not target:
553
+ return
554
+
555
+ process = target.GetProcess()
556
+ if not process:
557
+ return
558
+
559
+ thread = process.GetSelectedThread()
560
+ if not thread:
561
+ return
562
+
563
+ frame = thread.GetSelectedFrame()
564
+ if not frame:
565
+ return
549
566
 
550
- # Create a persistent variable by evaluating an expression
567
+ # Create a persistent variable by evaluating an assignment expression
551
568
  if hasattr(native_value, 'GetValue'):
552
569
  # It's an SBValue
553
570
  addr = native_value.GetValueAsUnsigned()
554
571
  type_name = native_value.GetType().GetName()
555
- frame.EvaluateExpression(f"({type_name})0x{addr:x}", lldb.SBExpressionOptions())
556
- # For now, simplified implementation
557
- # Full implementation would require more complex value handling
572
+
573
+ # Use expr command to create persistent variable
574
+ # Format: expr <type> $varname = (<type>)address
575
+ expr = f"{type_name} ${name} = ({type_name})0x{addr:x}"
576
+
577
+ options = lldb.SBExpressionOptions()
578
+ options.SetIgnoreBreakpoints(True)
579
+ options.SetFetchDynamicValue(lldb.eNoDynamicValues)
580
+ options.SetTimeoutInMicroSeconds(100000) # 0.1 second timeout
581
+ options.SetTryAllThreads(False)
582
+
583
+ # Evaluate the expression to create the persistent variable
584
+ result = frame.EvaluateExpression(expr, options)
585
+
586
+ # Check for errors
587
+ if result.GetError().Fail():
588
+ error_msg = result.GetError().GetCString()
589
+ # Silently ignore errors for now - convenience variables are optional
590
+ pass
558
591
 
559
592
 
560
593
  def execute(command, from_tty=False, to_string=False):
@@ -883,3 +916,71 @@ def create_value_from_int(int_value, value_type):
883
916
  return Value(result)
884
917
 
885
918
 
919
+ def register(name, handler_class, usage=None, category=COMMAND_USER):
920
+ """Register a command with LLDB using a handler class.
921
+
922
+ This creates a wrapper Command that handles parsing, terminal setup,
923
+ and delegates to the handler class for actual command logic.
924
+
925
+ Args:
926
+ name: Command name (e.g., "rb-print")
927
+ handler_class: Class to instantiate for handling the command
928
+ usage: Optional command.Usage specification for validation/help
929
+ category: Command category (COMMAND_USER, etc.)
930
+
931
+ Example:
932
+ class PrintHandler:
933
+ def invoke(self, arguments, terminal):
934
+ depth = arguments.get_option('depth', 1)
935
+ print(f"Depth: {depth}")
936
+
937
+ usage = command.Usage(
938
+ summary="Print something",
939
+ options={'depth': (int, 1)},
940
+ flags=['debug']
941
+ )
942
+ debugger.register("my-print", PrintHandler, usage=usage)
943
+
944
+ Returns:
945
+ The registered Command instance
946
+ """
947
+ class RegisteredCommand(Command):
948
+ def __init__(self):
949
+ super(RegisteredCommand, self).__init__(name, category)
950
+ self.usage_spec = usage
951
+ self.handler_class = handler_class
952
+
953
+ def invoke(self, arg, from_tty):
954
+ """LLDB entry point - parses arguments and delegates to handler."""
955
+ # Create terminal first (needed for help text)
956
+ import format
957
+ terminal = format.create_terminal(from_tty)
958
+
959
+ try:
960
+ # Parse and validate arguments
961
+ if self.usage_spec:
962
+ arguments = self.usage_spec.parse(arg if arg else "")
963
+ else:
964
+ # Fallback to basic parsing without validation
965
+ import command
966
+ arguments = command.parse_arguments(arg if arg else "")
967
+
968
+ # Instantiate handler and invoke
969
+ handler = self.handler_class()
970
+ handler.invoke(arguments, terminal)
971
+
972
+ except ValueError as e:
973
+ # Validation error - show colored help
974
+ print(f"Error: {e}")
975
+ if self.usage_spec:
976
+ print()
977
+ self.usage_spec.print_to(terminal, name)
978
+ except Exception as e:
979
+ print(f"Error: {e}")
980
+ import traceback
981
+ traceback.print_exc()
982
+
983
+ # Instantiate and register the command with LLDB
984
+ return RegisteredCommand()
985
+
986
+