310 lines
11 KiB
Python
310 lines
11 KiB
Python
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
|
|
|