diff --git a/tools/debug/customdebugtype.nim b/tools/debug/customdebugtype.nim index 1ffe632e9..f48979661 100644 --- a/tools/debug/customdebugtype.nim +++ b/tools/debug/customdebugtype.nim @@ -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: )" 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: as a child of CustomType instead of + ## myField: 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]: ## result = newSeqOfCap[int](intset.len) for val in intset: diff --git a/tools/debug/nimlldb.py b/tools/debug/nimlldb.py index b57e10133..4bc4e771f 100644 --- a/tools/debug/nimlldb.py +++ b/tools/debug/nimlldb.py @@ -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)