summary refs log tree commit diff stats
path: root/tools/debug
diff options
context:
space:
mode:
Diffstat (limited to 'tools/debug')
-rw-r--r--tools/debug/customdebugtype.nim72
-rw-r--r--tools/debug/nim-gdb.py697
-rw-r--r--tools/debug/nimlldb.py1380
3 files changed, 2149 insertions, 0 deletions
diff --git a/tools/debug/customdebugtype.nim b/tools/debug/customdebugtype.nim
new file mode 100644
index 000000000..f48979661
--- /dev/null
+++ b/tools/debug/customdebugtype.nim
@@ -0,0 +1,72 @@
+## This is a demo file containing an example of how to
+## create custom LLDB summaries and objects with synthetic
+## children. These are implemented in Nim and called from the Python
+## nimlldb.py module.
+##
+## For summaries, prefix your proc names with "lldbDebugSummary", use
+## the `{.exportc.}` pragma, and return a string. Also, any `$` proc
+## that is available will be used for a given type.
+##
+## For creating a synthetic object (LLDB will display the children), use
+## the prefix "lldbDebugSynthetic", use the `{.exportc.}` pragma, and
+## 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
+## 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: <int_val>)" as a summary
+  result = "CustomType" & $ty
+
+proc lldbDebugSyntheticCustomType*(ty: CustomType): CustomSyntheticReturn {.exportc.} =
+  ## 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
+
+proc lldbDebugSyntheticIntSet*(intset: IntSet): seq[int] {.exportc.} =
+  ## This will create a synthetic object to make it so that IntSet
+  ## 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:
+    result.add(val)
diff --git a/tools/debug/nim-gdb.py b/tools/debug/nim-gdb.py
new file mode 100644
index 000000000..8c9854bda
--- /dev/null
+++ b/tools/debug/nim-gdb.py
@@ -0,0 +1,697 @@
+import gdb
+import re
+import sys
+import traceback
+
+# some feedback that the nim runtime support is loading, isn't a bad
+# thing at all.
+gdb.write("Loading Nim Runtime support.\n", gdb.STDERR)
+
+# When error occure they occur regularly. This 'caches' known errors
+# and prevents them from being reprinted over and over again.
+errorSet = set()
+def printErrorOnce(id, message):
+  global errorSet
+  if id not in errorSet:
+    errorSet.add(id)
+    gdb.write("printErrorOnce: " + message, gdb.STDERR)
+
+def debugPrint(x):
+  gdb.write(str(x) + "\n", gdb.STDERR)
+
+NIM_STRING_TYPES = ["NimStringDesc", "NimStringV2"]
+
+################################################################################
+#####  Type pretty printers
+################################################################################
+
+type_hash_regex = re.compile("^([A-Za-z0-9]*)_([A-Za-z0-9]*)_+([A-Za-z0-9]*)$")
+
+def getNimName(typ):
+  if m := type_hash_regex.match(typ):
+    return m.group(2)
+  return f"unknown <{typ}>"
+
+def getNimRti(type_name):
+  """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """
+
+  # Get static const TNimType variable. This should be available for
+  # every non trivial Nim type.
+  m = type_hash_regex.match(type_name)
+  if m:
+    lookups = [
+      "NTI" + m.group(2).lower() + "__" + m.group(3) + "_",
+      "NTI" + "__" + m.group(3) + "_",
+      "NTI" + m.group(2).replace("colon", "58").lower() + "__" + m.group(3) + "_"
+      ]
+    for l in lookups:
+      try:
+        return gdb.parse_and_eval(l)
+      except:
+        pass
+  None
+
+def getNameFromNimRti(rti):
+  """ Return name (or None) given a Nim RTI ``gdb.Value`` """
+  try:
+    # sometimes there isn't a name field -- example enums
+    return rti['name'].string(encoding="utf-8", errors="ignore")
+  except:
+    return None
+
+class NimTypeRecognizer:
+  # this type map maps from types that are generated in the C files to
+  # how they are called in nim. To not mix up the name ``int`` from
+  # system.nim with the name ``int`` that could still appear in
+  # generated code, ``NI`` is mapped to ``system.int`` and not just
+  # ``int``.
+
+  type_map_static = {
+    'NI': 'system.int',  'NI8': 'int8', 'NI16': 'int16',  'NI32': 'int32',
+    'NI64': 'int64',
+    
+    'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32',
+    'NU64': 'uint64',
+    
+    'NF': 'float', 'NF32': 'float32', 'NF64': 'float64',
+    
+    'NIM_BOOL': 'bool',
+
+    'NIM_CHAR': 'char', 'NCSTRING': 'cstring', 'NimStringDesc': 'string', 'NimStringV2': 'string'
+  }
+
+  # object_type_pattern = re.compile("^(\w*):ObjectType$")
+
+  def recognize(self, type_obj):
+    # skip things we can't handle like functions
+    if type_obj.code in [gdb.TYPE_CODE_FUNC, gdb.TYPE_CODE_VOID]:
+      return None
+
+    tname = None
+    if type_obj.tag is not None:
+      tname = type_obj.tag
+    elif type_obj.name is not None:
+      tname = type_obj.name
+
+    # handle pointer types
+    if not tname:
+      target_type = type_obj
+      if type_obj.code in [gdb.TYPE_CODE_PTR]:
+        target_type = type_obj.target()
+
+      if target_type.name:
+        # visualize 'string' as non pointer type (unpack pointer type).
+        if target_type.name == "NimStringDesc":
+          tname = target_type.name # could also just return 'string'
+        else:
+          rti = getNimRti(target_type.name)
+          if rti:
+            return getNameFromNimRti(rti)
+
+    if tname:
+      result = self.type_map_static.get(tname, None)
+      if result:
+        return result
+      elif tname.startswith("tyEnum_"):
+        return getNimName(tname)
+      elif tname.startswith("tyTuple__"):
+        # We make the name be the field types (Just like in Nim)
+        fields = ", ".join([self.recognize(field.type) for field in type_obj.fields()])
+        return f"({fields})"
+
+      rti = getNimRti(tname)
+      if rti:
+        return getNameFromNimRti(rti)
+
+    return None
+
+class NimTypePrinter:
+  """Nim type printer. One printer for all Nim types."""
+
+  # enabling and disabling of type printers can be done with the
+  # following gdb commands:
+  #
+  #   enable  type-printer NimTypePrinter
+  #   disable type-printer NimTypePrinter
+  # relevant docs: https://sourceware.org/gdb/onlinedocs/gdb/Type-Printing-API.html
+
+  name = "NimTypePrinter"
+
+  def __init__(self):
+    self.enabled = True
+
+  def instantiate(self):
+    return NimTypeRecognizer()
+
+################################################################################
+#####  GDB Function, equivalent of Nim's $ operator
+################################################################################
+
+class DollarPrintFunction (gdb.Function):
+  "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)"
+
+  dollar_functions = re.findall(
+    r'(?:NimStringDesc \*|NimStringV2)\s?(dollar__[A-z0-9_]+?)\(([^,)]*)\);',
+    gdb.execute("info functions dollar__", True, True)
+  )
+
+  def __init__ (self):
+    super (DollarPrintFunction, self).__init__("dollar")
+
+
+  @staticmethod
+  def invoke_static(arg, ignore_errors = False):
+    if arg.type.code == gdb.TYPE_CODE_PTR and arg.type.target().name in NIM_STRING_TYPES:
+      return arg
+    argTypeName = str(arg.type)
+    for func, arg_typ in DollarPrintFunction.dollar_functions:
+      # this way of overload resolution cannot deal with type aliases,
+      # therefore it won't find all overloads.
+      if arg_typ == argTypeName:
+        func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTION_DOMAIN).value()
+        return func_value(arg)
+
+      elif arg_typ == argTypeName + " *":
+        func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTION_DOMAIN).value()
+        return func_value(arg.address)
+
+    if not ignore_errors:
+      debugPrint(f"No suitable Nim $ operator found for type: {getNimName(argTypeName)}\n")
+    return None
+
+  def invoke(self, arg):
+    return self.invoke_static(arg)
+
+DollarPrintFunction()
+
+
+################################################################################
+#####  GDB Function, Nim string comparison
+################################################################################
+
+class NimStringEqFunction (gdb.Function):
+  """Compare Nim strings for example in conditionals for breakpoints."""
+
+  def __init__ (self):
+    super (NimStringEqFunction, self).__init__("nimstreq")
+
+  @staticmethod
+  def invoke_static(arg1,arg2):
+    if arg1.type.code == gdb.TYPE_CODE_PTR and arg1.type.target().name in NIM_STRING_TYPES:
+      str1 = NimStringPrinter(arg1).to_string()
+    else:
+      str1 = arg1.string()
+    if arg2.type.code == gdb.TYPE_CODE_PTR and arg2.type.target().name in NIM_STRING_TYPES:
+      str2 = NimStringPrinter(arg1).to_string()
+    else:
+      str2 = arg2.string()
+
+    return str1 == str2
+
+  def invoke(self, arg1, arg2):
+    return self.invoke_static(arg1, arg2)
+
+NimStringEqFunction()
+
+
+################################################################################
+#####  GDB Command, equivalent of Nim's $ operator
+################################################################################
+
+class DollarPrintCmd (gdb.Command):
+  """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator and print the result."""
+
+  def __init__ (self):
+    super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
+
+  def invoke(self, arg, from_tty):
+    param = gdb.parse_and_eval(arg)
+    strValue = DollarPrintFunction.invoke_static(param)
+    if strValue:
+      gdb.write(
+        str(NimStringPrinter(strValue)) + "\n",
+        gdb.STDOUT
+      )
+
+    # could not find a suitable dollar overload. This here is the
+    # fallback to get sensible output of basic types anyway.
+
+    elif param.type.code == gdb.TYPE_CODE_ARRAY and param.type.target().name == "char":
+      gdb.write(param.string("utf-8", "ignore") + "\n", gdb.STDOUT)
+    elif param.type.code == gdb.TYPE_CODE_INT:
+      gdb.write(str(int(param)) + "\n", gdb.STDOUT)
+    elif param.type.name == "NIM_BOOL":
+      if int(param) != 0:
+        gdb.write("true\n", gdb.STDOUT)
+      else:
+        gdb.write("false\n", gdb.STDOUT)
+
+DollarPrintCmd()
+
+
+################################################################################
+#####  GDB Commands to invoke common nim tools.
+################################################################################
+
+
+import subprocess, os
+
+
+class KochCmd (gdb.Command):
+  """Command that invokes ``koch'', the build tool for the compiler."""
+
+  def __init__ (self):
+    super (KochCmd, self).__init__ ("koch",
+                                    gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
+    self.binary = os.path.join(
+      os.path.dirname(os.path.dirname(__file__)), "koch")
+
+  def invoke(self, argument, from_tty):
+    subprocess.run([self.binary] + gdb.string_to_argv(argument))
+
+KochCmd()
+
+
+class NimCmd (gdb.Command):
+  """Command that invokes ``nim'', the nim compiler."""
+
+  def __init__ (self):
+    super (NimCmd, self).__init__ ("nim",
+                                   gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
+    self.binary = os.path.join(
+      os.path.dirname(os.path.dirname(__file__)), "bin/nim")
+
+  def invoke(self, argument, from_tty):
+    subprocess.run([self.binary] + gdb.string_to_argv(argument))
+
+NimCmd()
+
+
+class NimbleCmd (gdb.Command):
+  """Command that invokes ``nimble'', the nim package manager and build tool."""
+
+  def __init__ (self):
+    super (NimbleCmd, self).__init__ ("nimble",
+                                      gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
+    self.binary = os.path.join(
+      os.path.dirname(os.path.dirname(__file__)), "bin/nimble")
+
+  def invoke(self, argument, from_tty):
+    subprocess.run([self.binary] + gdb.string_to_argv(argument))
+
+NimbleCmd()
+
+################################################################################
+#####  Value pretty printers
+################################################################################
+
+class NimBoolPrinter:
+
+  pattern = re.compile(r'^NIM_BOOL$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def to_string(self):
+    if self.val == 0:
+      return "false"
+    else:
+      return "true"
+
+################################################################################
+
+def strFromLazy(strVal):
+  if isinstance(strVal, str):
+    return strVal
+  else:
+    return strVal.value().string("utf-8")
+
+class NimStringPrinter:
+  pattern = re.compile(r'^(NimStringDesc \*|NimStringV2)$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'string'
+
+  def to_string(self):
+    if self.val:
+      if self.val.type.name == "NimStringV2":
+        l = int(self.val["len"])
+        data = self.val["p"]["data"]
+      else:
+        l = int(self.val['Sup']['len'])
+        data = self.val["data"]
+      return data.lazy_string(encoding="utf-8", length=l)
+    else:
+      return ""
+
+  def __str__(self):
+    return strFromLazy(self.to_string())
+
+class NimRopePrinter:
+  pattern = re.compile(r'^tyObject_RopeObj__([A-Za-z0-9]*) \*$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'string'
+
+  def to_string(self):
+    if self.val:
+      left  = NimRopePrinter(self.val["left"]).to_string()
+      data  = NimStringPrinter(self.val["data"]).to_string()
+      right = NimRopePrinter(self.val["right"]).to_string()
+      return left + data + right
+    else:
+      return ""
+
+
+################################################################################
+
+def reprEnum(e, typ):
+  # Casts the value to the enum type and then calls the enum printer
+  e = int(e)
+  val = gdb.Value(e).cast(typ)
+  return strFromLazy(NimEnumPrinter(val).to_string())
+
+def enumNti(typeNimName, idString):
+  typeInfoName = "NTI" + typeNimName.lower() + "__" + idString + "_"
+  nti = gdb.lookup_global_symbol(typeInfoName)
+  if nti is None:
+    typeInfoName = "NTI" + "__" + idString + "_"
+    nti = gdb.lookup_global_symbol(typeInfoName)
+  return (typeInfoName, nti)
+
+class NimEnumPrinter:
+  pattern = re.compile(r'^tyEnum_([A-Za-z0-9]+)__([A-Za-z0-9]*)$')
+  enumReprProc = gdb.lookup_global_symbol("reprEnum", gdb.SYMBOL_FUNCTION_DOMAIN)
+
+  def __init__(self, val):
+    self.val = val
+    typeName = self.val.type.name
+    match = self.pattern.match(typeName)
+    self.typeNimName  = match.group(1)
+    typeInfoName, self.nti = enumNti(self.typeNimName, match.group(2))
+
+  def to_string(self):
+    if NimEnumPrinter.enumReprProc and self.nti:
+      # Use the old runtimes enumRepr function.
+      # We call the Nim proc itself so that the implementation is correct
+      f = gdb.newest_frame()
+      # We need to strip the quotes so it looks like an enum instead of a string
+      reprProc = NimEnumPrinter.enumReprProc.value()
+      return str(reprProc(self.val, self.nti.value(f).address)).strip('"')
+    elif dollarResult := DollarPrintFunction.invoke_static(self.val):
+      # New runtime doesn't use enumRepr so we instead try and call the
+      # dollar function for it
+      return str(NimStringPrinter(dollarResult))
+    else:
+      return self.typeNimName + "(" + str(int(self.val)) + ")"
+
+################################################################################
+
+class NimSetPrinter:
+  ## the set printer is limited to sets that fit in an integer.  Other
+  ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to
+  ## gdb (currently).
+  pattern = re.compile(r'^tySet_tyEnum_([A-Za-z0-9]+)__([A-Za-z0-9]*)$')
+
+  def __init__(self, val):
+    self.val = val
+    typeName = self.val.type.name
+    match = self.pattern.match(typeName)
+    self.typeNimName = match.group(1)
+
+  def to_string(self):
+    # Remove the tySet from the type name
+    typ = gdb.lookup_type(self.val.type.name[6:])
+    enumStrings = []
+    val = int(self.val)
+    i   = 0
+    while val > 0:
+      if (val & 1) == 1:
+        enumStrings.append(reprEnum(i, typ))
+      val = val >> 1
+      i += 1
+
+    return '{' + ', '.join(enumStrings) + '}'
+
+################################################################################
+
+class NimHashSetPrinter:
+  pattern = re.compile(r'^tyObject_(HashSet)__([A-Za-z0-9]*)$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'array'
+
+  def to_string(self):
+    counter  = 0
+    capacity = 0
+    if self.val:
+      counter  = int(self.val['counter'])
+      if self.val['data']:
+        capacity = int(self.val['data']['Sup']['len'])
+
+    return 'HashSet({0}, {1})'.format(counter, capacity)
+
+  def children(self):
+    if self.val:
+      data = NimSeqPrinter(self.val['data'])
+      for idxStr, entry in data.children():
+        if int(entry['Field0']) > 0:
+          yield ("data." + idxStr + ".Field1", str(entry['Field1']))
+
+################################################################################
+
+class NimSeq:
+  # Wrapper around sequences.
+  # This handles the differences between old and new runtime
+
+  def __init__(self, val):
+    self.val = val
+    # new runtime has sequences on stack, old has them on heap
+    self.new = val.type.code != gdb.TYPE_CODE_PTR
+    if self.new:
+      # Some seqs are just the content and to save repeating ourselves we do
+      # handle them here. Only thing that needs to check this is the len/data getters
+      self.isContent = val.type.name.endswith("Content")
+
+  def __bool__(self):
+    if self.new:
+      return self.val is not None
+    else:
+      return bool(self.val)
+
+  def __len__(self):
+    if not self:
+      return 0
+    if self.new:
+      if self.isContent:
+        return int(self.val["cap"])
+      else:
+        return int(self.val["len"])
+    else:
+      return self.val["Sup"]["len"]
+
+  @property
+  def data(self):
+    if self.new:
+      if self.isContent:
+        return self.val["data"]
+      elif self.val["p"]:
+        return self.val["p"]["data"]
+    else:
+      return self.val["data"]
+
+  @property
+  def cap(self):
+    if not self:
+      return 0
+    if self.new:
+      if self.isContent:
+        return int(self.val["cap"])
+      elif self.val["p"]:
+        return int(self.val["p"]["cap"])
+      else:
+        return 0
+    return int(self.val['Sup']['reserved'])
+
+class NimSeqPrinter:
+  pattern = re.compile(r'^tySequence_\w*\s?\*?$')
+
+  def __init__(self, val):
+    self.val = NimSeq(val)
+
+
+  def display_hint(self):
+    return 'array'
+
+  def to_string(self):
+    return f'seq({len(self.val)}, {self.val.cap})'
+
+  def children(self):
+    if self.val:
+      val = self.val
+      length = len(val)
+
+      if length <= 0:
+        return
+
+      data = val.data
+
+      inaccessible = False
+      for i in range(length):
+        if inaccessible:
+          return
+        try:
+          str(data[i])
+          yield "data[{0}]".format(i), data[i]
+        except RuntimeError:
+          inaccessible = True
+          yield "data[{0}]".format(i), "inaccessible"
+      
+################################################################################
+
+class NimArrayPrinter:
+  pattern = re.compile(r'^tyArray_\w*$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'array'
+
+  def to_string(self):
+    return 'array'
+
+  def children(self):
+    length = self.val.type.sizeof // self.val[0].type.sizeof
+    align = len(str(length-1))
+    for i in range(length):
+      yield ("[{0:>{1}}]".format(i, align), self.val[i])
+
+################################################################################
+
+class NimStringTablePrinter:
+  pattern = re.compile(r'^tyObject_(StringTableObj)__([A-Za-z0-9]*)(:? \*)?$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'map'
+
+  def to_string(self):
+    counter  = 0
+    capacity = 0
+    if self.val:
+      counter  = int(self.val['counter'])
+      if self.val['data']:
+        capacity = int(self.val['data']['Sup']['len'])
+
+    return 'StringTableObj({0}, {1})'.format(counter, capacity)
+
+  def children(self):
+    if self.val:
+      data = NimSeqPrinter(self.val['data'].referenced_value())
+      for idxStr, entry in data.children():
+        if int(entry['Field0']) != 0:
+          yield (idxStr + ".Field0", entry['Field0'])
+          yield (idxStr + ".Field1", entry['Field1'])
+
+################################################################
+
+class NimTablePrinter:
+  pattern = re.compile(r'^tyObject_(Table)__([A-Za-z0-9]*)(:? \*)?$')
+
+  def __init__(self, val):
+    self.val = val
+
+  def display_hint(self):
+    return 'map'
+
+  def to_string(self):
+    counter  = 0
+    capacity = 0
+    if self.val:
+      counter  = int(self.val['counter'])
+      if self.val['data']:
+        capacity = NimSeq(self.val["data"]).cap
+
+    return 'Table({0}, {1})'.format(counter, capacity)
+
+  def children(self):
+    if self.val:
+      data = NimSeqPrinter(self.val['data'])
+      for idxStr, entry in data.children():
+        if int(entry['Field0']) != 0:
+          yield (idxStr + '.Field1', entry['Field1'])
+          yield (idxStr + '.Field2', entry['Field2'])
+
+################################################################################
+
+class NimTuplePrinter:
+  pattern = re.compile(r"^tyTuple__([A-Za-z0-9]*)")
+
+  def __init__(self, val):
+    self.val = val
+
+  def to_string(self):
+    # We don't have field names so just print out the tuple as if it was anonymous
+    tupleValues = [str(self.val[field.name]) for field in self.val.type.fields()]
+    return f"({', '.join(tupleValues)})"
+
+################################################################################
+
+class NimFrameFilter:
+  def __init__(self):
+    self.name = "nim-frame-filter"
+    self.enabled = True
+    self.priority = 100
+    self.hidden =  {"NimMainInner","NimMain", "main"}
+
+  def filter(self, iterator):
+    for framedecorator in iterator:
+      if framedecorator.function() not in self.hidden:
+        yield framedecorator
+
+################################################################################
+
+def makematcher(klass):
+  def matcher(val):
+    typeName = str(val.type)
+    try:
+      if hasattr(klass, 'pattern') and hasattr(klass, '__name__'):
+        # print(typeName + " <> " + klass.__name__)
+        if klass.pattern.match(typeName):
+          return klass(val)
+    except Exception as e:
+      print(klass)
+      printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n")
+  return matcher
+
+def register_nim_pretty_printers_for_object(objfile):
+  nimMainSym = gdb.lookup_global_symbol("NimMain", gdb.SYMBOL_FUNCTION_DOMAIN)
+  if nimMainSym and nimMainSym.symtab.objfile == objfile:
+    print("set Nim pretty printers for ", objfile.filename)
+
+    gdb.types.register_type_printer(objfile, NimTypePrinter())
+    objfile.pretty_printers = [makematcher(var) for var in list(globals().values()) if hasattr(var, 'pattern')]
+
+# Register pretty printers for all objfiles that are already loaded.
+for old_objfile in gdb.objfiles():
+  register_nim_pretty_printers_for_object(old_objfile)
+
+# Register an event handler to register nim pretty printers for all future objfiles.
+def new_object_handler(event):
+  register_nim_pretty_printers_for_object(event.new_objfile)
+
+gdb.events.new_objfile.connect(new_object_handler)
+
+gdb.frame_filters = {"nim-frame-filter": NimFrameFilter()}
diff --git a/tools/debug/nimlldb.py b/tools/debug/nimlldb.py
new file mode 100644
index 000000000..4bc4e771f
--- /dev/null
+++ b/tools/debug/nimlldb.py
@@ -0,0 +1,1380 @@
+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