import claripy
from ..errors import SimMemoryError
[docs]def obj_bit_size(o):
if type(o) is bytes:
return len(o) * 8
return o.size()
# TODO: get rid of is_bytes and have the bytes-backed objects be a separate class
[docs]class SimMemoryObject:
"""
A SimMemoryObject is a reference to a byte or several bytes in a specific object in memory. It should be used only
by the bottom layer of memory.
"""
__slots__ = (
"is_bytes",
"_byte_width",
"base",
"object",
"length",
"endness",
)
def __init__(self, obj, base, endness, length=None, byte_width=8):
if type(obj) is bytes:
assert byte_width == 8
elif not isinstance(obj, claripy.ast.Base):
raise SimMemoryError("memory can only store claripy Expression")
self.is_bytes = type(obj) is bytes
if self.is_bytes and endness != "Iend_BE":
raise SimMemoryError("bytes can only be stored big-endian")
self._byte_width = byte_width
self.base = base
self.object = obj
self.length = obj_bit_size(obj) // self._byte_width if length is None else length
self.endness = endness
[docs] def size(self):
return self.length * self._byte_width
def __len__(self):
return self.size()
@property
def variables(self):
return self.object.variables
@property
def cache_key(self):
return self.object.cache_key
@property
def symbolic(self):
return self.object.symbolic
@property
def last_addr(self):
return self.base + self.length - 1
[docs] def includes(self, x):
return 0 <= x - self.base < self.length
[docs] def bytes_at(self, addr, length, allow_concrete=False, endness="Iend_BE"):
rev = endness != self.endness
if allow_concrete and rev:
raise Exception("allow_concrete must be used with the stored endness")
if self.is_bytes:
if addr == self.base and length == self.length:
o = self.object
else:
start = addr - self.base
end = start + length
o = self.object[start:end]
return o if allow_concrete else claripy.BVV(o)
else:
offset = addr - self.base
try:
thing = bv_slice(self.object, offset, length, self.endness == "Iend_LE", self._byte_width)
except claripy.ClaripyOperationError:
# hacks to handle address space wrapping
if offset >= 0:
raise
if offset + 2**32 >= 0:
offset += 2**32
elif offset + 2**64 >= 0:
offset += 2**64
else:
raise
thing = bv_slice(self.object, offset, length, self.endness == "Iend_LE", self._byte_width)
if self.endness != endness:
thing = thing.reversed
return thing
def _object_equals(self, other):
if self.is_bytes != other.is_bytes:
return False
if self.is_bytes:
return self.object == other.object
else:
return self.object.cache_key == other.object.cache_key
def _length_equals(self, other):
if type(self.length) is not type(other.length): # noqa: E721
return False
if isinstance(self.length, int):
return self.length == other.length
else:
return self.length.cache_key == other.length.cache_key
def __eq__(self, other):
if self is other:
return True
if type(other) is not SimMemoryObject:
return NotImplemented
return self.base == other.base and self._object_equals(other) and self._length_equals(other)
def __hash__(self):
obj_hash = hash(self.object) if self.is_bytes else self.object.cache_key
return hash((obj_hash, self.base, hash(self.length)))
def __ne__(self, other):
return not self == other
def __repr__(self):
return "MO(%s)" % self.object
[docs]class SimLabeledMemoryObject(SimMemoryObject):
__slots__ = ("label",)
def __init__(self, obj, base, endness, length=None, byte_width=8, label=None):
super().__init__(obj, base, endness, length=length, byte_width=byte_width)
self.label = label
[docs]def bv_slice(value, offset, size, rev, bw):
"""
Extremely cute utility to pretend you've serialized a value to stored bytes, sliced it a la python slicing, and then
deserialized those bytes to an integer again.
:param value: The bitvector to slice
:param offset: The byte offset from the first stored byte to slice from, or a negative offset from the end.
:param size: The number of bytes to return. If None, return all bytes from the offset to the end. If larger than
the number of bytes from the offset to the end, return all bytes from the offset to the end.
:param rev: Whether the pretend-serialization should be little-endian
:param bw: The byte width
:return: The new bitvector
"""
vsize = len(value) // bw
if offset < 0:
offset = vsize + offset
if size is None or offset + size > vsize:
size = vsize - offset
if rev:
offset = vsize - (offset + size)
if offset == 0 and size == vsize:
return value
bitstart = len(value) - offset * bw
return value[bitstart - 1 : bitstart - size * bw]