gpt4 book ai didi

python - gdb 用 python 打印漂亮的递归结构

转载 作者:IT王子 更新时间:2023-10-29 00:00:58 28 4
gpt4 key购买 nike

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

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

我想调试一个带有“discriminated union”类型的 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 文件
 # file tiny-gdb.py in the public domain by Basile Starynkevitch
## see also tiny.c file
class my_val_Printer:
"""pretty prints a my_val"""
def __init__ (self, val):
self.val = val
def to_string (self):
outs = "my_val@" + self.val['ptr']
mytag = self.val['ptag'].dereference();
if (mytag):
outs = outs + mytag.to_string()
def display_hint (self):
return 'my_val'

def my_val_lookup(val):
lookup = val.type.tag
if (lookup == None):
return None
if lookup == "my_val":
return my_val_Printer(val)
return None

我被以下基本问题困住了。
  • 如何在 GDB 下的 python 中安装我的 pretty-print ? (我在文档中看到了几种方法,我无法选择合适的一种)。
  • 如何确保 GDB 漂亮地打印两者 union my_un及其类型定义的同义词 myval_t以同样的方式。
  • pretty-print 应该如何检测 NULL 指针?
  • 我 pretty-print 如何递归 struct boxsequence_st ?这意味着检测到指针非零,然后取消引用它的 ptag ,将该标签与 tag_sequence 进行比较, pretty-print valtab灵活的数组成员。
  • 如何避免对 pretty-print 的递归太深?
  • 最佳答案

    我对 gdb Python api 没有足够的经验来称其为答案;我认为这只是一位开发人员的一些研究笔记。我下面附上的代码也很粗糙和丑陋。但是,这确实适用于 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

    以上所有都是沼泽标准的 pretty-print 输出——我的理由是我经常想看看指针是什么,所以我不想覆盖它们。但是,引用指针会使用如下所示的 pretty-print :
    (gdb) print *iseq3
    $4 = (struct boxsequence_st)(3) = {(struct boxint_st)42, (struct boxstring_st)"hello"(5), NULL}
    (gdb) print *iv42
    $5 = (struct boxint_st)42
    (gdb) print *istrhello
    $6 = (struct boxstring_st)"hello"(5)
    (gdb) set print array
    (gdb) print *iseq3
    $7 = (struct boxsequence_st)(3) = {
    (struct boxint_st)42,
    (struct boxstring_st)"hello"(5),
    NULL
    }
    (gdb) info auto-load
    Loaded Script
    Yes /home/.../tiny-gdb.py

    最后一行显示调试时 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)

    我的选择背后的原因如下:
  • 如何将 pretty-print 安装到 gdb?

    这个问题有两个部分:在哪里安装 Python 文件,以及如何将 pretty-print 连接到 gdb。

    因为 pretty-print 的选择不能单独依赖推断类型,而是要查看实际数据字段,所以不能使用正则表达式匹配函数。相反,我选择添加自己的 pretty-print 选择器功能,typefilter() , 到全局 pretty-print 列表,如 in the documentation 所述.我没有实现启用/禁用功能,因为我相信只加载/不加载相关的 Python 脚本会更容易。

    ( typefilter() 每次变量引用都会被调用一次,除非其他 pretty-print 已经接受了它。)

    文件位置问题是一个更复杂的问题。对于特定于应用程序的 pretty-print ,将它们放入单个 Python 脚本文件听起来很合理,但对于库来说,似乎需要进行一些拆分。文档 recommends将函数打包成一个 Python 模块,这样一个简单的 python import module启用 pretty-print 。幸运的是,Python 打包非常简单。如果您要 import gdb到顶部并将其保存到 /usr/lib/pythonX.Y/tiny.py ,其中 X.Y是使用的python版本,你只需要运行python import tiny在 gdb 中启用 pretty-print 。

    当然正确packaging pretty-print 是一个非常好的主意,特别是如果您打算分发它,但它几乎可以归结为在脚本的开头添加一些变量等,假设您将其保留为单个文件。对于更复杂的 pretty-print ,使用目录布局可能是个好主意。
  • 如果您有一个值 val ,然后 val.typegdb.Type描述其类型的对象;将其转换为字符串会产生一个人类可读的类型名称。
    val.type.strip_typedefs()产生去除所有 typedef 的实际类型。我什至添加了 .unqualified() , 这样所有的 const/volatile/etc.类型限定符被删除。
  • NULL 指针检测有点棘手。

    我发现的最好方法是检查字符串化 .address目标 gdb.Value 对象的成员,查看是否为 "0x0" .

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

    我使用的方式 deref()是基于 "array" 的事实键入 pretty-print 会生成一个 2 元组列表,其中第一项是名称字符串,第二项是 gdb.Value 对象或字符串。此列表由 children() 返回 pretty-print 对象的方法。
  • 如果通用实体有一个单独的类型,则处理“有区别的 union ”类型会容易得多。也就是说,如果你有
    struct box_st {
    enum tag_en tag;
    };

    tag 时到处都使用它值(value)仍不确定;并且特定的结构类型仅用于它们的tag值是固定的。这将允许更简单的类型推断。

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

    序列情况其实很简单,因为valtab[]可以简单地视为一个空指针数组。序列标签用于选择正确的 union 成员。事实上,如果 valtab[] 只是一个空指针数组,那么 gdb.Value.cast(gdb.lookup_type()) 或 gdb.Value.reinterpret_cast(gdb.lookup_type()) 可用于根据需要更改每个指针类型,就像我对盒装结构类型所做的一样。
  • 递归限制?

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

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

  • 在我看来,你可能希望写一个 gdb 命令 除了用于深度嵌套或递归数据结构的 pretty-print 之外。在我的测试中,我写了一个命令,使用 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 片段,但没关系。请务必考虑这些仅初始测试版本;使用风险自负。 :)

    关于python - gdb 用 python 打印漂亮的递归结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23578312/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com