import os, shutil import re import inspect import subprocess import sys from os.path import join as pjoin from .config import ( TEMPLATE_REPOSITORY, PROJECTS_REPOSITORY, ELMO_TOOL_REPOSITORY, ELMO_EXECUTABLE_NAME, MODULE_PATH, ELMO_OUTPUT_ENCODING, SEARCH_EXCLUSION_TAG, ) from .project_base import SimulationProject from .utils import Color ############ GETTERS ############ def search_simulations_in_repository(repository, criteria=lambda x:True): """ Search simulation classes in the 'repository' verifying the 'criteria' Return a list of 'SimulationProject' subclasses :repository: Repository of the searched simulation classes (string) :criteria: Boolean function with 'SimulationProject' subclasses for input """ projects = {} from .utils import write for root, repositories, files in os.walk(repository): for filename in files: if re.fullmatch(r'.*project.*\.py', filename): complete_filename = pjoin(root, filename) # Encapsulate the project globals = { #'__builtins__': {'__build_class__': __build_class__}, 'SimulationProject': SimulationProject, 'write': write, } locals = {} # Read the project code with open(complete_filename, 'r') as _file: project = ''.join(_file.read()) if ('SimulationProject' not in project) or (SEARCH_EXCLUSION_TAG in project): continue # Exclude this file exec(project, globals, locals) # Extract the simulations for key, obj in locals.items(): if inspect.isclass(obj) and issubclass(obj, SimulationProject): if criteria(obj): if key in projects: print(Color.WARNING + \ 'Warning! Many simulations with the same name. ' + \ 'Simulation ignored: {} in {}'.format( key, complete_filename[len(repository)+1:]) + \ Color.ENDC ) else: obj.set_project_directory(os.path.abspath(root)) projects[key] = obj return projects def search_simulations_in_module(criteria=lambda x:True): """ Search simulation classes among the module projects verifying the 'criteria' Return a list of 'SimulationProject' subclasses :criteria: Boolean function with 'SimulationProject' subclasses for input """ projects_path = pjoin(MODULE_PATH, PROJECTS_REPOSITORY) return search_simulations_in_repository(projects_path, criteria) def search_simulations(repository='.', criteria=lambda x:True): """ Search simulation classes in the 'repository' and among the module projects verifying the 'criteria' Return a list of 'SimulationProject' subclasses :repository: Repository of the searched simulation classes (string) :criteria: Boolean function with 'SimulationProject' subclasses for input """ projects = search_simulations_in_repository(repository, criteria) module_projects = search_simulations_in_module(criteria) for key, project in module_projects.items(): if key not in projects: projects[key] = project return projects class SimulationNotFoundError(Exception): pass class TooManySimulationsError(Exception): pass def get_simulation(classname=None, repository='.'): """ Get a simulation class in the 'repository' with the specific 'classname' Return a subclass of 'SimulationProject' class :classname: Name of the searched simulation class (string, optional) :repository: Repository of the searched simulation class (string, optional) """ criteria = lambda x: True if classname is not None: criteria = lambda x: x.__name__ == classname.strip() projects = search_simulations(repository, criteria) if len(projects) == 1: return list(projects.values())[0] elif len(projects) < 1: raise SimulationNotFoundError() else: raise TooManySimulationsError() ############ SETTERS ############ def create_simulation(repository, classname, is_a_module_project=False): """ Create a Simulation Project. It builds a repository with the standard content for the project. Return the absolute path of the created repository :repository: Name of repository which will be created (string) :classname: Name of the Python class to manage the project (string) :is_a_module_project: If True and :repository: is a relative path, create the project among the module projects. """ # Update the repository, if necessary if is_a_module_project and (not os.path.isabs(repository)): repository = pjoin( MODULE_PATH, PROJECTS_REPOSITORY, repository, ) # Build the project repository try: os.makedirs(repository, exist_ok=False) except FileExistsError as err: raise FileExistsError('Error, a project with this repository already exists!') from err elmo_path = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY) template_path = pjoin(MODULE_PATH, TEMPLATE_REPOSITORY) project_path = repository # Add standard content in the project files_from_ELMO = [ pjoin('Examples', 'elmoasmfunctions.o'), pjoin('Examples', 'elmoasmfunctions.s'), pjoin('Examples', 'elmoasmfunctionsdef.h'), pjoin('Examples', 'DPATraces', 'MBedAES', 'vector.o'), ] files_from_templates = [ 'elmoasmfunctionsdef-extension.h', 'Makefile', 'project.c' ] for filename in files_from_ELMO: shutil.copy(pjoin(elmo_path, filename), project_path) for filename in files_from_templates: shutil.copy(pjoin(template_path, filename), project_path) shutil.copy( pjoin(elmo_path, 'Examples', 'DPATraces', 'MBedAES', 'MBedAES.ld'), pjoin(project_path, 'project.ld') ) # Create the project class with open(pjoin(template_path, 'projectclass.txt')) as _source: code = ''.join(_source.readlines()) code = code.replace('{{PROJECTCLASSNAME}}', classname) with open(pjoin(project_path, 'projectclass.py'), 'w') as _dest: _dest.write(code) # Return the path of the created repository return os.path.abspath(project_path) ############ USAGE FUNCTIONS ############ class DontFindBinaryError(Exception): pass def execute_simulation(project): """ Execute a simulation of the power leakage using ELMO tool Return the output and the errors of the execution of ELMO tool :project: Subclass of 'SimulationProject' defining all the parameters of the simulation """ elmo_path = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY) # Some checking before executing if not isinstance(project, SimulationProject): raise TypeError('The project is not an instance of \'SimulationProject\' class.') leaking_binary_path = project.get_binary_path() if not os.path.isabs(leaking_binary_path): leaking_binary_path = pjoin(project.get_project_directory(), project.get_binary_path()) if not os.path.isfile(leaking_binary_path): raise BinaryNotFoundError('Binary not found. Did you compile your project?') if not os.path.isfile(pjoin(elmo_path, ELMO_EXECUTABLE_NAME)): raise Exception('Installation Error: the executable of the ELMO tool is not found.') # Launch generation of the traces by launching ELMO command = '{} "{}"'.format( pjoin('.', ELMO_EXECUTABLE_NAME), leaking_binary_path, ) process = subprocess.Popen(command, shell=True, cwd=elmo_path, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # Follow the generation output, error = b'', b'' num_trace = 0 while True: output_line = process.stdout.readline() error_line = process.stderr.readline() if (not output_line) and (not error_line) and (process.poll() is not None): break if error_line: error += error_line if output_line: output += output_line if 'TRACE NO' in output_line.decode(ELMO_OUTPUT_ENCODING): num_trace += 1 return_code = process.poll() # Treat data output = output.decode(ELMO_OUTPUT_ENCODING) if output else None error = error.decode(ELMO_OUTPUT_ENCODING) if error else None nb_traces = None nb_instructions = None if output: nb_traces = output.count('TRACE NO') nb_instructions_pattern = re.compile(r'\s*instructions/cy..es\s+(\d+)\s*') for line in output.split('\n'): res = nb_instructions_pattern.fullmatch(line) if res: nb_instructions = res.group(1) # Return results return { 'nb_traces': nb_traces, 'nb_instructions': nb_instructions, 'output': output, 'error': error, }