Source code for memobj.allocation
from typing import TYPE_CHECKING, Any, Self
from memobj.utils import Type, get_type_size
if TYPE_CHECKING:
from memobj.process import Process
[docs]
class Allocation:
def __init__(self, address: int, process: "Process", size: int):
self.address = address
self.process = process
self.size = size
self._is_closed: bool = False
def __repr__(self) -> str:
return f"<Allocation at {hex(self.address)}>"
def __enter__(self) -> Self:
return self
def __exit__(self, *_):
self.free()
@property
def closed(self) -> bool:
return self._is_closed
[docs]
def free(self):
if self._is_closed:
raise ValueError("Cannot free an already freed allocation")
self.process.free_memory(self.address)
self._is_closed = True
[docs]
def read_typed(self, read_type: Type) -> Any:
if (type_size := get_type_size(read_type)) != self.size:
raise ValueError(
f"{read_type} size ({type_size}) does not match allocation size ({self.size})"
)
return self.process.read_typed(self.address, read_type)
[docs]
def write_typed(self, write_type: Type, value: Any) -> None:
if (type_size := get_type_size(write_type)) != self.size:
raise ValueError(
f"Write type ({type_size}) does not match allocation size ({self.size})"
)
return self.process.write_typed(self.address, write_type, value)
[docs]
class Allocator:
"""
with Allocator(process) as allocator:
with allocator.allocate() as allocation:
# do something with allocation
allocation = allocator.allocate()
"""
def __init__(self, process: "Process"):
self.process = process
self.allocations: list[Allocation] = []
self._is_closed: bool = False
def __enter__(self) -> Self:
if self._is_closed:
raise ValueError("Cannot reuse a closed allocator")
return self
def __exit__(self, *_):
self.close()
@property
def closed(self) -> bool:
return self._is_closed
[docs]
def allocate(self, size: int, *, preferred_start: int | None = None) -> Allocation:
"""
Allocates a block of memory for the process.
Allocates a specified block of memory for the associated process, keeping
track of the allocation for management purposes. The allocated memory
is represented as an `Allocation` object and is appended to the list of
current allocations.
Args:
size (int): The size of the memory block to allocate in bytes.
preferred_start: The preferred start address of the allocation
Returns:
Allocation: An object representing the allocated memory block.
"""
address = self.process.allocate_memory(size, preferred_start=preferred_start)
allocation = Allocation(address, self.process, size)
self.allocations.append(allocation)
return allocation
[docs]
def close(self):
"""
Closes the allocator, ensuring all current allocations are properly freed and the allocator
is set to a closed state. This method prevents further use of the allocator by marking it
as closed. If the allocator is already closed, an error will be raised.
Raises:
ValueError: If the allocator is already closed before the invocation of this method.
"""
if self._is_closed:
raise ValueError("Cannot close an already closed allocator")
for allocation in self.allocations:
if not allocation.closed:
allocation.free()
self.allocations = []
self._is_closed = True