summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--tests/untestable/gdb/gdb_pretty_printer_test.py26
-rw-r--r--tests/untestable/gdb/gdb_pretty_printer_test_program.nim57
-rw-r--r--tools/nim-gdb.py151
3 files changed, 157 insertions, 77 deletions
diff --git a/tests/untestable/gdb/gdb_pretty_printer_test.py b/tests/untestable/gdb/gdb_pretty_printer_test.py
index f002941ec..5b34bcb3d 100644
--- a/tests/untestable/gdb/gdb_pretty_printer_test.py
+++ b/tests/untestable/gdb/gdb_pretty_printer_test.py
@@ -6,31 +6,47 @@ import gdb
 # frontends might still be broken.
 
 gdb.execute("source ../../../tools/nim-gdb.py")
-# debug all instances of the generic function `myDebug`, should be 8
+# debug all instances of the generic function `myDebug`, should be 14
 gdb.execute("rbreak myDebug")
 gdb.execute("run")
 
 outputs = [
   'meTwo',
+  '""',
   '"meTwo"',
   '{meOne, meThree}',
   'MyOtherEnum(1)',
   '5',
   'array = {1, 2, 3, 4, 5}',
+  'seq(0, 0)',
+  'seq(0, 10)',
+  'array = {"one", "two"}',
+  'seq(3, 3) = {1, 2, 3}',
   'seq(3, 3) = {"one", "two", "three"}',
-  'Table(3, 64) = {["two"] = 2, ["three"] = 3, ["one"] = 1}',
+  'Table(3, 64) = {[4] = "four", [5] = "five", [6] = "six"}',
+  'Table(3, 8) = {["two"] = 2, ["three"] = 3, ["one"] = 1}',
 ]
 
 for i, expected in enumerate(outputs):
+  gdb.write(f"{i+1}) expecting: {expected}: ", gdb.STDLOG)
+  gdb.flush()
+
   functionSymbol = gdb.selected_frame().block().function
   assert functionSymbol.line == 21
 
-  if i == 5:
+  if i == 6:
     # myArray is passed as pointer to int to myDebug. I look up myArray up in the stack
     gdb.execute("up")
-    output = str(gdb.parse_and_eval("myArray"))
+    raw = gdb.parse_and_eval("myArray")    
+  elif i == 9:
+    # myOtherArray is passed as pointer to int to myDebug. I look up myOtherArray up in the stack
+    gdb.execute("up")
+    raw = gdb.parse_and_eval("myOtherArray")
   else:
-    output = str(gdb.parse_and_eval("arg"))
+    raw = gdb.parse_and_eval("arg")
+
+  output = str(raw)
 
   assert output == expected, output + " != " + expected
+  gdb.write(f"passed\n", gdb.STDLOG)
   gdb.execute("continue")
diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_program.nim b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim
index 458435c1a..c376ef89a 100644
--- a/tests/untestable/gdb/gdb_pretty_printer_test_program.nim
+++ b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim
@@ -23,29 +23,56 @@ proc myDebug[T](arg: T): void =
 
 proc testProc(): void =
   var myEnum = meTwo
-  myDebug(myEnum)
+  myDebug(myEnum) #1
+  
+  # create a string, but don't allocate it
+  var myString: string
+  myDebug(myString) #2
+
   # create a string object but also make the NTI for MyEnum is generated
-  var myString = $myEnum
-  myDebug(myString)
+  myString = $myEnum
+  myDebug(myString) #3
+  
   var mySet = {meOne,meThree}
-  myDebug(mySet)
+  myDebug(mySet) #4
 
   # for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer.
   var moEnum = moTwo
-  myDebug(moEnum)
+  myDebug(moEnum) #5
+
   var moSet = {moOne,moThree}
-  myDebug(moSet)
+  myDebug(moSet) #6
 
   let myArray = [1,2,3,4,5]
-  myDebug(myArray)
-  let mySeq   = @["one","two","three"]
-  myDebug(mySeq)
-
-  var myTable = initTable[string, int]()
-  myTable["one"] = 1
-  myTable["two"] = 2
-  myTable["three"] = 3
-  myDebug(myTable)
+  myDebug(myArray) #7
+
+  # implicitly initialized seq test
+  var mySeq: seq[string]
+  myDebug(mySeq) #8
+
+  # len not equal to capacity
+  let myOtherSeq = newSeqOfCap[string](10)
+  myDebug(myOtherSeq) #9
+
+  let myOtherArray = ["one","two"]
+  myDebug(myOtherArray) #10
+
+  # numeric sec
+  var mySeq3 = @[1,2,3]
+  myDebug(mySeq3) #11
+
+  # seq had to grow
+  var mySeq4 = @["one","two","three"]
+  myDebug(mySeq4) #12
+
+  var myTable = initTable[int, string]()
+  myTable[4] = "four"
+  myTable[5] = "five"
+  myTable[6] = "six"
+  myDebug(myTable) #13
+
+  var myOtherTable = {"one": 1, "two": 2, "three": 3}.toTable
+  myDebug(myOtherTable) #14
 
   echo(counter)
 
diff --git a/tools/nim-gdb.py b/tools/nim-gdb.py
index e994531b6..8143b94d5 100644
--- a/tools/nim-gdb.py
+++ b/tools/nim-gdb.py
@@ -34,6 +34,14 @@ def getNimRti(type_name):
     except:
       return None
 
+def getNameFromNimRti(rti_val):
+  """ 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
@@ -42,30 +50,25 @@ class NimTypeRecognizer:
   # ``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',
+    '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'
+    
+    'NIM_BOOL': 'bool',
+
+    'NIM_CHAR': 'char', 'NCSTRING': 'cstring', 'NimStringDesc': 'string'
   }
 
-  # Normally gdb distinguishes between the command `ptype` and
-  # `whatis`.  `ptype` prints a very detailed view of the type, and
-  # `whatis` a very brief representation of the type. I haven't
-  # figured out a way to know from the type printer that is
-  # implemented here how to know if a type printer should print the
-  # short representation or the long representation.  As a hacky
-  # workaround I just say I am not resposible for printing pointer
-  # types (seq and string are exception as they are semantically
-  # values).  this way the default type printer will handle pointer
-  # types and dive into the members of that type.  So I can still
-  # control with `ptype myval` and `ptype *myval` if I want to have
-  # detail or not.  I this this method stinks but I could not figure
-  # out a better solution.
-
-  object_type_pattern = re.compile("^(\w*):ObjectType$")
+  # 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:
@@ -75,44 +78,43 @@ class NimTypeRecognizer:
 
     # handle pointer types
     if not tname:
-      if type_obj.code == gdb.TYPE_CODE_PTR:
+      target_type = type_obj
+      if type_obj.code in [gdb.TYPE_CODE_PTR]:
         target_type = type_obj.target()
-        target_type_name = target_type.name
-        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'
-          # visualize 'seq[T]' as non pointer type.
-          if target_type_name.find('tySequence_') == 0:
-            tname = target_type_name
 
-    if not tname:
-      # We are not resposible for this type printing.
-      # Basically this means we don't print pointer types.
-      return None
+      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)
 
-    result = self.type_map_static.get(tname, None)
-    if result:
-      return result
+    if tname:
+      result = self.type_map_static.get(tname, None)
+      if result:
+        return result
 
-    rti = getNimRti(tname)
-    if rti:
-      return rti['name'].string("utf-8", "ignore")
-    else:
-      return None
+      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):
+
+  def __init__(self):
     self.enabled = True
 
   def instantiate(self):
@@ -309,9 +311,25 @@ class NimStringPrinter:
   def to_string(self):
     if self.val:
       l = int(self.val['Sup']['len'])
-      return self.val['data'][0].address.string("utf-8", "ignore", l)
+      return self.val['data'].lazy_string(encoding="utf-8", length=l)
     else:
-     return ""
+      return ""
+
+# class NimStringPrinter:
+#   pattern = re.compile(r'^NimStringDesc$')
+
+#   def __init__(self, val):
+#     self.val = val
+  
+#   def display_hint(self):
+#     return 'string'
+  
+#   def to_string(self):
+#     if self.val:
+#       l = int(self.val['Sup']['len'])
+#       return self.val['data'].lazy_string(encoding="utf-8", length=l)
+#     else:
+#       return ""
 
 class NimRopePrinter:
   pattern = re.compile(r'^tyObject_RopeObj__([A-Za-z0-9]*) \*$')
@@ -372,14 +390,15 @@ class NimEnumPrinter:
   pattern = re.compile(r'^tyEnum_(\w*)__([A-Za-z0-9]*)$')
 
   def __init__(self, val):
-    self.val      = val
-    match = self.pattern.match(self.val.type.name)
+    self.val = val
+    typeName = self.val.type.name
+    match = self.pattern.match(typeName)
     self.typeNimName  = match.group(1)
     typeInfoName = "NTI__" + match.group(2) + "_"
     self.nti = gdb.lookup_global_symbol(typeInfoName)
 
     if self.nti is None:
-      printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n")
+      printErrorOnce(typeInfoName, f"NimEnumPrinter: lookup global symbol '{typeInfoName}' failed for {typeName}.\n")
 
   def to_string(self):
     if self.nti:
@@ -476,11 +495,31 @@ class NimSeqPrinter:
 
   def children(self):
     if self.val:
-      length = int(self.val['Sup']['len'])
-      #align = len(str(length - 1))
-      for i in range(length):
-        yield ("data[{0}]".format(i), self.val["data"][i])
+      val = self.val
+      valType = val.type
+      length = int(val['Sup']['len'])
 
+      if length <= 0:
+        return
+
+      dataType = valType['data'].type
+      data = val['data']
+
+      if self.val.type.name is None:
+        dataType = valType['data'].type.target().pointer()
+        data = val['data'].cast(dataType)
+
+      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:
@@ -524,9 +563,9 @@ class NimStringTablePrinter:
 
   def children(self):
     if self.val:
-      data = NimSeqPrinter(self.val['data'])
+      data = NimSeqPrinter(self.val['data'].dereference())
       for idxStr, entry in data.children():
-        if int(entry['Field2']) > 0:
+        if int(entry['Field0']) != 0:
           yield (idxStr + ".Field0", entry['Field0'])
           yield (idxStr + ".Field1", entry['Field1'])
 
@@ -537,7 +576,6 @@ class NimTablePrinter:
 
   def __init__(self, val):
     self.val = val
-    # match = self.pattern.match(self.val.type.name)
 
   def display_hint(self):
     return 'map'
@@ -556,11 +594,10 @@ class NimTablePrinter:
     if self.val:
       data = NimSeqPrinter(self.val['data'])
       for idxStr, entry in data.children():
-        if int(entry['Field0']) > 0:
+        if int(entry['Field0']) != 0:
           yield (idxStr + '.Field1', entry['Field1'])
           yield (idxStr + '.Field2', entry['Field2'])
 
-
 ################################################################
 
 # this is untested, therefore disabled
@@ -651,7 +688,7 @@ def register_nim_pretty_printers_for_object(objfile):
   if nimMainSym and nimMainSym.symtab.objfile == objfile:
     print("set Nim pretty printers for ", objfile.filename)
 
-    objfile.type_printers = [NimTypePrinter()]
+    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.