499 lines
19 KiB
Python
499 lines
19 KiB
Python
import logging
|
|
|
|
from ScEpTIC.emulator.memory.virtual_global_symbol_table_unifier import VirtualGlobalSymbolTableUnifier
|
|
from ScEpTIC.emulator.memory.virtual_ram import VolatileRAM, NonVolatileRAM
|
|
from ScEpTIC.exceptions import MemoryException, ConfigurationException
|
|
|
|
|
|
class Memory:
|
|
"""
|
|
This class represents the memory and is in charge of interfacing with it.
|
|
It initializes both Volatile and Non-Volatile Memory, accordingly to a given configuration.
|
|
Stack and Heap must be present and can reside only into one Memory at a time, in any combination.
|
|
Global Symbol Table must be present and can reside in Volatile Memory, Non-Volatile Memory, or both.
|
|
|
|
All the memory spaces between stack, heap and global symbol tables are separated.
|
|
|
|
Memory has not a maximum dimension, since this simulator is not focused on memory representation.
|
|
In order to get a correct memory representation, I should also put all the things in the stack (saved PC, canaries)
|
|
and I would be bounded to the compiler back-end conversions and architecture-dependent optimizations.
|
|
|
|
Not having a memory size, makes difficult to have a continuous memory space such that at a certain address
|
|
the Volatile Memory ends and starts the NVM, but makes possible to test the program for a generic architecture and then verify if
|
|
it fits the arch. memory requirements.
|
|
This is why the Volatile Memory and NVM have overlapping addresses (but will be accessed correctly accordingly to the configuration).
|
|
|
|
Having variables in both Volatile Memory and NVM will require a sort of "lookup table", or the data layout should be performed.
|
|
In this implementation, the lookup table for the variables contains also them and extends the stack with some functionalities.
|
|
In this way:
|
|
- There is a better separation between global variables and local stack
|
|
- With the VirtualGlobalSymbolTableUnifier is easy to access a variable "transparently" (independently if it resides in NVM or Volatile Memory)
|
|
- There is no need to differentiate addresses between Volatile Memory and NVM.
|
|
- Is possible to compare directly a portion (stack, heap, vars) of the NVM and Volatile memory with a previous dump
|
|
|
|
Stack and heap are kept as separated objects (and not directly mapped to the same "memory") because I do not have to deal
|
|
with overflows that causes the stack or heap to be damaged.
|
|
"""
|
|
|
|
def __init__(self, config):
|
|
logging.debug('Initializing main memory.')
|
|
|
|
self.address_prefixes = {'stack': '', 'heap': '', 'volatile_gst': '', 'non_volatile_gst': ''}
|
|
|
|
# prefixes
|
|
self.address_prefixes['stack'] = f"{config.stack_prefix}-"
|
|
self.address_prefixes['heap'] = f"{config.heap_prefix}-"
|
|
|
|
# Check variables
|
|
stack_initialized = False
|
|
heap_initialized = False
|
|
gst_initialized = False
|
|
|
|
self.memory_positions = {'stack': None, 'heap': None, 'gst': []}
|
|
|
|
self.nvm_prefixes = []
|
|
self.nvm_content = {'stack': False, 'heap': False, 'gst': False}
|
|
|
|
self.mmus = {}
|
|
self.mmu_targets = {'stack': None, 'heap': None, 'volatile_gst': None, 'non_volatile_gst': None}
|
|
|
|
if config.stack_mmu is not None:
|
|
self.mmus[self.address_prefixes['stack']] = config.stack_mmu
|
|
self.mmu_targets['stack'] = config.stack_mmu
|
|
|
|
if config.heap_mmu is not None:
|
|
self.mmus[self.address_prefixes['heap']] = config.heap_mmu
|
|
self.mmu_targets['heap'] = config.heap_mmu
|
|
|
|
# Initialize Volatile Memory
|
|
if config.volatile.enabled:
|
|
# Address prefix
|
|
self.address_prefixes['volatile_gst'] = f"{config.volatile.gst_prefix}-"
|
|
|
|
# Set check variables
|
|
stack_initialized = config.volatile.contains_stack
|
|
heap_initialized = config.volatile.contains_heap
|
|
gst_initialized = config.volatile.contains_gst
|
|
|
|
# Initialize memory
|
|
volatile_conf = {
|
|
'stack': config.volatile.contains_stack,
|
|
'stack_base_address': config.stack_base_address,
|
|
'stack_prefix': self.address_prefixes['stack'],
|
|
'stack_mmu': config.stack_mmu,
|
|
'heap': config.volatile.contains_heap,
|
|
'heap_base_address': config.heap_base_address,
|
|
'heap_prefix': self.address_prefixes['heap'],
|
|
'heap_mmu': config.heap_mmu,
|
|
'gst': config.volatile.contains_gst,
|
|
'gst_base_address': config.volatile.gst_base_address,
|
|
'gst_prefix': self.address_prefixes['volatile_gst'],
|
|
'gst_mmu': config.volatile.gst_mmu,
|
|
'max_size': config.volatile.max_size,
|
|
}
|
|
self.volatile = VolatileRAM(volatile_conf)
|
|
|
|
if config.volatile.gst_mmu is not None:
|
|
self.mmus[self.address_prefixes['volatile_gst']] = config.volatile.gst_mmu
|
|
self.mmu_targets['volatile_gst'] = config.volatile.gst_mmu
|
|
|
|
if config.volatile.contains_stack:
|
|
self.memory_positions['stack'] = 'volatile'
|
|
|
|
if config.volatile.contains_heap:
|
|
self.memory_positions['heap'] = 'volatile'
|
|
|
|
if config.volatile.contains_gst:
|
|
self.memory_positions['gst'].append('volatile')
|
|
|
|
else:
|
|
self.volatile = None
|
|
|
|
# Initialize Non-Volatile Memory
|
|
if config.non_volatile.enabled:
|
|
# Address prefix
|
|
self.address_prefixes['non_volatile_gst'] = f"{config.non_volatile.gst_prefix}-"
|
|
|
|
non_volatile_conf = {
|
|
'stack': config.non_volatile.contains_stack,
|
|
'stack_base_address': config.stack_base_address,
|
|
'stack_prefix': self.address_prefixes['stack'],
|
|
'stack_mmu': config.stack_mmu,
|
|
'heap': config.non_volatile.contains_heap,
|
|
'heap_base_address': config.heap_base_address,
|
|
'heap_prefix': self.address_prefixes['heap'],
|
|
'heap_mmu': config.heap_mmu,
|
|
'gst': config.non_volatile.contains_gst,
|
|
'gst_base_address': config.non_volatile.gst_base_address,
|
|
'gst_prefix': self.address_prefixes['non_volatile_gst'],
|
|
'gst_mmu': config.non_volatile.gst_mmu,
|
|
'max_size': config.non_volatile.max_size,
|
|
}
|
|
self.non_volatile = NonVolatileRAM(non_volatile_conf)
|
|
|
|
if config.non_volatile.gst_mmu is not None:
|
|
self.mmus[self.address_prefixes['non_volatile_gst']] = config.non_volatile.gst_mmu
|
|
self.mmu_targets['non_volatile_gst'] = config.non_volatile.gst_mmu
|
|
|
|
if config.non_volatile.contains_stack:
|
|
if stack_initialized:
|
|
raise ConfigurationException('Stack cannot reside on both Volatile and Non-volatile Memory')
|
|
|
|
self.memory_positions['stack'] = 'non_volatile'
|
|
self.nvm_prefixes.append(self.address_prefixes['stack'])
|
|
self.nvm_content['stack'] = True
|
|
|
|
if config.non_volatile.contains_heap:
|
|
if heap_initialized:
|
|
raise ConfigurationException('Heap cannot reside on both Volatile and Non-volatile Memory')
|
|
|
|
self.memory_positions['heap'] = 'non_volatile'
|
|
self.nvm_prefixes.append(self.address_prefixes['heap'])
|
|
self.nvm_content['heap'] = True
|
|
|
|
if config.non_volatile.contains_gst:
|
|
self.memory_positions['gst'].append('non_volatile')
|
|
self.nvm_prefixes.append(self.address_prefixes['non_volatile_gst'])
|
|
self.nvm_content['gst'] = True
|
|
|
|
# adjust initialization variables
|
|
stack_initialized |= config.non_volatile.contains_stack
|
|
heap_initialized |= config.non_volatile.contains_heap
|
|
gst_initialized |= config.non_volatile.contains_gst
|
|
|
|
else:
|
|
self.non_volatile = None
|
|
|
|
# Verify initializations
|
|
if self.non_volatile is None and self.volatile is None:
|
|
raise ConfigurationException('Memory not initialized: you need at least one between Volatile and Non-volatile Memory.')
|
|
|
|
if not stack_initialized:
|
|
raise ConfigurationException('Stack not initialized: allocate it either onto Volatile or Non-volatile Memory.')
|
|
|
|
if not heap_initialized:
|
|
raise ConfigurationException('Heap not initialized: allocate it either onto Volatile or Non-volatile Memory.')
|
|
|
|
if not gst_initialized:
|
|
raise ConfigurationException('Global Symbol Table (i.e. global variables) not initialized: allocate it onto Volatile and/or Non-volatile Memory.')
|
|
|
|
# gst only in one memory location
|
|
if len(self.memory_positions['gst']) == 1:
|
|
other_section = None
|
|
|
|
# gst in both
|
|
else:
|
|
other_section = config.gst_other_memory_section
|
|
|
|
self.gst = VirtualGlobalSymbolTableUnifier(self, config.gst_default_memory, other_section)
|
|
|
|
self.address_dimension = config.address_size
|
|
|
|
logging.info('Main memory initialized.')
|
|
|
|
|
|
@staticmethod
|
|
def _is_absolute_address(address):
|
|
"""
|
|
Returns if an address is a valid absolute one.
|
|
"""
|
|
|
|
return isinstance(address, str) and '-0x' in address
|
|
|
|
|
|
@staticmethod
|
|
def extract_prefix_from_address(address):
|
|
"""
|
|
Extracts the prefix of an absolute address.
|
|
"""
|
|
|
|
if not Memory._is_absolute_address(address):
|
|
raise ValueError('Invalid address: {}'.format(address))
|
|
|
|
prefix = address.split('-0x')
|
|
|
|
return prefix[0]+'-'
|
|
|
|
|
|
@staticmethod
|
|
def _extract_relative_address(address):
|
|
"""
|
|
Returns the relative address from an absolute address.
|
|
"""
|
|
|
|
if not Memory._is_absolute_address(address):
|
|
raise ValueError('Invalid address: {}'.format(address))
|
|
|
|
address = address.split('-')
|
|
|
|
return int(address[1], 16)
|
|
|
|
|
|
@staticmethod
|
|
def _parse_absolute_address(address):
|
|
"""
|
|
Returns the prefix and the relative address from an absolute one.
|
|
"""
|
|
|
|
if not Memory._is_absolute_address(address):
|
|
raise ValueError('Invalid address: {}'.format(address))
|
|
|
|
address = address.split('-')
|
|
|
|
relative_address = int(address[1], 16)
|
|
prefix = address[0] + '-'
|
|
|
|
return prefix, relative_address
|
|
|
|
|
|
@staticmethod
|
|
def convert_dimension(dimension, dimension_in_bits):
|
|
"""
|
|
Converts a dimension from bits to bytes, if dimension_in_bits is True.
|
|
"""
|
|
|
|
if dimension_in_bits:
|
|
if dimension % 8 != 0:
|
|
raise MemoryException('[Memory] Invalid memory dimension: {} bits. Dimension must be a multiple of a byte (8 bits).'.format(dimension))
|
|
|
|
# convert dimension in bytes
|
|
dimension = dimension // 8 # int division
|
|
|
|
return dimension
|
|
|
|
|
|
def add_offset(self, address, offset, offset_in_bits = True):
|
|
"""
|
|
Add an offset to an absolute address
|
|
"""
|
|
|
|
offset = self.stack.convert_dimension(offset, offset_in_bits)
|
|
|
|
prefix, relative_address = self._parse_absolute_address(address)
|
|
relative_address = relative_address + offset
|
|
|
|
return self._convert_to_absolute_address(prefix, relative_address)
|
|
|
|
|
|
@staticmethod
|
|
def _convert_to_absolute_address(prefix, address):
|
|
"""
|
|
Returns an absolute address, given a prefix
|
|
"""
|
|
|
|
return '{}{}'.format(prefix, hex(address))
|
|
|
|
|
|
def write(self, address, dimension, content, dimension_in_bits = True):
|
|
"""
|
|
Performs the call to the correct write method.
|
|
"""
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.write(address, dimension, content, dimension_in_bits)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.write(address, dimension, content, dimension_in_bits)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.write(address, dimension, content, dimension_in_bits)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
|
|
def set_cell_input_lookup(self, address, input_lookup):
|
|
"""
|
|
Sets the input lookup information of cell in a given address.
|
|
"""
|
|
|
|
# NB: emptiness of input lookup if input_lookup is [] is guaranteed by write operations
|
|
# which removes completely the lookup infos.
|
|
|
|
if len(input_lookup) == 0:
|
|
return
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.set_cell_input_lookup(address, input_lookup)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.set_cell_input_lookup(address, input_lookup)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.set_cell_input_lookup(address, input_lookup)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
|
|
def get_cell_input_lookup(self, address):
|
|
"""
|
|
Returns the input lookup information of cell in a given address.
|
|
"""
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.get_cell_input_lookup(address)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.get_cell_input_lookup(address)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.get_cell_input_lookup(address)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
|
|
def get_cells_from_address(self, address, dimension, dimension_in_bits = True, resolve_address = True):
|
|
"""
|
|
Performs the call to the correct get_cells_from_address method
|
|
"""
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
def simulate_mmu_read_on_cells(self, cells):
|
|
if len(cells) == 0:
|
|
return
|
|
|
|
prefix = self.extract_prefix_from_address(cells[0].absolute_address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.simulate_mmu_read_on_cells(cells)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.simulate_mmu_read_on_cells(cells)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.simulate_mmu_read_on_cells(cells)
|
|
|
|
|
|
def set_cells_from_address(self, address, cells, resolve_address = True):
|
|
"""
|
|
Performs the call to the correct set_cells_from_address method
|
|
"""
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.set_cells_from_address(address, cells, resolve_address)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.set_cells_from_address(address, cells, resolve_address)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.set_cells_from_address(address, cells, resolve_address)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
def read_addr(self, address):
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
address = self.stack.get_real_address(address)
|
|
return self.stack._memory[address].content
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
address = self.heap.get_real_address(address)
|
|
return self.heap._memory[address].content
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
gst = self.gst._get_gst_from_address(address)
|
|
address = gst.get_real_address(address)
|
|
return gst._memory[address].content
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
def read(self, address, dimension, dimension_in_bits = True):
|
|
"""
|
|
Performs the call to the correct read method.
|
|
"""
|
|
|
|
prefix = self.extract_prefix_from_address(address)
|
|
|
|
if prefix == self.stack.address_prefix:
|
|
return self.stack.read(address, dimension, dimension_in_bits)
|
|
|
|
elif prefix == self.heap.address_prefix:
|
|
return self.heap.read(address, dimension, dimension_in_bits)
|
|
|
|
elif prefix in self.gst.address_prefix:
|
|
return self.gst.read(address, dimension, dimension_in_bits)
|
|
|
|
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
|
|
|
|
|
|
@property
|
|
def stack(self):
|
|
"""
|
|
Returns transparently the stack
|
|
Stack must be in volatile or non-volatile memory.
|
|
If volatile memory not initialized or stack not in volatile memory, it must be in nvm, otherwise
|
|
the memory has not been initialized and an exception have been raised on init.
|
|
"""
|
|
|
|
if self.volatile is not None and self.volatile.stack is not None:
|
|
return self.volatile.stack
|
|
|
|
return self.non_volatile.stack
|
|
|
|
|
|
@property
|
|
def heap(self):
|
|
"""
|
|
Returns transparently the heap
|
|
Heap must be in volatile or nvm.
|
|
If volatile memory not initialized or heap not in volatile memory, it must be in nvm, otherwise
|
|
the memory has not been initialized and an exception have been raised on init.
|
|
"""
|
|
|
|
if self.volatile is not None and self.volatile.heap is not None:
|
|
return self.volatile.heap
|
|
|
|
return self.non_volatile.heap
|
|
|
|
|
|
def reset(self):
|
|
"""
|
|
Performs the CPU reset operation
|
|
"""
|
|
|
|
logging.debug('Memory reset.')
|
|
|
|
if self.volatile is not None:
|
|
self.volatile.reset()
|
|
|
|
|
|
def force_nvm_reset(self):
|
|
"""
|
|
Resets the NVM.
|
|
"""
|
|
|
|
if self.non_volatile is not None:
|
|
self.non_volatile.force_reset()
|
|
|
|
|
|
def check_memory_size(self):
|
|
"""
|
|
Checks memory size
|
|
"""
|
|
|
|
if self.volatile is not None:
|
|
self.volatile.check_memory_size()
|
|
|
|
if self.non_volatile is not None:
|
|
self.non_volatile.check_memory_size()
|