资讯专栏INFORMATION COLUMN

用 Python 拓展 GDB(二)

AbnerMing / 685人阅读

摘要:欢迎来到用拓展的第二篇。到目前为止,我们都是在用实现内置领域特定语言也能实现的效果。这就是的全部要求了。构造函数接收一个表示被打印的的必选。在后被调用,可用于打印复杂的成员。能通过来自定义打印方式,无疑为的使用打开新的大门。

欢迎来到《用python拓展gdb》的第二篇。在上一篇,我们学习了gdb提供的常用python接口,并用python实现了自定义命令和调试脚本。

到目前为止,我们都是在用python实现内置DSL(领域特定语言)也能实现的效果。从本篇开始,我们将继续上路,去欣赏内置DSL所缺乏的新风景。

下一站,Pretty-Printer。

什么是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类仅需要实现指定的方法。你也可以视之为继承接口。

顺便一提,考虑到Pretty-Printer实在太长,请允许我为了偷懒,从下文开始用pprinter来简写之。

用户实现的pprinter会收到一个表示被打印对象的gdb.Value作为构造参数,另外还需要实现一个to_string方法,返回一个字符串作为该对象的打印结果。

这就是pprinter的全部要求了。此外你还可以实现children方法用于输出该类里面复杂成员的值,display_hint方法用于定义输出的样式。

还是老样子,边上代码边解释。

假设我们有如下一个Buffer结构体的定义:

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当前的使用程度。

# pprinter.py
class BufferPrinter:
    def __init__(self, val):
        "构造函数接收一个表示被打印的Buffer的gdb.Value"
        self.val = val

    def to_string(self):
        """必选。输出打印的结果。
        由于gdb会在调用to_string后调用children,这里我们只输出当前的使用程度。
        具体的数据留在children函数中输出。
        """
        return "used: %d
free: %d
" % (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中。下面是等价的代码:(打印的结果有所不同,不过差异不大)

    def _iterate(self, pointer, size, encoding):
        ...
            # 以上保持不变
            yield elem

    def to_string(self):
        status = "used: %d
free: %d
" % (self.val["used"], self.val["free"])
        data = "{" + " ".join(self._iterate(self.val["data"], int(self.val["used"]),
                              self.val["encoding"])) + "}"
        return status + data
注册Pretty-Printer

接下来是向gdb注册我们自定义的pprinter:

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)

使用效果如下:

(gdb) so pprinter.py
(gdb) info pretty-printer
global pretty-printers:
  .*
    bound
  BufferPrinter
(gdb) p buffer
$1 = used: 10
free: 0
data: {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会话中的内置函数)。敬请期待!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37992.html

相关文章

  • Python 拓展 GDB(三)

    摘要:欢迎来到用拓展的第三篇。它们必须以开头,以此区别于来自于上下文的函数。提供的基类名为。不过有一个区别是,的方法通常会返回一个对象,表示调用该函数后的返回值。它不能像通常意义上的函数独立使用,只能跟某个命令搭配。具体实现参见用拓展第一篇。 欢迎来到《用python拓展gdb》的第三篇。上一篇我们谈到了pretty printer,一个需要python支持的特性。这一篇我们谈论另一个需要p...

    quietin 评论0 收藏0
  • Python 拓展 GDB(四)

    摘要:欢迎来到用拓展的最后一篇。对于通用语言来说,暴露的接口不过是又一个库而已。这两者间的通讯使用协议。该客户端可以向外界暴露出调试时的信息。用拓展系列到此就结束了。 欢迎来到《用python拓展gdb》的最后一篇。第一篇结尾,我提到了通用语言相对于领域特定语言的一项优势,即在处理数据上更加灵活。其实通用语言还有着另一样优势,领域特定语言只能局限在宿主程序中使用,而通用语言则无此限制。对于通...

    Seay 评论0 收藏0
  • Python 拓展 GDB(一)

    摘要:在末尾,我提到了也可以用来实现拓展脚本。其中最为常用的是和。接受一个字符串作为表达式,并以的形式返回表达式求值的结果。当触发断点或收到信号时,就会调用事先注册的回调函数。对应的,撤销回调函数的接口是。本教程剩余部分会提及这一点。 之前写的《GDB 自动化操作的技术》一文介绍了可在gdb内部使用的DSL(领域特定语言)来自动化gdb的操作。借助该DSL,我们分别实现了一个名为mv的自定义...

    Cheng_Gang 评论0 收藏0
  • Python 调试方法

    摘要:背景这几天一直在查一个线上程序住的问题这个程序总是在运行分钟后住通过以下的一些调试手段发现是打日志的时候因为满被了日志是默认打到的无论日志级别而我这个程序是被另一个程序调起的父进程没有接收子进程的导致了被打满在调试的过程中用到了以下几种调试 FROM http://kamushin.github.io/debug/python.html 背景 这几天一直在查一个线上程序 hang 住的...

    klivitamJ 评论0 收藏0

发表评论

0条评论

AbnerMing

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<