通常调试问题的时候,经常会遇到链表,而gdb调试链表的手动计算和填入,非常麻烦。但是pwndbg提供了一个非常好的命令telescope会帮我们自动解析链表。
编程者在设计链表的时候结构各种各样,所以并不是每一种链表都是能够轻松打印的,但是一种简单的方式可以打印链表,那就是无限取值。下面介绍一下。
假设0xaaa地址的值是0xbbb,那么取到0xbbb之后,再读0xbbb的值0xccc,然后再读0xccc的值0xddd,依此类推。
这就是无限取值,它和打印链表有什么关系呢。
一个常用的双向链表如下
pwndbg> ptype list type = struct list_head { struct list_head *prev; struct list_head *next; } *
我们打印一下偏移值,如下
pwndbg> ptype /o list type = struct list_head { /* 0 | 8 */ struct list_head *prev; /* 8 | 8 */ struct list_head *next; /* total size (bytes): 16 */ } *
可以看到,list_head的prev永远是list_head的取值,也就意味着对list_head的无限取值就是获取list_head的prev链表的遍历。
首先需要一个体现双向链表的示例代码,可以以之前写的lru算法为例
git clone https://github.com/tangfeng-648/lru.git
直接调试
gdb ./lru
为了方便调试,这里直接在list_add加上断点
pwndbg> b list_add Breakpoint 1 at 0x400814: file list.h, line 24.
此时断点触发情况如下
22 static inline void list_add(struct list_head *item, struct list_head *list) 23 { ► 24 item->prev = list; 25 item->next = list->next; 26 list->next->prev = item; 27 list->next = item; 28 }
利用list可以打印这个list_head,如下
pwndbg> telescope list 1 00:0000│ x1 0x4132b8 —▸ 0x413e38 —▸ 0x413e68 —▸ 0x413e98 —▸ 0x413ec8 ◂— ... pwndbg> telescope 0x413ec8 1 00:0000│ 0x413ec8 —▸ 0x413ef8 —▸ 0x413f28 —▸ 0x413f58 —▸ 0x413f88 ◂— ...
因为list_head的偏移0是struct list_head *prev
,所以telescope实际上是对这个list的逆序查找。
下面验证一下是否正确,如下
pwndbg> p list $1 = (struct list_head *) 0x4132b8 pwndbg> p list->prev $2 = (struct list_head *) 0x413e38 pwndbg> p list->prev->prev $3 = (struct list_head *) 0x413e68 pwndbg> p list->prev->prev->prev $4 = (struct list_head *) 0x413e98 pwndbg> p list->prev->prev->prev->prev $5 = (struct list_head *) 0x413ec8 pwndbg> p list->prev->prev->prev->prev->prev $6 = (struct list_head *) 0x413ef8 pwndbg> p list->prev->prev->prev->prev->prev->prev $7 = (struct list_head *) 0x413f28 pwndbg> p list->prev->prev->prev->prev->prev->prev->prev $8 = (struct list_head *) 0x413f58 pwndbg> p list->prev->prev->prev->prev->prev->prev->prev->prev $9 = (struct list_head *) 0x413f88
可以看到,telescope相当于隐式的帮我们打印了这个链表。
实际上因为next相比于prev每次都要加上8的偏移,默认情况下pwndbg没有提供这样的命令,只能自行手动计算。
本文分享了一个简单的方法来打印链表,实际上,是否能够打印链表,取决于自己对内存布局是否熟悉,如果很熟悉的情况下,可以借助这个命令简化自己的计算,如果不熟悉的情况下,建议还是手动计算吧。否则telescope给你的信息并不一定对你有用。