Skip to content

Commit 86e7a07

Browse files
committed
feat: add support for pointer arithmetics
1 parent 9ff0e22 commit 86e7a07

2 files changed

Lines changed: 120 additions & 4 deletions

File tree

libdestruct/common/ptr/ptr.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,42 @@
66

77
from __future__ import annotations
88

9-
from typing import TYPE_CHECKING, TypeVar
9+
from typing import TypeVar
1010

11+
from libdestruct.backing.resolver import Resolver
1112
from libdestruct.common.field import Field
1213
from libdestruct.common.obj import obj
13-
14-
if TYPE_CHECKING: # pragma: no cover
15-
from libdestruct.backing.resolver import Resolver
14+
from libdestruct.common.utils import size_of
1615

1716
T = TypeVar("T")
1817

18+
19+
class _ArithmeticResolver(Resolver):
20+
"""A resolver for pointers produced by arithmetic operations.
21+
22+
Stores a fixed address but delegates memory access to the original resolver.
23+
"""
24+
25+
def __init__(self: _ArithmeticResolver, original: Resolver, address: int) -> None:
26+
self._original = original
27+
self._address = address
28+
29+
def resolve_address(self: _ArithmeticResolver) -> int:
30+
return self._address
31+
32+
def resolve(self: _ArithmeticResolver, size: int, _: int) -> bytes:
33+
return self._address.to_bytes(size, "little")
34+
35+
def modify(self: _ArithmeticResolver, _size: int, _index: int, _value: bytes) -> None:
36+
raise RuntimeError("Cannot modify a synthetic pointer.")
37+
38+
def absolute_from_own(self: _ArithmeticResolver, address: int) -> Resolver:
39+
return self._original.absolute_from_own(address)
40+
41+
def relative_from_own(self: _ArithmeticResolver, address_offset: int, _index_offset: int) -> Resolver:
42+
return self._original.absolute_from_own(self._address + address_offset)
43+
44+
1945
class ptr(obj[T]):
2046
"""A pointer to an object in memory."""
2147

@@ -96,6 +122,27 @@ def to_str(self: ptr, _: int = 0) -> str:
96122

97123
return f"{name}@0x{self.get():x}"
98124

125+
@property
126+
def _element_size(self: ptr) -> int:
127+
"""Return the byte size of the pointed-to element."""
128+
if self.wrapper is None:
129+
return 1
130+
return size_of(self.wrapper)
131+
132+
def __add__(self: ptr, n: int) -> ptr:
133+
"""Return a new pointer advanced by n elements."""
134+
new_addr = self.get() + n * self._element_size
135+
return ptr(_ArithmeticResolver(self.resolver, new_addr), self.wrapper)
136+
137+
def __sub__(self: ptr, n: int) -> ptr:
138+
"""Return a new pointer retreated by n elements."""
139+
new_addr = self.get() - n * self._element_size
140+
return ptr(_ArithmeticResolver(self.resolver, new_addr), self.wrapper)
141+
142+
def __getitem__(self: ptr, n: int) -> obj:
143+
"""Return the object at index n relative to this pointer."""
144+
return (self + n).unwrap()
145+
99146
def __str__(self: ptr) -> str:
100147
"""Return a string representation of the pointer."""
101148
return self.to_str()

test/scripts/types_unit_test.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import unittest
1111

1212
from libdestruct import c_int, c_long, c_str, c_uint, c_float, c_double, inflater, struct, ptr, ptr_to_self
13+
from libdestruct.backing.memory_resolver import MemoryResolver
1314

1415

1516
class ObjFromBytesTest(unittest.TestCase):
@@ -105,6 +106,74 @@ class test_t(struct):
105106
s = str(test.p)
106107
self.assertIn("0x0", s)
107108

109+
def test_ptr_add(self):
110+
"""ptr + 1 returns new ptr at addr + sizeof(target)."""
111+
# Array of 3 c_int values: [10, 20, 30]
112+
memory = bytearray(8 + 12)
113+
memory[0:8] = (8).to_bytes(8, "little") # pointer to offset 8
114+
memory[8:12] = (10).to_bytes(4, "little")
115+
memory[12:16] = (20).to_bytes(4, "little")
116+
memory[16:20] = (30).to_bytes(4, "little")
117+
118+
p = ptr(MemoryResolver(memory, 0), c_int)
119+
120+
p2 = p + 1
121+
self.assertEqual(p2.unwrap().value, 20)
122+
123+
p3 = p + 2
124+
self.assertEqual(p3.unwrap().value, 30)
125+
126+
def test_ptr_sub(self):
127+
"""ptr - 1 returns new ptr at addr - sizeof(target)."""
128+
memory = bytearray(8 + 12)
129+
memory[0:8] = (12).to_bytes(8, "little") # pointer to second element
130+
memory[8:12] = (10).to_bytes(4, "little")
131+
memory[12:16] = (20).to_bytes(4, "little")
132+
memory[16:20] = (30).to_bytes(4, "little")
133+
134+
p = ptr(MemoryResolver(memory, 0), c_int)
135+
136+
p2 = p - 1
137+
self.assertEqual(p2.unwrap().value, 10)
138+
139+
def test_ptr_add_raw(self):
140+
"""Untyped ptr: ptr + n advances by n bytes."""
141+
memory = bytearray(8 + 4)
142+
memory[0:8] = (8).to_bytes(8, "little") # pointer to offset 8
143+
memory[8:12] = (0x44332211).to_bytes(4, "little")
144+
145+
p = ptr(MemoryResolver(memory, 0))
146+
# No wrapper set, so element size is 1 byte
147+
148+
p2 = p + 2
149+
self.assertEqual(p2.get(), 10) # 8 + 2
150+
151+
def test_ptr_getitem(self):
152+
"""ptr[0] == unwrap(), ptr[1] == (ptr+1).unwrap()."""
153+
memory = bytearray(8 + 12)
154+
memory[0:8] = (8).to_bytes(8, "little")
155+
memory[8:12] = (100).to_bytes(4, "little")
156+
memory[12:16] = (200).to_bytes(4, "little")
157+
memory[16:20] = (300).to_bytes(4, "little")
158+
159+
p = ptr(MemoryResolver(memory, 0), c_int)
160+
161+
self.assertEqual(p[0].value, 100)
162+
self.assertEqual(p[1].value, 200)
163+
self.assertEqual(p[2].value, 300)
164+
165+
def test_ptr_arithmetic_chain(self):
166+
"""(ptr + 2)[0] accesses element at index 2."""
167+
memory = bytearray(8 + 12)
168+
memory[0:8] = (8).to_bytes(8, "little")
169+
memory[8:12] = (1).to_bytes(4, "little")
170+
memory[12:16] = (2).to_bytes(4, "little")
171+
memory[16:20] = (3).to_bytes(4, "little")
172+
173+
p = ptr(MemoryResolver(memory, 0), c_int)
174+
175+
self.assertEqual((p + 2)[0].value, 3)
176+
108177

109178
class FloatTest(unittest.TestCase):
110179
"""c_float and c_double types."""

0 commit comments

Comments
 (0)