diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 66f761174bbda..cc07486c5c1dc 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -504,6 +504,22 @@ void replaceHeader(Http::HeaderMap* map, absl::string_view key, absl::string_vie map->addCopy(lower_key, std::string(value)); } +const uint8_t* decodeVarint(const uint8_t* pos, const uint8_t* end, uint32_t* out) { + uint32_t ret = 0; + int shift = 0; + while (pos < end && (*pos & 0x80)) { + ret |= (*pos & 0x7f) << shift; + shift += 7; + pos++; + } + if (pos < end) { + ret |= *pos << shift; + pos++; + } + *out = ret; + return pos; +} + } // namespace void Context::setTickPeriod(std::chrono::milliseconds tick_period) { @@ -933,66 +949,68 @@ Wasm::Wasm(absl::string_view vm, absl::string_view id, absl::string_view initial initial_configuration_(initial_configuration) { wasm_vm_ = Common::Wasm::createWasmVm(vm); id_ = std::string(id); - if (wasm_vm_) { +} +void Wasm::registerFunctions() { #define _REGISTER(_fn) registerCallback(wasm_vm_.get(), #_fn, &_fn##Handler); + if (is_emscripten_) { _REGISTER(getTotalMemory); _REGISTER(_emscripten_get_heap_size); _REGISTER(_llvm_trap); + } #undef _REGISTER - // Calls with the "_proxy_" prefix. + // Calls with the "_proxy_" prefix. #define _REGISTER_PROXY(_fn) registerCallback(wasm_vm_.get(), "_proxy_" #_fn, &_fn##Handler); - _REGISTER_PROXY(log); - - _REGISTER_PROXY(getRequestStreamInfoProtocol); - _REGISTER_PROXY(getResponseStreamInfoProtocol); - - _REGISTER_PROXY(getRequestMetadata); - _REGISTER_PROXY(setRequestMetadata); - _REGISTER_PROXY(getRequestMetadataPairs); - _REGISTER_PROXY(getResponseMetadata); - _REGISTER_PROXY(setResponseMetadata); - _REGISTER_PROXY(getResponseMetadataPairs); - - _REGISTER_PROXY(continueRequest); - _REGISTER_PROXY(continueResponse); - - _REGISTER_PROXY(getSharedData); - _REGISTER_PROXY(setSharedData); - - _REGISTER_PROXY(getRequestHeader); - _REGISTER_PROXY(addRequestHeader); - _REGISTER_PROXY(replaceRequestHeader); - _REGISTER_PROXY(removeRequestHeader); - _REGISTER_PROXY(getRequestHeaderPairs); - - _REGISTER_PROXY(getRequestTrailer); - _REGISTER_PROXY(addRequestTrailer); - _REGISTER_PROXY(replaceRequestTrailer); - _REGISTER_PROXY(removeRequestTrailer); - _REGISTER_PROXY(getRequestTrailerPairs); - - _REGISTER_PROXY(getResponseHeader); - _REGISTER_PROXY(addResponseHeader); - _REGISTER_PROXY(replaceResponseHeader); - _REGISTER_PROXY(removeResponseHeader); - _REGISTER_PROXY(getResponseHeaderPairs); - - _REGISTER_PROXY(getResponseTrailer); - _REGISTER_PROXY(addResponseTrailer); - _REGISTER_PROXY(replaceResponseTrailer); - _REGISTER_PROXY(removeResponseTrailer); - _REGISTER_PROXY(getResponseTrailerPairs); - - _REGISTER_PROXY(getRequestBodyBufferBytes); - _REGISTER_PROXY(getResponseBodyBufferBytes); - - _REGISTER_PROXY(httpCall); - - _REGISTER_PROXY(setTickPeriodMilliseconds); + _REGISTER_PROXY(log); + + _REGISTER_PROXY(getRequestStreamInfoProtocol); + _REGISTER_PROXY(getResponseStreamInfoProtocol); + + _REGISTER_PROXY(getRequestMetadata); + _REGISTER_PROXY(setRequestMetadata); + _REGISTER_PROXY(getRequestMetadataPairs); + _REGISTER_PROXY(getResponseMetadata); + _REGISTER_PROXY(setResponseMetadata); + _REGISTER_PROXY(getResponseMetadataPairs); + + _REGISTER_PROXY(continueRequest); + _REGISTER_PROXY(continueResponse); + + _REGISTER_PROXY(getSharedData); + _REGISTER_PROXY(setSharedData); + + _REGISTER_PROXY(getRequestHeader); + _REGISTER_PROXY(addRequestHeader); + _REGISTER_PROXY(replaceRequestHeader); + _REGISTER_PROXY(removeRequestHeader); + _REGISTER_PROXY(getRequestHeaderPairs); + + _REGISTER_PROXY(getRequestTrailer); + _REGISTER_PROXY(addRequestTrailer); + _REGISTER_PROXY(replaceRequestTrailer); + _REGISTER_PROXY(removeRequestTrailer); + _REGISTER_PROXY(getRequestTrailerPairs); + + _REGISTER_PROXY(getResponseHeader); + _REGISTER_PROXY(addResponseHeader); + _REGISTER_PROXY(replaceResponseHeader); + _REGISTER_PROXY(removeResponseHeader); + _REGISTER_PROXY(getResponseHeaderPairs); + + _REGISTER_PROXY(getResponseTrailer); + _REGISTER_PROXY(addResponseTrailer); + _REGISTER_PROXY(replaceResponseTrailer); + _REGISTER_PROXY(removeResponseTrailer); + _REGISTER_PROXY(getResponseTrailerPairs); + + _REGISTER_PROXY(getRequestBodyBufferBytes); + _REGISTER_PROXY(getResponseBodyBufferBytes); + + _REGISTER_PROXY(httpCall); + + _REGISTER_PROXY(setTickPeriodMilliseconds); #undef _REGISTER_PROXY - } } void Wasm::getFunctions() { @@ -1028,9 +1046,23 @@ Wasm::Wasm(const Wasm& wasm) bool Wasm::initialize(const std::string& code, absl::string_view name, bool allow_precompiled) { if (!wasm_vm_) return false; - auto ok = wasm_vm_->initialize(code, name, allow_precompiled); + auto ok = wasm_vm_->load(code, allow_precompiled); if (!ok) return false; + auto metadata = wasm_vm_->getUserSection("emscripten_metadata"); + if (!metadata.empty()) { + is_emscripten_ = true; + auto start = reinterpret_cast(metadata.data()); + auto end = reinterpret_cast(metadata.data() + metadata.size()); + start = decodeVarint(start, end, &emscripten_major_version_); + start = decodeVarint(start, end, &emscripten_minor_version_); + start = decodeVarint(start, end, &emscripten_abi_major_version_); + start = decodeVarint(start, end, &emscripten_abi_minor_version_); + start = decodeVarint(start, end, &emscripten_memory_size_); + decodeVarint(start, end, &emscripten_table_size_); + } + registerFunctions(); + wasm_vm_->link(name, is_emscripten_); general_context_ = createContext(); wasm_vm_->start(general_context_.get()); code_ = code; diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 06f61a6b107e6..bdafacee71fbb 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -294,10 +294,24 @@ class Wasm : public Envoy::Server::Wasm, general_context_ = std::move(context); } + bool getEmscriptenVersion(uint32_t* emscripten_major_version, uint32_t* emscripten_minor_version, + uint32_t* emscripten_abi_major_version, + uint32_t* emscripten_abi_minor_version) { + if (!is_emscripten_) { + return false; + } + *emscripten_major_version = emscripten_major_version_; + *emscripten_minor_version = emscripten_minor_version_; + *emscripten_abi_major_version = emscripten_abi_major_version_; + *emscripten_abi_minor_version = emscripten_abi_minor_version_; + return true; + } + private: friend class Context; - void getFunctions(); + void registerFunctions(); // Register functions called out from WASM. + void getFunctions(); // Get functions call into WASM. Upstream::ClusterManager& cluster_manager_; Event::Dispatcher& dispatcher_; @@ -338,6 +352,14 @@ class Wasm : public Envoy::Server::Wasm, std::string code_; std::string initial_configuration_; bool allow_precompiled_ = false; + + bool is_emscripten_ = false; + uint32_t emscripten_major_version_ = 0; + uint32_t emscripten_minor_version_ = 0; + uint32_t emscripten_abi_major_version_ = 0; + uint32_t emscripten_abi_minor_version_ = 0; + uint32_t emscripten_memory_size_ = 0; + uint32_t emscripten_table_size_ = 0; }; inline WasmVm* Context::wasmVm() const { return wasm_->wasmVm(); } @@ -368,10 +390,11 @@ class WasmVm : public Logger::Loggable { virtual std::unique_ptr clone() PURE; // Load the WASM code from a file. Return true on success. - virtual bool initialize(const std::string& code, absl::string_view id, - bool allow_precompiled) PURE; + virtual bool load(const std::string& code, bool allow_precompiled) PURE; + // Link to registered function. + virtual void link(absl::string_view debug_name, bool needs_emscripten) PURE; - // Call the 'start' function or main() if there is no start funcition. + // Call the 'start' function and initialize globals. virtual void start(Context*) PURE; // Allocate a block of memory in the VM and return the pointer to use as a call arguments. @@ -381,6 +404,10 @@ class WasmVm : public Logger::Loggable { // Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. virtual bool setMemory(uint32_t pointer, uint32_t size, void* data) PURE; + // Get the contents of the user section with the given name or "" if it does not exist and + // optionally a presence indicator. + virtual absl::string_view getUserSection(absl::string_view name, bool* present = nullptr) PURE; + // Convenience functions. // Allocate a null-terminated string in the VM and return the pointer to use as a call arguments. diff --git a/source/extensions/common/wasm/wavm/wavm.cc b/source/extensions/common/wasm/wavm/wavm.cc index c07f5fb41c574..96142cdec1fdf 100644 --- a/source/extensions/common/wasm/wavm/wavm.cc +++ b/source/extensions/common/wasm/wavm/wavm.cc @@ -175,8 +175,7 @@ template EnvoyHandlerBase* MakeEnvoyHandler(F handler) { return new EnvoyHandler(handler); } -class Wavm : public WasmVm { -public: +struct Wavm : public WasmVm { Wavm() = default; ~Wavm() override; @@ -184,11 +183,13 @@ class Wavm : public WasmVm { absl::string_view vm() override { return WasmVmNames::get().Wavm; } bool clonable() override { return true; }; std::unique_ptr clone() override; - bool initialize(const std::string& code, absl::string_view name, bool allow_precompiled) override; + bool load(const std::string& code, bool allow_precompiled) override; + void link(absl::string_view debug_name, bool needs_emscripten) override; void start(Context* context) override; void* allocMemory(uint32_t size, uint32_t* pointer) override; absl::string_view getMemory(uint32_t pointer, uint32_t size) override; bool setMemory(uint32_t pointer, uint32_t size, void* data) override; + absl::string_view getUserSection(absl::string_view name, bool* present) override; WAVM::Runtime::Memory* memory() { return memory_; } WAVM::Runtime::Context* context() { return context_; } @@ -200,6 +201,7 @@ class Wavm : public WasmVm { bool hasInstantiatedModule_ = false; IR::Module irModule_; + WAVM::Runtime::ModuleRef module_ = nullptr; WAVM::Runtime::GCPointer moduleInstance_; WAVM::Runtime::Memory* memory_; Emscripten::Instance* emscriptenInstance_ = nullptr; @@ -239,7 +241,7 @@ std::unique_ptr Wavm::clone() { return wavm; } -bool Wavm::initialize(const std::string& code, absl::string_view name, bool allow_precompiled) { +bool Wavm::load(const std::string& code, bool allow_precompiled) { ASSERT(!hasInstantiatedModule_); hasInstantiatedModule_ = true; compartment_ = WAVM::Runtime::createCompartment(); @@ -247,7 +249,6 @@ bool Wavm::initialize(const std::string& code, absl::string_view name, bool allo if (!loadModule(code, irModule_)) { return false; } - WAVM::Runtime::ModuleRef module = nullptr; // todo check percompiled section is permitted const UserSection* precompiledObjectSection = nullptr; if (allow_precompiled) { @@ -259,21 +260,17 @@ bool Wavm::initialize(const std::string& code, absl::string_view name, bool allo } } if (!precompiledObjectSection) { - module = WAVM::Runtime::compileModule(irModule_); + module_ = WAVM::Runtime::compileModule(irModule_); } else { - module = WAVM::Runtime::loadPrecompiledModule(irModule_, precompiledObjectSection->data); + module_ = WAVM::Runtime::loadPrecompiledModule(irModule_, precompiledObjectSection->data); } + return true; +} + +void Wavm::link(absl::string_view name, bool needs_emscripten) { RootResolver rootResolver(compartment_); envoyModuleInstance_ = Intrinsics::instantiateModule(compartment_, envoy_module_, "envoy"); rootResolver.moduleNameToInstanceMap().set("envoy", envoyModuleInstance_); - // Auto-detect if WASM module needs Emscripten. - bool needs_emscripten = false; - for (const auto& func : irModule_.functions.imports) { - if (func.exportName == "_emscripten_memcpy_big" && func.moduleName == "env") { - needs_emscripten = true; - break; - } - } if (needs_emscripten) { emscriptenInstance_ = Emscripten::instantiate(compartment_, irModule_); rootResolver.moduleNameToInstanceMap().set("env", emscriptenInstance_->env); @@ -281,10 +278,9 @@ bool Wavm::initialize(const std::string& code, absl::string_view name, bool allo rootResolver.moduleNameToInstanceMap().set("global", emscriptenInstance_->global); } WAVM::Runtime::LinkResult linkResult = linkModule(irModule_, rootResolver); - moduleInstance_ = instantiateModule(compartment_, module, std::move(linkResult.resolvedImports), + moduleInstance_ = instantiateModule(compartment_, module_, std::move(linkResult.resolvedImports), std::string(name)); memory_ = getDefaultMemory(moduleInstance_); - return true; } void Wavm::start(Context* context) { @@ -334,6 +330,21 @@ bool Wavm::setMemory(uint32_t pointer, uint32_t size, void* data) { } } +absl::string_view Wavm::getUserSection(absl::string_view name, bool* present) { + for (auto& section : irModule_.userSections) { + if (section.name == name) { + if (present) { + *present = true; + } + return {reinterpret_cast(section.data.data()), section.data.size()}; + } + } + if (present) { + *present = false; + } + return {}; +} + std::unique_ptr createWavm() { return std::make_unique(); } } // namespace Wavm diff --git a/test/extensions/wasm/config_test.cc b/test/extensions/wasm/config_test.cc index 20f604a7247c0..75eb36848fc10 100644 --- a/test/extensions/wasm/config_test.cc +++ b/test/extensions/wasm/config_test.cc @@ -84,7 +84,7 @@ TEST(WasmFactoryTest, CreateWasmFromWAT) { envoy::config::wasm::v2::WasmConfig config; config.mutable_vm_config()->set_vm("envoy.wasm.vm.wavm"); config.mutable_vm_config()->mutable_code()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/extensions/wasm/test_data/logging.wat")); + TestEnvironment::substitute("{{ test_rundir }}/test/extensions/wasm/test_data/wat.wat")); config.set_singleton(true); Upstream::MockClusterManager cluster_manager; Event::MockDispatcher dispatcher; diff --git a/test/extensions/wasm/test_data/wat.wat b/test/extensions/wasm/test_data/wat.wat new file mode 100644 index 0000000000000..8c82d54b7aa92 --- /dev/null +++ b/test/extensions/wasm/test_data/wat.wat @@ -0,0 +1,16 @@ +(module + (type $0 (func (param i32 i32 i32))) + (type $1 (func)) + (import "env" "_proxy_log" (func $_proxy_log (param i32 i32 i32))) + (export "memory" (memory $2)) + (export "main" (func $main)) + (memory $2 17) + (data $2 (i32.const 1048576) "Hello, world!") + + (func $main (type $1) + i32.const 1 + i32.const 1048576 + i32.const 13 + call $_proxy_log + ) + ) diff --git a/test/extensions/wasm/wasm_test.cc b/test/extensions/wasm/wasm_test.cc index d9ab163599f17..1df295016b033 100644 --- a/test/extensions/wasm/wasm_test.cc +++ b/test/extensions/wasm/wasm_test.cc @@ -114,6 +114,28 @@ TEST(WasmTest, DivByZero) { "wavm.integerDivideByZeroOrOverflow.*"); } +TEST(WasmTest, EmscriptenVersion) { + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::SimulatedTimeSystem time_system; + Event::DispatcherImpl dispatcher(time_system, *api); + auto wasm = std::make_shared("envoy.wasm.vm.wavm", "", "", + cluster_manager, dispatcher); + EXPECT_NE(wasm, nullptr); + const auto code = TestEnvironment::readFileToStringForTest( + TestEnvironment::substitute("{{ test_rundir }}/test/extensions/wasm/test_data/segv.wasm")); + EXPECT_FALSE(code.empty()); + auto context = std::make_unique(wasm.get()); + EXPECT_TRUE(wasm->initialize(code, "", false)); + uint32_t major = 9, minor = 9, abi_major = 9, abi_minor = 9; + EXPECT_TRUE(wasm->getEmscriptenVersion(&major, &minor, &abi_major, &abi_minor)); + EXPECT_EQ(major, 0); + EXPECT_EQ(minor, 0); + EXPECT_EQ(abi_major, 0); + EXPECT_EQ(abi_minor, 1); +} + } // namespace Wasm } // namespace Extensions } // namespace Envoy