gdb漂亮的用python打印递归结构 [英] gdb pretty printing with python a recursive structure

查看:199
本文介绍了gdb漂亮的用python打印递归结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对Python不是很熟悉,我只是发现了GDB python脚本功能;我的问题的动机是增强GDB在 MELT监视器内打印值,该值将在以后连接到 GCC MELT 。但是这里是一个更简单的变种。



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



我想用一个歧视联盟类型调试一个C程序:



<$ p $通过Basile Starynkevitch
//在gcc -g3 -Wall -std = c99 tiny.c -o tiny
//下编译//在公共领域中文件tiny.c debug with gdb tiny
//在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; //为tag_int
int ival;
};
struct boxstring_st {
enum tag_en tag; //对于tag_string
char strval []; //以零结尾的C字符串
};
struct boxsequence_st {
enum tag_en tag; //为tag_sequence
unsigned slen;
myval_t valtab []; //长度为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(%s之前:%d gdb print iseq3 \\\
,__FILE__,__LINE__);

$ / code>

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

 #在公共领域中的文件tiny-gdb.py由Basile Starynkevitch 
##另请参阅tiny.c文件
class my_val_Printer :
pretty打印一个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

我坚持以下基本问题。 / p>


  1. 如何在GDB下的python中安装我漂亮的打印机? (我在文档中看到了几种方法,我不能选择合适的方法)。 如何确保GDB非常好地打印 union my_un 和它的typedef-ed同义词 myval_t 同样的方式。

  2. 漂亮的打印机应该如何检测NULL指针?

  3. 我的漂亮打印机如何执行 struct boxsequence_st ?这意味着检测到指针非零,然后取消引用它的 ptag ,将该标签与 tag_sequence 进行比较, valtab 灵活的数组成员。

  4. 如何避免递归太深的漂亮打印? ol>

    解决方案

    我没有足够的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 = 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 =( (boxbox_st)结构boxsequence_st)(3)= {
    (struct boxint_st)42,
    (struct boxstring_st)hello(5),
    NULL
    }
    (gdb) info自动加载
    加载脚本
    是/home/.../tiny-gdb.py

    最后一行显示在同一目录下调试 tiny tiny-gdb.py 时自动加载(尽管你可以禁用它,但我确信这是默认行为)。



    tiny-gdb.py 用于上面的文件:

      def deref(reference):
    target = reference.dereference()
    如果str(target.address)=='0x0':
    返回'NULL'
    else:
    返回目标

    class cstringprinter:
    def __init __(self,value,maxlen = 4096):
    try:
    ends = gdb.selected_inferior()。search_memory(value.address,maxlen,b'\')
    如果ends不是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))
    除外:
    self.data = None
    def to_string(self):
    如果self.data是无:
    返回'NULL'
    else:
    返回'\%s\(%s)'%(str(self.data).encode('string_escape') .repl ace(''','\\\''')。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.valu e
    tag = str(value ['tag'])
    count = int(str(value ['slen']))
    result = []
    if tag == 'tag_none':
    for xrange(0,count):
    result.append(('#%d'%i,deref(value ['valtab'] [i] ['ptag' ])))
    elif tag =='tag_int':
    for xrange(0,count):
    result.append(('#%d'%i,deref(value ['valtab'] [i] ['pint'])))
    elif tag =='tag_string':
    for xrange(0,count):
    result.append( ('#%d'%i,deref(value ['valtab'] [i] ['pstr']))
    elif tag =='tag_sequence':
    for xrange(0 ,count):
    result.append(('#%d'%i,deref(value ['valtab'] [i] ['pseq'])))
    返回结果

    def typefilter(value):
    为'value'选择一个漂亮的打印机。
    typename = str(value.type.strip_typedefs()。unqualified())

    如果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文件,以及如何钩住漂亮的p因为漂亮的打印机选择不能单独依赖推断的类型,而必须查看实际的数据字段,所以不能使用正则表达式匹配功能。相反,我选择将我自己的漂亮打印机选择器函数 typefilter()添加到全局漂亮打印机列表中,如在文档中。我没有实现启用/禁用功能,因为我相信只需加载/不加载相关的Python脚本就容易了。



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



      文件位置问题是更复杂的一个。对于特定于应用程序的漂亮打印机,将它们放入单个Python脚本文件听起来很明智,但对于图书馆来说,某些分割似乎是按顺序的。文档建议将函数打包到Python模块中,这样一个简单的 python导入模块可以启用漂亮的打印机。幸运的是,Python包装非常简单。如果您将导入gdb 到最上面并保存到 /usr/lib/pythonX.Y/tiny.py ,其中 XY 是使用的python版本,您只需要在gdb中运行 python import tiny 来启用漂亮-binter。



      当然,正确地包装漂亮的打印机是一个非常好的主意,尤其是如果你打算分发它的话,但是假设你把它保存为一个单独的文件,那么在脚本的开始部分增加一些变量等等可以归结为很多。对于更复杂的漂亮打印机,使用目录布局可能是一个好主意。

    2. c $ c>,那么 val.type gdb.Type 对象描述它的类型;将其转换为字符串会生成一个人类可读的类型名称。



      val.type.strip_typedefs()键入所有typedef消除。我甚至添加了 .unqualified(),这样所有的const / volatile / etc。

    3. NULL指针检测有点棘手。



      最好的方法是发现,是检查目标gdb.Value对象的字符串化的 .address 成员,并查看它是否为0x0 deref()函数,它会尝试取消引用指针。如果目标指向(void *)0,它将返回字符串NULL,否则返回目标gdb.Value对象。



      我使用 deref()的方式是基于以下事实:array type pretty-printers会生成一个2元组列表,其中第一个项目是名称字符串,第二个项目是gdb.Value对象或字符串。此列表由 children()返回,


    4. 处理歧视联盟类型会更容易,如果你对于通用实体有一个单独的类型。也就是说,如果你有

        struct box_st {
      enum tag_en tag;
      };

      标签价值仍不确定;并且只有在标记值固定的情况下才使用特定的结构类型。这将允许更简单的类型推断。



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

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

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

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


      ol>

      在我看来,除了用于深层嵌套或递归数据结构的漂亮打印机之外,您可能还希望编写 gdb命令。在我的测试过程中,我编写了一个使用Graphviz直接从gdb中绘制二叉树结构的命令;我绝对相信它打败纯文本输出。



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

       导入子流程
      导入gdb

      def漂亮(value,field,否则=''):
      try:
      如果str(value [field] .type)=='char []':
      data = str(gdb .selected_inferior()。read_memory(value [field] .address,64))
      try:
      size = data.index(\0)
      return'\\ ('''','\\\''%data [0:size] .encode )
      除外:
      返回'\\'%s \\..'%data.encode('string_escape')。replace(''','\\ ').replace(',\\')
      else:
      return str(value [field])
      除外:
      否则返回

      类tee:
      def __init __(self,cmd,filename):
      self.file = open(文件名,'wb')
      gdb.write(将DOT保存为'%s'。 \\ n%filename)
      self.cmd = cmd
      def __del __(self):
      如果self.file不是:
      self.file.flush()
      self.file.close()
      self.file = None
      def __call __(self,arg):
      self.cmd(arg)
      如果self.file不是无:
      self.file.write(arg)
      $ b $ def do_dot(value,output,visited,source,leg,label,left,right):
      if value.type .code!= gdb.TYPE_CODE_PTR:
      return
      target = value.dereference()
      $ b $ target_addr = int(str(target.address),16)
      if target_addr == 0:
      返回
      $ b $如果target_addr在visited:
      如果source不是None:
      path ='%s。%s'%(source, target_addr)
      如果路径未被访问:
      vi sited.add(path)
      output('\t%s - > %s[taillabel =%s]; \\\
      '%(source,target_addr,leg))
      return

      visited.add(target_addr)

      如果源不是None:
      path ='%s。%s'%(source,target_addr)
      如果路径未被访问:
      visited.add(path)
      output('\t%s - >%s[taillabel =%s]; \\\
      '%(source,target_addr,leg))

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

      如果不是无:
      try:
      target_left = target [left]
      do_dot(target_left,output,visited,target_addr,left,label,left,right)
      除外:
      通过

      如果不是无:
      试试:
      target_right = target [right]
      do_dot(target_right,output,visited,target_addr,right,label,left,right)
      除外:
      pass

      class Tree(gdb.Command):

      def __init __(self):
      super(Tree,self).__ init __('tree',gdb.COMMAND_DATA,gdb.COMPLETE_SYMBOL ,假)

      def do_invoke(self,name,filename,left,right,label,cmd,arg):
      try:
      node = gdb.selected_frame()。read_var(name)
      除外:
      gdb.write('当前上下文中没有符号%s。''str'(name))
      返回
      if len(arg)< 1:
      cmdlist = [cmd]
      else:
      cmdlist = [cmd,arg]
      sub = subprocess.Popen(cmdlist,bufsize = 16384,stdin = subprocess.PIPE, stdr = None,stderr = None)
      如果filename是None:
      output = sub.stdin.write
      else:
      output = tee(sub.stdin.write,filename)
      output('digraph {\ n')
      output('\ttitle =%s; \\\
      '%name)
      if len(label)< 1:label = None
      如果len(左)< 1:left = None
      如果len(右)< 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('用法:tree [选项]变量\ n')
      gdb.write('Options:\\\
      ')
      gdb.write('left = name指向左边child \\\
      '的成员名称)
      gdb .write('right = name Name right child pointer'\
      ')
      gdb.write('label = name [,name]定义节点字段\'')
      gdb.write('cmd = dot arg = -Tx11指定命令(和一个选项)\ n')
      gdb.write('dot = filename.dot将.dot保存到文件\ n')
      gdb。写('建议:\ n')
      gdb.write('tree cmd = neato variable\\\
      ')

      def invoke(self,argument,from_tty):
      args = argument.split()
      如果len(args) 1:
      self.help()
      返回
      num = 0
      cfg = {'left':'left','right':'right','label': 'value','cmd':'dot','arg':' - Tx11','dot':None} args中的
      [0:]:
      如果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导入树
      (gdb)树
      用法:tree [选项]变量
      选项:
      left = name指向左边子元素的成员
      右边=名称右边子指针
      标签=名称[,名称]定义节点字段
      cmd = dot arg = -Tx11指定命令(和一个选项)
      dot = filename.dot将.dot保存到文件
      建议:
      tree cmd = neato variable

      如果您有例如

        struct node {
      struct node * le;
      struct node * gt;
      长键;
      char val [];
      }

      struct node * sometree;

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

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

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



      我可能应该在发布之前清理我的Python片段, 但是不要紧。请考虑这些只有初始测试版本;使用需要您自担风险。 :)

      I'm not very familiar with Python, and I am just discovering GDB python scripting capabilities; the motivation of my question is to enhance the GDB printing of values inside the MELT monitor which will later be connected to GCC MELT. But here is a simpler variant.

      My system is Linux/Debian/Sid/x86-64. the GCC compiler is 4.8.2; the GDB debugger is 7.6.2; its python is 3.3

      I want to debug a C program with a "discriminated union" type:

      // 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__);
      }
      

      Here is my Python file to be read under 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
      

      I'm stuck with the following basic questions.

      1. How to install my pretty printer in python under GDB? (I'm seeing several ways in the documentation, and I can't choose the appropriate one).
      2. How to ensure that GDB pretty-prints both union my_un and its typedef-ed synonym myval_t the same way.
      3. How should the pretty printer detect NULL pointers?
      4. How can my pretty printer recurse for struct boxsequence_st ? This means detecting that the pointer is non-nil, then dereferencing its ptag, comparing that tag to tag_sequence, pretty printing the valtab flexible array member.
      5. How to avoid recursing too deeply the pretty printing?

      解决方案

      I don't have enough experience with the gdb Python api to call this an answer; I consider this just some research notes from a fellow developer. My code attached below is quite crude and ugly, too. However, this does work with gdb-7.4 and python-2.7.3. An example debugging run:

      $ 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
      

      All of the above are bog-standard pretty-printed outputs -- my reasoning is that I often want to see what the pointers are, so I didn't want to override those. However, dreferencing the pointers uses the prettyprinter shown further below:

      (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
      

      The last line shows that when debugging tiny, tiny-gdb.py in the same directory gets loaded automatically (although you can disable this, I do believe this is the default behaviour).

      The tiny-gdb.py file used for above:

      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)
      


      The reasoning behind my choices are as follows:

      1. How to install pretty-printers to gdb?

        There are two parts to this question: where to install the Python files, and how to hook the pretty-printers to gdb.

        Because the pretty-printer selection cannot rely on the inferred type alone, but has to peek into the actual data fields, you cannot use the regular expression matching functions. Instead, I chose to add my own pretty-printer selector function, typefilter(), to the global pretty-printers list, as described in the documentation. I did not implement the enable/disable functionality, because I believe it is easier to just load/not load the relevant Python script instead.

        (typefilter() gets called once per every variable reference, unless some other pretty-printer has already accepted it.)

        The file location issue is a more complicated one. For application-specific pretty-printers, putting them into a single Python script file sounds sensible, but for a library, some splitting seems to be in order. The documentation recommends packaging the functions into a Python module, so that a simple python import module enables the pretty-printer. Fortunately, Python packaging is quite straightforward. If you were to import gdb to the top and save it to /usr/lib/pythonX.Y/tiny.py, where X.Y is the python version used, you only need to run python import tiny in gdb to enable the pretty-printer.

        Of course, properly packaging the pretty-printer is a very good idea, especially if you intend to distribute it, but it does pretty much boil down to adding some variables et cetera to the beginning of the script, assuming you keep it as a single file. For more complex pretty-printers, using a directory layout might be a good idea.

      2. If you have a value val, then val.type is the gdb.Type object describing its type; converting it to string yields a human-readable type name.

        val.type.strip_typedefs() yields the actual type with all typedefs stripped. I even added .unqualified(), so that all const/volatile/etc. type qualifiers are removed.

      3. NULL pointer detection is a bit tricky.

        The best way I found, was to examine the stringified .address member of the target gdb.Value object, and see if it is "0x0".

        To make life easier, I was able to write a simple deref() function, which tries to dereference a pointer. If the target points to (void *)0, it returns the string "NULL", otherwise it returns the target gdb.Value object.

        The way I use deref() is based on the fact that "array" type pretty-printers yield a list of 2-tuples, where the first item is the name string, and the second item is either a gdb.Value object, or a string. This list is returned by the children() method of the pretty-printer object.

      4. Handling "discriminated union" types would be much easier, if you had a separate type for the generic entity. That is, if you had

        struct box_st {
            enum tag_en tag;
        };
        

        and it was used everywhere when the tag value is still uncertain; and the specific structure types only used where their tag value is fixed. This would allow a much simpler type inference.

        As it is, in tiny.c the struct box*_st types can be used interchangeably. (Or, more specifically, we cannot rely on a specific tag value based on the type alone.)

        The sequence case is actually quite simple, because valtab[] can be treated as simply as an array of void pointers. The sequence tag is used to pick the correct union member. In fact, if valtab[] was simply a void pointer array, then gdb.Value.cast(gdb.lookup_type()) or gdb.Value.reinterpret_cast(gdb.lookup_type()) can be used to change each pointer type as necessary, just like I do for the boxed structure types.

      5. Recursion limits?

        You can use the @ operator in print command to specify how many elements are printed, but that does not help with nesting.

        If you add iseq3->valtab[2] = (myval_t)iseq3; to tiny.c, you get an infinitely recursive sequence. gdb does print it nicely, especially with set print array, but it does not notice or care about the recursion.

      In my opinion, you might wish to write a gdb command in addition to a pretty-printer for deeply nested or recursive data structures. During my testing, I wrote a command that uses Graphviz to draw binary tree structures directly from within gdb; I'm absolutely convinced it beats plain text output.

      Added: If you save the following as /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()
      

      you can use it in 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
      

      If you have e.g.

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

      and you have X11 (local or remote) connection and Graphviz installed, you can use

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

      to view the tree structure. Because it retains a list of already visited nodes (as a Python set), it does not get fazed about recursive structures.

      I probably should have cleaned my Python snippets before posting, but no matter. Please do consider these only initial testing versions; use at your own risk. :)

      这篇关于gdb漂亮的用python打印递归结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆