nim-gdb.py fixes mostly for nimsuggest debugging (#16479)
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](https://github.com/nim-lang/Nim/pull/16479#issuecomment-751469536)) Attempts at improving nim-gdb.py More tests, few fixes for seq and type printing Tables debugging fixed added further tests Fixed type printing
This commit is contained in:
parent
8508c4e1c2
commit
84a7544988
3 changed files with 156 additions and 76 deletions
|
|
@ -6,31 +6,47 @@ import gdb
|
||||||
# frontends might still be broken.
|
# frontends might still be broken.
|
||||||
|
|
||||||
gdb.execute("source ../../../tools/nim-gdb.py")
|
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("rbreak myDebug")
|
||||||
gdb.execute("run")
|
gdb.execute("run")
|
||||||
|
|
||||||
outputs = [
|
outputs = [
|
||||||
'meTwo',
|
'meTwo',
|
||||||
|
'""',
|
||||||
'"meTwo"',
|
'"meTwo"',
|
||||||
'{meOne, meThree}',
|
'{meOne, meThree}',
|
||||||
'MyOtherEnum(1)',
|
'MyOtherEnum(1)',
|
||||||
'5',
|
'5',
|
||||||
'array = {1, 2, 3, 4, 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"}',
|
'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):
|
for i, expected in enumerate(outputs):
|
||||||
|
gdb.write(f"{i+1}) expecting: {expected}: ", gdb.STDLOG)
|
||||||
|
gdb.flush()
|
||||||
|
|
||||||
functionSymbol = gdb.selected_frame().block().function
|
functionSymbol = gdb.selected_frame().block().function
|
||||||
assert functionSymbol.line == 21
|
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
|
# myArray is passed as pointer to int to myDebug. I look up myArray up in the stack
|
||||||
gdb.execute("up")
|
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:
|
else:
|
||||||
output = str(gdb.parse_and_eval("arg"))
|
raw = gdb.parse_and_eval("arg")
|
||||||
|
|
||||||
|
output = str(raw)
|
||||||
|
|
||||||
assert output == expected, output + " != " + expected
|
assert output == expected, output + " != " + expected
|
||||||
|
gdb.write(f"passed\n", gdb.STDLOG)
|
||||||
gdb.execute("continue")
|
gdb.execute("continue")
|
||||||
|
|
|
||||||
|
|
@ -23,29 +23,56 @@ proc myDebug[T](arg: T): void =
|
||||||
|
|
||||||
proc testProc(): void =
|
proc testProc(): void =
|
||||||
var myEnum = meTwo
|
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
|
# create a string object but also make the NTI for MyEnum is generated
|
||||||
var myString = $myEnum
|
myString = $myEnum
|
||||||
myDebug(myString)
|
myDebug(myString) #3
|
||||||
|
|
||||||
var mySet = {meOne,meThree}
|
var mySet = {meOne,meThree}
|
||||||
myDebug(mySet)
|
myDebug(mySet) #4
|
||||||
|
|
||||||
# for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer.
|
# for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer.
|
||||||
var moEnum = moTwo
|
var moEnum = moTwo
|
||||||
myDebug(moEnum)
|
myDebug(moEnum) #5
|
||||||
|
|
||||||
var moSet = {moOne,moThree}
|
var moSet = {moOne,moThree}
|
||||||
myDebug(moSet)
|
myDebug(moSet) #6
|
||||||
|
|
||||||
let myArray = [1,2,3,4,5]
|
let myArray = [1,2,3,4,5]
|
||||||
myDebug(myArray)
|
myDebug(myArray) #7
|
||||||
let mySeq = @["one","two","three"]
|
|
||||||
myDebug(mySeq)
|
|
||||||
|
|
||||||
var myTable = initTable[string, int]()
|
# implicitly initialized seq test
|
||||||
myTable["one"] = 1
|
var mySeq: seq[string]
|
||||||
myTable["two"] = 2
|
myDebug(mySeq) #8
|
||||||
myTable["three"] = 3
|
|
||||||
myDebug(myTable)
|
# 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)
|
echo(counter)
|
||||||
|
|
||||||
|
|
|
||||||
151
tools/nim-gdb.py
151
tools/nim-gdb.py
|
|
@ -34,6 +34,14 @@ def getNimRti(type_name):
|
||||||
except:
|
except:
|
||||||
return None
|
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:
|
class NimTypeRecognizer:
|
||||||
# this type map maps from types that are generated in the C files to
|
# 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
|
# how they are called in nim. To not mix up the name ``int`` from
|
||||||
|
|
@ -42,30 +50,25 @@ class NimTypeRecognizer:
|
||||||
# ``int``.
|
# ``int``.
|
||||||
|
|
||||||
type_map_static = {
|
type_map_static = {
|
||||||
'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64',
|
'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32',
|
||||||
'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64',
|
'NI64': 'int64',
|
||||||
|
|
||||||
|
'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32',
|
||||||
|
'NU64': 'uint64',
|
||||||
|
|
||||||
'NF': 'float', 'NF32': 'float32', 'NF64': 'float64',
|
'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
|
# object_type_pattern = re.compile("^(\w*):ObjectType$")
|
||||||
# `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$")
|
|
||||||
|
|
||||||
def recognize(self, type_obj):
|
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
|
tname = None
|
||||||
if type_obj.tag is not None:
|
if type_obj.tag is not None:
|
||||||
|
|
@ -75,44 +78,43 @@ class NimTypeRecognizer:
|
||||||
|
|
||||||
# handle pointer types
|
# handle pointer types
|
||||||
if not tname:
|
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 = 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:
|
if target_type.name:
|
||||||
# We are not resposible for this type printing.
|
# visualize 'string' as non pointer type (unpack pointer type).
|
||||||
# Basically this means we don't print pointer types.
|
if target_type.name == "NimStringDesc":
|
||||||
return None
|
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 tname:
|
||||||
if result:
|
result = self.type_map_static.get(tname, None)
|
||||||
return result
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
rti = getNimRti(tname)
|
rti = getNimRti(tname)
|
||||||
if rti:
|
if rti:
|
||||||
return rti['name'].string("utf-8", "ignore")
|
return getNameFromNimRti(rti)
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class NimTypePrinter:
|
class NimTypePrinter:
|
||||||
"""Nim type printer. One printer for all Nim types."""
|
"""Nim type printer. One printer for all Nim types."""
|
||||||
|
|
||||||
|
|
||||||
# enabling and disabling of type printers can be done with the
|
# enabling and disabling of type printers can be done with the
|
||||||
# following gdb commands:
|
# following gdb commands:
|
||||||
#
|
#
|
||||||
# enable type-printer NimTypePrinter
|
# enable type-printer NimTypePrinter
|
||||||
# disable type-printer NimTypePrinter
|
# disable type-printer NimTypePrinter
|
||||||
|
# relevant docs: https://sourceware.org/gdb/onlinedocs/gdb/Type-Printing-API.html
|
||||||
|
|
||||||
name = "NimTypePrinter"
|
name = "NimTypePrinter"
|
||||||
def __init__ (self):
|
|
||||||
|
def __init__(self):
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
|
|
||||||
def instantiate(self):
|
def instantiate(self):
|
||||||
|
|
@ -309,9 +311,25 @@ class NimStringPrinter:
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
if self.val:
|
if self.val:
|
||||||
l = int(self.val['Sup']['len'])
|
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:
|
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:
|
class NimRopePrinter:
|
||||||
pattern = re.compile(r'^tyObject_RopeObj__([A-Za-z0-9]*) \*$')
|
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]*)$')
|
pattern = re.compile(r'^tyEnum_(\w*)__([A-Za-z0-9]*)$')
|
||||||
|
|
||||||
def __init__(self, val):
|
def __init__(self, val):
|
||||||
self.val = val
|
self.val = val
|
||||||
match = self.pattern.match(self.val.type.name)
|
typeName = self.val.type.name
|
||||||
|
match = self.pattern.match(typeName)
|
||||||
self.typeNimName = match.group(1)
|
self.typeNimName = match.group(1)
|
||||||
typeInfoName = "NTI__" + match.group(2) + "_"
|
typeInfoName = "NTI__" + match.group(2) + "_"
|
||||||
self.nti = gdb.lookup_global_symbol(typeInfoName)
|
self.nti = gdb.lookup_global_symbol(typeInfoName)
|
||||||
|
|
||||||
if self.nti is None:
|
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):
|
def to_string(self):
|
||||||
if self.nti:
|
if self.nti:
|
||||||
|
|
@ -476,11 +495,31 @@ class NimSeqPrinter:
|
||||||
|
|
||||||
def children(self):
|
def children(self):
|
||||||
if self.val:
|
if self.val:
|
||||||
length = int(self.val['Sup']['len'])
|
val = self.val
|
||||||
#align = len(str(length - 1))
|
valType = val.type
|
||||||
for i in range(length):
|
length = int(val['Sup']['len'])
|
||||||
yield ("data[{0}]".format(i), self.val["data"][i])
|
|
||||||
|
|
||||||
|
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:
|
class NimArrayPrinter:
|
||||||
|
|
@ -524,9 +563,9 @@ class NimStringTablePrinter:
|
||||||
|
|
||||||
def children(self):
|
def children(self):
|
||||||
if self.val:
|
if self.val:
|
||||||
data = NimSeqPrinter(self.val['data'])
|
data = NimSeqPrinter(self.val['data'].dereference())
|
||||||
for idxStr, entry in data.children():
|
for idxStr, entry in data.children():
|
||||||
if int(entry['Field2']) > 0:
|
if int(entry['Field0']) != 0:
|
||||||
yield (idxStr + ".Field0", entry['Field0'])
|
yield (idxStr + ".Field0", entry['Field0'])
|
||||||
yield (idxStr + ".Field1", entry['Field1'])
|
yield (idxStr + ".Field1", entry['Field1'])
|
||||||
|
|
||||||
|
|
@ -537,7 +576,6 @@ class NimTablePrinter:
|
||||||
|
|
||||||
def __init__(self, val):
|
def __init__(self, val):
|
||||||
self.val = val
|
self.val = val
|
||||||
# match = self.pattern.match(self.val.type.name)
|
|
||||||
|
|
||||||
def display_hint(self):
|
def display_hint(self):
|
||||||
return 'map'
|
return 'map'
|
||||||
|
|
@ -556,11 +594,10 @@ class NimTablePrinter:
|
||||||
if self.val:
|
if self.val:
|
||||||
data = NimSeqPrinter(self.val['data'])
|
data = NimSeqPrinter(self.val['data'])
|
||||||
for idxStr, entry in data.children():
|
for idxStr, entry in data.children():
|
||||||
if int(entry['Field0']) > 0:
|
if int(entry['Field0']) != 0:
|
||||||
yield (idxStr + '.Field1', entry['Field1'])
|
yield (idxStr + '.Field1', entry['Field1'])
|
||||||
yield (idxStr + '.Field2', entry['Field2'])
|
yield (idxStr + '.Field2', entry['Field2'])
|
||||||
|
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
|
|
||||||
# this is untested, therefore disabled
|
# this is untested, therefore disabled
|
||||||
|
|
@ -651,7 +688,7 @@ def register_nim_pretty_printers_for_object(objfile):
|
||||||
if nimMainSym and nimMainSym.symtab.objfile == objfile:
|
if nimMainSym and nimMainSym.symtab.objfile == objfile:
|
||||||
print("set Nim pretty printers for ", objfile.filename)
|
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')]
|
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.
|
# Register pretty printers for all objfiles that are already loaded.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue