Add synthetic objects with dynamic fields and add an example

This commit is contained in:
Joey Yakimowich-Payne 2023-03-13 16:26:47 -06:00
commit 246f232f0a
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
2 changed files with 114 additions and 15 deletions

View file

@ -12,27 +12,50 @@
## return any Nim object, array, or sequence. Returning a Nim object
## will display the fields and values of the object as children.
## Returning an array or sequence will display children with the index
## and value.
## surrounded by square brackets as the key name
##
## You may also return a Nim table that contains the string
## "LLDBDynamicObject" (case insensitive). This allows for dynamic
## fields to be created at runtime instead of at compile time if you
## return a Nim object as mentioned above. See the proc
## `lldbDebugSyntheticDynamicFields` below for an example
import intsets
import tables
type
CustomType* = object of RootObj # RootObj is not necessary, but can be used
myField*: int
DynamicFields* = object
customField*: string
CustomSyntheticReturn* = object
differentField*: float
LLDBDynamicObject = object
fields: TableRef[string, int]
LLDBDynamicObjectDynamicFields = object
fields: TableRef[string, string]
proc lldbDebugSummaryCustomType*(ty: CustomType): string {.exportc.} =
## Will display "CustomType(myField: 0)" as a summary
## Will display "CustomType(myField: <int_val>)" as a summary
result = "CustomType" & $ty
proc lldbDebugSyntheticCustomType*(ty: CustomType): CustomSyntheticReturn {.exportc.} =
## Will display the fields of CustomSyntheticReturn as children of
## CustomType
## Will display differentField: <float_val> as a child of CustomType instead of
## myField: <int_val>
result = CustomSyntheticReturn(differentField: ty.myField.float)
proc lldbDebugSyntheticDynamicFields*(ty: DynamicFields): LLDBDynamicObjectDynamicFields {.exportc.} =
## Returning an object that contains "LLDBDynamicObject" in the type name will expect an
## object with one property that is a Nim Table/TableRef. If the key is a string,
## it will appear in the debugger like an object field name. The value will be whatever you
## set it to here as well.
let fields = {"customFieldName": ty.customField & " MORE TEXT"}.newTable()
return LLDBDynamicObjectDynamicFields(fields: fields)
proc lldbDebugSummaryIntSet*(intset: IntSet): string {.exportc.} =
## This will print the object in the LLDB summary just as Nim prints it
result = $intset
@ -42,6 +65,7 @@ proc lldbDebugSyntheticIntSet*(intset: IntSet): seq[int] {.exportc.} =
## will appear as a Nim object in the LLDB debugger window
##
## returning a seq here will display children like:
## [0]: <child_value>
##
result = newSeqOfCap[int](intset.len)
for val in intset:

View file

@ -1012,14 +1012,45 @@ class StringTableChildrenProvider:
return bool(self.num_children())
class CustomObjectChildrenProvider:
"""
This children provider handles values returned from lldbDebugSynthetic*
Nim procedures
"""
class LLDBDynamicObjectProvider:
def __init__(self, value: lldb.SBValue, internalDict):
self.value: lldb.SBValue = get_custom_synthetic(value) or value
value = value.GetNonSyntheticValue()
self.value: lldb.SBValue = value[0]
self.children: OrderedDict[str, int] = OrderedDict()
self.child_list: list[lldb.SBValue] = []
while self.value.type.is_pointer:
self.value = self.value.Dereference()
self.update()
def num_children(self):
return len(self.child_list)
def get_child_index(self, name: str):
return self.children[name]
def get_child_at_index(self, index):
return self.child_list[index]
def update(self):
self.children.clear()
self.child_list = []
for i, child in enumerate(self.value.children):
name = child.name.strip('"')
new_child = child.CreateValueFromAddress(name, child.GetLoadAddress(), child.GetType())
self.children[name] = i
self.child_list.append(new_child)
def has_children(self):
return bool(self.num_children())
class LLDBBasicObjectProvider:
def __init__(self, value: lldb.SBValue, internalDict):
self.value: lldb.SBValue = value
def num_children(self):
if self.value is not None:
@ -1039,6 +1070,35 @@ class CustomObjectChildrenProvider:
return self.num_children() > 0
class CustomObjectChildrenProvider:
"""
This children provider handles values returned from lldbDebugSynthetic*
Nim procedures
"""
def __init__(self, value: lldb.SBValue, internalDict):
self.value: lldb.SBValue = get_custom_synthetic(value) or value
if "lldbdynamicobject" in self.value.type.name.lower():
self.provider = LLDBDynamicObjectProvider(self.value, internalDict)
else:
self.provider = LLDBBasicObjectProvider(self.value, internalDict)
def num_children(self):
return self.provider.num_children()
def get_child_index(self, name: str):
return self.provider.get_child_index(name)
def get_child_at_index(self, index):
return self.provider.get_child_at_index(index)
def update(self):
self.provider.update()
def has_children(self):
return self.provider.has_children()
def echo(debugger: lldb.SBDebugger, command: str, result, internal_dict):
debugger.HandleCommand("po " + command)
@ -1117,13 +1177,27 @@ def get_custom_synthetic(value: lldb.SBValue) -> Union[lldb.SBValue, None]:
value = value.Dereference()
if first_type.is_pointer:
command = f"{fn.name}(({first_type.name})" + str(value.GetLoadAddress()) + ");"
first_arg = f"({first_type.name}){value.GetLoadAddress()}"
else:
command = f"{fn.name}(*({first_type.GetPointerType().name})" + str(value.GetLoadAddress()) + ");"
first_arg = f"*({first_type.GetPointerType().name}){value.GetLoadAddress()}"
if arg_types.GetSize() > 1 and fn.GetArgumentName(1) == "Result":
ret_type = arg_types.GetTypeAtIndex(1)
ret_type = get_base_type(ret_type)
command = f"""
{ret_type.name} lldbT;
nimZeroMem((void*)(&lldbT), sizeof({ret_type.name}));
{fn.name}(({first_arg}), (&lldbT));
lldbT;
"""
else:
command = f"{fn.name}({first_arg});"
res = executeCommand(command)
if res.error.fail:
print(res.error)
return None
return res
@ -1202,8 +1276,9 @@ def breakpoint_function_wrapper(frame: lldb.SBFrame, bp_loc, internal_dict):
fn_type: lldb.SBType = fn.type
arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes()
# There should only be one argument to send to the function call
if arg_types.GetSize() != 1:
if arg_types.GetSize() > 1 and fn.GetArgumentName(1) == "Result":
pass # don't continue
elif arg_types.GetSize() != 1:
continue
arg_type: lldb.SBType = arg_types.GetTypeAtIndex(0)