lldb: support expression includes dereference, parentheses

This commit is contained in:
Li Jie
2024-09-22 14:47:16 +08:00
parent 5dadf9a087
commit 379abeb262
2 changed files with 93 additions and 92 deletions

View File

@@ -1,70 +1,91 @@
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
from typing import List, Optional, Dict, Any, Tuple
import re import re
import lldb import lldb
def __lldb_init_module(debugger, _): def log(*args: Any, **kwargs: Any) -> None:
print(*args, **kwargs, flush=True)
def __lldb_init_module(debugger: lldb.SBDebugger, _: Dict[str, Any]) -> None:
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(
'command script add -f llgo_plugin.print_all_variables v') 'command script add -f llgo_plugin.print_all_variables v')
def is_llgo_compiler(target): def is_llgo_compiler(_target: lldb.SBTarget) -> bool:
return True 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 print_go_expression(debugger, command, result, _internal_dict): def evaluate_expression(frame: lldb.SBFrame, expression: str) -> Optional[lldb.SBValue]:
target = debugger.GetSelectedTarget() parts = re.findall(r'\*|\w+|\(|\)|\[.*?\]|\.', expression)
if not is_llgo_compiler(target):
result.AppendMessage("Not a LLGo compiled binary.")
return
def evaluate_part(i: int) -> Tuple[Optional[lldb.SBValue], int]:
nonlocal parts
value: Optional[lldb.SBValue] = None
while i < len(parts):
part = parts[i]
if part == '*':
sub_value, i = evaluate_part(i + 1)
if sub_value and sub_value.IsValid():
value = sub_value.Dereference()
else:
return None, i
elif part == '(':
depth = 1
j = i + 1
while j < len(parts) and depth > 0:
if parts[j] == '(':
depth += 1
elif parts[j] == ')':
depth -= 1
j += 1
value, i = evaluate_part(i + 1)
i = j - 1
elif part == ')':
return value, i + 1
elif part == '.':
if value is None:
value = frame.FindVariable(parts[i+1])
else:
value = value.GetChildMemberWithName(parts[i+1])
i += 2
elif part.startswith('['):
index = int(part[1:-1])
value = value.GetChildAtIndex(index)
i += 1
else:
if value is None:
value = frame.FindVariable(part)
else:
value = value.GetChildMemberWithName(part)
i += 1
if not value or not value.IsValid():
return None, i
return value, i
value, _ = evaluate_part(0)
return value
def print_go_expression(debugger: lldb.SBDebugger, command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None:
frame = debugger.GetSelectedTarget().GetProcess( frame = debugger.GetSelectedTarget().GetProcess(
).GetSelectedThread().GetSelectedFrame() ).GetSelectedThread().GetSelectedFrame()
value = evaluate_expression(frame, command)
# Handle Go-style pointer member access if value and value.IsValid():
command = re.sub(r'(\w+)\.(\w+)', lambda m: f'(*{m.group(1)}).{m.group( result.AppendMessage(format_value(value, debugger))
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: else:
result.AppendMessage(f"Error: {var.error}") result.AppendMessage(
f"Error: Unable to evaluate expression '{command}'")
def print_all_variables(debugger, command, result, _internal_dict): def print_all_variables(debugger: lldb.SBDebugger, _command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None:
target = debugger.GetSelectedTarget() target = debugger.GetSelectedTarget()
if not is_llgo_compiler(target): if not is_llgo_compiler(target):
result.AppendMessage("Not a LLGo compiled binary.") result.AppendMessage("Not a LLGo compiled binary.")
@@ -74,7 +95,7 @@ def print_all_variables(debugger, command, result, _internal_dict):
).GetSelectedThread().GetSelectedFrame() ).GetSelectedThread().GetSelectedFrame()
variables = frame.GetVariables(True, True, True, False) variables = frame.GetVariables(True, True, True, False)
output = [] output: List[str] = []
for var in variables: for var in variables:
type_name = map_type_name(var.GetType().GetName()) type_name = map_type_name(var.GetType().GetName())
formatted = format_value(var, debugger, include_type=False, indent=0) formatted = format_value(var, debugger, include_type=False, indent=0)
@@ -83,14 +104,12 @@ def print_all_variables(debugger, command, result, _internal_dict):
result.AppendMessage("\n".join(output)) result.AppendMessage("\n".join(output))
def is_pointer(frame, var_name): def is_pointer(frame: lldb.SBFrame, var_name: str) -> bool:
var = frame.FindVariable(var_name) var = frame.FindVariable(var_name)
return var.IsValid() and var.GetType().IsPointerType() return var.IsValid() and var.GetType().IsPointerType()
# Format functions extracted from main.py
def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0) -> str:
def format_value(var, debugger, include_type=True, indent=0):
if not var.IsValid(): if not var.IsValid():
return "<variable not available>" return "<variable not available>"
@@ -120,10 +139,10 @@ def format_value(var, debugger, include_type=True, indent=0):
return "<variable not available>" return "<variable not available>"
def format_slice(var, debugger, indent): def format_slice(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str:
length = int(var.GetChildMemberWithName('len').GetValue()) length = int(var.GetChildMemberWithName('len').GetValue())
data_ptr = var.GetChildMemberWithName('data') data_ptr = var.GetChildMemberWithName('data')
elements = [] elements: List[str] = []
ptr_value = int(data_ptr.GetValue(), 16) ptr_value = int(data_ptr.GetValue(), 16)
element_type = data_ptr.GetType().GetPointeeType() element_type = data_ptr.GetType().GetPointeeType()
@@ -152,8 +171,8 @@ def format_slice(var, debugger, indent):
return result return result
def format_array(var, debugger, indent): def format_array(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str:
elements = [] elements: List[str] = []
indent_str = ' ' * indent indent_str = ' ' * indent
next_indent_str = ' ' * (indent + 1) next_indent_str = ' ' * (indent + 1)
@@ -166,13 +185,13 @@ def format_array(var, debugger, indent):
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}"
if len(elements) > 5: # 如果元素数量大于5则进行折行显示 if len(elements) > 5: # wrap line if too many elements
return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}"
else: else:
return f"{type_name}{{{', '.join(elements)}}}" return f"{type_name}{{{', '.join(elements)}}}"
def format_string(var): def format_string(var: lldb.SBValue) -> str:
summary = var.GetSummary() summary = var.GetSummary()
if summary is not None: if summary is not None:
return summary # Keep the quotes return summary # Keep the quotes
@@ -185,8 +204,8 @@ def format_string(var):
return '""' return '""'
def format_struct(var, debugger, include_type=True, indent=0, type_name=""): def format_struct(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0, type_name: str = "") -> str:
children = [] children: List[str] = []
indent_str = ' ' * indent indent_str = ' ' * indent
next_indent_str = ' ' * (indent + 1) next_indent_str = ' ' * (indent + 1)
@@ -209,13 +228,13 @@ def format_struct(var, debugger, include_type=True, indent=0, type_name=""):
return struct_content return struct_content
def format_pointer(var, debugger, indent, type_name): def format_pointer(var: lldb.SBValue, _debugger: lldb.SBDebugger, _indent: int, _type_name: str) -> str:
if not var.IsValid() or var.GetValueAsUnsigned() == 0: if not var.IsValid() or var.GetValueAsUnsigned() == 0:
return "<variable not available>" return "<variable not available>"
return var.GetValue() # Return the address as a string return var.GetValue() # Return the address as a string
def map_type_name(type_name): def map_type_name(type_name: str) -> str:
# Handle pointer types # Handle pointer types
if type_name.endswith('*'): if type_name.endswith('*'):
base_type = type_name[:-1].strip() base_type = type_name[:-1].strip()
@@ -223,7 +242,7 @@ def map_type_name(type_name):
return f"*{mapped_base_type}" return f"*{mapped_base_type}"
# Map other types # Map other types
type_mapping = { type_mapping: Dict[str, str] = {
'long': 'int', 'long': 'int',
'void': 'unsafe.Pointer', 'void': 'unsafe.Pointer',
'char': 'byte', 'char': 'byte',

View File

@@ -7,17 +7,14 @@ import signal
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Optional, Set, Dict, Any from typing import List, Optional, Set, Dict, Any
import lldb import lldb
import llgo_plugin # Add this import import llgo_plugin
from llgo_plugin import log
class LLDBTestException(Exception): class LLDBTestException(Exception):
pass pass
def log(*args: Any, **kwargs: Any) -> None:
print(*args, **kwargs, flush=True)
@dataclass @dataclass
class Test: class Test:
source_file: str source_file: str
@@ -60,7 +57,7 @@ class TestResults:
class LLDBDebugger: class LLDBDebugger:
def __init__(self, executable_path: str, plugin_path: Optional[str] = None): def __init__(self, executable_path: str, plugin_path: Optional[str] = None) -> None:
self.executable_path: str = executable_path self.executable_path: str = executable_path
self.plugin_path: Optional[str] = plugin_path self.plugin_path: Optional[str] = plugin_path
self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create() self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create()
@@ -70,7 +67,6 @@ class LLDBDebugger:
self.type_mapping: Dict[str, str] = { self.type_mapping: Dict[str, str] = {
'long': 'int', 'long': 'int',
'unsigned long': 'uint', 'unsigned long': 'uint',
# Add more mappings as needed
} }
def setup(self) -> None: def setup(self) -> None:
@@ -104,25 +100,10 @@ class LLDBDebugger:
def get_variable_value(self, var_expression: str) -> Optional[str]: def get_variable_value(self, var_expression: str) -> Optional[str]:
frame = self.process.GetSelectedThread().GetFrameAtIndex(0) frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
value = llgo_plugin.evaluate_expression(frame, var_expression)
parts = var_expression.split('.') if value and value.IsValid():
var = frame.FindVariable(parts[0]) return llgo_plugin.format_value(value, self.debugger)
return None
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:
var = var.GetChildMemberWithName(part)
return llgo_plugin.format_value(var, self.debugger) if var.IsValid() else None
def get_all_variable_names(self) -> Set[str]: def get_all_variable_names(self) -> Set[str]:
frame = self.process.GetSelectedThread().GetFrameAtIndex(0) frame = self.process.GetSelectedThread().GetFrameAtIndex(0)
@@ -189,7 +170,7 @@ class LLDBDebugger:
def parse_expected_values(source_files: List[str]) -> List[TestCase]: def parse_expected_values(source_files: List[str]) -> List[TestCase]:
test_cases = [] test_cases: List[TestCase] = []
for source_file in source_files: for source_file in source_files:
with open(source_file, 'r', encoding='utf-8') as f: with open(source_file, 'r', encoding='utf-8') as f:
content = f.readlines() content = f.readlines()
@@ -198,7 +179,7 @@ def parse_expected_values(source_files: List[str]) -> List[TestCase]:
line = content[i].strip() line = content[i].strip()
if line.startswith('// Expected:'): if line.startswith('// Expected:'):
start_line = i + 1 start_line = i + 1
tests = [] tests: List[Test] = []
i += 1 i += 1
while i < len(content): while i < len(content):
line = content[i].strip() line = content[i].strip()
@@ -224,7 +205,8 @@ def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: boo
debugger = LLDBDebugger(executable_path, plugin_path) debugger = LLDBDebugger(executable_path, plugin_path)
try: try:
if verbose: if verbose:
log(f"Setting breakpoint at {test_case.source_file}: {test_case.end_line}") log(
f"\nSetting breakpoint at {test_case.source_file}:{test_case.end_line}")
debugger.setup() debugger.setup()
debugger.set_breakpoint(test_case.source_file, test_case.end_line) debugger.set_breakpoint(test_case.source_file, test_case.end_line)
debugger.run_to_breakpoint() debugger.run_to_breakpoint()
@@ -274,7 +256,7 @@ def run_tests(executable_path: str, source_files: List[str], verbose: bool, inte
def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult: def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult:
results = [] results: List[TestResult] = []
for test in test_case.tests: for test in test_case.tests:
if test.variable == "all variables": if test.variable == "all variables":