2014-05-10 19 views
10

Tôi không quen thuộc với Python, và tôi chỉ khám phá khả năng kịch bản lệnh python GDB; động lực của câu hỏi của tôi là tăng cường GDB in các giá trị bên trong MELT monitor mà sau này sẽ được kết nối với GCC MELT. Nhưng đây là một biến thể đơn giản hơn.gdb in đẹp với python cấu trúc đệ quy

Hệ thống của tôi là Linux/Debian/Sid/x86-64. trình biên dịch GCC là 4.8.2; trình gỡ lỗi GDB là 7.6.2; python của nó là 3,3

Tôi muốn gỡ lỗi một chương trình C với một "liên minh phân biệt đối xử" kiểu:

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

Dưới đây là tập tin Python tôi để được đọc dưới 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 = "[email protected]" + 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 

Tôi mắc kẹt với các câu hỏi cơ bản sau đây.

  1. Làm thế nào để cài đặt máy in đẹp của tôi trong python dưới GDB? (Tôi thấy một số cách trong tài liệu và tôi không thể chọn cách thích hợp).
  2. Cách đảm bảo rằng GDB in đẹp cả hai union my_un và từ đồng nghĩa typedef-ed myval_t cùng một cách.
  3. Máy in đẹp sẽ phát hiện ra con trỏ NULL như thế nào?
  4. Làm cách nào máy in đẹp của tôi có thể sử dụng cho struct boxsequence_st? Điều này có nghĩa là phát hiện con trỏ không phải là số không, sau đó hủy đăng ký ptag, so sánh thẻ đó với tag_sequence, in ấn thành viên mảng linh hoạt valtab linh hoạt.
  5. Làm thế nào để tránh đệ quy quá sâu vào quá trình in đẹp?

Trả lời

9

Tôi không có đủ kinh nghiệm với api gdb Python để gọi đây là câu trả lời; Tôi xem đây chỉ là một số ghi chú nghiên cứu từ một nhà phát triển đồng nghiệp. Mã của tôi được đính kèm bên dưới khá thô và xấu xí. Tuy nhiên, điều này không làm việc với gdb-7,4 và python-2.7.3. Một ví dụ chạy gỡ lỗi:

$ 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 

Tất cả những điều trên là bog tiêu chuẩn khá-in kết quả đầu ra - lý luận của tôi là tôi thường muốn xem những gì con trỏ là, vì vậy tôi không muốn ghi đè những, cái đó. Tuy nhiên, dreferencing các con trỏ sử dụng prettyprinter hiện thêm dưới đây:

(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 

Dòng cuối cùng cho thấy khi gỡ lỗi tiny, tiny-gdb.py trong cùng thư mục được nạp tự động (mặc dù bạn có thể vô hiệu hóa này, tôi tin rằng đây là mặc định hành vi).

Các tiny-gdb.py tập tin được sử dụng để ở trên:

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) 

Lý do đằng sau sự lựa chọn của tôi là như sau:

  1. Làm thế nào để cài đặt khá-máy in để gdb?

    Có hai phần của câu hỏi này: nơi cài đặt các tệp Python và cách móc các máy in đẹp vào gdb.

    Vì lựa chọn máy in đẹp không thể dựa vào loại suy luận một mình, nhưng phải nhìn vào các trường dữ liệu thực tế, bạn không thể sử dụng các hàm so khớp cụm từ thông dụng. Thay vào đó, tôi đã chọn thêm chức năng chọn máy in đẹp của riêng mình, typefilter(), vào danh sách máy in khá toàn cầu, như được mô tả in the documentation. Tôi đã không thực hiện chức năng bật/tắt, bởi vì tôi tin rằng việc tải/không tải tập lệnh Python có liên quan sẽ dễ dàng hơn.

    (typefilter() được gọi một lần mỗi mỗi tài liệu tham khảo biến, trừ một số khá-in khác đã chấp nhận nó.) Vị trí

    File vấn đề là một phức tạp hơn. Đối với các máy in đẹp dành riêng cho ứng dụng, việc đưa chúng vào một tệp tập lệnh Python đơn giản có vẻ hợp lý, nhưng đối với một thư viện, một số phần tách có vẻ như theo thứ tự. Tài liệu hướng dẫn recommends đóng gói các chức năng vào một mô-đun Python, sao cho một đơn giản python import module cho phép máy in đẹp. May mắn thay, bao bì Python khá đơn giản. Nếu bạn đã import gdb lên trên cùng và lưu nó vào /usr/lib/pythonX.Y/tiny.py, trong đó X.Y là phiên bản python được sử dụng, bạn chỉ cần chạy python import tiny trong gdb để bật máy in đẹp. Tất nhiên, đúng là packaging máy in khá là một ý tưởng rất hay, đặc biệt là nếu bạn có ý định phân phối nó, nhưng nó hơi sôi xuống để thêm một số biến và cetera vào đầu kịch bản, giả sử bạn giữ nó như một tập tin duy nhất. Đối với các máy in khá phức tạp, việc sử dụng bố cục thư mục có thể là một ý tưởng hay.

  2. Nếu bạn có giá trị val, thì val.type là đối tượng gdb.Type mô tả loại của nó; chuyển đổi nó thành chuỗi mang lại một tên kiểu người có thể đọc được.

    val.type.strip_typedefs() mang lại loại thực tế với tất cả các typedef bị tước. Tôi thậm chí còn thêm .unqualified(), để tất cả const/volatile/etc. loại vòng loại được loại bỏ.

  3. Phát hiện con trỏ NULL khó hiểu một chút.

    Cách tốt nhất mà tôi tìm thấy là kiểm tra thành viên .address đã được hợp nhất của đối tượng gdb.Value đích và xem liệu đó có phải là "0x0" hay không.

    Để làm cho cuộc sống dễ dàng hơn, tôi đã có thể viết một hàm deref() đơn giản, cố gắng bỏ qua con trỏ. Nếu mục tiêu trỏ tới (void *) 0, nó trả về chuỗi "NULL", nếu không nó sẽ trả về đối tượng gdb.Value đích.

    Cách tôi sử dụng deref() dựa trên thực tế là "array" loại máy in đẹp mang lại danh sách 2 bộ, trong đó mục đầu tiên là chuỗi tên và mục thứ hai là đối tượng gdb.Value hoặc một chuỗi. Danh sách này được trả về bởi phương thức children() của đối tượng máy in đẹp.

  4. Xử lý các loại "công đoàn phân biệt đối xử" sẽ dễ dàng hơn nhiều, nếu bạn có loại riêng cho thực thể chung.Tức là, nếu bạn có

    struct box_st { 
        enum tag_en tag; 
    }; 
    

    và nó đã được sử dụng ở mọi nơi khi giá trị tag vẫn không chắc chắn; và các loại cấu trúc cụ thể chỉ được sử dụng khi giá trị tag của chúng được cố định. Điều này sẽ cho phép suy luận kiểu đơn giản hơn nhiều.

    Vì vậy, trong tiny.c các loại struct box*_st có thể được sử dụng thay thế cho nhau. (Hoặc, cụ thể hơn, chúng tôi không thể dựa vào giá trị thẻ cụ thể chỉ dựa trên loại giá.)

    Trường hợp trình đơn thực sự khá đơn giản, vì valtab[] có thể được coi là đơn giản. Thẻ trình tự được sử dụng để chọn thành viên công đoàn chính xác. Trong thực tế, nếu valtab [] chỉ đơn giản là một mảng con trỏ void, thì gdb.Value.cast (gdb.lookup_type()) hoặc gdb.Value.reinterpret_cast (gdb.lookup_type()) có thể được sử dụng để thay đổi từng loại con trỏ khi cần thiết , giống như tôi làm cho các loại cấu trúc đóng hộp.

  5. Giới hạn đệ quy?

    Bạn có thể sử dụng toán tử @ trong lệnh print để chỉ định số lượng phần tử được in, nhưng điều đó không giúp làm tổ.

    Nếu bạn thêm iseq3->valtab[2] = (myval_t)iseq3; vào tiny.c, bạn sẽ nhận được một chuỗi đệ quy vô hạn. gdb in nó độc đáo, đặc biệt là với set print array, nhưng nó không chú ý hoặc quan tâm đến đệ quy.

Theo tôi, bạn có thể muốn viết một lệnh gdb ngoài một khá-in cho các cấu trúc dữ liệu lồng nhau sâu sắc hoặc đệ quy. Trong quá trình thử nghiệm, tôi đã viết một lệnh sử dụng Graphviz để vẽ các cấu trúc cây nhị phân trực tiếp từ bên trong gdb; Tôi hoàn toàn tin rằng nó đánh bại đầu ra văn bản đơn giản.

Added: Nếu bạn lưu như sau /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() 

bạn có thể sử dụng nó trong 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 

Nếu bạn có ví dụ

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

struct node *sometree; 

và bạn có X11 (địa phương hoặc từ xa) kết nối và Graphviz cài đặt, bạn có thể sử dụng

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

để xem các cấu trúc cây. Bởi vì nó giữ lại một danh sách các nút đã được truy cập (như một bộ Python), nó không bị lúng túng về các cấu trúc đệ quy.

Tôi có lẽ phải làm sạch các đoạn mã Python trước khi đăng, nhưng không sao cả. Vui lòng xem xét những phiên bản thử nghiệm ban đầu này; Sử dụng có nguy cơ của riêng bạn. :)

+0

Cảm ơn bạn đã trả lời. Tôi chưa thử nghiệm (vì hiện tại tôi không cần 'gdb' để gỡ lỗi), nhưng nó rất thông tin! –

Các vấn đề liên quan