import copy import gc import logging from ScEpTIC.emulator.memory.virtual_stack import VirtualStack from ScEpTIC.exceptions import MemoryException class VirtualGlobalSymbolTable(VirtualStack): """ This class extends the stack and contains only symbols (i.e., global variables). Note that global variables resides on fixed positions on top of the stack. This class represents an abstraction that simplifies the analysis of intermittence anomalies w.r.t. global variables. """ def __init__(self, base_address, address_prefix, mmu=None): super().__init__(base_address, address_prefix, mmu) self._global_vars = {} self._cells_no = {} self._cells_used = {} self._base_state = {'memory': None, 'global_vars': None, 'top_address': None, 'cells_no': None, 'cells_used': None} def __eq__(self, other): if not isinstance(other, VirtualGlobalSymbolTable): return False return super().__eq__(other) and self._global_vars == other._global_vars def get_used_size(self): """ :return: the used size in bytes """ ignore_str_bss = '@.str' dimension = self.top_address - self.base_address for cell in self._memory.values(): if ignore_str_bss in cell.metadata: dimension -= cell.dimension return dimension def get_symbol_from_address(self, address): for name, addr in self._global_vars.items(): if address == addr: return name return None def _get_symbol_address(self, name, absolute_address=False): """ Returns the address of a symbol, given its name. """ logging.debug('[{}] Resolving address of symbol {}.'.format(self.mem_type, name)) if not self.has_symbol(name): raise MemoryException('[{}] Unable to find symbol {}!'.format(self.mem_type, name)) address = self._global_vars[name] if absolute_address: return '{}{}'.format(self.address_prefix, hex(address)) return address def allocate(self, name, composition, initial_values, dimension_in_bits=True, alignment=None): """ Allocates the required space for a given symbol name. The space is allocated accordingly to the given composition, which is a list of lists. Each element of the list is a sublist containing the number of elements and their dimension. It returns the address of the first allocated cell of the symbol. """ if self.has_symbol(name): raise MemoryException('[{}] Symbol {} already present!'.format(self.mem_type, name)) base_address = self.top_address logging.debug( '[{}] Allocating space for symbol {} at address {}.'.format(self.mem_type, name, hex(base_address))) self._global_vars[name] = base_address self._cells_no[name] = len(composition) self._cells_used[name] = [] for i in range(0, len(composition)): dimension = composition[i] addr = super().allocate(dimension, dimension_in_bits, 'Global variable {}'.format(name), alignment) if initial_values is not None: if i >= len(initial_values): initial_val = None else: initial_val = initial_values[i] self.write(addr, dimension, initial_val, dimension_in_bits, ignore_mmu=True) self._cells_used[name].append(addr) # return base address of symbol return '{}{}'.format(self.address_prefix, base_address) def sync_mmu(self): if self._mmu is not None: for addr, cell in self._memory.items(): self._mmu.memory_write(cell.get_content(), addr, cell.get_bit_size(), False) #print(f"Sync mmu: {addr} -> {cell.get_content()} ({cell.get_bit_size()}) -> {self._mmu.memory_read(addr, cell.get_bit_size(), False)}") def read_string_from_address(self, address): """ Reads a string from a memory address """ address = self.get_real_address(address) symbol_name = self._get_symbol_name_from_address(address) symbol_len = self._cells_no[symbol_name] content = '' for index in range(0, symbol_len): val = self._memory[address + index].content if val is not None and val > 0: content += chr(val) return content def deallocate(self, address): """ Deallocation not supported for this memory type. """ raise MemoryException('[{}] Memory operation not allowed!'.format(self.mem_type)) def has_symbol(self, name): """ Returns if a given symbol is in the VirtualGlobalSymbolTable """ return name in self._global_vars def read_from_symbol_name(self, name, dimension, dimension_in_bits=True): """ Returns the value of a symbol, given its name and dimension. The request is "forwared" to the stack's read function, after having calculated the needed address. """ logging.debug('[{}] Reading symbol {}.'.format(self.mem_type, name)) address = self._get_symbol_address(name) return self.read(address, dimension, dimension_in_bits, False) def write_from_symbol_name(self, name, dimension, content, dimension_in_bits=True): """ Writes the given value to the specified symbol, given its name and dimension. The request is "forwared" to the stack's write function, after having calculated the needed address. """ logging.debug('[{}] Writing symbol {} with content {}.'.format(self.mem_type, name, content)) address = self._get_symbol_address(name) return self.write(address, dimension, content, dimension_in_bits, False) def get_visual_dump(self, head_string): """ Returns a string representing the memory """ dump = '(' + head_string + ')\n' + str(self) + "\n" for var in sorted(self._global_vars.keys()): min_addr = {'txt': self._cells_used[var][0], 'num': self.get_real_address(self._cells_used[var][0])} max_addr = {'txt': self._cells_used[var][0], 'num': self.get_real_address(self._cells_used[var][0])} content = [] total_size = 0 for address in self._cells_used[var]: real_address = self.get_real_address(address) if real_address < min_addr['num']: min_addr = {'txt': address, 'num': real_address} if real_address > max_addr['num']: max_addr = {'txt': address, 'num': real_address} try: content.append(self._memory[real_address].content) total_size += self._memory[real_address].dimension except BaseException: print(f"Address {address} ({real_address}) not available!") address = f"{min_addr['txt']} - {max_addr['txt']}" if min_addr['txt'] != max_addr['txt'] else min_addr[ 'txt'] dump += '[{} ({} bytes)] {}: {}\n'.format(address, total_size, var, content) return dump def dump(self): """ Returns a dump of the GST. """ dump = super().dump() dump["global_vars"] = copy.deepcopy(self._global_vars) dump["cells_no"] = copy.deepcopy(self._cells_no) dump["cells_used"] = copy.deepcopy(self._cells_used) return dump def restore(self, dump): """ Restores a dump of the heap. """ del self._global_vars super().restore(dump) # self._global_vars = copy.deepcopy(dump._global_vars) self._global_vars = copy.deepcopy(dump["global_vars"]) def reset(self): """ Performs the CPU reset operation """ del self._global_vars del self._cells_no del self._cells_used del self._memory del self.top_address gc.collect() self._global_vars = copy.deepcopy(self._base_state['global_vars']) self._cells_no = copy.deepcopy(self._base_state['cells_no']) self._cells_used = copy.deepcopy(self._base_state['cells_used']) self._memory = copy.deepcopy(self._base_state['memory']) self.top_address = copy.deepcopy(self._base_state['top_address']) def set_state_as_base_state(self): """ Sets the current state as the base state, which is the one restored when reset() is called. """ self._base_state['global_vars'] = copy.deepcopy(self._global_vars) self._base_state['cells_no'] = copy.deepcopy(self._cells_no) self._base_state['cells_used'] = copy.deepcopy(self._cells_used) self._base_state['memory'] = copy.deepcopy(self._memory) self._base_state['top_address'] = copy.deepcopy(self.top_address) def _get_symbol_name_from_address(self, address): """ Returns the symbol name given its address. """ candidate = None candidate_addr = -1 for i in self._global_vars: addr = self._global_vars[i] if addr == address: return i elif candidate_addr < addr < address: candidate = i candidate_addr = addr return candidate def diff(self, dump): """ Returns the difference between the current state of the register file and the one saved inside a dump. """ logging.debug('[{}] Comparing current state with a given dump.'.format(self.mem_type)) if not isinstance(dump, self.__class__): raise MemoryException( 'Unable to compare {} dump: {} object expected, {} given.'.format(self.mem_type, self.mem_type, dump.__class__.__name__)) diff = [] # NB: global variables are allocated before running the program, so the runtime state cannot differ in terms of number of global variables if self._memory != dump._memory: mem_keys = list(self._memory.keys()) dump_keys = list(dump._memory.keys()) common_elements = [item for item in mem_keys if item in dump_keys] only_mem_keys = [item for item in mem_keys if item not in dump_keys] only_dump_keys = [item for item in dump_keys if item not in mem_keys] for i in common_elements: if self._memory[i] != dump._memory[i]: address = '{}{}'.format(self.address_prefix, hex(i)) diff.append({'var_name': '{}'.format(self._get_symbol_name_from_address(i)), 'address': address, 'dump_value': dump._memory[i].content, 'current_value': self._memory[i].content}) for i in only_mem_keys: address = '{}{}'.format(self.address_prefix, hex(i)) diff.append({'var_name': '{}'.format(self._get_symbol_name_from_address(i)), 'address': address, 'dump_value': None, 'current_value': self._memory[i].content}) for i in only_dump_keys: address = '{}{}'.format(self.address_prefix, hex(i)) diff.append({'var_name': '{}'.format(self._get_symbol_name_from_address(i)), 'address': address, 'dump_value': dump._memory[i].content, 'current_value': None}) return diff