背景
工作中使用gdb调试c/c++程序时,经常会遇到从运行的进程中dump数据结构的场景。对于简单或连续存储的数据我们可以直接使用p或x命令,而对于较为复杂的数据结构就使用p或x命令就力不从心了,为此本文以一个链表为例展示如何使用python脚本来dump所有元素。
介绍
gdb 中的 Python 脚本是使用 gdb 的 Python 命令来执行的。Python 命令会启动一个 Python 解释器,并将 gdb 的上下文传递给解释器。gdb 中的 Python 脚本可以访问 gdb 的所有功能。例如,可以使用 Python 脚本来设置断点、查看变量值、跟踪程序执行流程等,较单纯的输入命令更加灵活。
示例代码
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
struct list_head {
struct list_head *next, *prev;
};
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static inline void __list_add(struct list_head *recent,
struct list_head *prev,
struct list_head *next)
{
next->prev = recent;
recent->next = next;
recent->prev = prev;
prev->next = recent;
}
static inline void list_add_tail(struct list_head *recent, struct list_head *head)
{
__list_add(recent, head->prev, head);
}
struct node {
uint32_t value;
struct list_head list;
};
int main()
{
struct list_head head;
INIT_LIST_HEAD(&head);
for (int i = 0; i < 100; i++) {
struct node *n = malloc(sizeof(struct node));
if (!n)
return -1;
n->value = i;
list_add_tail(&n->list, &head);
}
printf("done\n"); // break point
// Ignore memory free
return 0;
}
gdb python脚本
# list_dump.py
import gdb
def offset_of(typeobj, field):
element = gdb.Value(0).cast(typeobj)
return int(str(element[field].address).split()[0], 16)
def container_of(ptr, typeobj, member):
return (ptr.cast(gdb.lookup_type('long')) -
offset_of(typeobj, member)).cast(typeobj)
class ListDump(gdb.Command):
def __init__(self):
super(ListDump, self).__init__(
'list_dump', gdb.COMMAND_USER
)
def complete(self, text, word):
return gdb.COMPLETE_SYMBOL
def _list_to_str(self, head_ptr):
result = []
pos_ptr = head_ptr['next']
while pos_ptr != head_ptr:
node_ptr = container_of(pos_ptr, gdb.lookup_type('struct node').pointer(), 'list')
result.append(str(node_ptr['value']))
pos_ptr = pos_ptr['next']
result.append('\n')
return ' '.join(result)
def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv) != 1:
gdb.write("ERROR: %s\n" % 'Expected fmt "list_dump ptr"', gdb.STDERR)
return
head_ptr = gdb.parse_and_eval(args)
if str(head_ptr.type) != 'struct list_head *':
gdb.write("ERROR: %s\n" % 'Expected pointer argument of type (list_head *)', gdb.STDERR)
return
print(self._list_to_str(head_ptr))
ListDump()
调试
$ gcc main.c -o main -g -O0
$ gdb ./main
(gdb) source list_dump.py
(gdb) b main.c:48
Breakpoint 1 at 0x1290: file main.c, line 48.
(gdb) run
Starting program: /home/st/clang/mxml/tmp/main
Breakpoint 1, main () at main.c:48
48 printf("done\n"); // break point
(gdb) list_dump &head
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
总结
- 自动化重复性的调试任务。例如,可以使用 Python 脚本来自动设置断点、查看变量值、跟踪程序执行流程等。
- 分析程序的运行状态。例如,可以使用 Python 脚本来计算程序的运行时间、内存使用情况等。
- 创建自定义的调试工具。例如,可以使用 Python 脚本来创建一个可以跟踪程序执行流程的图形工具。
gdb 中的 Python 脚本是调试 C 和 C++ 程序的强大工具。通过使用 Python 脚本,可以提高调试效率和准确性。