nim-gdb.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  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. ################################################################################
  16. ##### Type pretty printers
  17. ################################################################################
  18. type_hash_regex = re.compile("^\w*_([A-Za-z0-9]*)$")
  19. def getNimRti(type_name):
  20. """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """
  21. # Get static const TNimType variable. This should be available for
  22. # every non trivial Nim type.
  23. m = type_hash_regex.match(type_name)
  24. if m:
  25. try:
  26. return gdb.parse_and_eval("NTI__" + m.group(1) + "_")
  27. except:
  28. return None
  29. class NimTypeRecognizer:
  30. # this type map maps from types that are generated in the C files to
  31. # how they are called in nim. To not mix up the name ``int`` from
  32. # system.nim with the name ``int`` that could still appear in
  33. # generated code, ``NI`` is mapped to ``system.int`` and not just
  34. # ``int``.
  35. type_map_static = {
  36. 'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64',
  37. 'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64',
  38. 'NF': 'float', 'NF32': 'float32', 'NF64': 'float64',
  39. 'NIM_BOOL': 'bool', 'NIM_CHAR': 'char', 'NCSTRING': 'cstring',
  40. 'NimStringDesc': 'string'
  41. }
  42. # Normally gdb distinguishes between the command `ptype` and
  43. # `whatis`. `ptype` prints a very detailed view of the type, and
  44. # `whatis` a very brief representation of the type. I haven't
  45. # figured out a way to know from the type printer that is
  46. # implemented here how to know if a type printer should print the
  47. # short representation or the long representation. As a hacky
  48. # workaround I just say I am not resposible for printing pointer
  49. # types (seq and string are exception as they are semantically
  50. # values). this way the default type printer will handle pointer
  51. # types and dive into the members of that type. So I can still
  52. # control with `ptype myval` and `ptype *myval` if I want to have
  53. # detail or not. I this this method stinks but I could not figure
  54. # out a better solution.
  55. object_type_pattern = re.compile("^(\w*):ObjectType$")
  56. def recognize(self, type_obj):
  57. tname = None
  58. if type_obj.tag is not None:
  59. tname = type_obj.tag
  60. elif type_obj.name is not None:
  61. tname = type_obj.name
  62. # handle pointer types
  63. if not tname:
  64. if type_obj.code == gdb.TYPE_CODE_PTR:
  65. target_type = type_obj.target()
  66. target_type_name = target_type.name
  67. if target_type_name:
  68. # visualize 'string' as non pointer type (unpack pointer type).
  69. if target_type_name == "NimStringDesc":
  70. tname = target_type_name # could also just return 'string'
  71. # visualize 'seq[T]' as non pointer type.
  72. if target_type_name.find('tySequence_') == 0:
  73. tname = target_type_name
  74. if not tname:
  75. # We are not resposible for this type printing.
  76. # Basically this means we don't print pointer types.
  77. return None
  78. result = self.type_map_static.get(tname, None)
  79. if result:
  80. return result
  81. rti = getNimRti(tname)
  82. if rti:
  83. return rti['name'].string("utf-8", "ignore")
  84. else:
  85. return None
  86. class NimTypePrinter:
  87. """Nim type printer. One printer for all Nim types."""
  88. # enabling and disabling of type printers can be done with the
  89. # following gdb commands:
  90. #
  91. # enable type-printer NimTypePrinter
  92. # disable type-printer NimTypePrinter
  93. name = "NimTypePrinter"
  94. def __init__ (self):
  95. self.enabled = True
  96. def instantiate(self):
  97. return NimTypeRecognizer()
  98. ################################################################################
  99. ##### GDB Function, equivalent of Nim's $ operator
  100. ################################################################################
  101. class DollarPrintFunction (gdb.Function):
  102. "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)"
  103. dollar_functions = re.findall(
  104. 'NimStringDesc \*(dollar__[A-z0-9_]+?)\(([^,)]*)\);',
  105. gdb.execute("info functions dollar__", True, True)
  106. )
  107. def __init__ (self):
  108. super (DollarPrintFunction, self).__init__("dollar")
  109. @staticmethod
  110. def invoke_static(arg):
  111. if arg.type.code == gdb.TYPE_CODE_PTR and arg.type.target().name == "NimStringDesc":
  112. return arg
  113. argTypeName = str(arg.type)
  114. for func, arg_typ in DollarPrintFunction.dollar_functions:
  115. # this way of overload resolution cannot deal with type aliases,
  116. # therefore it won't find all overloads.
  117. if arg_typ == argTypeName:
  118. func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value()
  119. return func_value(arg)
  120. elif arg_typ == argTypeName + " *":
  121. func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value()
  122. return func_value(arg.address)
  123. printErrorOnce(argTypeName, "No suitable Nim $ operator found for type: " + argTypeName + "\n")
  124. return None
  125. def invoke(self, arg):
  126. return self.invoke_static(arg)
  127. DollarPrintFunction()
  128. ################################################################################
  129. ##### GDB Function, Nim string comparison
  130. ################################################################################
  131. class NimStringEqFunction (gdb.Function):
  132. """Compare Nim strings for example in conditionals for breakpoints."""
  133. def __init__ (self):
  134. super (NimStringEqFunction, self).__init__("nimstreq")
  135. @staticmethod
  136. def invoke_static(arg1,arg2):
  137. if arg1.type.code == gdb.TYPE_CODE_PTR and arg1.type.target().name == "NimStringDesc":
  138. str1 = NimStringPrinter(arg1).to_string()
  139. else:
  140. str1 = arg1.string()
  141. if arg2.type.code == gdb.TYPE_CODE_PTR and arg2.type.target().name == "NimStringDesc":
  142. str2 = NimStringPrinter(arg1).to_string()
  143. else:
  144. str2 = arg2.string()
  145. return str1 == str2
  146. def invoke(self, arg1, arg2):
  147. return self.invoke_static(arg1, arg2)
  148. NimStringEqFunction()
  149. ################################################################################
  150. ##### GDB Command, equivalent of Nim's $ operator
  151. ################################################################################
  152. class DollarPrintCmd (gdb.Command):
  153. """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator and print the result."""
  154. def __init__ (self):
  155. super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
  156. def invoke(self, arg, from_tty):
  157. param = gdb.parse_and_eval(arg)
  158. strValue = DollarPrintFunction.invoke_static(param)
  159. if strValue:
  160. gdb.write(
  161. NimStringPrinter(strValue).to_string() + "\n",
  162. gdb.STDOUT
  163. )
  164. # could not find a suitable dollar overload. This here is the
  165. # fallback to get sensible output of basic types anyway.
  166. elif param.type.code == gdb.TYPE_CODE_ARRAY and param.type.target().name == "char":
  167. gdb.write(param.string("utf-8", "ignore") + "\n", gdb.STDOUT)
  168. elif param.type.code == gdb.TYPE_CODE_INT:
  169. gdb.write(str(int(param)) + "\n", gdb.STDOUT)
  170. elif param.type.name == "NIM_BOOL":
  171. if int(param) != 0:
  172. gdb.write("true\n", gdb.STDOUT)
  173. else:
  174. gdb.write("false\n", gdb.STDOUT)
  175. DollarPrintCmd()
  176. ################################################################################
  177. ##### GDB Commands to invoke common nim tools.
  178. ################################################################################
  179. import subprocess, os
  180. class KochCmd (gdb.Command):
  181. """Command that invokes ``koch'', the build tool for the compiler."""
  182. def __init__ (self):
  183. super (KochCmd, self).__init__ ("koch",
  184. gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
  185. self.binary = os.path.join(
  186. os.path.dirname(os.path.dirname(__file__)), "koch")
  187. def invoke(self, argument, from_tty):
  188. import os
  189. subprocess.run([self.binary] + gdb.string_to_argv(argument))
  190. KochCmd()
  191. class NimCmd (gdb.Command):
  192. """Command that invokes ``nim'', the nim compiler."""
  193. def __init__ (self):
  194. super (NimCmd, self).__init__ ("nim",
  195. gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
  196. self.binary = os.path.join(
  197. os.path.dirname(os.path.dirname(__file__)), "bin/nim")
  198. def invoke(self, argument, from_tty):
  199. subprocess.run([self.binary] + gdb.string_to_argv(argument))
  200. NimCmd()
  201. class NimbleCmd (gdb.Command):
  202. """Command that invokes ``nimble'', the nim package manager and build tool."""
  203. def __init__ (self):
  204. super (NimbleCmd, self).__init__ ("nimble",
  205. gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
  206. self.binary = os.path.join(
  207. os.path.dirname(os.path.dirname(__file__)), "bin/nimble")
  208. def invoke(self, argument, from_tty):
  209. subprocess.run([self.binary] + gdb.string_to_argv(argument))
  210. NimbleCmd()
  211. ################################################################################
  212. ##### Value pretty printers
  213. ################################################################################
  214. class NimBoolPrinter:
  215. pattern = re.compile(r'^NIM_BOOL$')
  216. def __init__(self, val):
  217. self.val = val
  218. def to_string(self):
  219. if self.val == 0:
  220. return "false"
  221. else:
  222. return "true"
  223. ################################################################################
  224. class NimStringPrinter:
  225. pattern = re.compile(r'^NimStringDesc \*$')
  226. def __init__(self, val):
  227. self.val = val
  228. def display_hint(self):
  229. return 'string'
  230. def to_string(self):
  231. if self.val:
  232. l = int(self.val['Sup']['len'])
  233. return self.val['data'][0].address.string("utf-8", "ignore", l)
  234. else:
  235. return ""
  236. class NimRopePrinter:
  237. pattern = re.compile(r'^tyObject_RopeObj__([A-Za-z0-9]*) \*$')
  238. def __init__(self, val):
  239. self.val = val
  240. def display_hint(self):
  241. return 'string'
  242. def to_string(self):
  243. if self.val:
  244. left = NimRopePrinter(self.val["left"]).to_string()
  245. data = NimStringPrinter(self.val["data"]).to_string()
  246. right = NimRopePrinter(self.val["right"]).to_string()
  247. return left + data + right
  248. else:
  249. return ""
  250. ################################################################################
  251. # proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
  252. # ## Return string representation for enumeration values
  253. # var n = typ.node
  254. # if ntfEnumHole notin typ.flags:
  255. # let o = e - n.sons[0].offset
  256. # if o >= 0 and o <% typ.node.len:
  257. # return $n.sons[o].name
  258. # else:
  259. # # ugh we need a slow linear search:
  260. # var s = n.sons
  261. # for i in 0 .. n.len-1:
  262. # if s[i].offset == e:
  263. # return $s[i].name
  264. # result = $e & " (invalid data!)"
  265. def reprEnum(e, typ):
  266. """ this is a port of the nim runtime function `reprEnum` to python """
  267. e = int(e)
  268. n = typ["node"]
  269. flags = int(typ["flags"])
  270. # 1 << 2 is {ntfEnumHole}
  271. if ((1 << 2) & flags) == 0:
  272. o = e - int(n["sons"][0]["offset"])
  273. if o >= 0 and 0 < int(n["len"]):
  274. return n["sons"][o]["name"].string("utf-8", "ignore")
  275. else:
  276. # ugh we need a slow linear search:
  277. s = n["sons"]
  278. for i in range(0, int(n["len"])):
  279. if int(s[i]["offset"]) == e:
  280. return s[i]["name"].string("utf-8", "ignore")
  281. return str(e) + " (invalid data!)"
  282. class NimEnumPrinter:
  283. pattern = re.compile(r'^tyEnum_(\w*)__([A-Za-z0-9]*)$')
  284. def __init__(self, val):
  285. self.val = val
  286. match = self.pattern.match(self.val.type.name)
  287. self.typeNimName = match.group(1)
  288. typeInfoName = "NTI__" + match.group(2) + "_"
  289. self.nti = gdb.lookup_global_symbol(typeInfoName)
  290. if self.nti is None:
  291. printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n")
  292. def to_string(self):
  293. if self.nti:
  294. arg0 = self.val
  295. arg1 = self.nti.value(gdb.newest_frame())
  296. return reprEnum(arg0, arg1)
  297. else:
  298. return self.typeNimName + "(" + str(int(self.val)) + ")"
  299. ################################################################################
  300. class NimSetPrinter:
  301. ## the set printer is limited to sets that fit in an integer. Other
  302. ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to
  303. ## gdb (currently).
  304. pattern = re.compile(r'^tySet_tyEnum_(\w*)_([A-Za-z0-9]*)$')
  305. def __init__(self, val):
  306. self.val = val
  307. match = self.pattern.match(self.val.type.name)
  308. self.typeNimName = match.group(1)
  309. typeInfoName = "NTI__" + match.group(2) + "_"
  310. self.nti = gdb.lookup_global_symbol(typeInfoName)
  311. if self.nti is None:
  312. printErrorOnce(typeInfoName, "NimSetPrinter: lookup global symbol '"+ typeInfoName +" failed for " + self.val.type.name + ".\n")
  313. def to_string(self):
  314. if self.nti:
  315. nti = self.nti.value(gdb.newest_frame())
  316. enumStrings = []
  317. val = int(self.val)
  318. i = 0
  319. while val > 0:
  320. if (val & 1) == 1:
  321. enumStrings.append(reprEnum(i, nti))
  322. val = val >> 1
  323. i += 1
  324. return '{' + ', '.join(enumStrings) + '}'
  325. else:
  326. return str(int(self.val))
  327. ################################################################################
  328. class NimHashSetPrinter:
  329. pattern = re.compile(r'^tyObject_(HashSet)__([A-Za-z0-9]*)$')
  330. def __init__(self, val):
  331. self.val = val
  332. def display_hint(self):
  333. return 'array'
  334. def to_string(self):
  335. counter = 0
  336. capacity = 0
  337. if self.val:
  338. counter = int(self.val['counter'])
  339. if self.val['data']:
  340. capacity = int(self.val['data']['Sup']['len'])
  341. return 'HashSet({0}, {1})'.format(counter, capacity)
  342. def children(self):
  343. if self.val:
  344. data = NimSeqPrinter(self.val['data'])
  345. for idxStr, entry in data.children():
  346. if int(entry['Field0']) > 0:
  347. yield ("data." + idxStr + ".Field1", str(entry['Field1']))
  348. ################################################################################
  349. class NimSeqPrinter:
  350. # the pointer is explicity part of the type. So it is part of
  351. # ``pattern``.
  352. pattern = re.compile(r'^tySequence_\w* \*$')
  353. def __init__(self, val):
  354. self.val = val
  355. def display_hint(self):
  356. return 'array'
  357. def to_string(self):
  358. len = 0
  359. cap = 0
  360. if self.val:
  361. len = int(self.val['Sup']['len'])
  362. cap = int(self.val['Sup']['reserved'])
  363. return 'seq({0}, {1})'.format(len, cap)
  364. def children(self):
  365. if self.val:
  366. length = int(self.val['Sup']['len'])
  367. #align = len(str(length - 1))
  368. for i in range(length):
  369. yield ("data[{0}]".format(i), self.val["data"][i])
  370. ################################################################################
  371. class NimArrayPrinter:
  372. pattern = re.compile(r'^tyArray_\w*$')
  373. def __init__(self, val):
  374. self.val = val
  375. def display_hint(self):
  376. return 'array'
  377. def to_string(self):
  378. return 'array'
  379. def children(self):
  380. length = self.val.type.sizeof // self.val[0].type.sizeof
  381. align = len(str(length-1))
  382. for i in range(length):
  383. yield ("[{0:>{1}}]".format(i, align), self.val[i])
  384. ################################################################################
  385. class NimStringTablePrinter:
  386. pattern = re.compile(r'^tyObject_(StringTableObj)__([A-Za-z0-9]*)(:? \*)?$')
  387. def __init__(self, val):
  388. self.val = val
  389. def display_hint(self):
  390. return 'map'
  391. def to_string(self):
  392. counter = 0
  393. capacity = 0
  394. if self.val:
  395. counter = int(self.val['counter'])
  396. if self.val['data']:
  397. capacity = int(self.val['data']['Sup']['len'])
  398. return 'StringTableObj({0}, {1})'.format(counter, capacity)
  399. def children(self):
  400. if self.val:
  401. data = NimSeqPrinter(self.val['data'])
  402. for idxStr, entry in data.children():
  403. if int(entry['Field2']) > 0:
  404. yield (idxStr + ".Field0", entry['Field0'])
  405. yield (idxStr + ".Field1", entry['Field1'])
  406. ################################################################
  407. class NimTablePrinter:
  408. pattern = re.compile(r'^tyObject_(Table)__([A-Za-z0-9]*)(:? \*)?$')
  409. def __init__(self, val):
  410. self.val = val
  411. # match = self.pattern.match(self.val.type.name)
  412. def display_hint(self):
  413. return 'map'
  414. def to_string(self):
  415. counter = 0
  416. capacity = 0
  417. if self.val:
  418. counter = int(self.val['counter'])
  419. if self.val['data']:
  420. capacity = int(self.val['data']['Sup']['len'])
  421. return 'Table({0}, {1})'.format(counter, capacity)
  422. def children(self):
  423. if self.val:
  424. data = NimSeqPrinter(self.val['data'])
  425. for idxStr, entry in data.children():
  426. if int(entry['Field0']) > 0:
  427. yield (idxStr + '.Field1', entry['Field1'])
  428. yield (idxStr + '.Field2', entry['Field2'])
  429. ################################################################
  430. # this is untested, therefore disabled
  431. # class NimObjectPrinter:
  432. # pattern = re.compile(r'^tyObject_.*$')
  433. # def __init__(self, val):
  434. # self.val = val
  435. # def display_hint(self):
  436. # return 'object'
  437. # def to_string(self):
  438. # return str(self.val.type)
  439. # def children(self):
  440. # if not self.val:
  441. # yield "object", "<nil>"
  442. # raise StopIteration
  443. # for (i, field) in enumerate(self.val.type.fields()):
  444. # if field.type.code == gdb.TYPE_CODE_UNION:
  445. # yield _union_field
  446. # else:
  447. # yield (field.name, self.val[field])
  448. # def _union_field(self, i, field):
  449. # rti = getNimRti(self.val.type.name)
  450. # if rti is None:
  451. # return (field.name, "UNION field can't be displayed without RTI")
  452. # node_sons = rti['node'].dereference()['sons']
  453. # prev_field = self.val.type.fields()[i - 1]
  454. # descriminant_node = None
  455. # for i in range(int(node['len'])):
  456. # son = node_sons[i].dereference()
  457. # if son['name'].string("utf-8", "ignore") == str(prev_field.name):
  458. # descriminant_node = son
  459. # break
  460. # if descriminant_node is None:
  461. # raise ValueError("Can't find union descriminant field in object RTI")
  462. # if descriminant_node is None: raise ValueError("Can't find union field in object RTI")
  463. # union_node = descriminant_node['sons'][int(self.val[prev_field])].dereference()
  464. # union_val = self.val[field]
  465. # for f1 in union_val.type.fields():
  466. # for f2 in union_val[f1].type.fields():
  467. # if str(f2.name) == union_node['name'].string("utf-8", "ignore"):
  468. # return (str(f2.name), union_val[f1][f2])
  469. # raise ValueError("RTI is absent or incomplete, can't find union definition in RTI")
  470. ################################################################################
  471. class NimFrameFilter:
  472. def __init__(self):
  473. self.name = "nim-frame-filter"
  474. self.enabled = True
  475. self.priority = 100
  476. self.hidden = {"NimMainInner","NimMain", "main"}
  477. def filter(self, iterator):
  478. for framedecorator in iterator:
  479. if framedecorator.function() not in self.hidden:
  480. yield framedecorator
  481. ################################################################################
  482. def makematcher(klass):
  483. def matcher(val):
  484. typeName = str(val.type)
  485. try:
  486. if hasattr(klass, 'pattern') and hasattr(klass, '__name__'):
  487. # print(typeName + " <> " + klass.__name__)
  488. if klass.pattern.match(typeName):
  489. return klass(val)
  490. except Exception as e:
  491. print(klass)
  492. printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n")
  493. return matcher
  494. def register_nim_pretty_printers_for_object(objfile):
  495. nimMainSym = gdb.lookup_global_symbol("NimMain", gdb.SYMBOL_FUNCTIONS_DOMAIN)
  496. if nimMainSym and nimMainSym.symtab.objfile == objfile:
  497. print("set Nim pretty printers for ", objfile.filename)
  498. objfile.type_printers = [NimTypePrinter()]
  499. objfile.pretty_printers = [makematcher(var) for var in list(globals().values()) if hasattr(var, 'pattern')]
  500. # Register pretty printers for all objfiles that are already loaded.
  501. for old_objfile in gdb.objfiles():
  502. register_nim_pretty_printers_for_object(old_objfile)
  503. # Register an event handler to register nim pretty printers for all future objfiles.
  504. def new_object_handler(event):
  505. register_nim_pretty_printers_for_object(event.new_objfile)
  506. gdb.events.new_objfile.connect(new_object_handler)
  507. gdb.frame_filters = {"nim-frame-filter": NimFrameFilter()}