Add synthetic objects with dynamic fields and add an example
This commit is contained in:
parent
ce10f372d1
commit
246f232f0a
2 changed files with 114 additions and 15 deletions
|
|
@ -12,27 +12,50 @@
|
||||||
## return any Nim object, array, or sequence. Returning a Nim object
|
## return any Nim object, array, or sequence. Returning a Nim object
|
||||||
## will display the fields and values of the object as children.
|
## will display the fields and values of the object as children.
|
||||||
## Returning an array or sequence will display children with the index
|
## 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 intsets
|
||||||
|
import tables
|
||||||
|
|
||||||
type
|
type
|
||||||
CustomType* = object of RootObj # RootObj is not necessary, but can be used
|
CustomType* = object of RootObj # RootObj is not necessary, but can be used
|
||||||
myField*: int
|
myField*: int
|
||||||
|
|
||||||
|
DynamicFields* = object
|
||||||
|
customField*: string
|
||||||
|
|
||||||
CustomSyntheticReturn* = object
|
CustomSyntheticReturn* = object
|
||||||
differentField*: float
|
differentField*: float
|
||||||
|
|
||||||
|
LLDBDynamicObject = object
|
||||||
|
fields: TableRef[string, int]
|
||||||
|
|
||||||
|
LLDBDynamicObjectDynamicFields = object
|
||||||
|
fields: TableRef[string, string]
|
||||||
|
|
||||||
proc lldbDebugSummaryCustomType*(ty: CustomType): string {.exportc.} =
|
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
|
result = "CustomType" & $ty
|
||||||
|
|
||||||
proc lldbDebugSyntheticCustomType*(ty: CustomType): CustomSyntheticReturn {.exportc.} =
|
proc lldbDebugSyntheticCustomType*(ty: CustomType): CustomSyntheticReturn {.exportc.} =
|
||||||
## Will display the fields of CustomSyntheticReturn as children of
|
## Will display differentField: <float_val> as a child of CustomType instead of
|
||||||
## CustomType
|
## myField: <int_val>
|
||||||
result = CustomSyntheticReturn(differentField: ty.myField.float)
|
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.} =
|
proc lldbDebugSummaryIntSet*(intset: IntSet): string {.exportc.} =
|
||||||
## This will print the object in the LLDB summary just as Nim prints it
|
## This will print the object in the LLDB summary just as Nim prints it
|
||||||
result = $intset
|
result = $intset
|
||||||
|
|
@ -42,6 +65,7 @@ proc lldbDebugSyntheticIntSet*(intset: IntSet): seq[int] {.exportc.} =
|
||||||
## will appear as a Nim object in the LLDB debugger window
|
## will appear as a Nim object in the LLDB debugger window
|
||||||
##
|
##
|
||||||
## returning a seq here will display children like:
|
## returning a seq here will display children like:
|
||||||
|
## [0]: <child_value>
|
||||||
##
|
##
|
||||||
result = newSeqOfCap[int](intset.len)
|
result = newSeqOfCap[int](intset.len)
|
||||||
for val in intset:
|
for val in intset:
|
||||||
|
|
|
||||||
|
|
@ -1012,14 +1012,45 @@ class StringTableChildrenProvider:
|
||||||
return bool(self.num_children())
|
return bool(self.num_children())
|
||||||
|
|
||||||
|
|
||||||
class CustomObjectChildrenProvider:
|
class LLDBDynamicObjectProvider:
|
||||||
"""
|
|
||||||
This children provider handles values returned from lldbDebugSynthetic*
|
|
||||||
Nim procedures
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, value: lldb.SBValue, internalDict):
|
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):
|
def num_children(self):
|
||||||
if self.value is not None:
|
if self.value is not None:
|
||||||
|
|
@ -1039,6 +1070,35 @@ class CustomObjectChildrenProvider:
|
||||||
return self.num_children() > 0
|
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):
|
def echo(debugger: lldb.SBDebugger, command: str, result, internal_dict):
|
||||||
debugger.HandleCommand("po " + command)
|
debugger.HandleCommand("po " + command)
|
||||||
|
|
||||||
|
|
@ -1117,13 +1177,27 @@ def get_custom_synthetic(value: lldb.SBValue) -> Union[lldb.SBValue, None]:
|
||||||
value = value.Dereference()
|
value = value.Dereference()
|
||||||
|
|
||||||
if first_type.is_pointer:
|
if first_type.is_pointer:
|
||||||
command = f"{fn.name}(({first_type.name})" + str(value.GetLoadAddress()) + ");"
|
first_arg = f"({first_type.name}){value.GetLoadAddress()}"
|
||||||
else:
|
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)
|
res = executeCommand(command)
|
||||||
|
|
||||||
if res.error.fail:
|
if res.error.fail:
|
||||||
|
print(res.error)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
@ -1202,8 +1276,9 @@ def breakpoint_function_wrapper(frame: lldb.SBFrame, bp_loc, internal_dict):
|
||||||
fn_type: lldb.SBType = fn.type
|
fn_type: lldb.SBType = fn.type
|
||||||
arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes()
|
arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes()
|
||||||
|
|
||||||
# There should only be one argument to send to the function call
|
if arg_types.GetSize() > 1 and fn.GetArgumentName(1) == "Result":
|
||||||
if arg_types.GetSize() != 1:
|
pass # don't continue
|
||||||
|
elif arg_types.GetSize() != 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
arg_type: lldb.SBType = arg_types.GetTypeAtIndex(0)
|
arg_type: lldb.SBType = arg_types.GetTypeAtIndex(0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue