欢迎来到《用python拓展gdb》的第二篇。在上一篇,我们学习了gdb提供的常用python接口,并用python实现了自定义命令和调试脚本。
到目前为止,我们都是在用python实现内置DSL(领域特定语言)也能实现的效果。从本篇开始,我们将继续上路,去欣赏内置DSL所缺乏的新风景。
下一站,Pretty-Printer。
当我们在gdb中打印一个类/结构体时,gdb会尝试输出该类型的所有成员和它们的值。对于指针,即是输出指针所指向的地址。如果要想进一步查看指针指向的值,需要使用p *cls->x@range
这样的语法,来转换出该地址上对应的值。毕竟,C/C++是一门接近硬件的语言,如果你不指明某个地址上的具体意义,在计算机看来,不过是些字节罢了。
如果你调试过C++的STL容器,就会(惊喜地)发现:gdb并不会把容器里面各种乱七八糟的成员都打印一通,相反它仅仅输出容器里面的数据(除非你使用的gdb版本感人)。这一特性的背后,离不开Pretty-Printer的功劳。Pretty-Printer允许用户使用python给指定类编写自定义的打印方式。事实上,gdb内置了一个python脚本,正是这个脚本决定了STL容器的打印输出。
项目中的某个类太过于复杂?
正在使用某个自定义的数据结构?
想要快速看出某个属性的编码代表什么?
Pretty-Printer可以帮你解决以上所有问题。
跟自定义命令不同,Pretty-Printer不需要用户去继承某一类,用户编写的Pretty-Printer类仅需要实现指定的方法。你也可以视之为继承接口。
顺便一提,考虑到Pretty-Printer实在太长,请允许我为了偷懒,从下文开始用pprinter来简写之。
用户实现的pprinter会收到一个表示被打印对象的gdb.Value
作为构造参数,另外还需要实现一个to_string
方法,返回一个字符串作为该对象的打印结果。
这就是pprinter的全部要求了。此外你还可以实现children
方法用于输出该类里面复杂成员的值,display_hint
方法用于定义输出的样式。
还是老样子,边上代码边解释。
假设我们有如下一个Buffer结构体的定义:
123456789101112 | struct Buffer { int used; /// 已使用的数目 int free; /// 未使用的数目 void *data; int8_t encoding; /// 当前存储的数据类型 /* data的类型取决于encoding的值。encoding和data类型的对应关系如下: 0 -> int8_t 1 -> int16_t 2 -> int32_t 3 -> int64_t */}; |
现在我们需要编写一个pprinter,它能够输出该Buffer里面的数据,以及Buffer当前的使用程度。
123456789101112131415161718192021222324252627282930313233343536 | # pprinter.pyclass BufferPrinter: def __init__(self, val): \”构造函数接收一个表示被打印的Buffer的gdb.Value\” self.val = val def to_string(self): \”\”\”必选。输出打印的结果。 由于gdb会在调用to_string后调用children,这里我们只输出当前的使用程度。 具体的数据留在children函数中输出。 \”\”\” return \”used: %d\\nfree: %d\\n\” % (self.val[\’used\’], self.val[\’free\’]) def _iterate(self, pointer, size, encoding): # 根据encoding决定pointer的类型 typestrs = [\’int8_t\’, \’int16_t\’, \’int32_t\’, \’int64_t\’] pointer = pointer.cast(gdb.lookup_type(typestrs[encoding]).pointer()) for i in range(size): elem = pointer.dereference() pointer = pointer + 1 yield (\'[%d]\’ % i, elem) def children(self): \”\”\”可选。在to_string后被调用,可用于打印复杂的成员。 要求返回一个迭代器,该迭代器每次迭代返回(名字,值)形式的元组。 打印出来的效果类似于“名字 = 值”。 \”\”\” return self._iterate(self.val[\’data\’], int(self.val[\’used\’]), int(self.val[\’encoding\’])) def display_hint(self): \”\”\”可选。影响输出的样式。 可选值:array/map/string。 返回array表示按类似于vector的方式打印。其它选项同理。 \”\”\” return \’array\’ |
事实上,我们完全可以把children
方法打印的内容放到to_string
中。下面是等价的代码:(打印的结果有所不同,不过差异不大)
12345678910 | def _iterate(self, pointer, size, encoding): ... # 以上保持不变 yield elem def to_string(self): status = \”used: %d\\nfree: %d\\n\” % (self.val[\’used\’], self.val[\’free\’]) data = \'{\’ + \” \”.join(self._iterate(self.val[\’data\’], int(self.val[\’used\’]), self.val[\’encoding\’])) + \’}\’ return status + data |
接下来是向gdb注册我们自定义的pprinter:
123456789 | def lookup_buffer(val): \”\”\”val是一个gdb.Value的实例,通过type属性来获取它的类型。 如果类型为Buffer,那么就使用自定义的BufferPrinter。 \”\”\” if str(val.type) == \’Buffer\’: return BufferPrinter(val) return None gdb.pretty_printers.append(lookup_buffer) |
使用效果如下:
12345678910 | (gdb) so pprinter.py(gdb) info pretty–printerglobal pretty–printers: .* bound BufferPrinter(gdb) p buffer$1 = used: 10free: 0data: {512 129 512 129 512 129 512 129 512 129 } |
从本篇开始,我们接触了gdb更多的特性,登上了DSL所无法到达的高处。能通过python来自定义打印方式,无疑为gdb的使用打开新的大门。现在,gdb工具箱里又多了项新工具。
项目中的某个类太过于复杂?在pprinter中仅显示关键的成员属性。
正在使用某个自定义的数据结构?通过编写pprinter,我们也能像打印STL容器一样打印出它们的数据。
想要快速看出某个属性的编码代表什么?可以在pprinter中实现编码到可读字符串的转换,正如在示例中,我们从encoding中读出data属性的类型。
下一篇中,我们会谈论另一个内建DSL实现不了的功能——convenience function(可以理解为gdb会话中的内置函数)。敬请期待!