diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py index d45a1a5d..04b837b4 100644 --- a/_lldb/llgo_plugin.py +++ b/_lldb/llgo_plugin.py @@ -1,70 +1,91 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring +from typing import List, Optional, Dict, Any, Tuple import re import lldb -def __lldb_init_module(debugger, _): +def log(*args: Any, **kwargs: Any) -> None: + print(*args, **kwargs, flush=True) + + +def __lldb_init_module(debugger: lldb.SBDebugger, _: Dict[str, Any]) -> None: debugger.HandleCommand( 'command script add -f llgo_plugin.print_go_expression p') debugger.HandleCommand( 'command script add -f llgo_plugin.print_all_variables v') -def is_llgo_compiler(target): +def is_llgo_compiler(_target: lldb.SBTarget) -> bool: return True - module = target.GetModuleAtIndex(0) - - # Check for specific sections or symbols that might be unique to LLGo - llgo_indicators = ["__llgo_", "runtime.llgo", "llgo."] - - # Check sections - for i in range(module.GetNumSections()): - section = module.GetSectionAtIndex(i) - section_name = section.GetName() - if any(indicator in section_name for indicator in llgo_indicators): - return True - - # Check symbols - for symbol in module.symbols: - symbol_name = symbol.GetName() - if any(indicator in symbol_name for indicator in llgo_indicators): - return True - - # Check compile units - for i in range(module.GetNumCompileUnits()): - cu = module.GetCompileUnitAtIndex(i) - cu_name = cu.GetFileSpec().GetFilename() - print(f"Compile unit: {cu_name}") - # You can add more checks here if needed - - print("LLGo Compiler not detected") - return False -def print_go_expression(debugger, command, result, _internal_dict): - target = debugger.GetSelectedTarget() - if not is_llgo_compiler(target): - result.AppendMessage("Not a LLGo compiled binary.") - return +def evaluate_expression(frame: lldb.SBFrame, expression: str) -> Optional[lldb.SBValue]: + parts = re.findall(r'\*|\w+|\(|\)|\[.*?\]|\.', expression) + def evaluate_part(i: int) -> Tuple[Optional[lldb.SBValue], int]: + nonlocal parts + value: Optional[lldb.SBValue] = None + while i < len(parts): + part = parts[i] + + if part == '*': + sub_value, i = evaluate_part(i + 1) + if sub_value and sub_value.IsValid(): + value = sub_value.Dereference() + else: + return None, i + elif part == '(': + depth = 1 + j = i + 1 + while j < len(parts) and depth > 0: + if parts[j] == '(': + depth += 1 + elif parts[j] == ')': + depth -= 1 + j += 1 + value, i = evaluate_part(i + 1) + i = j - 1 + elif part == ')': + return value, i + 1 + elif part == '.': + if value is None: + value = frame.FindVariable(parts[i+1]) + else: + value = value.GetChildMemberWithName(parts[i+1]) + i += 2 + elif part.startswith('['): + index = int(part[1:-1]) + value = value.GetChildAtIndex(index) + i += 1 + else: + if value is None: + value = frame.FindVariable(part) + else: + value = value.GetChildMemberWithName(part) + i += 1 + + if not value or not value.IsValid(): + return None, i + + return value, i + + value, _ = evaluate_part(0) + return value + + +def print_go_expression(debugger: lldb.SBDebugger, command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: frame = debugger.GetSelectedTarget().GetProcess( ).GetSelectedThread().GetSelectedFrame() - - # Handle Go-style pointer member access - command = re.sub(r'(\w+)\.(\w+)', lambda m: f'(*{m.group(1)}).{m.group( - 2)}' if is_pointer(frame, m.group(1)) else m.group(0), command) - - var = frame.EvaluateExpression(command) - - if var.error.Success(): - formatted = format_value(var, debugger) - result.AppendMessage(formatted) + value = evaluate_expression(frame, command) + if value and value.IsValid(): + result.AppendMessage(format_value(value, debugger)) else: - result.AppendMessage(f"Error: {var.error}") + result.AppendMessage( + f"Error: Unable to evaluate expression '{command}'") -def print_all_variables(debugger, command, result, _internal_dict): +def print_all_variables(debugger: lldb.SBDebugger, _command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: target = debugger.GetSelectedTarget() if not is_llgo_compiler(target): result.AppendMessage("Not a LLGo compiled binary.") @@ -74,7 +95,7 @@ def print_all_variables(debugger, command, result, _internal_dict): ).GetSelectedThread().GetSelectedFrame() variables = frame.GetVariables(True, True, True, False) - output = [] + output: List[str] = [] for var in variables: type_name = map_type_name(var.GetType().GetName()) formatted = format_value(var, debugger, include_type=False, indent=0) @@ -83,14 +104,12 @@ def print_all_variables(debugger, command, result, _internal_dict): result.AppendMessage("\n".join(output)) -def is_pointer(frame, var_name): +def is_pointer(frame: lldb.SBFrame, var_name: str) -> bool: var = frame.FindVariable(var_name) return var.IsValid() and var.GetType().IsPointerType() -# Format functions extracted from main.py - -def format_value(var, debugger, include_type=True, indent=0): +def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0) -> str: if not var.IsValid(): return "" @@ -120,10 +139,10 @@ def format_value(var, debugger, include_type=True, indent=0): return "" -def format_slice(var, debugger, indent): +def format_slice(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: length = int(var.GetChildMemberWithName('len').GetValue()) data_ptr = var.GetChildMemberWithName('data') - elements = [] + elements: List[str] = [] ptr_value = int(data_ptr.GetValue(), 16) element_type = data_ptr.GetType().GetPointeeType() @@ -152,8 +171,8 @@ def format_slice(var, debugger, indent): return result -def format_array(var, debugger, indent): - elements = [] +def format_array(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: + elements: List[str] = [] indent_str = ' ' * indent next_indent_str = ' ' * (indent + 1) @@ -166,13 +185,13 @@ def format_array(var, debugger, indent): element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) type_name = f"[{array_size}]{element_type}" - if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + if len(elements) > 5: # wrap line if too many elements return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" else: return f"{type_name}{{{', '.join(elements)}}}" -def format_string(var): +def format_string(var: lldb.SBValue) -> str: summary = var.GetSummary() if summary is not None: return summary # Keep the quotes @@ -185,8 +204,8 @@ def format_string(var): return '""' -def format_struct(var, debugger, include_type=True, indent=0, type_name=""): - children = [] +def format_struct(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0, type_name: str = "") -> str: + children: List[str] = [] indent_str = ' ' * indent next_indent_str = ' ' * (indent + 1) @@ -209,13 +228,13 @@ def format_struct(var, debugger, include_type=True, indent=0, type_name=""): return struct_content -def format_pointer(var, debugger, indent, type_name): +def format_pointer(var: lldb.SBValue, _debugger: lldb.SBDebugger, _indent: int, _type_name: str) -> str: if not var.IsValid() or var.GetValueAsUnsigned() == 0: return "" return var.GetValue() # Return the address as a string -def map_type_name(type_name): +def map_type_name(type_name: str) -> str: # Handle pointer types if type_name.endswith('*'): base_type = type_name[:-1].strip() @@ -223,7 +242,7 @@ def map_type_name(type_name): return f"*{mapped_base_type}" # Map other types - type_mapping = { + type_mapping: Dict[str, str] = { 'long': 'int', 'void': 'unsafe.Pointer', 'char': 'byte', diff --git a/_lldb/test.py b/_lldb/test.py index f7bfcab8..5eca33d0 100644 --- a/_lldb/test.py +++ b/_lldb/test.py @@ -7,17 +7,14 @@ import signal from dataclasses import dataclass, field from typing import List, Optional, Set, Dict, Any import lldb -import llgo_plugin # Add this import +import llgo_plugin +from llgo_plugin import log class LLDBTestException(Exception): pass -def log(*args: Any, **kwargs: Any) -> None: - print(*args, **kwargs, flush=True) - - @dataclass class Test: source_file: str @@ -60,7 +57,7 @@ class TestResults: class LLDBDebugger: - def __init__(self, executable_path: str, plugin_path: Optional[str] = None): + def __init__(self, executable_path: str, plugin_path: Optional[str] = None) -> None: self.executable_path: str = executable_path self.plugin_path: Optional[str] = plugin_path self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create() @@ -70,7 +67,6 @@ class LLDBDebugger: self.type_mapping: Dict[str, str] = { 'long': 'int', 'unsigned long': 'uint', - # Add more mappings as needed } def setup(self) -> None: @@ -104,25 +100,10 @@ class LLDBDebugger: def get_variable_value(self, var_expression: str) -> Optional[str]: frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - - parts = var_expression.split('.') - var = frame.FindVariable(parts[0]) - - for part in parts[1:]: - if not var.IsValid(): - return None - - if '[' in part and ']' in part: - array_name, index = part.split('[') - index = int(index.rstrip(']')) - var = var.GetChildAtIndex(index) - elif var.GetType().IsPointerType(): - var = var.Dereference() - var = var.GetChildMemberWithName(part) - else: - var = var.GetChildMemberWithName(part) - - return llgo_plugin.format_value(var, self.debugger) if var.IsValid() else None + value = llgo_plugin.evaluate_expression(frame, var_expression) + if value and value.IsValid(): + return llgo_plugin.format_value(value, self.debugger) + return None def get_all_variable_names(self) -> Set[str]: frame = self.process.GetSelectedThread().GetFrameAtIndex(0) @@ -189,7 +170,7 @@ class LLDBDebugger: def parse_expected_values(source_files: List[str]) -> List[TestCase]: - test_cases = [] + test_cases: List[TestCase] = [] for source_file in source_files: with open(source_file, 'r', encoding='utf-8') as f: content = f.readlines() @@ -198,7 +179,7 @@ def parse_expected_values(source_files: List[str]) -> List[TestCase]: line = content[i].strip() if line.startswith('// Expected:'): start_line = i + 1 - tests = [] + tests: List[Test] = [] i += 1 while i < len(content): line = content[i].strip() @@ -224,7 +205,8 @@ def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: boo debugger = LLDBDebugger(executable_path, plugin_path) try: if verbose: - log(f"Setting breakpoint at {test_case.source_file}: {test_case.end_line}") + log( + f"\nSetting breakpoint at {test_case.source_file}:{test_case.end_line}") debugger.setup() debugger.set_breakpoint(test_case.source_file, test_case.end_line) debugger.run_to_breakpoint() @@ -274,7 +256,7 @@ def run_tests(executable_path: str, source_files: List[str], verbose: bool, inte def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult: - results = [] + results: List[TestResult] = [] for test in test_case.tests: if test.variable == "all variables":