mirror of https://github.com/pwndbg/pwndbg.git
Add a `chain` command to traverse liked lists (#1795)
This commit adds a command that traverses the linked list beginning at a given element, dumping its contents and the contents of all the elements that come after it in the list. Traversal is configurable and can handle multiple types of chains.pull/1811/head
parent
f218532073
commit
3e8b597929
@ -0,0 +1,393 @@
|
||||
import argparse
|
||||
|
||||
import gdb
|
||||
|
||||
import pwndbg.chain
|
||||
import pwndbg.commands
|
||||
import pwndbg.gdblib.memory
|
||||
from pwndbg.color import message
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
description="""Dumps the elements of a linked list.
|
||||
|
||||
This command traverses the linked list beginning at a given element, dumping its
|
||||
contents and the contents of all the elements that come after it in the list.
|
||||
Traversal is configurable and can handle multiple types of chains, but will
|
||||
always stop when a cycle is detected.
|
||||
|
||||
The path to the first element can be any GDB expression that evaluates to either
|
||||
the first element directly, or a to pointer to it. The next element is the name
|
||||
of the field containing the next pointer, in either the structure itself or in
|
||||
the structure given by --inner.
|
||||
|
||||
An address value may be given with --sentinel that signals the end of the list.
|
||||
By default, the value used is NULL (0).
|
||||
|
||||
If only one field inside each node is desired, it can be printed exclusively by
|
||||
specifying its name with --field.
|
||||
|
||||
This command supports traversing three types of linked lists, classified by how
|
||||
the next pointer can be found in the structure and what type it is:
|
||||
1 - Next pointer is field of structure, type is the same as structure.
|
||||
2 - Next pointer is field of inner nested structure, pointed to type is the
|
||||
same as outer structure.
|
||||
3 - Next pointer is field of inner nested structure, pointed to type is the
|
||||
same as inner structure.
|
||||
Types 2 and 3 require --inner to be specified.
|
||||
|
||||
Example 1:
|
||||
|
||||
```
|
||||
struct node {
|
||||
int value;
|
||||
struct node *next;
|
||||
};
|
||||
struct node node_c = { 2, NULL };
|
||||
struct node node_b = { 1, &node_c };
|
||||
struct node node_a = { 0, &node_b };
|
||||
```
|
||||
|
||||
pwndbg> chain node_a next
|
||||
0x4000011050 <node_a>: {
|
||||
value = 0,
|
||||
next = 0x4000011040 <node_b>
|
||||
}
|
||||
0x4000011040 <node_b>: {
|
||||
value = 1,
|
||||
next = 0x4000011010 <node_c>
|
||||
}
|
||||
0x4000011010 <node_c>: {
|
||||
value = 2,
|
||||
next = 0x0
|
||||
}
|
||||
|
||||
Example 2:
|
||||
|
||||
```
|
||||
struct node_inner_a {
|
||||
struct node_inner_a *next;
|
||||
};
|
||||
struct inner_a_node {
|
||||
int value;
|
||||
struct node_inner_a inner;
|
||||
};
|
||||
struct inner_a_node inner_a_node_c = { 2, { NULL } };
|
||||
struct inner_a_node inner_a_node_b = { 1, { &inner_a_node_c.inner } };
|
||||
struct inner_a_node inner_a_node_a = { 0, { &inner_a_node_b.inner } };
|
||||
```
|
||||
|
||||
pwndbg> chain inner_a_node_a -i inner next
|
||||
0x4000011070 <inner_a_node_a>: {
|
||||
value = 0,
|
||||
inner = {
|
||||
next = 0x4000011068 <inner_a_node_b+8>
|
||||
}
|
||||
}
|
||||
0x4000011060 <inner_a_node_b>: {
|
||||
value = 1,
|
||||
inner = {
|
||||
next = 0x4000011028 <inner_a_node_c+8>
|
||||
}
|
||||
}
|
||||
0x4000011020 <inner_a_node_c>: {
|
||||
value = 2,
|
||||
inner = {
|
||||
next = 0x0
|
||||
}
|
||||
}
|
||||
|
||||
Example 3:
|
||||
|
||||
```
|
||||
struct inner_b_node;
|
||||
struct node_inner_b {
|
||||
struct inner_b_node *next;
|
||||
};
|
||||
struct inner_b_node {
|
||||
int value;
|
||||
struct node_inner_b inner;
|
||||
};
|
||||
struct inner_b_node inner_b_node_c = { 2, { NULL } };
|
||||
struct inner_b_node inner_b_node_b = { 1, { &inner_b_node_c } };
|
||||
struct inner_b_node inner_b_node_a = { 0, { &inner_b_node_b } };
|
||||
```
|
||||
|
||||
pwndbg> chain inner_b_node_a -i inner next
|
||||
0x4000011090 <inner_b_node_a>: {
|
||||
value = 0,
|
||||
inner = {
|
||||
next = 0x4000011080 <inner_b_node_b>
|
||||
}
|
||||
}
|
||||
0x4000011080 <inner_b_node_b>: {
|
||||
value = 1,
|
||||
inner = {
|
||||
next = 0x4000011030 <inner_b_node_c>
|
||||
}
|
||||
}
|
||||
0x4000011030 <inner_b_node_c>: {
|
||||
value = 2,
|
||||
inner = {
|
||||
next = 0x0
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"path",
|
||||
type=str,
|
||||
help="The first element of the chain",
|
||||
)
|
||||
parser.add_argument(
|
||||
"next", type=str, help="The name of the field pointing to the next element in the chain"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sentinel",
|
||||
dest="sentinel",
|
||||
type=int,
|
||||
default=0,
|
||||
help="The address that stands in for an end of list marker in a non-cyclic list",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--inner",
|
||||
dest="inner_name",
|
||||
type=str,
|
||||
help="The name of the inner nested structure where the next pointer is stored",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--field",
|
||||
dest="field_name",
|
||||
type=str,
|
||||
help="The name of the field to be displayed, if only one is desired",
|
||||
)
|
||||
|
||||
|
||||
@pwndbg.commands.ArgparsedCommand(parser, command_name="chain")
|
||||
def chain(path, next, sentinel, inner_name, field_name) -> None:
|
||||
# Have GDB parse the path for us and check if it's valid.
|
||||
try:
|
||||
first = gdb.parse_and_eval(path)
|
||||
except gdb.error as e:
|
||||
print(message.error(f"{e}"))
|
||||
return
|
||||
|
||||
if first.is_optimized_out:
|
||||
print(message.error(f"{path} has been optimized out"))
|
||||
return
|
||||
|
||||
# We suport being passed either a pointer to the first structure or the
|
||||
# structure itself, for the sake of convenience. But we don't bother with
|
||||
# chains of pointers. Additionally, we pick the correct separator to use
|
||||
# for the error mesages.
|
||||
sep = "."
|
||||
deref = ""
|
||||
if first.type.code == gdb.TYPE_CODE_PTR:
|
||||
sep = "->"
|
||||
deref = "*"
|
||||
try:
|
||||
first = first.dereference()
|
||||
except gdb.error as e:
|
||||
print(message.error(f"Pointer at {path} could not be dereferenced: {e}"))
|
||||
return
|
||||
|
||||
if first.type.code == gdb.TYPE_CODE_PTR:
|
||||
print(message.error(f"{path} is not a value or a single pointer to one"))
|
||||
return
|
||||
|
||||
if first.address is None:
|
||||
print(message.error(f"{deref}{path} is not addressable"))
|
||||
return
|
||||
|
||||
if first.is_optimized_out:
|
||||
print(message.error(f"{deref}{path} has been optimized out"))
|
||||
return
|
||||
|
||||
# If there is an inner element we have to use, find it.
|
||||
inner = None
|
||||
inner_sep = ""
|
||||
if inner_name is not None:
|
||||
try:
|
||||
inner = first[inner_name]
|
||||
inner_sep = "->"
|
||||
except gdb.error as e:
|
||||
print(message.error(f"Cannot find component {inner_name} in {path}: {e}"))
|
||||
return
|
||||
if inner.is_optimized_out:
|
||||
print(message.error(f"{path}{sep}{inner_name} has been optimized out"))
|
||||
return
|
||||
|
||||
# Resolve the pointer to the next structure, wherever it may be, and make
|
||||
# sure that we can use it to traverse the chain.
|
||||
next_ptr_loc = first
|
||||
next_ptr_name = next
|
||||
try:
|
||||
if inner is None:
|
||||
next_ptr = first[next]
|
||||
else:
|
||||
next_ptr = inner[next]
|
||||
next_ptr_loc = inner
|
||||
next_ptr_name = f"{inner_name}.{next}"
|
||||
except gdb.error as e:
|
||||
print(message.error(f"Cannot find component {next_ptr_name} in {path}: {e}"))
|
||||
return
|
||||
|
||||
if next_ptr.is_optimized_out:
|
||||
print(message.error(f"{path}{sep}{next_ptr_name} has been optimized out"))
|
||||
return
|
||||
if next_ptr.type.code != gdb.TYPE_CODE_PTR:
|
||||
print(message.error(f"{path}{sep}{next_ptr_name} is not a pointer"))
|
||||
return
|
||||
|
||||
# If the user wants a specific field to be displayed, resolve it.
|
||||
field_offset = None
|
||||
field_type = None
|
||||
if field_name is not None:
|
||||
try:
|
||||
field = first[field_name]
|
||||
except gdb.error as e:
|
||||
print(message.error(f"Cannot find component {field_name} in {path}: {e}"))
|
||||
return
|
||||
field_type = field.type
|
||||
|
||||
bit_offset = bit_offset_of_field(first, field_name)
|
||||
if bit_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{path}{sep}{field_name} has no known offset \
|
||||
from {deref}{path}"
|
||||
)
|
||||
)
|
||||
return
|
||||
byte_offset = get_byte_offset(bit_offset)
|
||||
if byte_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{field_name} is a non-whole number of bytes \
|
||||
{bit_offset} bits) offset from {deref}{path}"
|
||||
)
|
||||
)
|
||||
return
|
||||
field_offset = byte_offset
|
||||
|
||||
# Figure out the offset of the inner structure, if any, in typeof(first).
|
||||
inner_offset = 0
|
||||
if inner is not None:
|
||||
bit_offset = bit_offset_of_field(first, inner_name)
|
||||
if bit_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{path}{sep}{inner_name} has no known offset \
|
||||
from {deref}{path}"
|
||||
)
|
||||
)
|
||||
return
|
||||
byte_offset = get_byte_offset(bit_offset)
|
||||
if byte_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{inner_name} is a non-whole number of bytes \
|
||||
{bit_offset} bits) offset from {deref}{path}"
|
||||
)
|
||||
)
|
||||
return
|
||||
inner_offset = byte_offset
|
||||
|
||||
# Figure out the offset of the next pointer in its containing type.
|
||||
bit_offset = bit_offset_of_field(next_ptr_loc, next)
|
||||
if bit_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{path}{sep}{next_ptr_name} has no known offset \
|
||||
from {deref}{path}{inner_sep}{inner_name}"
|
||||
)
|
||||
)
|
||||
return
|
||||
byte_offset = get_byte_offset(bit_offset)
|
||||
if byte_offset is None:
|
||||
print(
|
||||
message.error(
|
||||
f"{path}{sep}{next_ptr_name} is a non-whole \
|
||||
number of bytes {bit_offset} bits) offset from \
|
||||
{deref}{path}{inner_sep}{inner_name}"
|
||||
)
|
||||
)
|
||||
return
|
||||
next_offset = byte_offset
|
||||
|
||||
# If the next pointer points to an intance of the inner structure, we will
|
||||
# additionally have to do the equivalent of container_of(next, typeof(first),
|
||||
# inner_name).
|
||||
#
|
||||
# Here, we figure out how many bytes to subtract from *typeof(inner) so that
|
||||
# we can have a *typeof(first).
|
||||
pointee_offset = 0
|
||||
if inner is not None and next_ptr.type.target() == inner.type:
|
||||
pointee_offset = inner_offset
|
||||
elif next_ptr.type.target() == first.type:
|
||||
# We've already got everything we need for this mode.
|
||||
pass
|
||||
else:
|
||||
print(
|
||||
message.error(
|
||||
f"{deref}{path}{sep}{next_ptr_name} has a \
|
||||
different type than {path}"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Now, we follow the chain. We have to do this in two steps, as, because we
|
||||
# have the address of the first outer structure, in case pointee_offset is
|
||||
# not zero, the offset for the first element will not be the same as the one
|
||||
# for all of the elements after it.
|
||||
offset0 = inner_offset + next_offset
|
||||
offset1 = offset0 - pointee_offset
|
||||
addresses = pwndbg.chain.get(int(first.address), limit=1, offset=offset0)
|
||||
if len(addresses) > 1:
|
||||
addresses.extend(pwndbg.chain.get(addresses[1], offset=offset1, include_start=False))
|
||||
|
||||
# Finally, dump the information in the addresses we've just gathered.
|
||||
for i, address in enumerate(addresses):
|
||||
if address == sentinel:
|
||||
break
|
||||
try:
|
||||
# Always make sure we have the address of the outer structure.
|
||||
if i > 0:
|
||||
address -= pointee_offset
|
||||
|
||||
# Read the data and print it out.
|
||||
target_type = first.type
|
||||
target_address = address
|
||||
if field_offset is not None:
|
||||
target_type = field_type
|
||||
target_address = address + field_offset
|
||||
|
||||
value = pwndbg.gdblib.memory.poi(target_type, target_address)
|
||||
|
||||
symbol = pwndbg.gdblib.symbol.get(target_address)
|
||||
symbol = f"<{symbol}>" if symbol else ""
|
||||
|
||||
print(f"{target_address:#x} {symbol}: {value}")
|
||||
except gdb.error as e:
|
||||
print(message.error(f"Cannot dereference 0x{address:#x} for chain link #{i + 1}: {e}"))
|
||||
print(message.error("Is the chain corrupted or is the sentinel value wrong?"))
|
||||
return
|
||||
|
||||
|
||||
# Helper functions to resolve the offsets of fields inside a structure.
|
||||
def bit_offset_of_field(struct, field_name, inner_name=None):
|
||||
offset = None
|
||||
for field in struct.type.fields():
|
||||
if field.name == field_name:
|
||||
offset = field.bitpos
|
||||
return offset
|
||||
|
||||
|
||||
def get_byte_offset(bit_offset):
|
||||
if bit_offset % 8 != 0:
|
||||
return None
|
||||
return bit_offset // 8
|
||||
@ -0,0 +1,46 @@
|
||||
#include <stddef.h> /* For NULL. */
|
||||
|
||||
/* Linked list in which the pointer to the next element in inside the node
|
||||
* structure itself. */
|
||||
struct node {
|
||||
int value;
|
||||
struct node *next;
|
||||
};
|
||||
struct node node_c = { 2, NULL };
|
||||
struct node node_b = { 1, &node_c };
|
||||
struct node node_a = { 0, &node_b };
|
||||
|
||||
/* Linked list in which the nodes are inner structures of a larger structure. */
|
||||
struct node_inner_a {
|
||||
struct node_inner_a *next;
|
||||
};
|
||||
struct inner_a_node {
|
||||
int value;
|
||||
struct node_inner_a inner;
|
||||
};
|
||||
struct inner_a_node inner_a_node_c = { 2, { NULL } };
|
||||
struct inner_a_node inner_a_node_b = { 1, { &inner_a_node_c.inner } };
|
||||
struct inner_a_node inner_a_node_a = { 0, { &inner_a_node_b.inner } };
|
||||
|
||||
/* Linked list in which the pointer to the next element is nested inside the
|
||||
* structure. */
|
||||
struct inner_b_node;
|
||||
struct node_inner_b {
|
||||
struct inner_b_node *next;
|
||||
};
|
||||
struct inner_b_node {
|
||||
int value;
|
||||
struct node_inner_b inner;
|
||||
};
|
||||
struct inner_b_node inner_b_node_c = { 2, { NULL } };
|
||||
struct inner_b_node inner_b_node_b = { 1, { &inner_b_node_c } };
|
||||
struct inner_b_node inner_b_node_a = { 0, { &inner_b_node_b } };
|
||||
|
||||
|
||||
void break_here(void) {}
|
||||
int main(void)
|
||||
{
|
||||
break_here();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
import re
|
||||
|
||||
import gdb
|
||||
|
||||
import tests
|
||||
|
||||
LINKED_LISTS_BINARY = tests.binaries.get("linked-lists.out")
|
||||
|
||||
|
||||
def startup(start_binary):
|
||||
start_binary(LINKED_LISTS_BINARY)
|
||||
|
||||
gdb.execute("break break_here")
|
||||
gdb.execute("run")
|
||||
gdb.execute("up")
|
||||
|
||||
|
||||
def test_command_chain_flat_no_flags(start_binary):
|
||||
"""
|
||||
Tests the chain for a non-nested linked list
|
||||
"""
|
||||
startup(start_binary)
|
||||
|
||||
expected_out = re.compile(
|
||||
"""\
|
||||
0[xX][0-9a-fA-F]+ <node_a>: {\\s*
|
||||
value = 0,\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <node_b>\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <node_b>: {\\s*
|
||||
value = 1,\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <node_c>\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <node_c>: {\\s*
|
||||
value = 2,\\s*
|
||||
next = 0x0\\s*
|
||||
}"""
|
||||
)
|
||||
|
||||
result_str = gdb.execute("chain node_a next", to_string=True)
|
||||
assert expected_out.match(result_str) is not None
|
||||
|
||||
|
||||
def test_command_chain_flat_field(start_binary):
|
||||
"""
|
||||
Tests the chain command for a non-nested linked list with field flag
|
||||
"""
|
||||
startup(start_binary)
|
||||
|
||||
expected_out = re.compile(
|
||||
"""\
|
||||
0[xX][0-9a-fA-F]+ <node_a>: 0\\s*
|
||||
0[xX][0-9a-fA-F]+ <node_b>: 1\\s*
|
||||
0[xX][0-9a-fA-F]+ <node_c>: 2\\s*
|
||||
"""
|
||||
)
|
||||
|
||||
result_str = gdb.execute("chain node_a next -f value", to_string=True)
|
||||
assert expected_out.match(result_str) is not None
|
||||
|
||||
|
||||
def test_command_chain_flat_sentinel(start_binary):
|
||||
"""
|
||||
Tests the chain command for a non-nested linked list with field flag
|
||||
"""
|
||||
startup(start_binary)
|
||||
|
||||
sentinel = int(gdb.lookup_symbol("node_c")[0].value().address)
|
||||
expected_out = re.compile(
|
||||
"""\
|
||||
0[xX][0-9a-fA-F]+ <node_a>: {\\s*
|
||||
value = 0,\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <node_b>\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <node_b>: {\\s*
|
||||
value = 1,\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <node_c>\\s*
|
||||
}"""
|
||||
)
|
||||
|
||||
result_str = gdb.execute(f"chain node_a next -s {sentinel}", to_string=True)
|
||||
assert expected_out.match(result_str) is not None
|
||||
|
||||
|
||||
def test_command_chain_nested_direct(start_binary):
|
||||
"""
|
||||
Tests the chain for a nested linked list pointing to the outer structure
|
||||
"""
|
||||
startup(start_binary)
|
||||
|
||||
expected_out = re.compile(
|
||||
"""\
|
||||
0[xX][0-9a-fA-F]+ <inner_b_node_a>: {\\s*
|
||||
value = 0,\\s*
|
||||
inner = {\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <inner_b_node_b>\\s*
|
||||
}\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <inner_b_node_b>: {\\s*
|
||||
value = 1,\\s*
|
||||
inner = {\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <inner_b_node_c>\\s*
|
||||
}\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <inner_b_node_c>: {\\s*
|
||||
value = 2,\\s*
|
||||
inner = {\\s*
|
||||
next = 0x0\\s*
|
||||
}\\s*
|
||||
}"""
|
||||
)
|
||||
|
||||
result_str = gdb.execute("chain inner_b_node_a -i inner next", to_string=True)
|
||||
assert expected_out.match(result_str) is not None
|
||||
|
||||
|
||||
def test_command_chain_nested_indirect(start_binary):
|
||||
"""
|
||||
Tests the chain for a nested linked list pointing to the inner structure
|
||||
"""
|
||||
startup(start_binary)
|
||||
|
||||
expected_out = re.compile(
|
||||
"""\
|
||||
0[xX][0-9a-fA-F]+ <inner_a_node_a>: {\\s*
|
||||
value = 0,\\s*
|
||||
inner = {\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <inner_a_node_b\\+8>\\s*
|
||||
}\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <inner_a_node_b>: {\\s*
|
||||
value = 1,\\s*
|
||||
inner = {\\s*
|
||||
next = 0[xX][0-9a-fA-F]+ <inner_a_node_c\\+8>\\s*
|
||||
}\\s*
|
||||
}\\s*
|
||||
0[xX][0-9a-fA-F]+ <inner_a_node_c>: {\\s*
|
||||
value = 2,\\s*
|
||||
inner = {\\s*
|
||||
next = 0x0\\s*
|
||||
}\\s*
|
||||
}"""
|
||||
)
|
||||
|
||||
result_str = gdb.execute("chain inner_a_node_a -i inner next", to_string=True)
|
||||
assert expected_out.match(result_str) is not None
|
||||
Loading…
Reference in new issue