Skip to content

Commit

Permalink
nim-gdb.py fixes mostly for nimsuggest debugging (nim-lang#16479)
Browse files Browse the repository at this point in the history
These fixes were primarily developed to assist in nimsuggest debugging. There
is nothing intentionally specific done for nimsuggest, but beyond the automated
tests all practical testing was done with nimsuggest. Undoubltedly these will
also assist in other debugging scenarios.

The current nim-dbg.py script was broken in a few ways:
- failed to provide detailed value information for common types (see below)
- was not passing existing tests
- could not produce type summary information

Broken types now working somewhat better:
- sequences with ref types like strings
- sequences with value types like ints
- arrays with ref types like strings
- tables with int or string keys

Other improvements:
- slightly more test coverage

Future considerations:
- this, data used by it, should be something the compiler can generates
- account for different memory layouts ([arc/orc differ](nim-lang#16479 (comment)))

Attempts at improving nim-gdb.py

More tests, few fixes for seq and type printing

Tables debugging fixed added further tests

Fixed type printing
  • Loading branch information
saem authored and ardek66 committed Mar 26, 2021
1 parent c063984 commit 60fa40c
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 77 deletions.
26 changes: 21 additions & 5 deletions tests/untestable/gdb/gdb_pretty_printer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,47 @@
# 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")
57 changes: 42 additions & 15 deletions tests/untestable/gdb/gdb_pretty_printer_test_program.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
151 changes: 94 additions & 57 deletions tools/nim-gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -75,44 +78,43 @@ def recognize(self, type_obj):

# 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):
Expand Down Expand Up @@ -309,9 +311,25 @@ def display_hint(self):
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]*) \*$')
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -476,11 +495,31 @@ def to_string(self):

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:
Expand Down Expand Up @@ -524,9 +563,9 @@ def to_string(self):

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'])

Expand All @@ -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'
Expand All @@ -556,11 +594,10 @@ def children(self):
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
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 60fa40c

Please sign in to comment.