from utils import Colors, init_interpret, ParseFlags from opcodes import WASM_OP_Code from section_structs import Code_Section, Func_Body, WASM_Ins, Resizable_Limits, Memory_Section from execute import * import datetime as dti import os import sys import signal # McCabe cyclomatic complexity metric class Metric(): def __init__(self, code_section): self.code_section = code_section self.metric = [] self.soc = [] def mccabe(self): soc = 0 Edges = 1 Nodes = 1 for funcs in self.code_section.func_bodies: for ins in funcs.code: soc += 1 #print(repr(ins.opcodeint)) if ins.opcodeint == 4 or ins.opcodeint == 5 or ins.opcodeint == 12 \ or ins.opcodeint == 13 or ins.opcodeint == 14: Nodes += 2 Edges += 4 elif ins.opcode == 3: Nodes += 2 Edges += 3 else: pass self.metric.append(Edges - Nodes + 1) self.soc.append(soc) soc = 0 Edges = 1 Nodes = 1 def getMcCabe(self): return self.metric def getSOC(self): return self.soc # handles the debug option --memdump. dumps the contents of linear memories. def DumpLinearMems(linear_memories, threshold): count = int() strrep = [] linmem_cnt = int() for lin_mem in linear_memories: print('-----------------------------------------') print(Colors.blue + Colors.BOLD + 'Linear Memory '+ repr(linmem_cnt)+ ' :' + Colors.ENDC) for byte in lin_mem: if count >= threshold: break if count%16 == 0: for ch in strrep: # @DEVI-line feed messes the pretty format up if ord(ch) != 10: print(Colors.green + ' ' + ch + Colors.ENDC, end = '') else: pass print() strrep = [] print(Colors.cyan + hex(count), ':\t' + Colors.ENDC, end='') strrep.append(str(chr(byte))) print(Colors.blue + format(byte, '02x') + ' ' + Colors.ENDC, end='') else: strrep += str(chr(byte)) print(Colors.blue + format(byte, '02x') + ' ' + Colors.ENDC, end='') count += 1 count = 0 print() # handles the debug options --idxspc. dumps the index spaces. def DumpIndexSpaces(machinestate): print('-----------------------------------------') print(Colors.green + 'Function Index Space: ' + Colors.ENDC) for iter in machinestate.Index_Space_Function: print(Colors.blue + repr(iter) + Colors.ENDC) print('-----------------------------------------') print(Colors.green + 'Globa Index Space: ' + Colors.ENDC) for iter in machinestate.Index_Space_Global: print(Colors.blue + repr(iter) + Colors.ENDC) print('-----------------------------------------') print(Colors.green + 'Linear Memory Index Space: ' + Colors.ENDC) for iter in machinestate.Index_Space_Linear: print(Colors.blue + repr(iter) + Colors.ENDC) print('-----------------------------------------') print(Colors.green + 'Table Index Space: ' + Colors.ENDC) for iter in machinestate.Index_Space_Table: print(Colors.blue + repr(iter) + Colors.ENDC) print('-----------------------------------------') # WIP-the Truebit Machine class class TBMachine(): def __init__(self): # bytearray of size PAGE_SIZE self.Linear_Memory = [] self.Stack_Label = list() self.Stack_Label_Height = int() self.Stack_Control_Flow = list() self.Stack_Call = list() self.Stack_Value = list() self.Stack_Omni = list() self.Vector_Globals = list() self.Index_Space_Function = list() self.Index_Space_Global = list() self.Index_Space_Linear = list() self.Index_Space_Table = list() self.Index_Space_Locals = list() self.Index_Space_Label = list() # handles the initialization of the WASM machine class TBInit(): def __init__(self, module, machinestate): self.module = module self.machinestate = machinestate # a convenience function that runs the methods of the class. all methods # can be called separately manually as well. def run(self): self.InitFuncIndexSpace() self.InitGlobalIndexSpace() self.InitLinearMemoryIndexSpace() self.InitTableIndexSpace() self.InitializeLinearMemory() def InitFuncIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 0: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Function.append(name) if self.module.function_section is not None: for iter in self.module.function_section.type_section_index: self.machinestate.Index_Space_Function.append(iter) def InitGlobalIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 3: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Global.append(name) if self.module.global_section is not None: for iter in self.module.global_section.global_variables: self.machinestate.Index_Space_Global.append(iter.init_expr) def InitLinearMemoryIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 2: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Linear.append(name) if self.module.memory_section is not None: for iter in self.module.memory_section.memory_types: self.machinestate.Index_Space_Linear.append(iter.initial) def InitTableIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 1: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Table.append(name) if self.module.table_section is not None: for iter in self.module.table_section.table_types: self.machinestate.Index_Space_Table.append(iter.element_type) def InitializeLinearMemory(self): # @DEVI-we could try to pack the data in the linear memory ourselve to # decrease the machinestate size if self.module.memory_section is None: rsz_limits = Resizable_Limits() self.module.memory_section = Memory_Section() self.module.memory_section.memory_types = [rsz_limits] self.module.memory_section.count = 1 for iter in self.module.memory_section.memory_types: self.machinestate.Linear_Memory.append(bytearray( WASM_OP_Code.PAGE_SIZE)) if self.module.data_section is not None: for iter in self.module.data_section.data_segments: count = int() for byte in iter.data: self.machinestate.Linear_Memory[iter.index][init_interpret(iter.offset) + count] = byte count += 1 # returns the machinestate def getInits(self): return(self.machinestate) # WIP-holds the run-rime data structures for a wasm machine class RTE(): def __init__(self): Stack_Control_Flow = list() Stack_Value = list() Vector_Locals = list() Current_Position = int() Local_Stacks = list() def genFuncLocalStack(func_body): pass # palceholder for the class that holds the validation functions class ModuleValidation(): def __init__(self, module): self.module = module def TypeSection(self): pass def ImportSection(self): pass def FunctionSection(self): pass def TableSection(self): pass def MemorySection(self): pass def GlobalSection(self): pass def ExportSection(self): pass def StartSection(self): pass def ElementSection(self): pass def CodeSection(self): pass def DataSection(self): pass def TBCustom(self): pass def ValidateAll(self): self.TypeSection() self.ImportSection() self.FunctionSection() self.TableSection() self.MemorySection() self.GlobalSection() self.ExportSection() self.StartSection() self.ElementSection() self.CodeSection() self.DataSection() self.TBCustom() return(True) # a convinience class that handles the initialization of the wasm machine and # interpretation of the code. class VM(): def __init__(self, modules): self.modules = modules self.machinestate = TBMachine() # @DEVI-FIXME- the first implementation is single-module only self.init = TBInit(self.modules[0], self.machinestate) self.init.run() self.machinestate = self.init.getInits() self.start_function = Func_Body() self.ins_cache = WASM_Ins() self.executewasm = Execute(self.machinestate) self.totGas = int() self.metric = Metric(modules[0].code_section) self.parseflags = None def setFlags(self, parseflags): self.parseflags = parseflags def getState(self): return(self.machinestate) def initLocalIndexSpace(self, local_count): for i in range(0, local_count): self.machinestate.Index_Space_Locals.append(0) def getStartFunctionIndex(self): if self.modules[0].start_section is None: if self.parseflags.entry is None: raise Exception(Colors.red + "module does not have a start section. no function index was provided with the --entry option.quitting..." + Colors.ENDC) else: start_index = int(self.parseflags.entry) else: print(Colors.green + "found start section: " + Colors.ENDC, end = '') start_index = self.modules[0].start_section.function_section_index print(Colors.blue + Colors.BOLD + "running function at index " + repr(start_index) + Colors.ENDC) if (start_index > len(self.modules[0].code_section.func_bodies) - 1): raise Exception(Colors.red + "invalid function index: the function index does not exist." + Colors.ENDC) return(start_index) def getStartFunctionBody(self): start_index = self.getStartFunctionIndex() if isinstance(start_index, int): self.start_function = self.modules[0].code_section.func_bodies[start_index] elif isinstance(start_index, str): # we have to import the function from another module/library. we # assume sys calls are not present.:w pass else: raise Exception(Colors.red + "invalid entry for start function index" + Colors.ENDC) def execute(self): print(Colors.blue + Colors.BOLD + 'running module with code: ' + Colors.ENDC) for ins in self.start_function.code: print(Colors.purple + repr(ins.opcode) + ' ' + repr(ins.operands) + Colors.ENDC) for ins in self.start_function.code: self.executewasm.getInstruction(ins.opcodeint, ins.operands) self.executewasm.callExecuteMethod() self.getState() # pre-execution hook def startHook(self): if self.parseflags.metric: for mem in self.modules[0].memory_section.memory_types: self.executewasm.chargeGasMem(mem.initial) self.metric.mccabe() print(Colors.red + "mccabe: " + repr(self.metric.getMcCabe()) + Colors.ENDC) print(Colors.red + "soc: " + repr(self.metric.getSOC()) + Colors.ENDC) # post-execution hook def endHook(self): if self.parseflags.gas: self.totGas = self.executewasm.getOPGas() print(Colors.red + "total gas cost: " + repr(self.totGas) + Colors.ENDC) if self.machinestate.Stack_Omni: print(Colors.green + "stack top: " + repr(self.machinestate.Stack_Omni.pop()) + Colors.ENDC) # a convinience method def run(self): self.startHook() self.getStartFunctionBody() self.initLocalIndexSpace(self.start_function.local_count) self.execute() self.endHook() # a wrapper class for VM. it timeouts instructions that take too long to # execute. class Judicator(): def __int__(self, op_time_table, module): self.op_time_table = op_time_table self.vm = VM(modules) self.vm.getStartFunctionBody() def overseer(): # @DEVI- forking introduces a new source of non-determinism pid = os.fork() # child process if pid == 0: sys.stdout = open('./jstdout', 'w') sys.stderr = open('./jstderr', 'w') self.vm.execute() sys.exit() # parent process if pid > 0: cpid, status = os.waitpid(pid, 0) if status == 0: print('overseer child exited successfully.') else: print('overseer child exited with non-zero.') # pid < 0 else: raise Exception(Colors.red + 'could not fork judicator overseer.' + Colors.ENDC) def setup(self): signal.signal(signal.SIGALRM, self.to_sighandler) def set_alarm(t): signal.alaram(t) def to_sighandler(signum, frame): print(Colors.red + "execution time out..." + Colors.ENDC) raise Exception(Colors.red + "execution time out" + Colors.ENDC) def run(self): self.setup() self.set_alaram(10) self.overseer()