241 lines
9.7 KiB
Python
241 lines
9.7 KiB
Python
import logging
|
|
|
|
from ScEpTIC.emulator.memory.virtual_memory import VirtualMemory
|
|
from ScEpTIC.emulator.memory.virtual_memory_cell import VirtualMemoryCell
|
|
from ScEpTIC.exceptions import MemoryException
|
|
|
|
|
|
class VirtualStack(VirtualMemory):
|
|
"""
|
|
Extension of VirtualMemory which represents the stack of the simulated program.
|
|
In this implementation the stack grows from lower to higher addresses.
|
|
|
|
Write and read functions are necessary due to the memory allocation method given by LLVM-IR.
|
|
It does not handle the stack: firstly it allocates some space, then it writes to it.
|
|
|
|
PUSH and POP operations are to support function calls.
|
|
"""
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, VirtualStack):
|
|
return False
|
|
|
|
return super().__eq__(other)
|
|
|
|
|
|
def _get_top_cell_address(self):
|
|
"""
|
|
Returns the address of the memory cell in top of the stack.
|
|
"""
|
|
|
|
logging.debug('[{}] Getting address of cell on top of the stack.'.format(self.mem_type))
|
|
indexes = sorted(self._memory.keys())
|
|
|
|
try:
|
|
index = indexes.index(self.top_address)-1
|
|
|
|
except ValueError:
|
|
index = -1
|
|
|
|
try:
|
|
return indexes[index]
|
|
|
|
except ValueError:
|
|
raise MemoryException('[{}] Stack seems to be empty!'.format(self.mem_type))
|
|
|
|
|
|
def allocate(self, dimension, dimension_in_bits = True, metadata = None, alignment = None):
|
|
"""
|
|
Allocates elements on top of the stack.
|
|
"""
|
|
|
|
dimension = self.convert_dimension(dimension, dimension_in_bits)
|
|
# get address of first free space
|
|
address = self.top_address
|
|
|
|
logging.debug('[{}] Allocating {} bytes at address {}{}.'.format(self.mem_type, dimension, self.address_prefix, hex(address)))
|
|
|
|
if address in self._memory:
|
|
cell = self._memory[address]
|
|
|
|
if cell.dimension == dimension:
|
|
cell.metadata = metadata
|
|
# clear tracked data
|
|
cell.other_memory_trace_addresses = []
|
|
|
|
elif cell.dimension > dimension:
|
|
# redimensionate cell and create nulled space
|
|
|
|
# create new cell
|
|
new_cell = VirtualMemoryCell(address, self.address_prefix, dimension, alignment=alignment)
|
|
new_cell.set_lookup(None, self._vmstate.register_file.pc, self._vmstate.global_clock, True)
|
|
self._memory[address] = new_cell
|
|
|
|
# create remaining part of space
|
|
diff_addr = address + dimension
|
|
diff_dim = cell.dimension - dimension
|
|
diff_cell = VirtualMemoryCell(diff_addr, self.address_prefix, diff_dim, alignment=alignment)
|
|
diff_cell.set_lookup(None, self._vmstate.register_file.pc, self._vmstate.global_clock, True)
|
|
self._memory[diff_addr] = diff_cell
|
|
|
|
# For memory trace: in this case, we "allocate" a smaller cell.
|
|
# Allocation in stack happens only on function call, the start memory address remains unchanged
|
|
# so eventual anomalies are still analyzable (we are sizing down)
|
|
|
|
else:
|
|
# need more cells
|
|
required_dimension = dimension
|
|
|
|
also_track_addresses = []
|
|
|
|
# for each element which can start inside the address interval
|
|
for i in range(address, address + dimension):
|
|
|
|
# if a cell exists at such interval
|
|
if i in self._memory:
|
|
cell = self._memory[i]
|
|
|
|
# decrement required dimension
|
|
required_dimension -= cell.dimension
|
|
|
|
# For memory trace: in this case, we "allocate" a bigger cell.
|
|
# We need to trace the memory access as if a write on the whole cells happened
|
|
also_track_addresses.append(cell.absolute_address)
|
|
|
|
# remove memory cell
|
|
del self._memory[i]
|
|
|
|
# if considered cell bigger than required dimension (exceed address + dimension)
|
|
if required_dimension < 0:
|
|
diff_addr = cell.address + cell.dimension + required_dimension # NB: required_dimension is negative!!!
|
|
diff_dim = -required_dimension
|
|
diff_cell = VirtualMemoryCell(diff_addr, self.address_prefix, diff_dim, alignment=alignment)
|
|
diff_cell.set_lookup(None, self._vmstate.register_file.pc, self._vmstate.global_clock, True)
|
|
self._memory[diff_addr] = diff_cell
|
|
break
|
|
|
|
# Here two cases: Required dimension reached or not. If not, top of the modified stack have been reached and space needs to be allocated.
|
|
# If yes, cells are deleted and must be recreated
|
|
|
|
new_cell = VirtualMemoryCell(address, self.address_prefix, dimension, alignment=alignment)
|
|
new_cell.set_lookup(None, self._vmstate.register_file.pc, self._vmstate.global_clock, True)
|
|
self._memory[address] = new_cell
|
|
new_cell.other_memory_trace_addresses = also_track_addresses
|
|
|
|
self._memory[address].metadata = metadata
|
|
|
|
self.top_address = self.top_address + dimension
|
|
return '{}{}'.format(self.address_prefix, hex(address))
|
|
|
|
return self._allocate(dimension, False, metadata, alignment)
|
|
|
|
def deallocate(self, address, resolve_address = True, ignore_exception = False):
|
|
"""
|
|
Decrements the ESP (-> deallocation all the cells until a given address).
|
|
It doesn't really remove data.
|
|
"""
|
|
|
|
if resolve_address:
|
|
address = self.get_real_address(address)
|
|
|
|
logging.debug('[{}] Deallocating memory starting from address {}{}.'.format(self.mem_type, self.address_prefix, hex(address)))
|
|
|
|
if address not in self._memory:
|
|
|
|
if ignore_exception:
|
|
return
|
|
|
|
raise MemoryException('[{}] Unable to perform memory deallocation operation: no memory cell starts at {}'.format(self.mem_type, address))
|
|
|
|
self.top_address = address
|
|
|
|
|
|
def _allocate(self, dimension, dimension_in_bits = True, metadata = None, alignment = None):
|
|
"""
|
|
Allocates a single cell in top of the stack with a given dimension, and returns the address.
|
|
(PUSH-like)
|
|
"""
|
|
|
|
dimension = self.convert_dimension(dimension, dimension_in_bits)
|
|
# get address of first free space
|
|
address = self.top_address
|
|
|
|
logging.debug('[{}] Allocating {} bytes at address {}{}.'.format(self.mem_type, dimension, self.address_prefix, hex(address)))
|
|
|
|
# allocate space
|
|
cell = VirtualMemoryCell(address, self.address_prefix, dimension, alignment=alignment)
|
|
cell.set_lookup(None, self._vmstate.register_file.pc, self._vmstate.global_clock, True)
|
|
cell.metadata = metadata
|
|
|
|
# append to memory
|
|
self._memory[address] = cell
|
|
|
|
# update address of first free space
|
|
self.top_address = self.top_address + dimension
|
|
|
|
return '{}{}'.format(self.address_prefix, hex(address))
|
|
|
|
|
|
def _deallocate(self, address, resolve_address = True, ignore_exception = False):
|
|
"""
|
|
Deallocates all the cells until address. Is used to remove the cells in top of the stack.
|
|
(POP-like)
|
|
"""
|
|
|
|
if resolve_address:
|
|
address = self.get_real_address(address)
|
|
|
|
logging.debug('[{}] Deallocating memory starting from address {}{}.'.format(self.mem_type, self.address_prefix, hex(address)))
|
|
|
|
if address not in self._memory:
|
|
|
|
if ignore_exception:
|
|
return
|
|
|
|
raise MemoryException('[{}] Unable to perform memory deallocation operation: no memory cell starts at {}. {}'.format(self.mem_type, address, sorted(self._memory.keys())))
|
|
|
|
# use list(...) to avoid RuntimeError: dictionary changed size during iteration
|
|
for addr in list(self._memory.keys()):
|
|
if addr >= address:
|
|
logging.debug('[{}] Deallocating memory cell at address {}{} ({} bytes).'.format(self.mem_type, self.address_prefix, hex(addr), self._memory[addr].dimension))
|
|
del self._memory[addr]
|
|
|
|
self.top_address = address
|
|
|
|
|
|
def _set_metadata(self, address, metadata):
|
|
"""
|
|
Sets metadata information to help finding anomalies in stack.
|
|
"""
|
|
|
|
address = self.get_real_address(address)
|
|
self._memory[address].metadata = metadata
|
|
|
|
|
|
def push(self, dimension, content, metadata = None, dimension_in_bits = True):
|
|
"""
|
|
Push a given value on top of the stack
|
|
"""
|
|
|
|
address = self.allocate(dimension, dimension_in_bits, metadata)
|
|
self.write(address, dimension, content, dimension_in_bits)
|
|
|
|
# metadata info for stack anomalies
|
|
if metadata is not None:
|
|
self._set_metadata(address, metadata)
|
|
|
|
|
|
def pop(self, dimension, dimension_in_bits = True):
|
|
"""
|
|
Pops the value on the top of the stack
|
|
"""
|
|
|
|
address = self._get_top_cell_address()
|
|
|
|
# address is the real one
|
|
value = self.read(address, dimension, dimension_in_bits, False)
|
|
|
|
self.deallocate(address, False)
|
|
|
|
return value
|