gdb用python漂亮地打印递归结构

2024-10-01 13:33:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我对Python不是很熟悉,我只是在发现GDB Python脚本功能;我的问题的动机是增强GDB打印MELT monitor中的值,后者稍后将连接到GCC MELT。但这里有一个更简单的变体。在

我的系统是Linux/Debian/Sid/x86-64。GCC编译器是4.8.2;GDB调试器是7.6.2;python是3.3

我想调试一个具有“区分联合”类型的C程序:

// file tiny.c in the public domain by Basile Starynkevitch
// compile with gcc -g3 -Wall -std=c99 tiny.c -o tiny
// debug with gdb tiny
// under gdb: python tiny-gdb.py
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef union my_un myval_t;
enum tag_en {
  tag_none,
  tag_int,
  tag_string,
  tag_sequence
};
struct boxint_st;
struct boxstring_st;
struct boxsequence_st;
union my_un {
  void* ptr;
  enum tag_en *ptag;
  struct boxint_st *pint;
  struct boxstring_st *pstr;
  struct boxsequence_st *pseq;
};

struct boxint_st {
  enum tag_en tag;      // for tag_int
  int ival;
};
struct boxstring_st {
  enum tag_en tag;      // for tag_string
  char strval[];        // a zero-terminated C string 
};
struct boxsequence_st {
  enum tag_en tag;      // for tag_sequence
  unsigned slen;
  myval_t valtab[];     // of length slen
};


int main (int argc, char **argv) {
  printf ("start %s, argc=%d", argv[0], argc);
  struct boxint_st *iv42 = malloc (sizeof (struct boxint_st));
  iv42->tag = tag_int;
  iv42->ival = 42;
  struct boxstring_st *istrhello =
    malloc (sizeof (struct boxstring_st) + sizeof ("hello") + 1);
  istrhello->tag = tag_string;
  strcpy (istrhello->strval, "hello");
  struct boxsequence_st *iseq3 =
    malloc (sizeof (struct boxsequence_st) + 3 * sizeof (myval_t));
  iseq3->tag = tag_sequence;
  iseq3->slen = 3;
  iseq3->valtab[0] = (myval_t)iv42;
  iseq3->valtab[1] = (myval_t)istrhello;
  iseq3->valtab[2] = (myval_t)NULL;
  printf ("before %s:%d gdb print iseq3\n", __FILE__, __LINE__);
}

这是在gdb下要读取的Python文件

^{pr2}$

我有以下几个基本问题。在

  1. 如何在GDB下用python安装我的漂亮打印机?(我在文档中看到了几种方法,我无法选择合适的方法)。在
  2. 如何确保GDB以相同的方式打印union my_un及其typedef的同义词myval_t。在
  3. 漂亮的打印机应该如何检测空指针?在
  4. 我的漂亮的打印机如何递归struct boxsequence_st?这意味着检测到指针是非nil,然后取消对其ptag的引用,将该标记与tag_sequence进行比较,很好地打印valtab灵活数组成员。在
  5. 如何避免将漂亮的印刷品递归太深?在

Tags: stringtagenumstructeninttinyst
1条回答
网友
1楼 · 发布于 2024-10-01 13:33:02

我没有足够的gdbpythonapi经验来回答这个问题;我认为这只是一位开发人员的研究笔记。我下面所附的代码也相当粗糙和丑陋。但是,这确实适用于gdb-7.4和python-2.7.3。调试运行示例:

$ gcc -Wall -g3 tiny.c -o tiny
$ gdb tiny
(gdb) b 58
(gdb) run
(gdb) print iseq3
$1 = (struct boxsequence_st *) 0x602050
(gdb) print iv42
$2 = (struct boxint_st *) 0x602010
(gdb) print istrhello
$3 = (struct boxstring_st *) 0x602030

以上都是bog标准的打印输出我的理由是我经常想知道指针是什么,所以我不想覆盖那些。但是,引用指针时使用的pretyprinter如下所示:

^{pr2}$

最后一行显示,在调试tiny时,同一目录中的tiny-gdb.py会自动加载(尽管您可以禁用此功能,但我相信这是默认行为)。在

用于上述操作的tiny-gdb.py文件:

def deref(reference):
    target = reference.dereference()
    if str(target.address) == '0x0':
        return 'NULL'
    else:
        return target

class cstringprinter:
    def __init__(self, value, maxlen=4096):
        try:
            ends = gdb.selected_inferior().search_memory(value.address, maxlen, b'\0')
            if ends is not None:
                maxlen = ends - int(str(value.address), 16)
                self.size = str(maxlen)
            else:
                self.size = '%s+' % str(maxlen)
            self.data = bytearray(gdb.selected_inferior().read_memory(value.address, maxlen))
        except:
            self.data = None
    def to_string(self):
        if self.data is None:
            return 'NULL'
        else:
            return '\"%s\"(%s)' % (str(self.data).encode('string_escape').replace('"', '\\"').replace("'", "\\\\'"), self.size)

class boxintprinter:
    def __init__(self, value):
        self.value = value.cast(gdb.lookup_type('struct boxint_st'))
    def to_string(self):
        return '(struct boxint_st)%s' % str(self.value['ival'])

class boxstringprinter:
    def __init__(self, value):
        self.value = value.cast(gdb.lookup_type('struct boxstring_st'))
    def to_string(self):
        return '(struct boxstring_st)%s' % (self.value['strval'])

class boxsequenceprinter:
    def __init__(self, value):
        self.value = value.cast(gdb.lookup_type('struct boxsequence_st'))
    def display_hint(self):
        return 'array'
    def to_string(self):
        return '(struct boxsequence_st)(%s)' % str(self.value['slen'])
    def children(self):
        value = self.value
        tag = str(value['tag'])
        count = int(str(value['slen']))
        result = []
        if tag == 'tag_none':
            for i in xrange(0, count):
                result.append( ( '#%d' % i, deref(value['valtab'][i]['ptag']) ))
        elif tag == 'tag_int':
            for i in xrange(0, count):
                result.append( ( '#%d' % i, deref(value['valtab'][i]['pint']) ))
        elif tag == 'tag_string':
            for i in xrange(0, count):
                result.append( ( '#%d' % i, deref(value['valtab'][i]['pstr']) ))
        elif tag == 'tag_sequence':
            for i in xrange(0, count):
                result.append( ( '#%d' % i, deref(value['valtab'][i]['pseq']) ))
        return result

def typefilter(value):
    "Pick a pretty-printer for 'value'."
    typename = str(value.type.strip_typedefs().unqualified())

    if typename == 'char []':
        return cstringprinter(value)

    if (typename == 'struct boxint_st' or
        typename == 'struct boxstring_st' or
        typename == 'struct boxsequence_st'):
        tag = str(value['tag'])
        if tag == 'tag_int':
            return boxintprinter(value)
        if tag == 'tag_string':
            return boxstringprinter(value)
        if tag == 'tag_sequence':
            return boxsequenceprinter(value)

    return None

gdb.pretty_printers.append(typefilter)

我选择的理由如下:

  1. 如何将漂亮的打印机安装到gdb?在

    这个问题有两个部分:在哪里安装Python文件,以及如何将漂亮的打印机挂接到gdb。在

    因为漂亮的打印机选择不能仅仅依赖于推断的类型,而是必须窥视实际的数据字段,所以不能使用正则表达式匹配函数。相反,我选择将我自己的漂亮打印机选择器函数typefilter()添加到全局漂亮打印机列表中,如in the documentation所述。我没有实现enable/disable功能,因为我相信只加载/不加载相关的Python脚本更容易。在

    typefilter()每个变量引用调用一次,除非其他漂亮的打印机已经接受它。)

    文件位置问题更为复杂。对于特定于应用程序的漂亮打印机,将它们放在一个Python脚本文件中听起来很明智,但对于库来说,似乎应该进行一些拆分。文档recommends将函数打包到一个Python模块中,这样一个简单的python import module就可以启动漂亮的打印机了。幸运的是,Python打包非常简单。如果您要import gdb到顶部并将其保存到/usr/lib/pythonX.Y/tiny.py,其中{}是使用的python版本,那么您只需要在gdb中运行python import tiny来启用漂亮的打印机。在

    当然,适当地packaging漂亮的打印机是一个非常好的主意,特别是如果您打算分发它,但是它可以归结为在脚本的开头添加一些变量等,假设您将其作为一个文件保存。对于更复杂的漂亮打印机,使用目录布局可能是个好主意。

  2. 如果您有一个值val,那么val.type是描述其类型的gdb.Type对象;将其转换为字符串将生成一个人类可读的类型名。在

    val.type.strip_typedefs()生成实际类型,去掉所有typedef。我甚至添加了.unqualified(),这样所有const/volatile/etc.type限定符都被删除了。

  3. 空指针检测有点棘手。在

    我发现的最好的方法是检查目标的字符串化.address成员gdb.价值对象,并查看它是否是"0x0"。在

    为了让生活更简单,我编写了一个简单的deref()函数,它试图取消对指针的引用。如果目标指向(void*)0,则返回字符串"NULL",否则返回目标gdb.价值对象。在

    我使用deref()的方法是基于这样一个事实,即"array"类型的漂亮打印机会产生一个2元组的列表,其中第一项是名称字符串,第二项是gdb.价值对象或字符串。这个列表由pretty printer对象的^{}方法返回。

  4. 如果泛型实体有一个单独的类型,那么处理“区分的联合”类型会容易得多。如果你有

    struct box_st {
        enum tag_en tag;
    };
    

    tag值仍然不确定时,它在任何地方都被使用;而特定的结构类型只在其tag值固定的地方使用。这将允许更简单的类型推断。在

    在实际上,在tiny.c中,struct box*_st类型可以互换使用。(或者,更具体地说,我们不能仅依赖于基于类型的特定标记值。)

    sequence case实际上非常简单,因为valtab[]可以简单地当作一个空指针数组来处理。sequence标记用于选择正确的联合成员。实际上,如果valtab[]只是一个空指针数组,那么gdb.Value.cast(gdb.lookup_类型())或gdb.Value.reinterpret_cast(gdb.lookup_类型())可用于根据需要更改每个指针类型,就像我对装箱结构类型所做的那样。

  5. 递归极限?在

    您可以在print命令中使用@运算符来指定打印多少个元素,但这对嵌套没有帮助。在

    如果将iseq3->valtab[2] = (myval_t)iseq3;添加到tiny.c,则得到一个无限递归序列。gdb确实很好地打印了它,尤其是使用set print array,但它不注意或不关心递归。

在我看来,除了为深度嵌套或递归数据结构编写一个漂亮的打印机之外,您可能还希望编写一个gdb命令。在测试期间,我编写了一个命令,它使用Graphviz直接从gdb中绘制二叉树结构;我确信它比纯文本输出要好。在

添加:如果将以下内容另存为/usr/lib/pythonX.Y/tree.py

import subprocess
import gdb

def pretty(value, field, otherwise=''):
    try:
        if str(value[field].type) == 'char []':
            data = str(gdb.selected_inferior().read_memory(value[field].address, 64))
            try:
                size = data.index("\0")
                return '\\"%s\\"' % data[0:size].encode('string_escape').replace('"', '\\"').replace("'", "\\'")
            except:
                return '\\"%s\\"..' % data.encode('string_escape').replace('"', '\\"').replace("'", "\\'")
        else:
            return str(value[field])
    except:
        return otherwise

class tee:
    def __init__(self, cmd, filename):
        self.file = open(filename, 'wb')
        gdb.write("Saving DOT to '%s'.\n" % filename)
        self.cmd = cmd
    def __del__(self):
        if self.file is not None:
            self.file.flush()
            self.file.close()
            self.file = None
    def __call__(self, arg):
        self.cmd(arg)
        if self.file is not None:
            self.file.write(arg)

def do_dot(value, output, visited, source, leg, label, left, right):
    if value.type.code != gdb.TYPE_CODE_PTR:
        return
    target = value.dereference()

    target_addr = int(str(target.address), 16)
    if target_addr == 0:
        return

    if target_addr in visited:
        if source is not None:
            path='%s.%s' % (source, target_addr)
            if path not in visited:
                visited.add(path)
                output('\t"%s" -> "%s" [ taillabel="%s" ];\n' % (source, target_addr, leg))
        return

    visited.add(target_addr)

    if source is not None:
        path='%s.%s' % (source, target_addr)
        if path not in visited:
            visited.add(path)
            output('\t"%s" -> "%s" [ taillabel="%s" ];\n' % (source, target_addr, leg))

    if label is None:
        output('\t"%s" [ label="%s" ];\n' % (target_addr, target_addr))
    elif "," in label:
        lab = ''
        for one in label.split(","):
            cur = pretty(target, one, '')
            if len(cur) > 0:
                if len(lab) > 0:
                    lab = '|'.join((lab,cur))
                else:
                    lab = cur
        output('\t"%s" [ shape=record, label="{%s}" ];\n' % (target_addr, lab))
    else:
        output('\t"%s" [ label="%s" ];\n' % (target_addr, pretty(target, label, target_addr)))

    if left is not None:
        try:
            target_left = target[left]
            do_dot(target_left, output, visited, target_addr, left, label, left, right)
        except:
            pass

    if right is not None:
        try:
            target_right = target[right]
            do_dot(target_right, output, visited, target_addr, right, label, left, right)
        except:
            pass

class Tree(gdb.Command):

    def __init__(self):
        super(Tree, self).__init__('tree', gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL, False)

    def do_invoke(self, name, filename, left, right, label, cmd, arg):
        try:
            node = gdb.selected_frame().read_var(name)
        except:
            gdb.write('No symbol "%s" in current context.\n' % str(name))
            return
        if len(arg) < 1:
            cmdlist = [ cmd ]
        else:
            cmdlist = [ cmd, arg ]
        sub = subprocess.Popen(cmdlist, bufsize=16384, stdin=subprocess.PIPE, stdout=None, stderr=None)
        if filename is None:
            output = sub.stdin.write
        else:
            output = tee(sub.stdin.write, filename)
        output('digraph {\n')
        output('\ttitle = "%s";\n' % name)
        if len(label) < 1: label = None
        if len(left)  < 1: left  = None
        if len(right) < 1: right = None
        visited = set((0,))
        do_dot(node, output, visited, None, None, label, left, right)
        output('}\n')
        sub.communicate()
        sub.wait()

    def help(self):
        gdb.write('Usage: tree [OPTIONS] variable\n')
        gdb.write('Options:\n')
        gdb.write('   left=name          Name member pointing to left child\n')
        gdb.write('   right=name         Name right child pointer\n')
        gdb.write('   label=name[,name]  Define node fields\n')
        gdb.write('   cmd=dot arg=-Tx11  Specify the command (and one option)\n')
        gdb.write('   dot=filename.dot   Save .dot to a file\n')
        gdb.write('Suggestions:\n')
        gdb.write('   tree cmd=neato variable\n')

    def invoke(self, argument, from_tty):
        args = argument.split()
        if len(args) < 1:
            self.help()
            return
        num = 0
        cfg = { 'left':'left', 'right':'right', 'label':'value', 'cmd':'dot', 'arg':'-Tx11', 'dot':None }
        for arg in args[0:]:
            if '=' in arg:
                key, val = arg.split('=', 1)
                cfg[key] = val
            else:
                num += 1
                self.do_invoke(arg, cfg['dot'], cfg['left'], cfg['right'], cfg['label'], cfg['cmd'], cfg['arg'])
        if num < 1:
            self.help()

Tree()

您可以在gdb中使用它:

(gdb) python import tree
(gdb) tree
Usage: tree [OPTIONS] variable
Options:
   left=name          Name member pointing to left child
   right=name         Name right child pointer
   label=name[,name]  Define node fields
   cmd=dot arg=-Tx11  Specify the command (and one option)
   dot=filename.dot   Save .dot to a file
Suggestions:
   tree cmd=neato variable

如果你有

struct node {
    struct node *le;
    struct node *gt;
    long         key;
    char         val[];
}

struct node *sometree;

并且您已经安装了X11(本地或远程)连接和Graphviz,您可以使用

(gdb) tree left=le right=gt label=key,val sometree

查看树结构。因为它保留了一个已经访问过的节点的列表(作为Python集合),所以它不会对递归结构感到厌烦。在

我可能应该在发布之前清理我的Python代码片段,但没关系。请务必考虑这些只是初始测试版本;使用风险自负。:)

相关问题 更多 >