def eval_ctors_wasm(js, wasm_file, num): ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) cmd = [os.path.join(binaryen_bin, 'wasm-ctor-eval'), wasm_file, '-o', wasm_file, '--ctors=' + ','.join(ctors)] cmd += extra_args if debug_info: cmd += ['-g'] logging.debug('wasm ctor cmd: ' + str(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) try: err = jsrun.timeout_run(proc, timeout=10, full_output=True, throw_on_failure=False) except Exception as e: if 'Timed out' not in str(e): raise logging.debug('ctors timed out\n') return 0, js if proc.returncode != 0: shared.exit_with_error('unexpected error while trying to eval ctors:\n' + err) num_successful = err.count('success on') logging.debug(err) if len(ctors) == num_successful: new_ctors = '' else: elements = [] for ctor in all_ctors[num_successful:]: elements.append('{ func: function() { %s() } }' % ctor) new_ctors = '__ATINIT__.push(' + ', '.join(elements) + ');' js = js[:ctors_start] + new_ctors + js[ctors_end:] return num_successful, js
def eval_ctors_js(js, mem_init, num): def kill_func(asm, name): asm = asm.replace('function ' + name + '(', 'function KILLED_' + name + '(', 1) return asm def add_func(asm, func): before = len(asm) asm = asm.replace('function ', ' ' + func + '\nfunction ', 1) assert len(asm) > before name = func[func.find(' ') + 1:func.find('(')] asm = asm.replace('return {', 'return { ' + name + ': ' + name + ',') return asm # Find the global ctors ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) logging.debug('trying to eval ctors: ' + ', '.join(ctors)) # Find the asm module, and receive the mem init. asm = get_asm(js) assert len(asm) asm = asm.replace('use asm', 'not asm') # don't try to validate this # Substitute sbrk with a failing stub: the dynamic heap memory area shouldn't get increased during static ctor initialization. asm = asm.replace( 'function _sbrk(', 'function _sbrk(increment) { throw "no sbrk when evalling ctors!"; } function KILLED_sbrk(', 1) # find all global vars, and provide only safe ones. Also add dumping for those. pre_funcs_start = asm.find(';') + 1 pre_funcs_end = asm.find('function ', pre_funcs_start) pre_funcs_end = asm.rfind(';', pre_funcs_start, pre_funcs_end) + 1 pre_funcs = asm[pre_funcs_start:pre_funcs_end] parts = [ x for x in [x.strip() for x in pre_funcs.split(';')] if x.startswith('var ') ] global_vars = [] new_globals = '\n' for part in parts: part = part[4:] # skip 'var ' bits = [x.strip() for x in part.split(',')] for bit in bits: name, value = [x.strip() for x in bit.split('=', 1)] if value in ['0', '+0', '0.0'] or name in [ 'STACKTOP', 'STACK_MAX', 'DYNAMICTOP_PTR', 'HEAP8', 'HEAP16', 'HEAP32', 'HEAPU8', 'HEAPU16', 'HEAPU32', 'HEAPF32', 'HEAPF64', 'Int8View', 'Int16View', 'Int32View', 'Uint8View', 'Uint16View', 'Uint32View', 'Float32View', 'Float64View', 'nan', 'inf', '_emscripten_memcpy_big', '___dso_handle', '_atexit', '___cxa_atexit', ] or name.startswith('Math_'): if 'new ' not in value: global_vars.append(name) new_globals += ' var ' + name + ' = ' + value + ';\n' asm = asm[:pre_funcs_start] + new_globals + asm[pre_funcs_end:] asm = add_func( asm, 'function dumpGlobals() { return [ ' + ', '.join(global_vars) + '] }') # find static bump. this is the maximum area we'll write to during startup. static_bump_op = 'STATICTOP = STATIC_BASE + ' static_bump_start = js.find(static_bump_op) static_bump_end = js.find(';', static_bump_start) static_bump = int(js[static_bump_start + len(static_bump_op):static_bump_end]) # Generate a safe sandboxed environment. We replace all ffis with errors. Otherwise, # asm.js can't call outside, so we are ok. # if shared.DEBUG: # temp_file = os.path.join(shared.CANONICAL_TEMP_DIR, 'ctorEval.js') # shared.safe_ensure_dirs(shared.CANONICAL_TEMP_DIR) # else: # temp_file = config.get_temp_files().get('.ctorEval.js').name with config.get_temp_files().get_file('.ctorEval.js') as temp_file: open(temp_file, 'w').write(''' var totalMemory = %d; var totalStack = %d; var buffer = new ArrayBuffer(totalMemory); var heap = new Uint8Array(buffer); var heapi32 = new Int32Array(buffer); var memInit = %s; var globalBase = %d; var staticBump = %d; heap.set(memInit, globalBase); var staticTop = globalBase + staticBump; var staticBase = staticTop; var stackTop = staticTop; while (stackTop %% 16 !== 0) stackTop--; var stackBase = stackTop; var stackMax = stackTop + totalStack; if (stackMax >= totalMemory) throw 'not enough room for stack'; var dynamicTopPtr = stackMax; heapi32[dynamicTopPtr >> 2] = stackMax; if (!Math.imul) { Math.imul = Math.imul || function(a, b) { var ah = (a >>> 16) & 0xffff; var al = a & 0xffff; var bh = (b >>> 16) & 0xffff; var bl = b & 0xffff; // the shift by 0 fixes the sign on the high part // the final |0 converts the unsigned value into a signed value return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); }; } if (!Math.fround) { var froundBuffer = new Float32Array(1); Math.fround = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; } var atexits = []; // we record and replay atexits var globalArg = { Int8Array: Int8Array, Int16Array: Int16Array, Int32Array: Int32Array, Uint8Array: Uint8Array, Uint16Array: Uint16Array, Uint32Array: Uint32Array, Float32Array: Float32Array, Float64Array: Float64Array, NaN: NaN, Infinity: Infinity, Math: Math, }; var libraryArg = { STACKTOP: stackTop, STACK_MAX: stackMax, DYNAMICTOP_PTR: dynamicTopPtr, ___dso_handle: 0, // used by atexit, value doesn't matter _emscripten_memcpy_big: function(dest, src, num) { heap.set(heap.subarray(src, src+num), dest); return dest; }, _atexit: function(x) { atexits.push([x, 0]); return 0; }, ___cxa_atexit: function(x, y) { atexits.push([x, y]); return 0; }, }; // Instantiate asm %s (globalArg, libraryArg, buffer); // Try to run the constructors var allCtors = %s; var numSuccessful = 0; for (var i = 0; i < allCtors.length; i++) { try { var globalsBefore = asm['dumpGlobals'](); asm[allCtors[i]](); var globalsAfter = asm['dumpGlobals'](); if (JSON.stringify(globalsBefore) !== JSON.stringify(globalsAfter)) { console.warn('globals modified'); break; } if (heapi32[dynamicTopPtr >> 2] !== stackMax) { console.warn('dynamic allocation was performend'); break; } // this one was ok. numSuccessful = i + 1; } catch (e) { console.warn(e.stack); break; } } // Write out new mem init. It might be bigger if we added to the zero section, look for zeros var newSize = globalBase + staticBump; while (newSize > globalBase && heap[newSize-1] == 0) newSize--; console.log(JSON.stringify([numSuccessful, Array.prototype.slice.call(heap.subarray(globalBase, newSize)), atexits])); ''' % (total_memory, total_stack, mem_init, global_base, static_bump, asm, json.dumps(ctors))) def read_and_delete(filename): result = '' try: result = open(filename, 'r').read() finally: try_delete(filename) return result # Execute the sandboxed code. If an error happened due to calling an ffi, that's fine, # us exiting with an error tells the caller that we failed. If it times out, give up. out_file = config.get_temp_files().get('.out').name err_file = config.get_temp_files().get('.err').name out_file_handle = open(out_file, 'w') err_file_handle = open(err_file, 'w') proc = subprocess.Popen(shared.NODE_JS + [temp_file], stdout=out_file_handle, stderr=err_file_handle, universal_newlines=True) try: jsrun.timeout_run(proc, timeout=10, full_output=True, throw_on_failure=False) except Exception as e: if 'Timed out' not in str(e): raise logger.debug('ctors timed out\n') return (0, 0, 0, 0) if shared.WINDOWS: time.sleep( 0.5 ) # On Windows, there is some kind of race condition with Popen output stream related functions, where file handles are still in use a short period after the process has finished. out_file_handle.close() err_file_handle.close() out_result = read_and_delete(out_file) err_result = read_and_delete(err_file) if proc.returncode != 0: # TODO(sbc): This should never happen under normal circumstances. # switch to exit_with_error once we fix https://github.com/emscripten-core/emscripten/issues/7463 logger.debug('unexpected error while trying to eval ctors:\n' + out_result + '\n' + err_result) return (0, 0, 0, 0) # out contains the new mem init and other info num_successful, mem_init_raw, atexits = json.loads(out_result) mem_init = bytes(bytearray(mem_init_raw)) total_ctors = len(all_ctors) if num_successful < total_ctors: logger.debug( 'not all ctors could be evalled, something was used that was not safe (and therefore was not defined, and caused an error):\n========\n' + err_result + '========') # Remove the evalled ctors, add a new one for atexits if needed, and write that out if len(ctors) == total_ctors and len(atexits) == 0: new_ctors = '' else: elements = [] if len(atexits): elements.append('{ func: function() { %s } }' % '; '.join([ '_atexit(' + str(x[0]) + ',' + str(x[1]) + ')' for x in atexits ])) for ctor in all_ctors[num:]: elements.append('{ func: function() { %s() } }' % ctor) new_ctors = '__ATINIT__.push(' + ', '.join(elements) + ');' js = js[:ctors_start] + new_ctors + js[ctors_end:] return (num_successful, js, mem_init, ctors)