Skip to content

Commit d21abff

Browse files
committed
feat: partially migrate from memory to a resolver system
1 parent aa95cdb commit d21abff

10 files changed

Lines changed: 148 additions & 58 deletions

File tree

libdestruct/backing/__init__.py

Whitespace-only changes.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3+
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
4+
# Licensed under the MIT license. See LICENSE file in the project root for details.
5+
#
6+
7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING
10+
11+
from libdestruct.backing.resolver import Resolver
12+
13+
if TYPE_CHECKING:
14+
from collections.abc import MutableSequence
15+
16+
17+
class MemoryResolver(Resolver):
18+
"""A class that can resolve itself to a value in a referenced memory storage."""
19+
20+
def __init__(self: MemoryResolver, memory: MutableSequence, address: int | None) -> MemoryResolver:
21+
"""Initializes a basic memory resolver."""
22+
self.memory = memory
23+
self.address = address
24+
self.parent = None
25+
self.offset = None
26+
27+
def resolve_address(self: MemoryResolver) -> int:
28+
"""Resolves self's address, mainly used by childs to determine their own address."""
29+
if self.address is not None:
30+
return self.address
31+
32+
return self.parent.resolve_address() + self.offset
33+
34+
def relative_from_own(self: MemoryResolver, address_offset: int, _: int) -> MemoryResolver:
35+
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
36+
new_resolver = MemoryResolver(self.memory, None)
37+
new_resolver.parent = self
38+
new_resolver.offset = address_offset
39+
return new_resolver
40+
41+
def absolute_from_own(self: Resolver, address: int) -> MemoryResolver:
42+
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
43+
return MemoryResolver(self.memory, address)
44+
45+
def resolve(self: MemoryResolver, size: int, _: int) -> bytes:
46+
"""Resolves itself, providing the bytes it references for the specified size and index."""
47+
address = self.resolve_address()
48+
return self.memory[address : address + size]
49+
50+
def modify(self: Resolver, size: int, _: int, value: bytes) -> None:
51+
"""Modifies itself in memory."""
52+
address = self.resolve_address()
53+
self.memory[address : address + size] = value

libdestruct/backing/resolver.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
3+
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
4+
# Licensed under the MIT license. See LICENSE file in the project root for details.
5+
#
6+
7+
from __future__ import annotations
8+
9+
from abc import ABC, abstractmethod
10+
from typing import Self
11+
12+
13+
class Resolver(ABC):
14+
"""A class that can resolve itself to a value, either in memory or in other storage types."""
15+
16+
parent: Self
17+
18+
@abstractmethod
19+
def relative_from_own(self: Resolver, address_offset: int, index_offset: int) -> Self:
20+
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
21+
22+
@abstractmethod
23+
def absolute_from_own(self: Resolver, address: int) -> Self:
24+
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
25+
26+
@abstractmethod
27+
def resolve_address(self: Resolver) -> int:
28+
"""Resolves self's address, mainly used by childs to determine their own address."""
29+
30+
@abstractmethod
31+
def resolve(self: Resolver, size: int, index: int) -> bytes:
32+
"""Resolves itself, providing the bytes it references for the specified size and index."""
33+
34+
@abstractmethod
35+
def modify(self: Resolver, size: int, index: int, value: bytes) -> None:
36+
"""Modifies itself."""

libdestruct/c/c_integer_types.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,25 @@ class _c_integer(obj):
2323

2424
def get(self: _c_integer) -> int:
2525
"""Return the value of the integer."""
26-
return int.from_bytes(self.memory[self.address : self.address + self.size], self.endianness, signed=self.signed)
26+
return int.from_bytes(self.resolver.resolve(self.size, 0), self.endianness, signed=self.signed)
2727

2828
def to_bytes(self: _c_integer) -> bytes:
2929
"""Return the serialized representation of the object."""
3030
if self._frozen:
3131
return self._frozen_value.to_bytes(self.size, self.endianness, signed=self.signed)
3232

33-
return self.memory[self.address : self.address + self.size]
33+
return self.resolver.resolve(self.size, 0)
3434

3535
def _set(self: _c_integer, value: int) -> None:
3636
"""Set the value of the integer to the given value."""
37-
self.memory[self.address : self.address + self.size] = value.to_bytes(
37+
self.resolver.modify(
3838
self.size,
39-
self.endianness,
40-
signed=self.signed,
39+
0,
40+
value.to_bytes(
41+
self.size,
42+
self.endianness,
43+
signed=self.signed,
44+
),
4145
)
4246

4347
def __int__(self: _c_integer) -> int:

libdestruct/c/c_str.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,38 @@ def size(self: c_str) -> int:
1717
size = 0
1818

1919
try:
20-
while self.memory[self.address + size] != b"\x00":
20+
while self.resolver.resolve(size, 0)[-1:] != b"\x00":
2121
size += 1
2222
except IndexError as e:
2323
raise RuntimeError("String is not null-terminated.") from e
2424

25-
return size
25+
return size - 1
2626

2727
def get(self: c_str, index: int = -1) -> bytes:
2828
"""Return the character at the given index."""
2929
if index != -1 and index < 0 or index >= self.size():
3030
raise IndexError("String index out of range.")
3131

3232
if index == -1:
33-
return self.memory[self.address : self.address + self.size()]
33+
return self.resolver.resolve(self.size(), 0)
3434

35-
return bytes([self.memory[self.address + index]])
35+
return bytes([self.resolver.resolve(index)[-1]])
3636

3737
def to_bytes(self: c_str) -> bytes:
3838
"""Return the serialized representation of the object."""
39-
return self.memory[self.address : self.address + self.size()]
39+
return self.resolver.resolve(self.size(), 0)
4040

4141
def _set(self: c_str, value: bytes, index: int = -1) -> None:
4242
"""Set the character at the given index to the given value."""
4343
if index != -1 and index < 0 or index >= self.size():
4444
raise IndexError("String index out of range.")
4545

4646
if index == -1:
47-
self.memory[self.address] = value
47+
# This is rather clunky
48+
self.resolver.modify(len(value), 0, value)
4849
else:
49-
self.memory[self.address + index] = value
50+
prev = self.resolver.resolve(index, 0)
51+
self.resolver.modify(index + len(value), 0, prev + value)
5052

5153
def __iter__(self: c_str) -> iter:
5254
"""Return an iterator over the string."""

libdestruct/common/inflater.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
from typing import TYPE_CHECKING
1010

11+
from libdestruct.backing.memory_resolver import MemoryResolver
1112
from libdestruct.common.type_registry import TypeRegistry
1213

1314
if TYPE_CHECKING:
1415
from collections.abc import MutableSequence
1516

17+
from libdestruct.backing.resolver import Resolver
1618
from libdestruct.common.obj import obj
1719

1820

@@ -24,7 +26,7 @@ def __init__(self: Inflater, memory: MutableSequence) -> None:
2426
self.memory = memory
2527
self.type_registry = TypeRegistry()
2628

27-
def inflate(self: Inflater, item: type, address: int | tuple[obj, int]) -> obj:
29+
def inflate(self: Inflater, item: type, address: int | Resolver) -> obj:
2830
"""Inflate a memory-referencing type.
2931
3032
Args:
@@ -34,4 +36,8 @@ def inflate(self: Inflater, item: type, address: int | tuple[obj, int]) -> obj:
3436
Returns:
3537
The inflated object.
3638
"""
37-
return self.type_registry.inflater_for(item)(self.memory, address)
39+
if isinstance(address, int):
40+
# Create a memory resolver from the address
41+
address = MemoryResolver(self.memory, address)
42+
43+
return self.type_registry.inflater_for(item)(address)

libdestruct/common/obj.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,36 @@
1010
from typing import TYPE_CHECKING
1111

1212
if TYPE_CHECKING:
13-
from collections.abc import MutableSequence
13+
from libdestruct.backing.resolver import Resolver
1414

1515

1616
class obj(ABC):
1717
"""A generic object, with reference to the backing memory view."""
1818

19-
address: int
20-
"""The address of the object in the memory view."""
21-
2219
endianness: str = "little"
2320
"""The endianness of the backing reference view."""
2421

25-
memory: MutableSequence
26-
"""The backing memory view."""
22+
resolver: Resolver
23+
"""The backing storage that resolves to this instance of a type."""
2724

2825
_frozen: bool = False
2926
"""Whether the object is frozen."""
3027

3128
_frozen_value: object = None
3229
"""The frozen value of the object."""
3330

34-
def __init__(self: obj, memory: MutableSequence, address: int | tuple[obj, int]) -> None:
31+
def __init__(self: obj, resolver: Resolver) -> None:
3532
"""Initialize a generic object.
3633
3734
Args:
38-
memory: The backing memory view.
39-
address: The address of the object in the memory view
35+
resolver: The resolver for the value of this object.
4036
"""
41-
self.memory = memory
42-
43-
if isinstance(address, tuple):
44-
self._address = None
45-
self._reference = address[0]
46-
self._offset = address[1]
47-
else:
48-
self._address = address
37+
self.resolver = resolver
4938

5039
@property
5140
def address(self: obj) -> int:
5241
"""Return the address of the object in the memory view."""
53-
if self._address is not None:
54-
return self._address
55-
56-
return self._reference.address + self._offset
42+
return self.resolver.resolve_address()
5743

5844
@abstractmethod
5945
def get(self: obj) -> object:

libdestruct/common/ptr.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,46 @@
66

77
from __future__ import annotations
88

9+
from typing import TYPE_CHECKING
10+
911
from libdestruct.common.field import Field
1012
from libdestruct.common.obj import obj
1113

14+
if TYPE_CHECKING:
15+
from libdestruct.backing.resolver import Resolver
16+
1217

1318
class ptr(obj):
1419
"""A pointer to an object in memory."""
1520

1621
size: int = 8
1722
"""The size of a pointer in bytes."""
1823

19-
def __init__(self: ptr, memory: bytearray, address: int, wrapper: type | None = None) -> None:
24+
def __init__(self: ptr, resolver: Resolver, wrapper: type | None = None) -> None:
2025
"""Initialize a pointer.
2126
2227
Args:
23-
memory: The backing memory view.
24-
address: The address of the pointer in the memory view.
28+
resolver: The backing value resolver.
2529
wrapper: The object this pointer points to.
2630
"""
27-
super().__init__(memory, address)
31+
super().__init__(resolver)
2832
self.wrapper = wrapper
2933

3034
def get(self: ptr) -> int:
3135
"""Return the value of the pointer."""
32-
return int.from_bytes(self.memory[self.address : self.address + self.size], self.endianness)
36+
value = self.resolver.resolve(self.size, 0)
37+
return int.from_bytes(value, self.endianness)
3338

3439
def to_bytes(self: obj) -> bytes:
3540
"""Return the serialized representation of the object."""
3641
if self._frozen:
3742
return self._frozen_value.to_bytes(self.size, self.endianness)
3843

39-
return self.memory[self.address : self.address + self.size]
44+
return self.resolver.resolve(self.size, 0)
4045

4146
def _set(self: ptr, value: int) -> None:
4247
"""Set the value of the pointer to the given value."""
43-
self.memory[self.address : self.address + self.size] = value.to_bytes(self.size, self.endianness)
48+
self.resolver.modify(self.size, 0, value.to_bytes(self.size, self.endianness))
4449

4550
def unwrap(self: ptr, length: int | None = None) -> obj:
4651
"""Return the object pointed to by the pointer.
@@ -54,12 +59,12 @@ def unwrap(self: ptr, length: int | None = None) -> obj:
5459
if length:
5560
raise ValueError("Length is not supported when unwrapping a pointer to a wrapper object.")
5661

57-
return self.wrapper(self.memory, address)
62+
return self.wrapper(self.resolver.absolute_from_own(address))
5863

5964
if not length:
6065
length = 1
6166

62-
return self.memory[address : address + length]
67+
return self.resolver.resolve(length, 0)
6368

6469
def try_unwrap(self: ptr, length: int | None = None) -> obj | None:
6570
"""Return the object pointed to by the pointer, if it is valid.
@@ -71,7 +76,7 @@ def try_unwrap(self: ptr, length: int | None = None) -> obj | None:
7176

7277
try:
7378
# If the address is invalid, this will raise an IndexError or ValueError.
74-
self.memory[address]
79+
self.resolver.absolute_from_own(address).resolve(length)
7580
except (IndexError, ValueError):
7681
return None
7782

@@ -90,7 +95,6 @@ def to_str(self: ptr, indent: int = 0) -> str:
9095

9196
return f"{' ' * indent}{name}@0x{self.get():x}"
9297

93-
9498
def __str__(self: ptr) -> str:
9599
"""Return a string representation of the pointer."""
96100
return self.to_str()

libdestruct/common/struct/ptr_struct_field.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
from libdestruct.common.struct.struct_field import StructField
1414

1515
if TYPE_CHECKING:
16-
from collections.abc import MutableSequence
17-
16+
from libdestruct.backing.resolver import Resolver
1817
from libdestruct.common.obj import obj
1918

2019

@@ -29,9 +28,9 @@ def __init__(self: PtrStructField, backing_type: type | Field) -> None:
2928
"""
3029
self.backing_type = backing_type
3130

32-
def inflate(self: PtrStructField, memory: MutableSequence, address: int | tuple[obj, int]) -> obj:
31+
def inflate(self: PtrStructField, resolver: Resolver) -> obj:
3332
"""Inflate the field."""
3433
if isinstance(self.backing_type, Field):
35-
return ptr(memory, address, self.backing_type.inflate)
34+
return ptr(resolver, self.backing_type.inflate)
3635

37-
return ptr(memory, address, self.backing_type)
36+
return ptr(resolver, self.backing_type)

0 commit comments

Comments
 (0)