/* * Copyright 2020 WebAssembly Community Group participants * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Emit a C wrapper file that can run the wasm after it is compiled with // wasm2c, useful for fuzzing. // #include #include "wasm.h" namespace wasm { // Mangle a name in (hopefully) exactly the same way wasm2c does. static std::string wasm2cMangle(Name name, Signature sig) { const char escapePrefix = 'Z'; std::string mangled = "Z_"; const char* original = name.str; unsigned char c; while ((c = *original++)) { if ((isalnum(c) && c != escapePrefix) || c == '_') { // This character is ok to emit as it is. mangled += c; } else { // This must be escaped, as prefix + hex character code. mangled += escapePrefix; std::stringstream ss; ss << std::hex << std::uppercase << unsigned(c); mangled += ss.str(); } } // Emit the result and params. mangled += "Z_"; auto wasm2cSignature = [](Type type) { TODO_SINGLE_COMPOUND(type); switch (type.getBasic()) { case Type::none: return 'v'; case Type::i32: return 'i'; case Type::i64: return 'j'; case Type::f32: return 'f'; case Type::f64: return 'd'; default: Fatal() << "unhandled wasm2c wrapper signature type: " << type; } }; mangled += wasm2cSignature(sig.results); if (sig.params.isTuple()) { for (const auto& param : sig.params) { mangled += wasm2cSignature(param); } } else { mangled += wasm2cSignature(sig.params); } return mangled; } static std::string generateWasm2CWrapper(Module& wasm) { // First, emit implementations of the wasm's imports so that the wasm2c code // can call them. The names use wasm2c's name mangling. std::string ret = R"( #include #include #include #include "wasm-rt-impl.h" #include "wasm.h" void _Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi(u32 x) { printf("[LoggingExternalInterface logging %d]\n", x); } void (*Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi)(u32) = _Z_fuzzingZ2DsupportZ_logZ2Di32Z_vi; void _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj(u64 x) { printf("[LoggingExternalInterface logging %" PRId64 "]\n", (int64_t)x); } void (*Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj)(u64) = _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vj; void _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vii(u32 x, u32 y) { printf("[LoggingExternalInterface logging %d %d]\n", x, y); } void (*Z_fuzzingZ2DsupportZ_logZ2Di64Z_vii)(u32, u32) = _Z_fuzzingZ2DsupportZ_logZ2Di64Z_vii; void _Z_fuzzingZ2DsupportZ_logZ2Df32Z_vf(f32 x) { printf("[LoggingExternalInterface logging %.17e]\n", x); } void (*Z_fuzzingZ2DsupportZ_logZ2Df32Z_vf)(f32) = _Z_fuzzingZ2DsupportZ_logZ2Df32Z_vf; void _Z_fuzzingZ2DsupportZ_logZ2Df64Z_vd(f64 x) { printf("[LoggingExternalInterface logging %.17le]\n", x); } void (*Z_fuzzingZ2DsupportZ_logZ2Df64Z_vd)(f64) = _Z_fuzzingZ2DsupportZ_logZ2Df64Z_vd; // Miscellaneous imports u32 tempRet0 = 0; void _Z_envZ_setTempRet0Z_vi(u32 x) { tempRet0 = x; } void (*Z_envZ_setTempRet0Z_vi)(u32) = _Z_envZ_setTempRet0Z_vi; u32 _Z_envZ_getTempRet0Z_iv(void) { return tempRet0; } u32 (*Z_envZ_getTempRet0Z_iv)(void) = _Z_envZ_getTempRet0Z_iv; // Main int main(int argc, char** argv) { init(); // We go through each export and call it, in turn. Note that we use a loop // so we can do all this with a single setjmp. A setjmp is needed to handle // wasm traps, and emitting a single one helps compilation speed into wasm as // compile times are O(size * num_setjmps). for (size_t curr = 0;; curr++) { )"; if (wasm.getExportOrNull("hangLimitInitializer")) { ret += R"( // If present, call the hang limit initializer before each export. (*Z_hangLimitInitializerZ_vv)(); )"; } ret += R"( // Prepare to call the export, so we can catch traps. if (WASM_RT_SETJMP(g_jmp_buf) != 0) { puts("exception!"); } else { // Call the proper export. switch(curr) { )"; // For each function export in the wasm, emit code to call it and log its // result, similar to the other wrappers. size_t functionExportIndex = 0; for (auto& exp : wasm.exports) { if (exp->kind != ExternalKind::Function) { continue; } ret += " case " + std::to_string(functionExportIndex++) + ":\n"; auto* func = wasm.getFunction(exp->value); ret += std::string(" puts(\"[fuzz-exec] calling ") + exp->name.str + "\");\n"; auto result = func->sig.results; // Emit the call itself. ret += " "; if (result != Type::none) { ret += std::string("printf(\"[fuzz-exec] note result: ") + exp->name.str + " => "; TODO_SINGLE_COMPOUND(result); switch (result.getBasic()) { case Type::i32: ret += "%d\\n\", "; break; case Type::i64: ret += "%\" PRId64 \"\\n\", (int64_t)"; break; case Type::f32: ret += "%.17e\\n\", "; break; case Type::f64: ret += "%.17le\\n\", "; break; default: Fatal() << "unhandled wasm2c wrapper result type: " << result; } } // Call the export. ret += "(*"; // Emit the callee's name with wasm2c name mangling. ret += wasm2cMangle(exp->name, func->sig); ret += ")("; // Emit the parameters (all 0s, like the other wrappers). bool first = true; for (const auto& param : func->sig.params) { WASM_UNUSED(param); if (!first) { ret += ", "; } ret += "0"; first = false; } if (result != Type::none) { ret += ")"; } ret += ");\n"; // Break from the big switch. ret += " break;\n"; } ret += R"( default: return 0; // All done. } } } return 0; } )"; return ret; } } // namespace wasm