lldb: add llgo plugin
This commit is contained in:
124
_lldb/README.md
Normal file
124
_lldb/README.md
Normal file
@@ -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 = <variable not available>
|
||||||
|
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 = <variable not available>
|
||||||
|
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 }
|
||||||
|
```
|
||||||
277
_lldb/llgo_plugin.py
Normal file
277
_lldb/llgo_plugin.py
Normal file
@@ -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 "<variable not available>"
|
||||||
|
|
||||||
|
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 "<variable not available>"
|
||||||
|
|
||||||
|
|
||||||
|
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 "<variable not available>"
|
||||||
|
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
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
# See https://github.com/llvm/llvm-project/issues/70453
|
# See https://github.com/llvm/llvm-project/issues/70453
|
||||||
|
|
||||||
# go run ./cmd/llgo build -o cl/_testdata/debug/out -dbg ./cl/_testdata/debug
|
# 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)
|
script main.run_tests("cl/_testdata/debug/out", ["cl/_testdata/debug/in.go"], True, False, None)
|
||||||
quit
|
quit
|
||||||
@@ -361,8 +361,6 @@ def run_tests(executable_path, source_files, verbose, interactive, plugin_path):
|
|||||||
|
|
||||||
results = execute_tests(executable_path, test_cases, verbose,
|
results = execute_tests(executable_path, test_cases, verbose,
|
||||||
interactive, plugin_path)
|
interactive, plugin_path)
|
||||||
if not interactive:
|
|
||||||
print_test_results(results, verbose)
|
|
||||||
|
|
||||||
if results.total != results.passed:
|
if results.total != results.passed:
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
@@ -498,3 +496,9 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
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')
|
||||||
Reference in New Issue
Block a user