lldb: support expression includes dereference, parentheses
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
Reference in New Issue
Block a user