diff --git a/_lldb/README.md b/_lldb/README.md new file mode 100644 index 00000000..4cd73757 --- /dev/null +++ b/_lldb/README.md @@ -0,0 +1,124 @@ +## LLGo Plugin of LLDB + +### Build with debug info + +```shell +llgo build -o cl/_testdata/debug/out -dbg ./cl/_testdata/debug +``` + +### Debug with lldb + +```shell +lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out +``` + +```lldb +/opt/homebrew/bin/lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out +Breakpoint 1: no locations (pending). +Breakpoint set in dummy target, will get copied into future targets. +(lldb) command script import _lldb/llgo_plugin.py +(lldb) target create "./cl/_testdata/debug/out" +Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64). +(lldb) r +Process 16088 launched: '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64) +globalInt: 301 +s: 0x100123e40 +0x100123be0 +5 8 +called function with struct +1 2 3 4 5 6 7 8 9 10 +1.100000e+01 +1.200000e+01 true (+1.300000e+01+1.400000e+01i) (+1.500000e+01+1.600000e+01i) [3/3]0x1001129a0 [3/3]0x100112920 hello 0x1001149b0 0x100123ab0 0x100123d10 0x1001149e0 (0x100116810,0x1001149d0) 0x10011bf00 0x10010fa80 (0x100116840,0x100112940) 0x10001b4a4 +9 +1 (0x1001167e0,0x100112900) +called function with types +0x100123e40 +0x1000343d0 +Process 16088 stopped +* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 + frame #0: 0x000000010001b3b4 out`main at in.go:225:12 + 222 // s.i8: '\x01' + 223 // s.i16: 2 + 224 s.i8 = 0x12 +-> 225 println(s.i8) + 226 // Expected: + 227 // all variables: globalInt globalStruct globalStructPtr s i err + 228 // s.i8: '\x12' +(lldb) v +var i int = +var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { + i8: '\x12', + i16: 2, + i32: 3, + i64: 4, + i: 5, + u8: '\x06', + u16: 7, + u32: 8, + u64: 9, + u: 10, + f32: 11, + f64: 12, + b: true, + c64: { real: 13, imag: 14 }, + c128: { real: 15, imag: 16 }, + slice: []int{ + 21, 22, 23 + }, + arr: [3]int{ + 24, 25, 26, + }, + arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{ + { i: 27 }, { i: 28 }, { i: 29 }, + }, + s: "hello", + e: { i: 30 }, + pf: 0x0000000100123d10, + pi: 0x00000001001149e0, + intr: { type: 0x0000000100116810, data: 0x00000001001149d0 }, + m: { count: 4296130304 }, + c: { }, + err: { type: 0x0000000100116840, data: 0x0000000100112940 }, + fn: { f: 0x000000010001b4a4, data: 0x00000001001149c0 }, + pad1: 100, + pad2: 200, +} +var globalStructPtr *github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = +var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { + i8: '\x01', + i16: 2, + i32: 3, + i64: 4, + i: 5, + u8: '\x06', + u16: 7, + u32: 8, + u64: 9, + u: 10, + f32: 11, + f64: 12, + b: true, + c64: { real: 13, imag: 14 }, + c128: { real: 15, imag: 16 }, + slice: []int{ + 21, 22, 23 + }, + arr: [3]int{ + 24, 25, 26, + }, + arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{ + { i: 27 }, { i: 28 }, { i: 29 }, + }, + s: "hello", + e: { i: 30 }, + pf: 0x0000000100123d10, + pi: 0x00000001001149e0, + intr: { type: 0x0000000100116810, data: 0x00000001001149d0 }, + m: { count: 4296130304 }, + c: { }, + err: { type: 0x0000000100116840, data: 0x0000000100112940 }, + fn: { f: 0x000000010001b4a4, data: 0x00000001001149c0 }, + pad1: 100, + pad2: 200, +} +var globalInt int = 301 +var err error = { type: 0x0000000100112900, data: 0x000000000000001a } +``` diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py new file mode 100644 index 00000000..db6e2c4c --- /dev/null +++ b/_lldb/llgo_plugin.py @@ -0,0 +1,277 @@ +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring + +import re +import lldb + + +def __lldb_init_module(debugger, _): + debugger.HandleCommand( + 'command script add -f llgo_plugin.format_go_variable gv') + 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): + 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 format_go_variable(debugger, command, result, _internal_dict): + target = debugger.GetSelectedTarget() + if not is_llgo_compiler(target): + result.AppendMessage("Not a LLGo compiled binary.") + return + + frame = debugger.GetSelectedTarget().GetProcess( + ).GetSelectedThread().GetSelectedFrame() + var = frame.EvaluateExpression(command) + + if var.error.Success(): + formatted = format_value(var, debugger) + result.AppendMessage(formatted) + else: + result.AppendMessage(f"Error: {var.error}") + + +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 + + 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) + else: + result.AppendMessage(f"Error: {var.error}") + + +def print_all_variables(debugger, command, result, _internal_dict): + target = debugger.GetSelectedTarget() + if not is_llgo_compiler(target): + result.AppendMessage("Not a LLGo compiled binary.") + return + + frame = debugger.GetSelectedTarget().GetProcess( + ).GetSelectedThread().GetSelectedFrame() + variables = frame.GetVariables(True, True, True, False) + + output = [] + for var in variables: + type_name = map_type_name(var.GetType().GetName()) + formatted = format_value(var, debugger, include_type=False, indent=0) + output.append(f"var {var.GetName()} {type_name} = {formatted}") + + result.AppendMessage("\n".join(output)) + + +def is_pointer(frame, var_name): + 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): + if not var.IsValid(): + return "" + + var_type = var.GetType() + type_class = var_type.GetTypeClass() + type_name = map_type_name(var_type.GetName()) + + if var_type.IsPointerType(): + return format_pointer(var, debugger, indent, type_name) + + if type_name.startswith('[]'): # Slice + return format_slice(var, debugger, indent) + elif var_type.IsArrayType(): + return format_array(var, debugger, indent) + elif type_name == 'string': # String + return format_string(var) + elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: + return format_struct(var, debugger, include_type, indent, type_name) + else: + value = var.GetValue() + summary = var.GetSummary() + if value is not None: + return f"{value}" if include_type else str(value) + elif summary is not None: + return f"{summary}" if include_type else summary + else: + return "" + + +def format_slice(var, debugger, indent): + length = int(var.GetChildMemberWithName('len').GetValue()) + data_ptr = var.GetChildMemberWithName('data') + elements = [] + + ptr_value = int(data_ptr.GetValue(), 16) + element_type = data_ptr.GetType().GetPointeeType() + element_size = element_type.GetByteSize() + + target = debugger.GetSelectedTarget() + for i in range(length): + element_address = ptr_value + i * element_size + element = target.CreateValueFromAddress( + f"element_{i}", lldb.SBAddress(element_address, target), element_type) + value = format_value( + element, debugger, include_type=False, indent=indent+1) + elements.append(value) + + type_name = var.GetType().GetName() + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + if len(elements) > 5: # For long slices, print only first and last few elements + result = f"{type_name}{{\n{next_indent_str}{', '.join(elements[:3])},\n{ + next_indent_str}...,\n{next_indent_str}{', '.join(elements[-2:])}\n{indent_str}}}" + else: + result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)}\n{ + indent_str}}}" + + return result + + +def format_array(var, debugger, indent): + elements = [] + for i in range(var.GetNumChildren()): + value = format_value(var.GetChildAtIndex( + i), debugger, include_type=False, indent=indent+1) + elements.append(value) + array_size = var.GetNumChildren() + element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) + type_name = f"[{array_size}]{element_type}" + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + if len(elements) > 5: # For long arrays, print only first and last few elements + result = f"{type_name}{{\n{next_indent_str}{', '.join(elements[:3])},\n{ + next_indent_str}...,\n{next_indent_str}{', '.join(elements[-2:])},\n{indent_str}}}" + else: + result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)},\n{ + indent_str}}}" + + return result + + +def format_string(var): + summary = var.GetSummary() + if summary is not None: + return summary + else: + data = var.GetChildMemberWithName('data').GetValue() + length = int(var.GetChildMemberWithName('len').GetValue()) + if data and length: + error = lldb.SBError() + return '"%s"' % var.process.ReadCStringFromMemory(int(data, 16), length + 1, error) + return '""' + + +def format_struct(var, debugger, include_type=True, indent=0, type_name=""): + children = [] + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + for i in range(var.GetNumChildren()): + child = var.GetChildAtIndex(i) + child_name = child.GetName() + child_type = map_type_name(child.GetType().GetName()) + child_value = format_value( + child, debugger, include_type=False, indent=indent+1) + + if '\n' in child_value or var.GetNumChildren() > 3: + children.append(f"{next_indent_str}{child_name}: {child_value},") + else: + children.append(f"{child_name}: {child_value}") + + if var.GetNumChildren() <= 3 and all('\n' not in child for child in children): + struct_content = f"{{ {', '.join(children)} }}" + else: + struct_content = "{\n" + "\n".join(children) + "\n" + indent_str + "}" + + if include_type: + return f"{type_name}{struct_content}" + else: + return struct_content + + +def format_pointer(var, debugger, indent, type_name): + if not var.IsValid() or var.GetValueAsUnsigned() == 0: + return "" + pointee = var.Dereference() + if pointee.IsValid(): + pointee_value = format_value( + pointee, debugger, include_type=False, indent=indent) + return f"{var.GetValue()}" + else: + return f"{var.GetValue()}" + + +def map_type_name(type_name): + # Handle pointer types + if type_name.endswith('*'): + base_type = type_name[:-1].strip() + mapped_base_type = map_type_name(base_type) + return f"*{mapped_base_type}" + + # Map other types + type_mapping = { + 'long': 'int', + 'void': 'unsafe.Pointer', + 'char': 'byte', + 'short': 'int16', + 'int': 'int32', + 'long long': 'int64', + 'unsigned char': 'uint8', + 'unsigned short': 'uint16', + 'unsigned int': 'uint32', + 'unsigned long': 'uint', + 'unsigned long long': 'uint64', + 'float': 'float32', + 'double': 'float64', + } + + for c_type, go_type in type_mapping.items(): + if type_name.startswith(c_type): + return type_name.replace(c_type, go_type, 1) + + return type_name diff --git a/_lldbtest/runmain.lldb b/_lldb/runtest.lldb similarity index 81% rename from _lldbtest/runmain.lldb rename to _lldb/runtest.lldb index de8abff9..520a54cb 100644 --- a/_lldbtest/runmain.lldb +++ b/_lldb/runtest.lldb @@ -2,8 +2,8 @@ # See https://github.com/llvm/llvm-project/issues/70453 # go run ./cmd/llgo build -o cl/_testdata/debug/out -dbg ./cl/_testdata/debug -# lldb -S _lldbtest/runmain.lldb +# lldb -S _lldb/runtest.lldb -command script import _lldbtest/main.py +command script import _lldb/test.py script main.run_tests("cl/_testdata/debug/out", ["cl/_testdata/debug/in.go"], True, False, None) quit diff --git a/_lldbtest/main.py b/_lldb/test.py similarity index 98% rename from _lldbtest/main.py rename to _lldb/test.py index 3c639f6e..7dd045d6 100644 --- a/_lldbtest/main.py +++ b/_lldb/test.py @@ -361,8 +361,6 @@ def run_tests(executable_path, source_files, verbose, interactive, plugin_path): results = execute_tests(executable_path, test_cases, verbose, interactive, plugin_path) - if not interactive: - print_test_results(results, verbose) if results.total != results.passed: os._exit(1) @@ -498,3 +496,9 @@ def main(): if __name__ == "__main__": main() + + +def __lldb_init_module(debugger, _internal_dict): + run_tests("cl/_testdata/debug/out", + ["cl/_testdata/debug/in.go"], True, False, None) + debugger.HandleCommand('quit')