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()