我对Python不太熟悉,我只是发现了GDB python脚本功能; 我的问题的动机是增强MELT监视器内的值的GDB打印,该监视器稍后将连接到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#include #include 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中安装我漂亮的打印机?(我在文档中看到了几种方法,我无法选择合适的方法).
如何确保GDB 以同样的方式打印两者union my_un
及其typedef-ed同义词myval_t
.
漂亮的打印机应该如何检测NULL指针?
我的漂亮打印机怎么能递归struct boxsequence_st
呢?这意味着检测到指针是非零的,然后解除引用它ptag
,将该标记与tag_sequence
漂亮打印valtab
灵活的数组成员进行比较.
如何避免漂亮的印刷过于复杂?
Nominal Anim.. 8
我没有足够的经验使用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
以上所有都是标准的漂亮打印输出 - 我的理由是我经常想看看指针是什么,所以我不想覆盖它们.但是,引用指针使用下面进一步显示的prettyprinter:
(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)
我选择的理由如下:
如何在gdb上安装漂亮的打印机?
这个问题分为两部分:安装Python文件的位置,以及如何将漂亮的打印机挂钩到gdb.
因为漂亮的打印机选择不能单独依赖于推断类型,而是必须查看实际的数据字段,所以不能使用正则表达式匹配函数.相反,我选择将我自己的漂亮打印机选择器功能添加typefilter()
到全局漂亮打印机列表中,如文档中所述.我没有实现启用/禁用功能,因为我认为只是加载/不加载相关的Python脚本更容易.
(typefilter()
每个变量引用被调用一次,除非其他漂亮的打印机已经接受它.)
文件位置问题是一个更复杂的问题.对于特定于应用程序的漂亮打印机,将它们放入单个Python脚本文件听起来很合理,但对于库来说,某些拆分似乎是有序的.文档建议将函数打包到Python模块中,以便简单地python import module
启用漂亮的打印机.幸运的是,Python包装非常简单.如果你到import gdb
顶部并保存到/usr/lib/pythonX.Y/tiny.py
,X.Y
使用的python版本在哪里,你只需要python import tiny
在gdb中运行以启用漂亮的打印机.
当然,正确包装漂亮的打印机是一个非常好的主意,特别是如果你打算分发它,但它确实可以归结为在脚本的开头添加一些变量等等,假设你把它保持为单个文件.对于更复杂的漂亮打印机,使用目录布局可能是个好主意.
如果你有一个值val
,则val.type
是gdb.Type描述其类型的对象; 将其转换为字符串会产生一个人类可读的类型名称.
val.type.strip_typedefs()
产生实际类型,并删除所有typedef.我甚至添加.unqualified()
了所有const/volatile /等.类型限定符被删除.
NULL指针检测有点棘手.
我找到的最好方法是检查.address
目标gdb.Value对象的字符串化成员,看看是否存在"0x0"
.
为了让生活更轻松,我能够编写一个简单的deref()
函数,它试图取消引用一个指针.如果目标指向(void*)0,则返回字符串"NULL"
,否则返回目标gdb.Value对象.
我使用的方式deref()
是基于"array"
类型漂亮打印机产生2元组列表的事实,其中第一项是名称字符串,第二项是gdb.Value对象或字符串.此列表由children()
pretty-printer对象的方法返回.
如果您有一个通用实体的单独类型,处理"区别联合"类型会容易得多.也就是说,如果你有的话
struct box_st { enum tag_en tag; };
当tag
价值仍然不确定时,它在任何地方都被使用; 并且仅在其tag
值固定的情况下使用特定结构类型.这将允许更简单的类型推断.
实际上,tiny.c
这些struct box*_st
类型可以互换使用.(或者,更具体地说,我们不能仅仅基于类型依赖于特定的标记值.)
序列情况实际上非常简单,因为valtab[]
可以简单地将其视为一个void指针数组.序列标记用于选择正确的联合成员.实际上,如果valtab []只是一个void指针数组,那么gdb.Value.cast(gdb.lookup_type())或gdb.Value.reinterpret_cast(gdb.lookup_type())可用于根据需要更改每个指针类型就像我对盒装结构类型一样.
递归限制?
您可以使用@
operator in 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片段,但无论如何.请考虑这些仅初始测试版本; 使用风险自负.:)