diff --git a/_lldb/README.md b/_lldb/README.md index 4cd73757..bee02779 100644 --- a/_lldb/README.md +++ b/_lldb/README.md @@ -12,15 +12,16 @@ llgo build -o cl/_testdata/debug/out -dbg ./cl/_testdata/debug lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out ``` -```lldb +```shell /opt/homebrew/bin/lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out +# github.com/goplus/llgo/cl/_testdata/debug 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) +Process 21992 launched: '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64) globalInt: 301 s: 0x100123e40 0x100123be0 @@ -32,7 +33,7 @@ called function with struct called function with types 0x100123e40 0x1000343d0 -Process 16088 stopped +Process 21992 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' @@ -45,80 +46,68 @@ Process 16088 stopped (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, + 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, + 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 } +var err error = {type = 0x0000000100112900, data = 0x000000000000001a} ``` diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py index db6e2c4c..d45a1a5d 100644 --- a/_lldb/llgo_plugin.py +++ b/_lldb/llgo_plugin.py @@ -5,8 +5,6 @@ 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( @@ -44,23 +42,6 @@ def is_llgo_compiler(target): 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): @@ -149,6 +130,9 @@ def format_slice(var, debugger, indent): element_size = element_type.GetByteSize() target = debugger.GetSelectedTarget() + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + for i in range(length): element_address = ptr_value + i * element_size element = target.CreateValueFromAddress( @@ -158,45 +142,40 @@ def format_slice(var, debugger, indent): 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}}}" + if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + result = f"{type_name}{{\n{next_indent_str}" + \ + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" else: - result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)}\n{ - indent_str}}}" + result = f"{type_name}{{{', '.join(elements)}}}" return result def format_array(var, debugger, indent): elements = [] + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + 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}}}" + if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" else: - result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)},\n{ - indent_str}}}" - - return result + return f"{type_name}{{{', '.join(elements)}}}" def format_string(var): summary = var.GetSummary() if summary is not None: - return summary + return summary # Keep the quotes else: data = var.GetChildMemberWithName('data').GetValue() length = int(var.GetChildMemberWithName('len').GetValue()) @@ -214,19 +193,15 @@ def format_struct(var, debugger, include_type=True, indent=0, type_name=""): 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) + children.append(f"{child_name} = {child_value}") - 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)} }}" + if len(children) > 5: # 如果字段数量大于5,则进行折行显示 + struct_content = "{\n" + ",\n".join( + [f"{next_indent_str}{child}" for child in children]) + f"\n{indent_str}}}" else: - struct_content = "{\n" + "\n".join(children) + "\n" + indent_str + "}" + struct_content = f"{{{', '.join(children)}}}" if include_type: return f"{type_name}{struct_content}" @@ -237,13 +212,7 @@ def format_struct(var, debugger, include_type=True, indent=0, type_name=""): 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()}" + return var.GetValue() # Return the address as a string def map_type_name(type_name): diff --git a/_lldb/runlldb.sh b/_lldb/runlldb.sh new file mode 100755 index 00000000..1dfb1fad --- /dev/null +++ b/_lldb/runlldb.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +go run ./cmd/llgo build -o ./cl/_testdata/debug/out -dbg ./cl/_testdata/debug/ +lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out diff --git a/_lldb/runtest.sh b/_lldb/runtest.sh new file mode 100755 index 00000000..08c642fd --- /dev/null +++ b/_lldb/runtest.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +go run ./cmd/llgo build -o ./cl/_testdata/debug/out -dbg ./cl/_testdata/debug/ + +/opt/homebrew/bin/lldb -S _lldb/runtest.lldb diff --git a/_lldb/test.py b/_lldb/test.py index 7dd045d6..67282973 100644 --- a/_lldb/test.py +++ b/_lldb/test.py @@ -7,6 +7,7 @@ import signal from dataclasses import dataclass, field from typing import List import lldb +import llgo_plugin # Add this import class LLDBTestException(Exception): @@ -81,6 +82,11 @@ class LLDBDebugger: raise LLDBTestException(f"Failed to create target for { self.executable_path}") + self.debugger.HandleCommand( + 'command script add -f llgo_plugin.print_go_expression p') + self.debugger.HandleCommand( + 'command script add -f llgo_plugin.print_all_variables v') + def set_breakpoint(self, file_spec, line_number): bp = self.target.BreakpointCreateByLocation( file_spec, line_number) @@ -97,121 +103,30 @@ class LLDBDebugger: if self.process.GetState() != lldb.eStateStopped: raise LLDBTestException("Process didn't stop at breakpoint") - def get_variable_value(self, var_name): + def get_variable_value(self, var_expression): frame = self.process.GetSelectedThread().GetFrameAtIndex(0) - if isinstance(var_name, lldb.SBValue): - var = var_name - else: - # process struct field access - parts = var_name.split('.') - if len(parts) > 1: - var = frame.FindVariable(parts[0]) - for part in parts[1:]: - if var.IsValid(): - var = var.GetChildMemberWithName(part) - else: - return None + # 处理结构体成员访问、指针解引用和数组索引 + 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: - 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) + var = var.GetChildMemberWithName(part) - return self.format_value(var) if var.IsValid() else None - - def format_value(self, var, include_type=True): - if var.IsValid(): - type_name = var.GetTypeName() - var_type = var.GetType() - type_class = var_type.GetTypeClass() - - if type_name.startswith('[]'): # Slice - return self.format_slice(var) - elif var_type.IsArrayType(): - return self.format_array(var) - elif type_name == 'string': # String - return self.format_string(var) - elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: - return self.format_struct(var, include_type) - else: - 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, include_type=False) - 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), include_type=False) - 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_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 + return llgo_plugin.format_value(var, self.debugger) if var.IsValid() else None def get_all_variable_names(self): frame = self.process.GetSelectedThread().GetFrameAtIndex(0) diff --git a/cl/_testdata/debug/in.go b/cl/_testdata/debug/in.go index 085cb229..3bc7c082 100644 --- a/cl/_testdata/debug/in.go +++ b/cl/_testdata/debug/in.go @@ -74,7 +74,7 @@ func FuncWithAllTypeStructParam(s StructWithAllTypeFields) { // s.slice: []int{21, 22, 23} // s.arr: [3]int{24, 25, 26} // s.arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}} - // s.s: hello + // s.s: "hello" // s.e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30} // s.pad1: 100 // s.pad2: 200 @@ -147,7 +147,7 @@ func FuncWithAllTypeParams( // 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 + // s: "hello" // e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30} i8 = 9 // Expected: