sceptic-biomem/ScEpTIC/emulator/memory/virtual_global_symbol_table.py
2026-07-10 10:38:57 +02:00

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