我不是很熟悉Python,我只是发现了GDB的python脚本function; 我的问题的动机是增强GEL打印值,这些值将在后面连接到GCC MELT 。 但是这是一个更简单的变种。
我的系统是Linux / Debian / Sid / x86-64。 GCC编译器是4.8.2; GDBdebugging器是7.6.2; 它的Python是3.3
我想用“歧视联盟”types来debuggingC程序:
// 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__); }
这是我的Python文件在gdb下读取
# 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
我坚持以下基本问题。
union my_un
和typedef-ed同义词myval_t
。 struct boxsequence_st
? 这意味着检测到指针是非零,然后解引用它的ptag
,比较该标签到tag_sequence
,漂亮的打印valtab
灵活的数组成员。 我没有足够的经验与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
,其中XY
是所使用的Python版本,则只需要在gdb中运行python import tiny
以启用漂亮的打印机。
当然,正确地打包漂亮的打印机是一个非常好的主意,特别是如果你打算分发这个打印机的话,但是,在脚本的开始部分添加一些变量等等,假设你把它作为一个单独的文件。 对于更复杂的漂亮打印机,使用目录布局可能是一个好主意。
如果你有一个val
值,那么val.type
是描述它的类型的gdb.Type对象; 将其转换为字符串将生成一个人类可读的类型名称。
val.type.strip_typedefs()
产生所有类型定义被剥离的实际类型。 我甚至添加了.unqualified()
,以便所有的const / volatile / .unqualified()
类型限定符被删除。
NULL指针检测有点棘手。
我发现的最好的方法是检查目标gdb.Value对象的字符串.address成员,看它是否为"0x0"
。
为了使生活更轻松,我能够编写一个简单的deref()
函数,它试图解引用一个指针。 如果目标指向(void *)0,则返回字符串"NULL"
,否则返回目标gdb.Value对象。
我使用deref()
是基于这样一个事实,即"array"
类型的漂亮打印机产生一个2元组列表,其中第一个项目是名称字符串,第二个项目是gdb.Value对象,或者一个字符串。 这个列表是由漂亮的打印机对象的children()
方法返回的。
如果你有一个单独的类型的通用实体,处理“歧视的联合”类型会容易得多。 那就是,如果你有的话
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())来根据需要更改每个指针类型,就像我为盒装结构类型做的一样。
递归限制?
您可以在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片段,但不管。 请考虑这些只有初始测试版本; 使用风险自负。 🙂