import lldb from collections import OrderedDict from typing import Union def sbvaluegetitem(self: lldb.SBValue, name: Union[int, str]) -> lldb.SBValue: if isinstance(name, str): return self.GetChildMemberWithName(name) else: return self.GetChildAtIndex(name) # Make this easier to work with lldb.SBValue.__getitem__ = sbvaluegetitem NIM_IS_V2 = True def get_nti(value: lldb.SBValue, nim_name=None): name_split = value.type.name.split("_") type_nim_name = nim_name or name_split[1] id_string = name_split[-1].split(" ")[0] type_info_name = "NTI" + type_nim_name.lower() + "__" + id_string + "_" nti = value.target.FindFirstGlobalVariable(type_info_name) if not nti.IsValid(): type_info_name = "NTI" + "__" + id_string + "_" nti = value.target.FindFirstGlobalVariable(type_info_name) if not nti.IsValid(): print(f"NimEnumPrinter: lookup global symbol: '{type_info_name}' failed for {value.type.name}.\n") return type_nim_name, nti def enum_to_string(value: lldb.SBValue, int_val=None, nim_name=None): tname = nim_name or value.type.name.split("_")[1] enum_val = value.signed if int_val is not None: enum_val = int_val default_val = f"{tname}.{str(enum_val)}" fn_syms = value.target.FindFunctions("reprEnum") if not fn_syms.GetSize() > 0: return default_val fn_sym: lldb.SBSymbolContext = fn_syms.GetContextAtIndex(0) fn: lldb.SBFunction = fn_sym.function fn_type: lldb.SBType = fn.type arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes() if arg_types.GetSize() < 2: return default_val arg1_type: lldb.SBType = arg_types.GetTypeAtIndex(0) arg2_type: lldb.SBType = arg_types.GetTypeAtIndex(1) ty_info_name, nti = get_nti(value, nim_name=tname) if not nti.IsValid(): return default_val call = f"{fn.name}(({arg1_type.name}){enum_val}, ({arg2_type.name})" + str(nti.GetLoadAddress()) + ");" res = executeCommand(call) if res.error.fail: return default_val return f"{tname}.{res.summary[1:-1]}" def to_string(value: lldb.SBValue): # For getting NimStringDesc * value value = value.GetNonSyntheticValue() # Check if data pointer is Null if value.type.is_pointer and value.unsigned == 0: return None size = int(value["Sup"]["len"].unsigned) if size == 0: return "" if size > 2**14: return "... (too long) ..." data = value["data"] # Check if first element is NULL base_data_type = value.target.FindFirstType("char") cast = data.Cast(base_data_type) if cast.unsigned == 0: return None cast = data.Cast(value.target.FindFirstType("char").GetArrayType(size)) return bytearray(cast.data.uint8s).decode("utf-8") def to_stringV2(value: lldb.SBValue): # For getting NimStringV2 value value = value.GetNonSyntheticValue() data = value["p"]["data"] # Check if data pointer is Null if value["p"].unsigned == 0: return None size = int(value["len"].signed) if size == 0: return "" if size > 2**14: return "... (too long) ..." # Check if first element is NULL base_data_type = data.type.GetArrayElementType().GetTypedefedType() cast = data.Cast(base_data_type) if cast.unsigned == 0: return None cast = data.Cast(base_data_type.GetArrayType(size)) return bytearray(cast.data.uint8s).decode("utf-8") def NimString(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary if NIM_IS_V2: res = to_stringV2(value) else: res = to_string(value) if res is not None: return f'"{res}"' else: return "nil" def rope_helper(value: lldb.SBValue) -> str: value = value.GetNonSyntheticValue() if value.type.is_pointer and value.unsigned == 0: return "" if value["length"].unsigned == 0: return "" if NIM_IS_V2: str_val = to_stringV2(value["data"]) else: str_val = to_string(value["data"]) if str_val is None: str_val = "" return rope_helper(value["left"]) + str_val + rope_helper(value["right"]) def Rope(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary rope_str = rope_helper(value) if len(rope_str) == 0: rope_str = "nil" else: rope_str = f'"{rope_str}"' return f"Rope({rope_str})" def NCSTRING(value: lldb.SBValue, internal_dict=None): if is_local(value): if not is_in_scope(value): return "undefined" ty = value.Dereference().type val = value.target.CreateValueFromAddress( value.name or "temp", lldb.SBAddress(value.unsigned, value.target), ty ).AddressOf() return val.summary def ObjectV2(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" orig_value = value.GetNonSyntheticValue() if orig_value.type.is_pointer and orig_value.unsigned == 0: return "nil" custom_summary = get_custom_summary(value) if custom_summary is not None: return custom_summary while orig_value.type.is_pointer: orig_value = orig_value.Dereference() if "_" in orig_value.type.name: obj_name = orig_value.type.name.split("_")[1].replace("colonObjectType", "") else: obj_name = orig_value.type.name num_children = value.num_children fields = [] for i in range(num_children): fields.append(f"{value[i].name}: {value[i].summary}") res = f"{obj_name}(" + ", ".join(fields) + ")" return res def Number(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" if value.type.is_pointer and value.signed == 0: return "nil" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return str(value.signed) def Float(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return str(value.value) def UnsignedNumber(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return str(value.unsigned) def Bool(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return str(value.value) def CharArray(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return str([f"'{char}'" for char in value.uint8s]) def Array(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" value = value.GetNonSyntheticValue() custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary value = value.GetNonSyntheticValue() return "[" + ", ".join([value[i].summary for i in range(value.num_children)]) + "]" def Tuple(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary while value.type.is_pointer: value = value.Dereference() num_children = value.num_children fields = [] for i in range(num_children): key = value[i].name val = value[i].summary if key.startswith("Field"): fields.append(f"{val}") else: fields.append(f"{key}: {val}") return "(" + ", ".join(fields) + f")" def is_local(value: lldb.SBValue) -> bool: line: lldb.SBLineEntry = value.frame.GetLineEntry() decl: lldb.SBDeclaration = value.GetDeclaration() if line.file == decl.file and decl.line != 0: return True return False def is_in_scope(value: lldb.SBValue) -> bool: line: lldb.SBLineEntry = value.frame.GetLineEntry() decl: lldb.SBDeclaration = value.GetDeclaration() if is_local(value) and decl.line < line.line: return True return False def Enum(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_value_summary(value) if custom_summary is not None: return custom_summary return enum_to_string(value) def EnumSet(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary vals = [] max_vals = 7 for child in value.children: vals.append(child.summary) if len(vals) > max_vals: vals.append("...") break return "{" + ", ".join(vals) + "}" def Set(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if custom_summary is not None: return custom_summary vals = [] max_vals = 7 for child in value.children: vals.append(child.value) if len(vals) > max_vals: vals.append("...") break return "{" + ", ".join(vals) + "}" def Table(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if custom_summary is not None: return custom_summary fields = [] for i in range(value.num_children): key = value[i].name val = value[i].summary fields.append(f"{key}: {val}") return "Table({" + ", ".join(fields) + "})" def HashSet(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if custom_summary is not None: return custom_summary fields = [] for i in range(value.num_children): fields.append(f"{value[i].summary}") return "HashSet({" + ", ".join(fields) + "})" def StringTable(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary fields = [] for i in range(value.num_children - 1): key = value[i].name val = value[i].summary fields.append(f"{key}: {val}") mode = value[value.num_children - 1].summary return "StringTable({" + ", ".join(fields) + f"}}, mode={mode})" def Sequence(value: lldb.SBValue, internal_dict): if is_local(value): if not is_in_scope(value): return "undefined" custom_summary = get_custom_summary(value) if not custom_summary is None: return custom_summary return "@[" + ", ".join([value[i].summary for i in range(value.num_children)]) + "]" class StringChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.data_type: lldb.SBType if not NIM_IS_V2: self.data_type = self.value.target.FindFirstType("char") self.first_element: lldb.SBValue self.update() self.count = 0 def num_children(self): return self.count def get_child_index(self, name): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): offset = index * self.data_size return self.first_element.CreateChildAtOffset("[" + str(index) + "]", offset, self.data_type) def get_data(self) -> lldb.SBValue: return self.value["p"]["data"] if NIM_IS_V2 else self.value["data"] def get_len(self) -> int: if NIM_IS_V2: if self.value["p"].unsigned == 0: return 0 size = int(self.value["len"].signed) if size == 0: return 0 data = self.value["p"]["data"] # Check if first element is NULL base_data_type = data.type.GetArrayElementType().GetTypedefedType() cast = data.Cast(base_data_type) if cast.unsigned == 0: return 0 else: if self.value.type.is_pointer and self.value.unsigned == 0: return 0 size = int(self.value["Sup"]["len"].unsigned) if size == 0: return 0 data = self.value["data"] # Check if first element is NULL base_data_type = self.value.target.FindFirstType("char") cast = data.Cast(base_data_type) if cast.unsigned == 0: return 0 return size def update(self): if is_local(self.value): if not is_in_scope(self.value): return data = self.get_data() size = self.get_len() self.count = size self.first_element = data if NIM_IS_V2: self.data_type = data.type.GetArrayElementType().GetTypedefedType() self.data_size = self.data_type.GetByteSize() def has_children(self): return bool(self.num_children()) class ArrayChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.data_type: lldb.SBType self.first_element: lldb.SBValue self.update() def num_children(self): return self.has_children() and self.value.num_children def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): offset = index * self.value[index].GetByteSize() return self.first_element.CreateChildAtOffset("[" + str(index) + "]", offset, self.data_type) def update(self): if not self.has_children(): return self.first_element = self.value[0] self.data_type = self.value.type.GetArrayElementType() def has_children(self): if is_local(self.value): if not is_in_scope(self.value): return False return bool(self.value.num_children) class SeqChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.data_type: lldb.SBType self.first_element: lldb.SBValue self.data: lldb.SBValue self.count = 0 self.update() def num_children(self): return self.count def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): offset = index * self.data[index].GetByteSize() return self.first_element.CreateChildAtOffset("[" + str(index) + "]", offset, self.data_type) def get_data(self) -> lldb.SBValue: return self.value["p"]["data"] if NIM_IS_V2 else self.value["data"] def get_len(self) -> lldb.SBValue: return self.value["len"] if NIM_IS_V2 else self.value["Sup"]["len"] def update(self): self.count = 0 if is_local(self.value): if not is_in_scope(self.value): return self.count = self.get_len().unsigned if not self.has_children(): return data = self.get_data() self.data_type = data.type.GetArrayElementType() self.data = data.Cast(self.data_type.GetArrayType(self.num_children())) self.first_element = self.data def has_children(self): return bool(self.num_children()) class ObjectChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.data_type: lldb.SBType self.first_element: lldb.SBValue self.data: lldb.SBValue self.children: OrderedDict[str, int] = OrderedDict() self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.children) def get_child_index(self, name: str): return self.children[name] def get_child_at_index(self, index): return self.child_list[index] def populate_children(self): self.children.clear() self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return stack = [self.value.GetNonSyntheticValue()] index = 0 while stack: cur_val = stack.pop() if cur_val.type.is_pointer and cur_val.unsigned == 0: continue while cur_val.type.is_pointer: cur_val = cur_val.Dereference() # Add super objects if they exist if cur_val.num_children > 0 and cur_val[0].name == "Sup" and cur_val[0].type.name.startswith("tyObject"): stack.append(cur_val[0]) for child in cur_val.children: child = child.GetNonSyntheticValue() if child.name == "Sup": continue self.children[child.name] = index self.child_list.append(child) index += 1 def update(self): self.populate_children() def has_children(self): return bool(self.num_children()) class HashSetChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.child_list) def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): return self.child_list[index] def get_data(self) -> lldb.SBValue: return self.value["data"]["p"]["data"] if NIM_IS_V2 else self.value["data"]["data"] def get_len(self) -> lldb.SBValue: return self.value["data"]["len"] if NIM_IS_V2 else self.value["data"]["Sup"]["len"] def update(self): self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return tuple_len = int(self.get_len().unsigned) tuple = self.get_data() base_data_type = tuple.type.GetArrayElementType() cast = tuple.Cast(base_data_type.GetArrayType(tuple_len)) index = 0 for i in range(tuple_len): el = cast[i] field0 = int(el[0].unsigned) if field0 == 0: continue key = el[1] child = key.CreateValueFromAddress(f"[{str(index)}]", key.GetLoadAddress(), key.GetType()) index += 1 self.child_list.append(child) def has_children(self): return bool(self.num_children()) class SetCharChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.ty = self.value.target.FindFirstType("char") self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.child_list) def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): return self.child_list[index] def update(self): self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return cur_pos = 0 for child in self.value.children: child_val = child.signed if child_val != 0: temp = child_val num_bits = 8 while temp != 0: is_set = temp & 1 if is_set == 1: data = lldb.SBData.CreateDataFromInt(cur_pos) child = self.value.synthetic_child_from_data(f"[{len(self.child_list)}]", data, self.ty) self.child_list.append(child) temp = temp >> 1 cur_pos += 1 num_bits -= 1 cur_pos += num_bits else: cur_pos += 8 def has_children(self): return bool(self.num_children()) def create_set_children(value: lldb.SBValue, child_type: lldb.SBType, starting_pos: int) -> list[lldb.SBValue]: child_list: list[lldb.SBValue] = [] cur_pos = starting_pos if value.num_children > 0: children = value.children else: children = [value] for child in children: child_val = child.signed if child_val != 0: temp = child_val num_bits = 8 while temp != 0: is_set = temp & 1 if is_set == 1: data = lldb.SBData.CreateDataFromInt(cur_pos) child = value.synthetic_child_from_data(f"[{len(child_list)}]", data, child_type) child_list.append(child) temp = temp >> 1 cur_pos += 1 num_bits -= 1 cur_pos += num_bits else: cur_pos += 8 return child_list class SetIntChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.ty = self.value.target.FindFirstType(f"NI64") self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.child_list) def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): return self.child_list[index] def update(self): self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return bits = self.value.GetByteSize() * 8 starting_pos = -(bits // 2) self.child_list = create_set_children(self.value, self.ty, starting_pos) def has_children(self): return bool(self.num_children()) class SetUIntChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.ty = self.value.target.FindFirstType(f"NU64") self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.child_list) def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): return self.child_list[index] def update(self): self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return self.child_list = create_set_children(self.value, self.ty, starting_pos=0) def has_children(self): return bool(self.num_children()) class SetEnumChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.ty = self.value.target.FindFirstType(self.value.type.name.replace("tySet_", "")) self.child_list: list[lldb.SBValue] = [] self.update() def num_children(self): return len(self.child_list) def get_child_index(self, name: str): return int(name.lstrip("[").rstrip("]")) def get_child_at_index(self, index): return self.child_list[index] def update(self): if is_local(self.value): if not is_in_scope(self.value): return self.child_list = create_set_children(self.value, self.ty, starting_pos=0) def has_children(self): return bool(self.num_children()) class TableChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.children: OrderedDict[str, int] = OrderedDict() self.child_list: list[lldb.SBValue] = [] 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 get_data(self) -> lldb.SBValue: return self.value["data"]["p"]["data"] if NIM_IS_V2 else self.value["data"]["data"] def get_len(self) -> lldb.SBValue: return self.value["data"]["len"] if NIM_IS_V2 else self.value["data"]["Sup"]["len"] def update(self): self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return tuple_len = int(self.get_len().unsigned) tuple = self.get_data() base_data_type = tuple.type.GetArrayElementType() cast = tuple.Cast(base_data_type.GetArrayType(tuple_len)) index = 0 for i in range(tuple_len): el = cast[i] field0 = int(el[0].unsigned) if field0 == 0: continue key = el[1] val = el[2] key_summary = key.summary child = self.value.CreateValueFromAddress(key_summary, val.GetLoadAddress(), val.GetType()) self.child_list.append(child) self.children[key_summary] = index index += 1 def has_children(self): return bool(self.num_children()) class StringTableChildrenProvider: def __init__(self, value: lldb.SBValue, internalDict): self.value = value self.children: OrderedDict[str, int] = OrderedDict() self.child_list: list[lldb.SBValue] = [] 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 get_data(self) -> lldb.SBValue: return self.value["data"]["p"]["data"] if NIM_IS_V2 else self.value["data"]["data"] def get_len(self) -> lldb.SBValue: return self.value["data"]["len"] if NIM_IS_V2 else self.value["data"]["Sup"]["len"] def update(self): self.children.clear() self.child_list = [] if is_local(self.value): if not is_in_scope(self.value): return tuple_len = int(self.get_len().unsigned) tuple = self.get_data() base_data_type = tuple.type.GetArrayElementType() cast = tuple.Cast(base_data_type.GetArrayType(tuple_len)) index = 0 for i in range(tuple_len): el = cast[i] field0 = int(el[2].unsigned) if field0 == 0: continue key = el[0] val = el[1] child = val.CreateValueFromAddress(key.summary, val.GetLoadAddress(), val.GetType()) self.child_list.append(child) self.children[key.summary] = index index += 1 self.child_list.append(self.value["mode"]) self.children["mode"] = index def has_children(self): return bool(self.num_children()) class LLDBDynamicObjectProvider: def __init__(self, value: lldb.SBValue, internalDict): 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: return self.value.num_children return 0 def get_child_index(self, name: str): return self.value.GetIndexOfChildWithName(name) def get_child_at_index(self, index): return self.value.GetChildAtIndex(index) def update(self): pass def has_children(self): 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) SUMMARY_FUNCTIONS: dict[str, lldb.SBFunction] = {} SYNTHETIC_FUNCTIONS: dict[str, lldb.SBFunction] = {} def get_custom_summary(value: lldb.SBValue) -> Union[str, None]: """Get a custom summary if a function exists for it""" value = value.GetNonSyntheticValue() if value.GetAddress().GetOffset() == 0: return None base_type = get_base_type(value.type) fn = SUMMARY_FUNCTIONS.get(base_type.name) if fn is None: return None fn_type: lldb.SBType = fn.type arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes() first_type = arg_types.GetTypeAtIndex(0) while value.type.is_pointer: value = value.Dereference() if first_type.is_pointer: command = f"{fn.name}(({first_type.name})" + str(value.GetLoadAddress()) + ");" else: command = f"{fn.name}(*({first_type.GetPointerType().name})" + str(value.GetLoadAddress()) + ");" res = executeCommand(command) if res.error.fail: return None return res.summary.strip('"') def get_custom_value_summary(value: lldb.SBValue) -> Union[str, None]: """Get a custom summary if a function exists for it""" fn: lldb.SBFunction = SUMMARY_FUNCTIONS.get(value.type.name) if fn is None: return None command = f"{fn.name}(({value.type.name})" + str(value.signed) + ");" res = executeCommand(command) if res.error.fail: return None return res.summary.strip('"') def get_custom_synthetic(value: lldb.SBValue) -> Union[lldb.SBValue, None]: """Get a custom synthetic object if a function exists for it""" value = value.GetNonSyntheticValue() if value.GetAddress().GetOffset() == 0: return None base_type = get_base_type(value.type) fn = SYNTHETIC_FUNCTIONS.get(base_type.name) if fn is None: return None fn_type: lldb.SBType = fn.type arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes() first_type = arg_types.GetTypeAtIndex(0) while value.type.is_pointer: value = value.Dereference() if first_type.is_pointer: first_arg = f"({first_type.name}){value.GetLoadAddress()}" else: 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 def get_base_type(ty: lldb.SBType) -> lldb.SBType: """Get the base type of the type""" temp = ty while temp.IsPointerType(): temp = temp.GetPointeeType() return temp def use_base_type(ty: lldb.SBType) -> bool: types_to_check = [ "NF", "NF32", "NF64", "NI", "NI8", "NI16", "NI32", "NI64", "bool", "NIM_BOOL", "NU", "NU8", "NU16", "NU32", "NU64", ] for type_to_check in types_to_check: if ty.name.startswith(type_to_check): return False return True def breakpoint_function_wrapper(frame: lldb.SBFrame, bp_loc, internal_dict): """This allows function calls to Nim for custom object summaries and synthetic children""" debugger = lldb.debugger global SUMMARY_FUNCTIONS global SYNTHETIC_FUNCTIONS global NIM_IS_V2 for tname, fn in SYNTHETIC_FUNCTIONS.items(): debugger.HandleCommand(f"type synthetic delete -w nim {tname}") SUMMARY_FUNCTIONS = {} SYNTHETIC_FUNCTIONS = {} target: lldb.SBTarget = debugger.GetSelectedTarget() NIM_IS_V2 = target.FindFirstType("TNimTypeV2").IsValid() module = frame.GetSymbolContext(lldb.eSymbolContextModule).module for sym in module: if ( not sym.name.startswith("lldbDebugSummary") and not sym.name.startswith("lldbDebugSynthetic") and not sym.name.startswith("dollar___") ): continue fn_syms: lldb.SBSymbolContextList = target.FindFunctions(sym.name) if not fn_syms.GetSize() > 0: continue fn_sym: lldb.SBSymbolContext = fn_syms.GetContextAtIndex(0) fn: lldb.SBFunction = fn_sym.function fn_type: lldb.SBType = fn.type arg_types: lldb.SBTypeList = fn_type.GetFunctionArgumentTypes() 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) if use_base_type(arg_type): arg_type = get_base_type(arg_type) if sym.name.startswith("lldbDebugSummary") or sym.name.startswith("dollar___"): SUMMARY_FUNCTIONS[arg_type.name] = fn elif sym.name.startswith("lldbDebugSynthetic"): SYNTHETIC_FUNCTIONS[arg_type.name] = fn debugger.HandleCommand( f"type synthetic add -w nim -l {__name__}.CustomObjectChildrenProvider {arg_type.name}" ) def executeCommand(command, *args): debugger = lldb.debugger process = debugger.GetSelectedTarget().GetProcess() frame: lldb.SBFrame = process.GetSelectedThread().GetSelectedFrame() expr_options = lldb.SBExpressionOptions() expr_options.SetIgnoreBreakpoints(False) expr_options.SetFetchDynamicValue(lldb.eDynamicCanRunTarget) expr_options.SetTimeoutInMicroSeconds(30 * 1000 * 1000) # 30 second timeout expr_options.SetTryAllThreads(True) expr_options.SetUnwindOnError(False) expr_options.SetGenerateDebugInfo(True) expr_options.SetLanguage(lldb.eLanguageTypeC) expr_options.SetCoerceResultToId(True) res = frame.EvaluateExpression(command, expr_options) return res def __lldb_init_module(debugger, internal_dict): # fmt: off debugger.HandleCommand(f"breakpoint command add -F {__name__}.breakpoint_function_wrapper --script-type python 1") debugger.HandleCommand(f"type summary add -w nim -n sequence -F {__name__}.Sequence -x tySequence_+[[:alnum:]]+$") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SeqChildrenProvider -x tySequence_+[[:alnum:]]+$") debugger.HandleCommand(f"type summary add -w nim -n chararray -F {__name__}.CharArray -x char\s+[\d+]") debugger.HandleCommand(f"type summary add -w nim -n array -F {__name__}.Array -x tyArray_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.ArrayChildrenProvider -x tyArray_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n string -F {__name__}.NimString NimStringDesc") debugger.HandleCommand(f"type summary add -w nim -n stringv2 -F {__name__}.NimString -x NimStringV2$") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.StringChildrenProvider -x NimStringV2$") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.StringChildrenProvider -x NimStringDesc$") debugger.HandleCommand(f"type summary add -w nim -n cstring -F {__name__}.NCSTRING NCSTRING") debugger.HandleCommand(f"type summary add -w nim -n object -F {__name__}.ObjectV2 -x tyObject_+[[:alnum:]]+_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.ObjectChildrenProvider -x tyObject_+[[:alnum:]]+_+[[:alnum:]]+$") debugger.HandleCommand(f"type summary add -w nim -n tframe -F {__name__}.ObjectV2 -x TFrame$") debugger.HandleCommand(f"type summary add -w nim -n rootobj -F {__name__}.ObjectV2 -x RootObj$") debugger.HandleCommand(f"type summary add -w nim -n enum -F {__name__}.Enum -x tyEnum_+[[:alnum:]]+_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n hashset -F {__name__}.HashSet -x tyObject_+HashSet_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.HashSetChildrenProvider -x tyObject_+HashSet_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n rope -F {__name__}.Rope -x tyObject_+Rope[[:alnum:]]+_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n setuint -F {__name__}.Set -x tySet_+tyInt_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SetIntChildrenProvider -x tySet_+tyInt[0-9]+_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n setint -F {__name__}.Set -x tySet_+tyInt[0-9]+_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n setuint2 -F {__name__}.Set -x tySet_+tyUInt[0-9]+_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SetUIntChildrenProvider -x tySet_+tyUInt[0-9]+_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SetUIntChildrenProvider -x tySet_+tyInt_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n setenum -F {__name__}.EnumSet -x tySet_+tyEnum_+[[:alnum:]]+_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SetEnumChildrenProvider -x tySet_+tyEnum_+[[:alnum:]]+_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n setchar -F {__name__}.Set -x tySet_+tyChar_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.SetCharChildrenProvider -x tySet_+tyChar_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n table -F {__name__}.Table -x tyObject_+Table_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.TableChildrenProvider -x tyObject_+Table_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n stringtable -F {__name__}.StringTable -x tyObject_+StringTableObj_+[[:alnum:]]+") debugger.HandleCommand(f"type synthetic add -w nim -l {__name__}.StringTableChildrenProvider -x tyObject_+StringTableObj_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n tuple2 -F {__name__}.Tuple -x tyObject_+Tuple_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n tuple -F {__name__}.Tuple -x tyTuple_+[[:alnum:]]+") debugger.HandleCommand(f"type summary add -w nim -n float -F {__name__}.Float NF") debugger.HandleCommand(f"type summary add -w nim -n float32 -F {__name__}.Float NF32") debugger.HandleCommand(f"type summary add -w nim -n float64 -F {__name__}.Float NF64") debugger.HandleCommand(f"type summary add -w nim -n integer -F {__name__}.Number -x NI") debugger.HandleCommand(f"type summary add -w nim -n integer8 -F {__name__}.Number -x NI8") debugger.HandleCommand(f"type summary add -w nim -n integer16 -F {__name__}.Number -x NI16") debugger.HandleCommand(f"type summary add -w nim -n integer32 -F {__name__}.Number -x NI32") debugger.HandleCommand(f"type summary add -w nim -n integer64 -F {__name__}.Number -x NI64") debugger.HandleCommand(f"type summary add -w nim -n bool -F {__name__}.Bool -x bool") debugger.HandleCommand(f"type summary add -w nim -n bool2 -F {__name__}.Bool -x NIM_BOOL") debugger.HandleCommand(f"type summary add -w nim -n uinteger -F {__name__}.UnsignedNumber -x NU") debugger.HandleCommand(f"type summary add -w nim -n uinteger8 -F {__name__}.UnsignedNumber -x NU8") debugger.HandleCommand(f"type summary add -w nim -n uinteger16 -F {__name__}.UnsignedNumber -x NU16") debugger.HandleCommand(f"type summary add -w nim -n uinteger32 -F {__name__}.UnsignedNumber -x NU32") debugger.HandleCommand(f"type summary add -w nim -n uinteger64 -F {__name__}.UnsignedNumber -x NU64") debugger.HandleCommand("type category enable nim") debugger.HandleCommand(f"command script add -f {__name__}.echo echo") # fmt: on