From 98c628f3eb7eb3485696e4d83253f7000eaec87c Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 19 Sep 2024 21:06:45 +0800 Subject: [PATCH] lldb test: fix formatting --- _lldbtest/main.py | 189 +++++++++++++++++++++++++++++++++------ cl/_testdata/debug/in.go | 6 +- 2 files changed, 166 insertions(+), 29 deletions(-) diff --git a/_lldbtest/main.py b/_lldbtest/main.py index 64fe68ed..90e60bc9 100644 --- a/_lldbtest/main.py +++ b/_lldbtest/main.py @@ -9,9 +9,11 @@ import cmd sys.stdout = io.TextIOWrapper(sys.stdout.buffer, line_buffering=False) + def print_to_lldb(*args, **kwargs): print(*args, **kwargs, flush=True) + @dataclass class Test: source_file: str @@ -19,6 +21,7 @@ class Test: variable: str expected_value: str + @dataclass class TestResult: test: Test @@ -28,6 +31,7 @@ class TestResult: missing: set = None extra: set = None + @dataclass class TestCase: source_file: str @@ -35,12 +39,14 @@ class TestCase: end_line: int tests: List[Test] + @dataclass class CaseResult: test_case: TestCase function: str results: List[TestResult] + @dataclass class TestResults: total: int = 0 @@ -48,6 +54,7 @@ class TestResults: failed: int = 0 case_results: List[CaseResult] = field(default_factory=list) + class LLDBDebugger: def __init__(self, executable_path, plugin_path=None): self.executable_path = executable_path @@ -56,19 +63,28 @@ class LLDBDebugger: self.debugger.SetAsync(False) self.target = None self.process = None + self.type_mapping = { + 'long': 'int', + 'unsigned long': 'uint', + # Add more mappings as needed + } def setup(self): if self.plugin_path: - self.debugger.HandleCommand(f'command script import "{self.plugin_path}"') + self.debugger.HandleCommand( + f'command script import "{self.plugin_path}"') self.target = self.debugger.CreateTarget(self.executable_path) if not self.target: - raise Exception(f"Failed to create target for {self.executable_path}") + raise Exception(f"Failed to create target for { + self.executable_path}") def set_breakpoint(self, file_spec, line_number): - breakpoint = self.target.BreakpointCreateByLocation(file_spec, line_number) + breakpoint = self.target.BreakpointCreateByLocation( + file_spec, line_number) if not breakpoint.IsValid(): - raise Exception(f"Failed to set breakpoint at {file_spec}:{line_number}") + raise Exception(f"Failed to set breakpoint at { + file_spec}:{line_number}") return breakpoint def run_to_breakpoint(self): @@ -82,21 +98,128 @@ class LLDBDebugger: def get_variable_value(self, var_name): frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - var = frame.FindVariable(var_name) + + if isinstance(var_name, lldb.SBValue): + var = var_name + else: + actual_var_name = var_name.split('=')[0].strip() + if '(' in actual_var_name: + actual_var_name = actual_var_name.split('(')[-1].strip() + var = frame.FindVariable(actual_var_name) + + return self.format_value(var) + + def format_value(self, var): if var.IsValid(): type_name = var.GetTypeName() - if var.GetNumChildren() > 0: - children = [] - for i in range(var.GetNumChildren()): - child = var.GetChildAtIndex(i) - child_name = child.GetName() - child_value = child.GetValue() - children.append(f"{child_name} = {child_value}") - return f"{type_name}({', '.join(children)})" + var_type = var.GetType() + type_class = var_type.GetTypeClass() + + if type_name.startswith('[]'): # Slice + return self.format_slice(var) + elif var_type.IsArrayType(): + if type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: + return self.format_custom_array(var) + else: + return self.format_array(var) + elif type_name == 'string': # String + return self.format_string(var) + elif type_name in ['complex64', 'complex128']: + return self.format_complex(var) + elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: + return self.format_struct(var) else: - value = var.GetValue() or var.GetSummary() or str(var) - return str(value) if value is not None else "None" - return None + value = var.GetValue() + summary = var.GetSummary() + if value is not None: + return str(value) + elif summary is not None: + return summary + else: + return "None" + return "None" + + def format_slice(self, var): + length = int(var.GetChildMemberWithName('len').GetValue()) + data_ptr = var.GetChildMemberWithName('data') + elements = [] + + # Get the actual pointer value + ptr_value = int(data_ptr.GetValue(), 16) + element_type = data_ptr.GetType().GetPointeeType() + element_size = element_type.GetByteSize() + + for i in range(length): + element_address = ptr_value + i * element_size + element = self.target.CreateValueFromAddress( + f"element_{i}", lldb.SBAddress(element_address, self.target), element_type) + value = self.format_value(element) + elements.append(value) + + type_name = var.GetType().GetName().split( + '[]')[-1] # Extract element type from slice type + type_name = self.type_mapping.get(type_name, type_name) # Use mapping + result = f"[]{type_name}[{', '.join(elements)}]" + return result + + def format_array(self, var): + elements = [] + for i in range(var.GetNumChildren()): + value = self.format_value(var.GetChildAtIndex(i)) + elements.append(value) + array_size = var.GetNumChildren() + type_name = var.GetType().GetArrayElementType().GetName() + type_name = self.type_mapping.get(type_name, type_name) # Use mapping + return f"[{array_size}]{type_name}[{', '.join(elements)}]" + + def format_custom_array(self, var): + elements = [] + for i in range(var.GetNumChildren()): + element = var.GetChildAtIndex(i) + formatted = self.format_struct(element, include_type=False) + elements.append(formatted) + array_size = var.GetNumChildren() + type_name = var.GetType().GetArrayElementType().GetName() + return f"[{array_size}]{type_name}[{', '.join(elements)}]" + + def format_pointer(self, var): + target = var.Dereference() + if target.IsValid(): + return f"*{self.get_variable_value(target.GetName())}" + else: + return str(var.GetValue()) + + def format_string(self, var): + summary = var.GetSummary() + if summary is not None: + return summary.strip('"') + else: + data = var.GetChildMemberWithName('data').GetValue() + length = int(var.GetChildMemberWithName('len').GetValue()) + if data and length: + error = lldb.SBError() + return self.process.ReadCStringFromMemory(int(data, 16), length + 1, error) + return "None" + + def format_struct(self, var, include_type=True): + children = [] + for i in range(var.GetNumChildren()): + child = var.GetChildAtIndex(i) + child_name = child.GetName() + child_value = self.format_value(child) + children.append(f"{child_name} = {child_value}") + + struct_content = f"({', '.join(children)})" + if include_type: + struct_name = var.GetTypeName() + return f"{struct_name}{struct_content}" + else: + return struct_content + + def format_complex(self, var): + real = var.GetChildMemberWithName('real').GetValue() + imag = var.GetChildMemberWithName('imag').GetValue() + return f"{var.GetTypeName()}(real = {real}, imag = {imag})" def get_all_variable_names(self): frame = self.process.GetSelectedThread().GetFrameAtIndex(0) @@ -112,7 +235,8 @@ class LLDBDebugger: lldb.SBDebugger.Destroy(self.debugger) def run_console(self): - print_to_lldb("\nEntering LLDB interactive mode. Type 'quit' to exit and continue with the next test case.") + print_to_lldb( + "\nEntering LLDB interactive mode. Type 'quit' to exit and continue with the next test case.") old_stdin, old_stdout, old_stderr = sys.stdin, sys.stdout, sys.stderr sys.stdin, sys.stdout, sys.stderr = sys.__stdin__, sys.__stdout__, sys.__stderr__ @@ -211,13 +335,15 @@ def execute_tests(debugger, test_cases, interactive): results.case_results.append(case_result) # Print the current case test results before entering interactive mode - print_to_lldb(f"\nTest case: {case_result.test_case.source_file}:{case_result.test_case.start_line}-{case_result.test_case.end_line} in function '{case_result.function}'") + print_to_lldb(f"\nTest case: {case_result.test_case.source_file}:{ + case_result.test_case.start_line}-{case_result.test_case.end_line} in function '{case_result.function}'") for result in case_result.results: # Always print verbose results here print_test_result(result, True) if interactive and any(r.status != 'pass' for r in case_result.results): - print_to_lldb("\nTest case failed. Entering LLDB interactive mode.") + print_to_lldb( + "\nTest case failed. Entering LLDB interactive mode.") debugger.run_console() # After exiting the console, we need to ensure the process is in a valid state if debugger.process.GetState() == lldb.eStateRunning: @@ -294,7 +420,8 @@ def execute_single_variable_test(debugger, test): def print_test_results(results: TestResults, verbose): for case_result in results.case_results: - print_to_lldb(f"\nTest case: {case_result.test_case.source_file}:{case_result.test_case.start_line}-{case_result.test_case.end_line} in function '{case_result.function}'") + print_to_lldb(f"\nTest case: {case_result.test_case.source_file}:{ + case_result.test_case.start_line}-{case_result.test_case.end_line} in function '{case_result.function}'") for result in case_result.results: print_test_result(result, verbose) @@ -317,7 +444,8 @@ def print_test_result(result: TestResult, verbose): print_to_lldb( f"{status_symbol} Line {result.test.line_number}, {result.test.variable}: {status_text}") if result.test.variable == 'all variables': - print_to_lldb(f" Variables: {', '.join(sorted(result.actual))}") + print_to_lldb(f" Variables: { + ', '.join(sorted(result.actual))}") else: # fail or error print_to_lldb( f"{status_symbol} Line {result.test.line_number}, {result.test.variable}: {status_text}") @@ -337,27 +465,36 @@ def print_test_result(result: TestResult, verbose): print_to_lldb(f" Expected: {result.test.expected_value}") print_to_lldb(f" Actual: {result.actual}") + def main(): print_to_lldb(sys.argv) - parser = argparse.ArgumentParser(description="LLDB 18 Debug Script with DWARF 5 Support") + parser = argparse.ArgumentParser( + description="LLDB 18 Debug Script with DWARF 5 Support") parser.add_argument("executable", help="Path to the executable") parser.add_argument("sources", nargs='+', help="Paths to the source files") - parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output") - parser.add_argument("-i", "--interactive", action="store_true", help="Enable interactive mode on test failure") + parser.add_argument("-v", "--verbose", action="store_true", + help="Enable verbose output") + parser.add_argument("-i", "--interactive", action="store_true", + help="Enable interactive mode on test failure") parser.add_argument("--plugin", help="Path to the LLDB plugin") args = parser.parse_args() - plugin_path = args.plugin or os.path.join(os.path.dirname(os.path.realpath(__file__)), "go_lldb_plugin.py") - run_tests(args.executable, args.sources, args.verbose, args.interactive, plugin_path) + plugin_path = args.plugin or os.path.join(os.path.dirname( + os.path.realpath(__file__)), "go_lldb_plugin.py") + run_tests(args.executable, args.sources, + args.verbose, args.interactive, plugin_path) + if __name__ == "__main__": main() + def run_commands(debugger, command, result, internal_dict): print_to_lldb(sys.argv) main() debugger.HandleCommand("quit") + def __lldb_init_module(debugger, internal_dict): # debugger.HandleCommand('command script add -f main.run_commands run_tests') pass diff --git a/cl/_testdata/debug/in.go b/cl/_testdata/debug/in.go index 89b83274..6fc37b70 100644 --- a/cl/_testdata/debug/in.go +++ b/cl/_testdata/debug/in.go @@ -118,10 +118,10 @@ func FuncWithAllTypeParams( // c64: complex64(real = 13, imag = 14) // c128: complex128(real = 15, imag = 16) // slice: []int[21, 22, 23] - // arr: [3]int[24, 25, 26) - // arr2: [3]E[(i = 27), (i = 28), (i = 29)] + // arr: [3]int[24, 25, 26] + // arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E[github.com/goplus/llgo/cl/_testdata/debug.E(i = 27), github.com/goplus/llgo/cl/_testdata/debug.E(i = 28), github.com/goplus/llgo/cl/_testdata/debug.E(i = 29)] // s: hello - // e: (i = 30) + // e: github.com/goplus/llgo/cl/_testdata/debug.E(i = 30) return 1, errors.New("some error") }