lldb: debug info test, lldb plugin, readme

This commit is contained in:
Li Jie
2024-09-21 00:08:44 +08:00
parent fb47ea301f
commit 12439f2b99
6 changed files with 125 additions and 239 deletions

View File

@@ -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 -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 /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 1: no locations (pending).
Breakpoint set in dummy target, will get copied into future targets. Breakpoint set in dummy target, will get copied into future targets.
(lldb) command script import _lldb/llgo_plugin.py (lldb) command script import _lldb/llgo_plugin.py
(lldb) target create "./cl/_testdata/debug/out" (lldb) target create "./cl/_testdata/debug/out"
Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64). Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64).
(lldb) r (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 globalInt: 301
s: 0x100123e40 s: 0x100123e40
0x100123be0 0x100123be0
@@ -32,7 +33,7 @@ called function with struct
called function with types called function with types
0x100123e40 0x100123e40
0x1000343d0 0x1000343d0
Process 16088 stopped Process 21992 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010001b3b4 out`main at in.go:225:12 frame #0: 0x000000010001b3b4 out`main at in.go:225:12
222 // s.i8: '\x01' 222 // s.i8: '\x01'
@@ -45,80 +46,68 @@ Process 16088 stopped
(lldb) v (lldb) v
var i int = <variable not available> var i int = <variable not available>
var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = {
i8: '\x12', i8 = '\x12',
i16: 2, i16 = 2,
i32: 3, i32 = 3,
i64: 4, i64 = 4,
i: 5, i = 5,
u8: '\x06', u8 = '\x06',
u16: 7, u16 = 7,
u32: 8, u32 = 8,
u64: 9, u64 = 9,
u: 10, u = 10,
f32: 11, f32 = 11,
f64: 12, f64 = 12,
b: true, b = true,
c64: { real: 13, imag: 14 }, c64 = {real = 13, imag = 14},
c128: { real: 15, imag: 16 }, c128 = {real = 15, imag = 16},
slice: []int{ slice = []int{21, 22, 23},
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}},
arr: [3]int{ s = "hello",
24, 25, 26, e = {i = 30},
}, pf = 0x0000000100123d10,
arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{ pi = 0x00000001001149e0,
{ i: 27 }, { i: 28 }, { i: 29 }, intr = {type = 0x0000000100116810, data = 0x00000001001149d0},
}, m = {count = 4296130304},
s: "hello", c = {},
e: { i: 30 }, err = {type = 0x0000000100116840, data = 0x0000000100112940},
pf: 0x0000000100123d10, fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0},
pi: 0x00000001001149e0, pad1 = 100,
intr: { type: 0x0000000100116810, data: 0x00000001001149d0 }, pad2 = 200
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 = <variable not available> var globalStructPtr *github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = <variable not available>
var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = {
i8: '\x01', i8 = '\x01',
i16: 2, i16 = 2,
i32: 3, i32 = 3,
i64: 4, i64 = 4,
i: 5, i = 5,
u8: '\x06', u8 = '\x06',
u16: 7, u16 = 7,
u32: 8, u32 = 8,
u64: 9, u64 = 9,
u: 10, u = 10,
f32: 11, f32 = 11,
f64: 12, f64 = 12,
b: true, b = true,
c64: { real: 13, imag: 14 }, c64 = {real = 13, imag = 14},
c128: { real: 15, imag: 16 }, c128 = {real = 15, imag = 16},
slice: []int{ slice = []int{21, 22, 23},
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}},
arr: [3]int{ s = "hello",
24, 25, 26, e = {i = 30},
}, pf = 0x0000000100123d10,
arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{ pi = 0x00000001001149e0,
{ i: 27 }, { i: 28 }, { i: 29 }, intr = {type = 0x0000000100116810, data = 0x00000001001149d0},
}, m = {count = 4296130304},
s: "hello", c = {},
e: { i: 30 }, err = {type = 0x0000000100116840, data = 0x0000000100112940},
pf: 0x0000000100123d10, fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0},
pi: 0x00000001001149e0, pad1 = 100,
intr: { type: 0x0000000100116810, data: 0x00000001001149d0 }, pad2 = 200
m: { count: 4296130304 },
c: { },
err: { type: 0x0000000100116840, data: 0x0000000100112940 },
fn: { f: 0x000000010001b4a4, data: 0x00000001001149c0 },
pad1: 100,
pad2: 200,
} }
var globalInt int = 301 var globalInt int = 301
var err error = { type: 0x0000000100112900, data: 0x000000000000001a } var err error = {type = 0x0000000100112900, data = 0x000000000000001a}
``` ```

View File

@@ -5,8 +5,6 @@ import lldb
def __lldb_init_module(debugger, _): def __lldb_init_module(debugger, _):
debugger.HandleCommand(
'command script add -f llgo_plugin.format_go_variable gv')
debugger.HandleCommand( debugger.HandleCommand(
'command script add -f llgo_plugin.print_go_expression p') 'command script add -f llgo_plugin.print_go_expression p')
debugger.HandleCommand( debugger.HandleCommand(
@@ -44,23 +42,6 @@ def is_llgo_compiler(target):
return False 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): def print_go_expression(debugger, command, result, _internal_dict):
target = debugger.GetSelectedTarget() target = debugger.GetSelectedTarget()
if not is_llgo_compiler(target): if not is_llgo_compiler(target):
@@ -149,6 +130,9 @@ def format_slice(var, debugger, indent):
element_size = element_type.GetByteSize() element_size = element_type.GetByteSize()
target = debugger.GetSelectedTarget() target = debugger.GetSelectedTarget()
indent_str = ' ' * indent
next_indent_str = ' ' * (indent + 1)
for i in range(length): for i in range(length):
element_address = ptr_value + i * element_size element_address = ptr_value + i * element_size
element = target.CreateValueFromAddress( element = target.CreateValueFromAddress(
@@ -158,45 +142,40 @@ def format_slice(var, debugger, indent):
elements.append(value) elements.append(value)
type_name = var.GetType().GetName() 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 if len(elements) > 5: # 如果元素数量大于5则进行折行显示
result = f"{type_name}{{\n{next_indent_str}{', '.join(elements[:3])},\n{ result = f"{type_name}{{\n{next_indent_str}" + \
next_indent_str}...,\n{next_indent_str}{', '.join(elements[-2:])}\n{indent_str}}}" f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}"
else: else:
result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)}\n{ result = f"{type_name}{{{', '.join(elements)}}}"
indent_str}}}"
return result return result
def format_array(var, debugger, indent): def format_array(var, debugger, indent):
elements = [] elements = []
indent_str = ' ' * indent
next_indent_str = ' ' * (indent + 1)
for i in range(var.GetNumChildren()): for i in range(var.GetNumChildren()):
value = format_value(var.GetChildAtIndex( value = format_value(var.GetChildAtIndex(
i), debugger, include_type=False, indent=indent+1) i), debugger, include_type=False, indent=indent+1)
elements.append(value) elements.append(value)
array_size = var.GetNumChildren() array_size = var.GetNumChildren()
element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) element_type = map_type_name(var.GetType().GetArrayElementType().GetName())
type_name = f"[{array_size}]{element_type}" 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 if len(elements) > 5: # 如果元素数量大于5则进行折行显示
result = f"{type_name}{{\n{next_indent_str}{', '.join(elements[:3])},\n{ return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}"
next_indent_str}...,\n{next_indent_str}{', '.join(elements[-2:])},\n{indent_str}}}"
else: else:
result = f"{type_name}{{\n{next_indent_str}{', '.join(elements)},\n{ return f"{type_name}{{{', '.join(elements)}}}"
indent_str}}}"
return result
def format_string(var): def format_string(var):
summary = var.GetSummary() summary = var.GetSummary()
if summary is not None: if summary is not None:
return summary return summary # Keep the quotes
else: else:
data = var.GetChildMemberWithName('data').GetValue() data = var.GetChildMemberWithName('data').GetValue()
length = int(var.GetChildMemberWithName('len').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()): for i in range(var.GetNumChildren()):
child = var.GetChildAtIndex(i) child = var.GetChildAtIndex(i)
child_name = child.GetName() child_name = child.GetName()
child_type = map_type_name(child.GetType().GetName())
child_value = format_value( child_value = format_value(
child, debugger, include_type=False, indent=indent+1) child, debugger, include_type=False, indent=indent+1)
children.append(f"{child_name} = {child_value}")
if '\n' in child_value or var.GetNumChildren() > 3: if len(children) > 5: # 如果字段数量大于5则进行折行显示
children.append(f"{next_indent_str}{child_name}: {child_value},") struct_content = "{\n" + ",\n".join(
else: [f"{next_indent_str}{child}" for child in children]) + f"\n{indent_str}}}"
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: else:
struct_content = "{\n" + "\n".join(children) + "\n" + indent_str + "}" struct_content = f"{{{', '.join(children)}}}"
if include_type: if include_type:
return f"{type_name}{struct_content}" 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): def format_pointer(var, debugger, indent, type_name):
if not var.IsValid() or var.GetValueAsUnsigned() == 0: if not var.IsValid() or var.GetValueAsUnsigned() == 0:
return "<variable not available>" return "<variable not available>"
pointee = var.Dereference() return var.GetValue() # Return the address as a string
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): def map_type_name(type_name):

6
_lldb/runlldb.sh Executable file
View File

@@ -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

7
_lldb/runtest.sh Executable file
View File

@@ -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

View File

@@ -7,6 +7,7 @@ import signal
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List from typing import List
import lldb import lldb
import llgo_plugin # Add this import
class LLDBTestException(Exception): class LLDBTestException(Exception):
@@ -81,6 +82,11 @@ class LLDBDebugger:
raise LLDBTestException(f"Failed to create target for { raise LLDBTestException(f"Failed to create target for {
self.executable_path}") 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): def set_breakpoint(self, file_spec, line_number):
bp = self.target.BreakpointCreateByLocation( bp = self.target.BreakpointCreateByLocation(
file_spec, line_number) file_spec, line_number)
@@ -97,121 +103,30 @@ class LLDBDebugger:
if self.process.GetState() != lldb.eStateStopped: if self.process.GetState() != lldb.eStateStopped:
raise LLDBTestException("Process didn't stop at breakpoint") 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) frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
if isinstance(var_name, lldb.SBValue): # 处理结构体成员访问、指针解引用和数组索引
var = var_name parts = var_expression.split('.')
else: var = frame.FindVariable(parts[0])
# process struct field access
parts = var_name.split('.') for part in parts[1:]:
if len(parts) > 1: if not var.IsValid():
var = frame.FindVariable(parts[0]) return None
for part in parts[1:]:
if var.IsValid(): # 处理数组索引
var = var.GetChildMemberWithName(part) if '[' in part and ']' in part:
else: array_name, index = part.split('[')
return None index = int(index.rstrip(']'))
var = var.GetChildAtIndex(index)
# 处理指针解引用
elif var.GetType().IsPointerType():
var = var.Dereference()
var = var.GetChildMemberWithName(part)
else: else:
actual_var_name = var_name.split('=')[0].strip() var = var.GetChildMemberWithName(part)
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) if var.IsValid() else None return llgo_plugin.format_value(var, self.debugger) 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
def get_all_variable_names(self): def get_all_variable_names(self):
frame = self.process.GetSelectedThread().GetFrameAtIndex(0) frame = self.process.GetSelectedThread().GetFrameAtIndex(0)

View File

@@ -74,7 +74,7 @@ func FuncWithAllTypeStructParam(s StructWithAllTypeFields) {
// s.slice: []int{21, 22, 23} // s.slice: []int{21, 22, 23}
// s.arr: [3]int{24, 25, 26} // 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.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.e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30}
// s.pad1: 100 // s.pad1: 100
// s.pad2: 200 // s.pad2: 200
@@ -147,7 +147,7 @@ func FuncWithAllTypeParams(
// slice: []int{21, 22, 23} // slice: []int{21, 22, 23}
// arr: [3]int{24, 25, 26} // arr: [3]int{24, 25, 26}
// arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}} // 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} // e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30}
i8 = 9 i8 = 9
// Expected: // Expected: