Fix plist to support pointer-sized integer fields (#3448) (#3453)

* Fix plist to support pointer-sized integer fields like size_t

Resolves #3448

The plist command previously rejected fields that were not pointer types,
even if they were pointer-sized integers (like size_t, uintptr_t) used to
store addresses.

Changes:
- Accept integer types with size equal to architecture pointer size
- Handle type validation for pointer-sized integers
- Assume pointer-sized integers point to outer structure type

This allows plist to work with structs like:
  struct node {
      size_t next;  // Previously rejected, now works
      size_t prev;
  };

* Strip typedefs before checking if field is pointer-sized int

size_t and similar types are TypeCode.TYPEDEF, not TypeCode.INT.
We need to call strip_typedefs() to get the underlying type
(e.g., size_t -> unsigned long) before checking the type code.

Thanks to @jackmisbach for catching this issue!

* Add test cases for plist with size_t fields

- Add test struct with size_t next pointer to linked-lists.c
- Add test_command_plist_size_t_field() to both GDB and DBG test suites
- Tests verify plist correctly handles typedef-wrapped pointer-sized integers

* Fix include placement in linked-lists.c

Move stdint.h include to top of file with other includes

* Set dereference-limit in size_t test cases

Ensure the test runs with a sufficient dereference limit to traverse all 3 nodes

* Use explicit count flag instead of dereference-limit in size_t test

Use -c 3 flag to explicitly request 3 nodes, matching the pattern used in other plist tests

* Simplify size_t test to use -f value flag

Use -f value flag to only display the value field, avoiding issues with
size_t formatting. This matches the pattern in test_command_plist_flat_field
and provides a cleaner, more focused test of the core functionality.
pull/3465/head
Eshaan Gupta 6 days ago committed by GitHub
parent a3721aecaf
commit 8d1adc2a1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,6 +3,7 @@ from __future__ import annotations
import argparse
from typing import Optional
import pwndbg.aglib.arch
import pwndbg.aglib.memory
import pwndbg.aglib.symbol
import pwndbg.chain
@ -272,8 +273,21 @@ def plist(
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 != pwndbg.dbg_mod.TypeCode.POINTER:
print(message.error(f"{path}{sep}{next_ptr_name} is not a pointer"))
# Check if the field is a pointer type, or an integer type with pointer size
# (e.g., size_t, uintptr_t, unsigned long on 64-bit systems)
# Strip typedefs to get the underlying type (e.g., size_t -> unsigned long)
underlying_type = next_ptr.type.strip_typedefs()
is_pointer_type = underlying_type.code == pwndbg.dbg_mod.TypeCode.POINTER
is_pointer_sized_int = (
underlying_type.code == pwndbg.dbg_mod.TypeCode.INT
and underlying_type.sizeof == pwndbg.aglib.arch.ptrsize
)
if not (is_pointer_type or is_pointer_sized_int):
print(
message.error(f"{path}{sep}{next_ptr_name} is not a pointer or pointer-sized integer")
)
return
# If the user wants a specific field to be displayed, resolve it.
@ -359,7 +373,17 @@ def plist(
# 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:
# For pointer-sized integers (like size_t), we can't validate the target type
# since they don't have a target() method. We assume they point to the outer structure.
if is_pointer_sized_int:
# For integer types, we assume pointee_offset is 0 (pointing to outer structure)
# unless we have an inner structure, in which case the user needs to use --inner
# and we can't automatically determine the offset.
if inner is not None:
# We can't determine if it points to inner or outer type, so assume outer
pointee_offset = 0
elif 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.

@ -1,4 +1,5 @@
#include <stddef.h> /* For NULL. */
#include <stdint.h> /* For size_t. */
/* Linked list in which the pointer to the next element in inside the node
* structure itself. */
@ -39,6 +40,15 @@ 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 } };
/* Linked list using size_t for next pointer to test pointer-sized integer support. */
struct size_t_node {
int value;
size_t next;
};
struct size_t_node size_t_node_c = { 42, 0 };
struct size_t_node size_t_node_b = { 21, (size_t)&size_t_node_c };
struct size_t_node size_t_node_a = { 10, (size_t)&size_t_node_b };
void break_here(void) {}
int main(void)

@ -309,3 +309,22 @@ async def test_command_plist_nested_indirect(ctrl: Controller):
result_str = await ctrl.execute_and_capture("plist inner_a_node_a -i inner next")
assert expected_out.match(result_str) is not None
@pwndbg_test
async def test_command_plist_size_t_field(ctrl: Controller):
"""
Tests the plist command with size_t fields (pointer-sized integers)
"""
await startup(ctrl)
expected_out = re.compile(
"""\
0[xX][0-9a-fA-F]+ <size_t_node_a>:.* 10\\s*
0[xX][0-9a-fA-F]+ <size_t_node_b>:.* 21\\s*
0[xX][0-9a-fA-F]+ <size_t_node_c>:.* 42\\s*
"""
)
result_str = await ctrl.execute_and_capture("plist size_t_node_a next -f value -c 3")
assert expected_out.match(result_str) is not None

@ -297,3 +297,21 @@ def test_command_plist_nested_indirect(start_binary):
result_str = gdb.execute("plist inner_a_node_a -i inner next", to_string=True)
assert expected_out.match(result_str) is not None
def test_command_plist_size_t_field(start_binary):
"""
Tests the plist command with size_t fields (pointer-sized integers)
"""
startup(start_binary)
expected_out = re.compile(
"""\
0[xX][0-9a-fA-F]+ <size_t_node_a>: 10\\s*
0[xX][0-9a-fA-F]+ <size_t_node_b>: 21\\s*
0[xX][0-9a-fA-F]+ <size_t_node_c>: 42\\s*
"""
)
result_str = gdb.execute("plist size_t_node_a next -f value -c 3", to_string=True)
assert expected_out.match(result_str) is not None

Loading…
Cancel
Save