nim-gdb.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import gdb
  2. import re
  3. import sys
  4. # some feedback that the nim runtime support is loading, isn't a bad
  5. # thing at all.
  6. gdb.write("Loading Nim Runtime support.\n", gdb.STDERR)
  7. # When error occure they occur regularly. This 'caches' known errors
  8. # and prevents them from being reprinted over and over again.
  9. errorSet = set()
  10. def printErrorOnce(id, message):
  11. global errorSet
  12. if id not in errorSet:
  13. errorSet.add(id)
  14. gdb.write(message, gdb.STDERR)
  15. nimobjfile = gdb.current_objfile() or gdb.objfiles()[0]
  16. nimobjfile.type_printers = []
  17. ################################################################################
  18. ##### Type pretty printers
  19. ################################################################################
  20. type_hash_regex = re.compile("^\w*_([A-Za-z0-9]*)$")
  21. def getNimRti(type_name):
  22. """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """
  23. # Get static const TNimType variable. This should be available for
  24. # every non trivial Nim type.
  25. m = type_hash_regex.match(type_name)
  26. if m:
  27. try:
  28. return gdb.parse_and_eval("NTI_" + m.group(1) + "_")
  29. except:
  30. return None
  31. class NimTypeRecognizer:
  32. # this type map maps from types that are generated in the C files to
  33. # how they are called in nim. To not mix up the name ``int`` from
  34. # system.nim with the name ``int`` that could still appear in
  35. # generated code, ``NI`` is mapped to ``system.int`` and not just
  36. # ``int``.
  37. type_map_static = {
  38. 'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64',
  39. 'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64',
  40. 'NF': 'float', 'NF32': 'float32', 'NF64': 'float64',
  41. 'NIM_BOOL': 'bool', 'NIM_CHAR': 'char', 'NCSTRING': 'cstring',
  42. 'NimStringDesc': 'string'
  43. }
  44. # Normally gdb distinguishes between the command `ptype` and
  45. # `whatis`. `ptype` prints a very detailed view of the type, and
  46. # `whatis` a very brief representation of the type. I haven't
  47. # figured out a way to know from the type printer that is
  48. # implemented here how to know if a type printer should print the
  49. # short representation or the long representation. As a hacky
  50. # workaround I just say I am not resposible for printing pointer
  51. # types (seq and string are exception as they are semantically
  52. # values). this way the default type printer will handle pointer
  53. # types and dive into the members of that type. So I can still
  54. # control with `ptype myval` and `ptype *myval` if I want to have
  55. # detail or not. I this this method stinks but I could not figure
  56. # out a better solution.
  57. object_type_pattern = re.compile("^(\w*):ObjectType$")
  58. def recognize(self, type_obj):
  59. tname = None
  60. if type_obj.tag is not None:
  61. tname = type_obj.tag
  62. elif type_obj.name is not None:
  63. tname = type_obj.name
  64. # handle pointer types
  65. if not tname:
  66. if type_obj.code == gdb.TYPE_CODE_PTR:
  67. target_type = type_obj.target()
  68. target_type_name = target_type.name
  69. if target_type_name:
  70. # visualize 'string' as non pointer type (unpack pointer type).
  71. if target_type_name == "NimStringDesc":
  72. tname = target_type_name # could also just return 'string'
  73. # visualize 'seq[T]' as non pointer type.
  74. if target_type_name.find('tySequence_') == 0:
  75. tname = target_type_name
  76. if not tname:
  77. # We are not resposible for this type printing.
  78. # Basically this means we don't print pointer types.
  79. return None
  80. result = self.type_map_static.get(tname, None)
  81. if result:
  82. return result
  83. rti = getNimRti(tname)
  84. if rti:
  85. return rti['name'].string("utf-8", "ignore")
  86. else:
  87. return None
  88. class NimTypePrinter:
  89. """Nim type printer. One printer for all Nim types."""
  90. # enabling and disabling of type printers can be done with the
  91. # following gdb commands:
  92. #
  93. # enable type-printer NimTypePrinter
  94. # disable type-printer NimTypePrinter
  95. name = "NimTypePrinter"
  96. def __init__ (self):
  97. self.enabled = True
  98. def instantiate(self):
  99. return NimTypeRecognizer()
  100. nimobjfile.type_printers = [NimTypePrinter()]
  101. ################################################################################
  102. ##### GDB Function, equivalent of Nim's $ operator
  103. ################################################################################
  104. class DollarPrintFunction (gdb.Function):
  105. "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)"
  106. _gdb_dollar_functions = gdb.execute("info functions dollar__", True, True)
  107. dollar_functions = re.findall('NimStringDesc \*(dollar__[A-z0-9_]+?)\(([^,)]*)\);', _gdb_dollar_functions)
  108. def __init__ (self):
  109. super (DollarPrintFunction, self).__init__("dollar")
  110. @staticmethod
  111. def invoke_static(arg):
  112. for func, arg_typ in DollarPrintFunction.dollar_functions:
  113. if arg.type.name == arg_typ:
  114. func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value()
  115. return func_value(arg)
  116. if arg.type.name + " *" == arg_typ:
  117. func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value()
  118. return func_value(arg.address)
  119. typeName = arg.type.name
  120. printErrorOnce(typeName, "No suitable Nim $ operator found for type: " + typeName + ".\n")
  121. def invoke(self, arg):
  122. return self.invoke_static(arg)
  123. DollarPrintFunction()
  124. ################################################################################
  125. ##### GDB Command, equivalent of Nim's $ operator
  126. ################################################################################
  127. class DollarPrintCmd (gdb.Command):
  128. """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator"""
  129. def __init__ (self):
  130. super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
  131. def invoke (self, arg, from_tty):
  132. param = gdb.parse_and_eval(arg)
  133. gdb.write(str(DollarPrintFunction.invoke_static(param)) + "\n", gdb.STDOUT)
  134. DollarPrintCmd()
  135. ################################################################################
  136. ##### Value pretty printers
  137. ################################################################################
  138. class NimBoolPrinter:
  139. pattern = re.compile(r'^NIM_BOOL$')
  140. def __init__(self, val):
  141. self.val = val
  142. def to_string(self):
  143. if self.val == 0:
  144. return "false"
  145. else:
  146. return "true"
  147. ################################################################################
  148. class NimStringPrinter:
  149. pattern = re.compile(r'^NimStringDesc \*$')
  150. def __init__(self, val):
  151. self.val = val
  152. def display_hint(self):
  153. return 'string'
  154. def to_string(self):
  155. if self.val:
  156. l = int(self.val['Sup']['len'])
  157. return self.val['data'][0].address.string("utf-8", "ignore", l)
  158. else:
  159. return ""
  160. class NimRopePrinter:
  161. pattern = re.compile(r'^tyObject_RopeObj_OFzf0kSiPTcNreUIeJgWVA \*$')
  162. def __init__(self, val):
  163. self.val = val
  164. def display_hint(self):
  165. return 'string'
  166. def to_string(self):
  167. if self.val:
  168. left = NimRopePrinter(self.val["left"]).to_string()
  169. data = NimStringPrinter(self.val["data"]).to_string()
  170. right = NimRopePrinter(self.val["right"]).to_string()
  171. return left + data + right
  172. else:
  173. return ""
  174. ################################################################################
  175. # proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
  176. # ## Return string representation for enumeration values
  177. # var n = typ.node
  178. # if ntfEnumHole notin typ.flags:
  179. # let o = e - n.sons[0].offset
  180. # if o >= 0 and o <% typ.node.len:
  181. # return $n.sons[o].name
  182. # else:
  183. # # ugh we need a slow linear search:
  184. # var s = n.sons
  185. # for i in 0 .. n.len-1:
  186. # if s[i].offset == e:
  187. # return $s[i].name
  188. # result = $e & " (invalid data!)"
  189. def reprEnum(e, typ):
  190. """ this is a port of the nim runtime function `reprEnum` to python """
  191. e = int(e)
  192. n = typ["node"]
  193. flags = int(typ["flags"])
  194. # 1 << 2 is {ntfEnumHole}
  195. if ((1 << 2) & flags) == 0:
  196. o = e - int(n["sons"][0]["offset"])
  197. if o >= 0 and 0 < int(n["len"]):
  198. return n["sons"][o]["name"].string("utf-8", "ignore")
  199. else:
  200. # ugh we need a slow linear search:
  201. s = n["sons"]
  202. for i in range(0, int(n["len"])):
  203. if int(s[i]["offset"]) == e:
  204. return s[i]["name"].string("utf-8", "ignore")
  205. return str(e) + " (invalid data!)"
  206. class NimEnumPrinter:
  207. pattern = re.compile(r'^tyEnum_(\w*)_([A-Za-z0-9]*)$')
  208. def __init__(self, val):
  209. self.val = val
  210. match = self.pattern.match(self.val.type.name)
  211. self.typeNimName = match.group(1)
  212. typeInfoName = "NTI_" + match.group(2) + "_"
  213. self.nti = gdb.lookup_global_symbol(typeInfoName)
  214. if self.nti is None:
  215. printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n")
  216. def to_string(self):
  217. if self.nti:
  218. arg0 = self.val
  219. arg1 = self.nti.value(gdb.newest_frame())
  220. return reprEnum(arg0, arg1)
  221. else:
  222. return self.typeNimName + "(" + str(int(self.val)) + ")"
  223. ################################################################################
  224. class NimSetPrinter:
  225. ## the set printer is limited to sets that fit in an integer. Other
  226. ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to
  227. ## gdb (currently).
  228. pattern = re.compile(r'^tySet_tyEnum_(\w*)_([A-Za-z0-9]*)$')
  229. def __init__(self, val):
  230. self.val = val
  231. match = self.pattern.match(self.val.type.name)
  232. self.typeNimName = match.group(1)
  233. typeInfoName = "NTI_" + match.group(2) + "_"
  234. self.nti = gdb.lookup_global_symbol(typeInfoName)
  235. if self.nti is None:
  236. printErrorOnce(typeInfoName, "NimSetPrinter: lookup global symbol '"+ typeInfoName +" failed for " + self.val.type.name + ".\n")
  237. def to_string(self):
  238. if self.nti:
  239. nti = self.nti.value(gdb.newest_frame())
  240. enumStrings = []
  241. val = int(self.val)
  242. i = 0
  243. while val > 0:
  244. if (val & 1) == 1:
  245. enumStrings.append(reprEnum(i, nti))
  246. val = val >> 1
  247. i += 1
  248. return '{' + ', '.join(enumStrings) + '}'
  249. else:
  250. return str(int(self.val))
  251. ################################################################################
  252. class NimHashSetPrinter:
  253. pattern = re.compile(r'^tyObject_(HashSet)_([A-Za-z0-9]*)$')
  254. def __init__(self, val):
  255. self.val = val
  256. def display_hint(self):
  257. return 'array'
  258. def to_string(self):
  259. counter = 0
  260. capacity = 0
  261. if self.val:
  262. counter = int(self.val['counter'])
  263. if self.val['data']:
  264. capacity = int(self.val['data']['Sup']['len'])
  265. return 'HashSet({0}, {1})'.format(counter, capacity)
  266. def children(self):
  267. if self.val:
  268. data = NimSeqPrinter(self.val['data'])
  269. for idxStr, entry in data.children():
  270. if int(entry['Field0']) > 0:
  271. yield ("data." + idxStr + ".Field1", str(entry['Field1']))
  272. ################################################################################
  273. class NimSeqPrinter:
  274. # the pointer is explicity part of the type. So it is part of
  275. # ``pattern``.
  276. pattern = re.compile(r'^tySequence_\w* \*$')
  277. def __init__(self, val):
  278. self.val = val
  279. def display_hint(self):
  280. return 'array'
  281. def to_string(self):
  282. len = 0
  283. cap = 0
  284. if self.val:
  285. len = int(self.val['Sup']['len'])
  286. cap = int(self.val['Sup']['reserved'])
  287. return 'seq({0}, {1})'.format(len, cap)
  288. def children(self):
  289. if self.val:
  290. length = int(self.val['Sup']['len'])
  291. #align = len(str(length - 1))
  292. for i in range(length):
  293. yield ("data[{0}]".format(i), self.val["data"][i])
  294. ################################################################################
  295. class NimArrayPrinter:
  296. pattern = re.compile(r'^tyArray_\w*$')
  297. def __init__(self, val):
  298. self.val = val
  299. def display_hint(self):
  300. return 'array'
  301. def to_string(self):
  302. return 'array'
  303. def children(self):
  304. length = self.val.type.sizeof // self.val[0].type.sizeof
  305. align = len(str(length-1))
  306. for i in range(length):
  307. yield ("[{0:>{1}}]".format(i, align), self.val[i])
  308. ################################################################################
  309. class NimStringTablePrinter:
  310. pattern = re.compile(r'^tyObject_(StringTableObj)_([A-Za-z0-9]*)(:? \*)?$')
  311. def __init__(self, val):
  312. self.val = val
  313. def display_hint(self):
  314. return 'map'
  315. def to_string(self):
  316. counter = 0
  317. capacity = 0
  318. if self.val:
  319. counter = int(self.val['counter'])
  320. if self.val['data']:
  321. capacity = int(self.val['data']['Sup']['len'])
  322. return 'StringTableObj({0}, {1})'.format(counter, capacity)
  323. def children(self):
  324. if self.val:
  325. data = NimSeqPrinter(self.val['data'])
  326. for idxStr, entry in data.children():
  327. if int(entry['Field2']) > 0:
  328. yield (idxStr + ".Field0", entry['Field0'])
  329. yield (idxStr + ".Field1", entry['Field1'])
  330. ################################################################
  331. class NimTablePrinter:
  332. pattern = re.compile(r'^tyObject_(Table)_([A-Za-z0-9]*)(:? \*)?$')
  333. def __init__(self, val):
  334. self.val = val
  335. # match = self.pattern.match(self.val.type.name)
  336. def display_hint(self):
  337. return 'map'
  338. def to_string(self):
  339. counter = 0
  340. capacity = 0
  341. if self.val:
  342. counter = int(self.val['counter'])
  343. if self.val['data']:
  344. capacity = int(self.val['data']['Sup']['len'])
  345. return 'Table({0}, {1})'.format(counter, capacity)
  346. def children(self):
  347. if self.val:
  348. data = NimSeqPrinter(self.val['data'])
  349. for idxStr, entry in data.children():
  350. if int(entry['Field0']) > 0:
  351. yield (idxStr + '.Field1', entry['Field1'])
  352. yield (idxStr + '.Field2', entry['Field2'])
  353. ################################################################
  354. # this is untested, therefore disabled
  355. # class NimObjectPrinter:
  356. # pattern = re.compile(r'^tyObject_.*$')
  357. # def __init__(self, val):
  358. # self.val = val
  359. # def display_hint(self):
  360. # return 'object'
  361. # def to_string(self):
  362. # return str(self.val.type)
  363. # def children(self):
  364. # if not self.val:
  365. # yield "object", "<nil>"
  366. # raise StopIteration
  367. # for (i, field) in enumerate(self.val.type.fields()):
  368. # if field.type.code == gdb.TYPE_CODE_UNION:
  369. # yield _union_field
  370. # else:
  371. # yield (field.name, self.val[field])
  372. # def _union_field(self, i, field):
  373. # rti = getNimRti(self.val.type.name)
  374. # if rti is None:
  375. # return (field.name, "UNION field can't be displayed without RTI")
  376. # node_sons = rti['node'].dereference()['sons']
  377. # prev_field = self.val.type.fields()[i - 1]
  378. # descriminant_node = None
  379. # for i in range(int(node['len'])):
  380. # son = node_sons[i].dereference()
  381. # if son['name'].string("utf-8", "ignore") == str(prev_field.name):
  382. # descriminant_node = son
  383. # break
  384. # if descriminant_node is None:
  385. # raise ValueError("Can't find union descriminant field in object RTI")
  386. # if descriminant_node is None: raise ValueError("Can't find union field in object RTI")
  387. # union_node = descriminant_node['sons'][int(self.val[prev_field])].dereference()
  388. # union_val = self.val[field]
  389. # for f1 in union_val.type.fields():
  390. # for f2 in union_val[f1].type.fields():
  391. # if str(f2.name) == union_node['name'].string("utf-8", "ignore"):
  392. # return (str(f2.name), union_val[f1][f2])
  393. # raise ValueError("RTI is absent or incomplete, can't find union definition in RTI")
  394. ################################################################################
  395. def makematcher(klass):
  396. def matcher(val):
  397. typeName = str(val.type)
  398. try:
  399. if hasattr(klass, 'pattern') and hasattr(klass, '__name__'):
  400. # print(typeName + " <> " + klass.__name__)
  401. if klass.pattern.match(typeName):
  402. return klass(val)
  403. except Exception as e:
  404. print(klass)
  405. printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n")
  406. return matcher
  407. nimobjfile.pretty_printers = []
  408. nimobjfile.pretty_printers.extend([makematcher(var) for var in list(vars().values()) if hasattr(var, 'pattern')])