Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/internal/lldb/lldb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ func TestEmbeddedPluginIdentity(t *testing.T) {
"string_summary",
"slice_summary",
"SliceSyntheticProvider",
"interface_summary",
"function_summary",
"llgo status",
"llgo print",
"llgo vars",
Expand Down
183 changes: 183 additions & 0 deletions cmd/internal/lldb/llgo_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
LLGO_DEBUGGER_SCHEMA_FILENAME = "llgo_debugger_schema_v1.json"
LLGO_TYPE_CATEGORY = "LLGo"
LLGO_MAX_STRING_SUMMARY_BYTES = 256
LLGO_MAX_TYPE_NAME_BYTES = 4096
LLGO_DEFAULT_MAX_CHILDREN = 256
_TARGET_INFO_CACHE: Dict[Tuple[Any, ...], "LLGoTargetInfo"] = {}

Expand Down Expand Up @@ -77,6 +78,21 @@ class LLGoRuntimeLayout:
slice_data: str
slice_len: str
slice_cap: str
interface_type_pattern: str
interface_type: str
interface_data: str
empty_interface_type: str
runtime_itab_type: str
runtime_itab_concrete_type: str
runtime_type: str
runtime_type_tflag: str
runtime_type_extra_star_flag: int
runtime_type_string: str
function_type_pattern: str
function_code: str
function_data: str
function_closure_symbol_pattern: str
function_bound_symbol_suffix: str


@dataclass(frozen=True)
Expand All @@ -94,6 +110,9 @@ def _runtime_layouts() -> Dict[int, LLGoRuntimeLayout]:
"runtime_layouts", {}).items():
string_layout = raw.get("string", {})
slice_layout = raw.get("slice", {})
interface_layout = raw.get("interface", {})
runtime_type_layout = raw.get("runtime_type", {})
function_layout = raw.get("function", {})
try:
layouts[int(version)] = LLGoRuntimeLayout(
string_type=string_layout["type_name"],
Expand All @@ -103,6 +122,25 @@ def _runtime_layouts() -> Dict[int, LLGoRuntimeLayout]:
slice_data=slice_layout["data"],
slice_len=slice_layout["length"],
slice_cap=slice_layout["capacity"],
interface_type_pattern=interface_layout["type_pattern"],
interface_type=interface_layout["type"],
interface_data=interface_layout["data"],
empty_interface_type=interface_layout["empty_type"],
runtime_itab_type=interface_layout["itab_type"],
runtime_itab_concrete_type=(
interface_layout["itab_concrete_type"]),
runtime_type=runtime_type_layout["type_name"],
runtime_type_tflag=runtime_type_layout["tflag"],
runtime_type_extra_star_flag=(
runtime_type_layout["extra_star_flag"]),
runtime_type_string=runtime_type_layout["string"],
function_type_pattern=function_layout["type_pattern"],
function_code=function_layout["code"],
function_data=function_layout["data"],
function_closure_symbol_pattern=(
function_layout["closure_symbol_pattern"]),
function_bound_symbol_suffix=(
function_layout["bound_symbol_suffix"]),
)
except (KeyError, TypeError, ValueError):
continue
Expand Down Expand Up @@ -192,6 +230,18 @@ def register_type_formatters(debugger: lldb.SBDebugger) -> None:
lldb.SBTypeSynthetic.CreateWithClassName(
"llgo_plugin.SliceSyntheticProvider", _type_options()),
)
category.AddTypeSummary(
lldb.SBTypeNameSpecifier(
layout.interface_type_pattern, True),
lldb.SBTypeSummary.CreateWithFunctionName(
"llgo_plugin.interface_summary", _type_options()),
)
category.AddTypeSummary(
lldb.SBTypeNameSpecifier(
layout.function_type_pattern, True),
lldb.SBTypeSummary.CreateWithFunctionName(
"llgo_plugin.function_summary", _type_options()),
)
category.SetEnabled(True)


Expand Down Expand Up @@ -676,6 +726,136 @@ def slice_summary(value: lldb.SBValue, _internal_dict: Dict[str, Any]) -> Option
return f"len={fields.length} cap={fields.capacity}"


def _value_from_address(target: lldb.SBTarget, name: str, address: int,
type_name: str) -> Optional[lldb.SBValue]:
value_type = target.FindFirstType(type_name)
if not value_type or not value_type.IsValid():
return None
value = target.CreateValueFromAddress(
name, lldb.SBAddress(address, target), value_type)
return value if value and value.IsValid() else None


def _runtime_type_name(value: lldb.SBValue, address: int,
layout: LLGoRuntimeLayout) -> Optional[str]:
runtime_type = _value_from_address(
value.GetTarget(), "__llgo_runtime_type", address,
layout.runtime_type)
if runtime_type is None:
return None

name_value = runtime_type.GetChildMemberWithName(
layout.runtime_type_string)
fields = _string_fields(name_value, layout)
if fields is None:
return None
data, length = fields
if length > LLGO_MAX_TYPE_NAME_BYTES:
return None
data_address = _value_as_int(data)
process = value.GetProcess()
if (data_address is None or length < 0 or
(length != 0 and data_address == 0) or not process or
not process.IsValid()):
return None

error = lldb.SBError()
contents = process.ReadMemory(data_address, length, error)
if not error.Success() or contents is None:
return None
if isinstance(contents, str):
contents = contents.encode("latin-1", errors="surrogateescape")
try:
name = bytes(contents).decode("utf-8")
except UnicodeDecodeError:
return None

tflag = runtime_type.GetChildMemberWithName(layout.runtime_type_tflag)
if (tflag and tflag.IsValid() and
tflag.GetValueAsUnsigned(0) & layout.runtime_type_extra_star_flag):
name = "*" + name
return name


def _interface_type_address(value: lldb.SBValue,
layout: LLGoRuntimeLayout) -> Optional[int]:
raw = _raw_value(value)
if not re.fullmatch(layout.interface_type_pattern,
_canonical_type_name(raw)):
return None
type_word = raw.GetChildMemberWithName(layout.interface_type)
data_word = raw.GetChildMemberWithName(layout.interface_data)
if not data_word or not data_word.IsValid():
return None
type_address = _value_as_int(type_word)
if type_address is None or type_address == 0:
return type_address
if _canonical_type_name(raw) == layout.empty_interface_type:
return type_address

itab = _value_from_address(
value.GetTarget(), "__llgo_itab", type_address,
layout.runtime_itab_type)
if itab is None:
return None
return _value_as_int(itab.GetChildMemberWithName(
layout.runtime_itab_concrete_type))


def interface_summary(value: lldb.SBValue,
_internal_dict: Dict[str, Any]) -> Optional[str]:
layout = _runtime_layout(value)
if layout is None:
return None
type_address = _interface_type_address(value, layout)
if type_address is None:
return None
if type_address == 0:
return "nil"
type_name = _runtime_type_name(value, type_address, layout)
return f"type={type_name}" if type_name else None


def _symbol_name(target: lldb.SBTarget, address: int) -> Optional[str]:
if address == 0:
return None
resolved = target.ResolveLoadAddress(address)
if not resolved or not resolved.IsValid():
return None
function = resolved.GetFunction()
if function and function.IsValid():
return function.GetName()
symbol = resolved.GetSymbol()
return symbol.GetName() if symbol and symbol.IsValid() else None


def function_summary(value: lldb.SBValue,
_internal_dict: Dict[str, Any]) -> Optional[str]:
layout = _runtime_layout(value)
raw = _raw_value(value)
if (layout is None or
not re.fullmatch(layout.function_type_pattern,
_canonical_type_name(raw))):
return None
code = _value_as_int(raw.GetChildMemberWithName(layout.function_code))
data = _value_as_int(raw.GetChildMemberWithName(layout.function_data))
if code is None or data is None:
return None
if code == 0:
return "nil"

name = _symbol_name(value.GetTarget(), code)
if name and name.startswith("__llgo_stub."):
name = name[len("__llgo_stub."):]
if not name:
name = f"0x{code:x}"
if name.endswith(layout.function_bound_symbol_suffix):
return f"{name} (bound method)"
if re.search(layout.function_closure_symbol_pattern, name):
return f"{name} (closure)"
return name


class SliceSyntheticProvider:
def __init__(self, value: lldb.SBValue, _internal_dict: Dict[str, Any]) -> None:
self.value = value
Expand Down Expand Up @@ -872,6 +1052,9 @@ def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: boo
elif type_name == 'string': # String
return format_string(var)
elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]:
summary = var.GetSummary()
if summary is not None:
return summary
return format_struct(var, debugger, include_type, indent, original_type_name)
else:
value = var.GetValue()
Expand Down
59 changes: 59 additions & 0 deletions cmd/llgo/lldbtest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,63 @@ func (s *Struct) Foo(a []int, b string) int {
return 1
}

type Counter struct {
base int
}

func (c *Counter) Add(value int) int {
return c.base + value
}

func Plain(value int) int {
return value + 1
}

type IntFunc func(int) int

type InterfaceResults struct {
intResult int
textResult string
fooResult int
errResult string
}

func RuntimeInterfaceValues() {
var nilAny any
anyInt := any(42)
anyText := any("interface")
var nilFoo Interface
foo := Interface(&Struct{})
err := errors.New("interface error")
results := &InterfaceResults{
intResult: anyInt.(int) + 1,
textResult: anyText.(string) + "!",
fooResult: foo.Foo([]int{1, 2}, "x"),
errResult: err.Error(),
}
InspectInterfaceValues(nilAny, anyInt, anyText, nilFoo, foo, err, results)
}

func InspectInterfaceValues(nilAny, anyInt, anyText any, nilFoo, foo Interface, err error, results *InterfaceResults) {
println(nilAny, anyInt, anyText, nilFoo, foo, err,
results.intResult, results.textResult, results.fooResult, results.errResult) // LLDB_BREAK: interface_values
}

func RuntimeFunctionValues() {
plain := Plain
named := IntFunc(Plain)
base := 5
closure := func(value int) int { return base + value }
counter := &Counter{base: 10}
bound := counter.Add
var nilFunc func(int) int
plainResult := plain(1)
namedResult := named(2)
closureResult := closure(2)
boundResult := bound(3)
println(plain, named, closure, bound, nilFunc, plainResult, namedResult, closureResult, boundResult) // LLDB_BREAK: function_values
}

func RuntimeValues() {
text := "hello"
empty := ""
Expand Down Expand Up @@ -340,6 +397,8 @@ func main() {
println("s:", &s) // LLDB_BREAK: main_globals
FuncWithAllTypeStructParam(s)
RuntimeValues()
RuntimeInterfaceValues()
RuntimeFunctionValues()
println("called function with struct")
i, err := FuncWithAllTypeParams(
s.i8, s.i16, s.i32, s.i64, s.i, s.u8, s.u16, s.u32, s.u64, s.u,
Expand Down
23 changes: 23 additions & 0 deletions cmd/llgo/lldbtest/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ def test_case(marker: str, expectations: List[tuple]) -> TestCase:
("namedInts", "[0]=11, [1]=12, [2]=13, [3]=14", "synthetic"),
("ints", "[]int{7, ... (1 more)}", "limited"),
]),
test_case("interface_values", [
("nilAny", "nil", "summary"),
("anyInt", "type=int", "summary"),
("anyText", "type=string", "summary"),
("nilFoo", "nil", "summary"),
("foo", "type=*main.Struct", "summary"),
("err", "type=*errors.errorString", "summary"),
("results.intResult", "43"),
("results.textResult", '"interface!"'),
("results.fooResult", "1"),
("results.errResult", '"interface error"'),
]),
test_case("function_values", [
("plain", "main.Plain", "summary"),
("named", "main.Plain", "summary"),
("closure", "main.RuntimeFunctionValues$1 (closure)", "summary"),
("bound", "main.(*Counter).Add$bound (bound method)", "summary"),
("nilFunc", "nil", "summary"),
("plainResult", "2"),
("namedResult", "3"),
("closureResult", "7"),
("boundResult", "13"),
]),
test_case("struct_values_initial", STRUCT_VALUES_INITIAL),
test_case("struct_values_updated", STRUCT_VALUES_UPDATED),
test_case("struct_ptrs_initial", STRUCT_VALUES_INITIAL),
Expand Down
Loading