Add WASI ABI compatibility check for multi-module (#913)
Refer to https://github.com/WebAssembly/WASI/blob/main/design/application-abi.md to check the WASI ABI compatibility: - Command (main module) may export _start function with signature "()" - Reactor (sub module) may export _initialize function with signature "()" - _start and _initialize can not be exported at the same time - Reactor cannot export _start function - Command and Reactor must export memory And - Rename module->is_wasi_module to module->import_wasi_api - Refactor wasm_loader_find_export() - Remove MULTI_MODULE related codes from mini_loader - Update multi-module samples - Fix a "use-after-free" issue. Since we reuse the memory instance of sub module, just to protect it from freeing an imported memory instance
This commit is contained in:
@ -403,7 +403,7 @@ struct WASMModule {
|
||||
|
||||
#if WASM_ENABLE_LIBC_WASI != 0
|
||||
WASIArguments wasi_args;
|
||||
bool is_wasi_module;
|
||||
bool import_wasi_api;
|
||||
#endif
|
||||
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
|
||||
@ -647,11 +647,11 @@ adjust_table_max_size(uint32 init_size, uint32 max_size_flag, uint32 *max_size)
|
||||
static WASMExport *
|
||||
wasm_loader_find_export(const WASMModule *module, const char *module_name,
|
||||
const char *field_name, uint8 export_kind,
|
||||
uint32 export_index_boundary, char *error_buf,
|
||||
uint32 error_buf_size)
|
||||
char *error_buf, uint32 error_buf_size)
|
||||
{
|
||||
WASMExport *export;
|
||||
uint32 i;
|
||||
uint32 export_index_boundary = 0;
|
||||
|
||||
for (i = 0, export = module->exports; i < module->export_count;
|
||||
++i, ++export) {
|
||||
@ -674,6 +674,27 @@ wasm_loader_find_export(const WASMModule *module, const char *module_name,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (export_kind) {
|
||||
case EXPORT_KIND_FUNC:
|
||||
export_index_boundary =
|
||||
module->import_function_count + module->function_count;
|
||||
break;
|
||||
case EXPORT_KIND_GLOBAL:
|
||||
export_index_boundary =
|
||||
module->import_global_count + module->global_count;
|
||||
break;
|
||||
case EXPORT_KIND_MEMORY:
|
||||
export_index_boundary =
|
||||
module->import_memory_count + module->memory_count;
|
||||
break;
|
||||
case EXPORT_KIND_TABLE:
|
||||
export_index_boundary =
|
||||
module->import_table_count + module->table_count;
|
||||
break;
|
||||
default:
|
||||
bh_assert(0);
|
||||
}
|
||||
|
||||
if (export->index >= export_index_boundary) {
|
||||
LOG_DEBUG("%s in the module %s is out of index (%d >= %d )", field_name,
|
||||
module_name, export->index, export_index_boundary);
|
||||
@ -704,10 +725,9 @@ wasm_loader_resolve_function(const char *module_name, const char *function_name,
|
||||
}
|
||||
|
||||
module = (WASMModule *)module_reg;
|
||||
export = wasm_loader_find_export(
|
||||
module, module_name, function_name, EXPORT_KIND_FUNC,
|
||||
module->import_function_count + module->function_count, error_buf,
|
||||
error_buf_size);
|
||||
export =
|
||||
wasm_loader_find_export(module, module_name, function_name,
|
||||
EXPORT_KIND_FUNC, error_buf, error_buf_size);
|
||||
if (!export) {
|
||||
return NULL;
|
||||
}
|
||||
@ -755,10 +775,9 @@ wasm_loader_resolve_table(const char *module_name, const char *table_name,
|
||||
}
|
||||
|
||||
module = (WASMModule *)module_reg;
|
||||
export = wasm_loader_find_export(
|
||||
module, module_name, table_name, EXPORT_KIND_TABLE,
|
||||
module->table_count + module->import_table_count, error_buf,
|
||||
error_buf_size);
|
||||
export =
|
||||
wasm_loader_find_export(module, module_name, table_name,
|
||||
EXPORT_KIND_TABLE, error_buf, error_buf_size);
|
||||
if (!export) {
|
||||
return NULL;
|
||||
}
|
||||
@ -800,10 +819,9 @@ wasm_loader_resolve_memory(const char *module_name, const char *memory_name,
|
||||
}
|
||||
|
||||
module = (WASMModule *)module_reg;
|
||||
export = wasm_loader_find_export(
|
||||
module, module_name, memory_name, EXPORT_KIND_MEMORY,
|
||||
module->import_memory_count + module->memory_count, error_buf,
|
||||
error_buf_size);
|
||||
export =
|
||||
wasm_loader_find_export(module, module_name, memory_name,
|
||||
EXPORT_KIND_MEMORY, error_buf, error_buf_size);
|
||||
if (!export) {
|
||||
return NULL;
|
||||
}
|
||||
@ -846,10 +864,9 @@ wasm_loader_resolve_global(const char *module_name, const char *global_name,
|
||||
}
|
||||
|
||||
module = (WASMModule *)module_reg;
|
||||
export = wasm_loader_find_export(
|
||||
module, module_name, global_name, EXPORT_KIND_GLOBAL,
|
||||
module->import_global_count + module->global_count, error_buf,
|
||||
error_buf_size);
|
||||
export =
|
||||
wasm_loader_find_export(module, module_name, global_name,
|
||||
EXPORT_KIND_GLOBAL, error_buf, error_buf_size);
|
||||
if (!export) {
|
||||
return NULL;
|
||||
}
|
||||
@ -969,7 +986,7 @@ load_depended_module(const WASMModule *parent_module,
|
||||
}
|
||||
|
||||
sub_module =
|
||||
wasm_loader_load(buffer, buffer_size, error_buf, error_buf_size);
|
||||
wasm_loader_load(buffer, buffer_size, false, error_buf, error_buf_size);
|
||||
if (!sub_module) {
|
||||
LOG_DEBUG("error: can not load the sub_module %s", sub_module_name);
|
||||
/* others will be destroyed in runtime_destroy() */
|
||||
@ -1736,7 +1753,7 @@ load_import_section(const uint8 *buf, const uint8 *buf_end, WASMModule *module,
|
||||
if (!strcmp(import->u.names.module_name, "wasi_unstable")
|
||||
|| !strcmp(import->u.names.module_name,
|
||||
"wasi_snapshot_preview1")) {
|
||||
module->is_wasi_module = true;
|
||||
module->import_wasi_api = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -3440,9 +3457,129 @@ fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
#if (WASM_ENABLE_MULTI_MODULE != 0) && (WASM_ENABLE_LIBC_WASI != 0)
|
||||
/*
|
||||
* refer to
|
||||
* https://github.com/WebAssembly/WASI/blob/main/design/application-abi.md
|
||||
*/
|
||||
static bool
|
||||
check_wasi_abi_compatibility(const WASMModule *module, bool main_module,
|
||||
char *error_buf, uint32 error_buf_size)
|
||||
{
|
||||
/*
|
||||
* need to handle:
|
||||
* - non-wasi compatiable modules
|
||||
* - a fake wasi compatiable module
|
||||
* - a command acts as a main_module
|
||||
* - a command acts as a sub_module
|
||||
* - a reactor acts as a main_module
|
||||
* - a reactor acts as a sub_module
|
||||
*
|
||||
* be careful with:
|
||||
* wasi compatiable modules(command/reactor) which don't import any wasi
|
||||
* APIs. usually, a command has to import a "prox_exit" at least. but a
|
||||
* reactor can depend on nothing. At the same time, each has its own entry
|
||||
* point.
|
||||
*
|
||||
* observations:
|
||||
* - clang always injects `_start` into a command
|
||||
* - clang always injects `_initialize` into a reactor
|
||||
* - `iwasm -f` allows to run a function in the reactor
|
||||
*
|
||||
* strong assumptions:
|
||||
* - no one will define either `_start` or `_initialize` on purpose
|
||||
* - `_start` should always be `void _start(void)`
|
||||
* - `_initialize` should always be `void _initialize(void)`
|
||||
*/
|
||||
|
||||
WASMExport *initialize = NULL, *memory = NULL, *start = NULL;
|
||||
|
||||
/* (func (export "_start") (...) */
|
||||
start = wasm_loader_find_export(module, "", "_start", EXPORT_KIND_FUNC,
|
||||
error_buf, error_buf_size);
|
||||
if (start) {
|
||||
WASMType *func_type =
|
||||
module->functions[start->index - module->import_function_count]
|
||||
->func_type;
|
||||
if (func_type->param_count || func_type->result_count) {
|
||||
set_error_buf(error_buf, error_buf_size,
|
||||
"The builtin _start() is with a wrong signature");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* (func (export "_initialize") (...) */
|
||||
initialize = wasm_loader_find_export(
|
||||
module, "", "_initialize", EXPORT_KIND_FUNC, error_buf, error_buf_size);
|
||||
if (initialize) {
|
||||
WASMType *func_type =
|
||||
module->functions[initialize->index - module->import_function_count]
|
||||
->func_type;
|
||||
if (func_type->param_count || func_type->result_count) {
|
||||
set_error_buf(
|
||||
error_buf, error_buf_size,
|
||||
"The builtin _initiazlie() is with a wrong signature");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* filter out non-wasi compatiable modules */
|
||||
if (!module->import_wasi_api && !start && !initialize) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* should have one at least */
|
||||
if (module->import_wasi_api && !start && !initialize) {
|
||||
set_error_buf(
|
||||
error_buf, error_buf_size,
|
||||
"A module with WASI apis should be either a command or a reactor");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* there is at least one of `_start` and `_initialize` in below cases.
|
||||
* according to the assumption, they should be all wasi compatiable
|
||||
*/
|
||||
|
||||
/* always can not have both at the same time */
|
||||
if (start && initialize) {
|
||||
set_error_buf(
|
||||
error_buf, error_buf_size,
|
||||
"Neither a command nor a reactor can have both at the same time");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* filter out commands (with `_start`) cases */
|
||||
if (start && !main_module) {
|
||||
set_error_buf(error_buf, error_buf_size,
|
||||
"A command(with _start) can not be a sud-module");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* it is ok a reactor acts as a main module,
|
||||
* so skip the check about (with `_initialize`)
|
||||
*/
|
||||
|
||||
memory = wasm_loader_find_export(module, "", "memory", EXPORT_KIND_MEMORY,
|
||||
error_buf, error_buf_size);
|
||||
if (!memory) {
|
||||
set_error_buf(
|
||||
error_buf, error_buf_size,
|
||||
"A module with WASI apis should export memory by default");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
WASMModule *
|
||||
wasm_loader_load(const uint8 *buf, uint32 size, char *error_buf,
|
||||
uint32 error_buf_size)
|
||||
wasm_loader_load(const uint8 *buf, uint32 size,
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
bool main_module,
|
||||
#endif
|
||||
char *error_buf, uint32 error_buf_size)
|
||||
{
|
||||
WASMModule *module = create_module(error_buf, error_buf_size);
|
||||
if (!module) {
|
||||
@ -3458,6 +3595,14 @@ wasm_loader_load(const uint8 *buf, uint32 size, char *error_buf,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if (WASM_ENABLE_MULTI_MODULE != 0) && (WASM_ENABLE_LIBC_WASI != 0)
|
||||
/* do a check about WASI Application ABI */
|
||||
if (!check_wasi_abi_compatibility(module, main_module, error_buf,
|
||||
error_buf_size)) {
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_VERBOSE("Load module success.\n");
|
||||
return module;
|
||||
|
||||
|
||||
@ -24,8 +24,11 @@ extern "C" {
|
||||
* @return return module loaded, NULL if failed
|
||||
*/
|
||||
WASMModule *
|
||||
wasm_loader_load(const uint8 *buf, uint32 size, char *error_buf,
|
||||
uint32 error_buf_size);
|
||||
wasm_loader_load(const uint8 *buf, uint32 size,
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
bool main_module,
|
||||
#endif
|
||||
char *error_buf, uint32 error_buf_size);
|
||||
|
||||
/**
|
||||
* Load a WASM module from a specified WASM section list.
|
||||
|
||||
@ -851,7 +851,7 @@ load_import_section(const uint8 *buf, const uint8 *buf_end, WASMModule *module,
|
||||
if (!strcmp(import->u.names.module_name, "wasi_unstable")
|
||||
|| !strcmp(import->u.names.module_name,
|
||||
"wasi_snapshot_preview1")) {
|
||||
module->is_wasi_module = true;
|
||||
module->import_wasi_api = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2120,7 +2120,6 @@ load_from_sections(WASMModule *module, WASMSection *sections,
|
||||
}
|
||||
}
|
||||
|
||||
#if WASM_ENABLE_MULTI_MODULE == 0
|
||||
if (module->import_memory_count) {
|
||||
memory_import = &module->import_memories[0].u.memory;
|
||||
/* Memory init page count cannot be larger than 65536, we don't
|
||||
@ -2128,6 +2127,7 @@ load_from_sections(WASMModule *module, WASMSection *sections,
|
||||
memory_import->num_bytes_per_page *= memory_import->init_page_count;
|
||||
memory_import->init_page_count = memory_import->max_page_count = 1;
|
||||
}
|
||||
|
||||
if (module->memory_count) {
|
||||
/* Memory init page count cannot be larger than 65536, we don't
|
||||
check integer overflow again. */
|
||||
@ -2135,7 +2135,6 @@ load_from_sections(WASMModule *module, WASMSection *sections,
|
||||
memory->num_bytes_per_page *= memory->init_page_count;
|
||||
memory->init_page_count = memory->max_page_count = 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WASM_ENABLE_MEMORY_TRACING != 0
|
||||
@ -2159,9 +2158,6 @@ create_module(char *error_buf, uint32 error_buf_size)
|
||||
/* Set start_function to -1, means no start function */
|
||||
module->start_function = (uint32)-1;
|
||||
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
module->import_module_list = &module->import_module_list_head;
|
||||
#endif
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,11 @@ set_error_buf_v(char *error_buf, uint32 error_buf_size, const char *format, ...)
|
||||
WASMModule *
|
||||
wasm_load(const uint8 *buf, uint32 size, char *error_buf, uint32 error_buf_size)
|
||||
{
|
||||
return wasm_loader_load(buf, size, error_buf, error_buf_size);
|
||||
return wasm_loader_load(buf, size,
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
true,
|
||||
#endif
|
||||
error_buf, error_buf_size);
|
||||
}
|
||||
|
||||
WASMModule *
|
||||
@ -105,7 +109,7 @@ memories_deinstantiate(WASMModuleInstance *module_inst,
|
||||
for (i = 0; i < count; i++) {
|
||||
if (memories[i]) {
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
if (memories[i]->owner != module_inst)
|
||||
if (i < module_inst->module->import_memory_count)
|
||||
continue;
|
||||
#endif
|
||||
#if WASM_ENABLE_SHARED_MEMORY != 0
|
||||
@ -384,13 +388,6 @@ memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst,
|
||||
memories_deinstantiate(module_inst, memories, memory_count);
|
||||
return NULL;
|
||||
}
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
/* The module of the import memory is a builtin module, and
|
||||
the memory is created by current module, set its owner
|
||||
to current module, so the memory can be destroyed in
|
||||
memories_deinstantiate. */
|
||||
memory->owner = module_inst;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -404,9 +401,6 @@ memories_instantiate(const WASMModule *module, WASMModuleInstance *module_inst,
|
||||
memories_deinstantiate(module_inst, memories, memory_count);
|
||||
return NULL;
|
||||
}
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
memory->owner = module_inst;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mem_index == 0) {
|
||||
@ -1007,15 +1001,17 @@ sub_module_instantiate(WASMModule *module, WASMModuleInstance *module_inst,
|
||||
bh_list_first_elem(module->import_module_list);
|
||||
|
||||
while (sub_module_list_node) {
|
||||
WASMSubModInstNode *sub_module_inst_list_node;
|
||||
WASMSubModInstNode *sub_module_inst_list_node = NULL;
|
||||
WASMModule *sub_module = (WASMModule *)sub_module_list_node->module;
|
||||
WASMModuleInstance *sub_module_inst =
|
||||
WASMModuleInstance *sub_module_inst = NULL;
|
||||
|
||||
sub_module_inst =
|
||||
wasm_instantiate(sub_module, false, stack_size, heap_size,
|
||||
error_buf, error_buf_size);
|
||||
if (!sub_module_inst) {
|
||||
LOG_DEBUG("instantiate %s failed",
|
||||
sub_module_list_node->module_name);
|
||||
return false;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
sub_module_inst_list_node = runtime_malloc(sizeof(WASMSubModInstNode),
|
||||
@ -1023,8 +1019,7 @@ sub_module_instantiate(WASMModule *module, WASMModuleInstance *module_inst,
|
||||
if (!sub_module_inst_list_node) {
|
||||
LOG_DEBUG("Malloc WASMSubModInstNode failed, SZ:%d",
|
||||
sizeof(WASMSubModInstNode));
|
||||
wasm_deinstantiate(sub_module_inst, false);
|
||||
return false;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
sub_module_inst_list_node->module_inst = sub_module_inst;
|
||||
@ -1036,6 +1031,39 @@ sub_module_instantiate(WASMModule *module, WASMModuleInstance *module_inst,
|
||||
(void)ret;
|
||||
|
||||
sub_module_list_node = bh_list_elem_next(sub_module_list_node);
|
||||
|
||||
#if WASM_ENABLE_LIBC_WASI != 0
|
||||
{
|
||||
/*
|
||||
* reactor instances may assume that _initialize will be called by
|
||||
* the environment at most once, and that none of their other
|
||||
* exports are accessed before that call.
|
||||
*
|
||||
* let the loader decide how to act if there is no _initialize
|
||||
* in a reactor
|
||||
*/
|
||||
WASMFunctionInstance *initialize =
|
||||
wasm_lookup_function(sub_module_inst, "_initialize", NULL);
|
||||
if (initialize
|
||||
&& !wasm_create_exec_env_and_call_function(
|
||||
sub_module_inst, initialize, 0, NULL, false)) {
|
||||
set_error_buf(error_buf, error_buf_size,
|
||||
"Call _initialize failed ");
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
continue;
|
||||
failed:
|
||||
if (sub_module_inst_list_node) {
|
||||
bh_list_remove(sub_module_inst_list, sub_module_inst_list_node);
|
||||
wasm_runtime_free(sub_module_inst_list_node);
|
||||
}
|
||||
|
||||
if (sub_module_inst)
|
||||
wasm_deinstantiate(sub_module_inst, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1518,7 +1546,7 @@ wasm_instantiate(WASMModule *module, bool is_sub_inst, uint32 stack_size,
|
||||
|
||||
#if WASM_ENABLE_BULK_MEMORY != 0
|
||||
#if WASM_ENABLE_LIBC_WASI != 0
|
||||
if (!module->is_wasi_module) {
|
||||
if (!module->import_wasi_api) {
|
||||
#endif
|
||||
/* Only execute the memory init function for main instance because
|
||||
the data segments will be dropped once initialized.
|
||||
|
||||
@ -40,11 +40,6 @@ struct WASMMemoryInstance {
|
||||
/* The heap created */
|
||||
void *heap_handle;
|
||||
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
/* to indicate which module instance create it */
|
||||
WASMModuleInstance *owner;
|
||||
#endif
|
||||
|
||||
#if WASM_ENABLE_SHARED_MEMORY != 0
|
||||
/* mutex lock for the memory, used in atomic operation */
|
||||
korp_mutex mem_lock;
|
||||
|
||||
Reference in New Issue
Block a user