When debugging issues, it is common to encounter linked lists, and manually calculating and filling in linked lists with GDB can be quite cumbersome. However, pwndbg provides a very useful command called telescope that helps us automatically parse linked lists.
Conditions for Printing Linked Lists with Telescope
When programmers design linked lists, the structures can vary widely, so not every type of linked list can be easily printed. However, a simple way to print a linked list is through infinite dereferencing. Here’s how it works.
Assuming the value at address 0xaaa is 0xbbb, after obtaining 0xbbb, we read the value at 0xbbb, which is 0xccc, and then read the value at 0xccc, which is 0xddd, and so on.
This is infinite dereferencing. How is it related to printing linked lists?
A commonly used doubly linked list is as follows:
pwndbg> ptype list
type = struct list_head {
struct list_head *prev;
struct list_head *next;
} *
Let’s print the offset values as follows:
pwndbg> ptype /o list
type = struct list_head {
/* 0 | 8 */ struct list_head *prev;
/* 8 | 8 */ struct list_head *next;
/* total size (bytes): 16 */
} *
As we can see, the prev of list_head is always the dereferenced value of list_head, which means that infinite dereferencing of list_head is equivalent to traversing the prev linked list of list_head.
Practice
First, we need an example code that demonstrates a doubly linked list, which can be based on the previously written LRU algorithm.
git clone https://github.com/tangfeng-648/lru.git
Now, let’s debug it directly:
gdb ./lru
For convenience, we will set a breakpoint directly in list_add:
pwndbg> b list_add
Breakpoint 1 at 0x400814: file list.h, line 24.
The situation when the breakpoint is triggered is as follows:
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 }
Using list, we can print this list_head as follows:
pwndbg> telescope list 1
00:0000│ x1 0x4132b8 —▸ 0x413e38 —▸ 0x413e68 —▸ 0x413e98 —▸ 0x413ec8 ◂— ...
pwndbg> telescope 0x413ec8 1
00:0000│ 0x413ec8 —▸ 0x413ef8 —▸ 0x413f28 —▸ 0x413f58 —▸ 0x413f88 ◂— ...
Since the offset 0 of list_head is <span>struct list_head *prev</span>, telescope is actually performing a reverse lookup on this list.
Let’s verify if this is correct:
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
As we can see, telescope implicitly helps us print this linked list.
How to Print Next
In fact, since next requires adding an offset of 8 compared to prev, by default, pwndbg does not provide such a command, and manual calculation is necessary.
Conclusion
This article shares a simple method to print linked lists. In reality, whether you can print a linked list depends on your familiarity with the memory layout. If you are very familiar, you can use this command to simplify your calculations; if not, it is recommended to calculate manually. Otherwise, the information provided by telescope may not be useful to you.