JulietteCarter opened Issue #2547:
I am having issues understanding how to access data stored in the different parts of a wasm module's memory (data, stack and heap), from a runtime in C (using libwasmtime.a).
I'm trying to pass a C string literal pointer from a WebAssembly program to an imported function implemented in C in the wasm runtime. Adding the 'base memory pointer' to the guest pointer address doesn't yield a valid host address when strings are stored on the stack or in the heap. It only works for global data. I don't know what I'm doing wrong. How do I compute the host address for all global, stack and heap memory addresses?
I have constructed the following reproducible test case to illustrate the problem.
I have produced a small c++ example, which creates a static string (global data)globalStr
, a string allocated on the stackstackStr
, and string allocated on the heapheapStr
.For each of those 3 strings, I print the value of the string and its memory address. I then call the custom import
printHostAddress
(which I implement in the runtime - see later), which tries to access the memory at the address passed from the wasm module to the runtime.Produced from wasmtime main branch, commit hash
128c3bd74
.
Using clang++ version 10.0.0.Example below, compiled to wasm using the wasi-sdk.
/* example.cpp Compile to wasm: clang++ --target=wasm32-wasi -Wl,--allow-undefined -o example.wasm example.cpp */ #include <stdio.h> #include <string.h> extern "C" void printHostAddress(const char* str); // custom import int main() { // Global data static const char* globalStr = "Global data"; printf("Global data str: %s, ptr: %p\n", globalStr, (void *)globalStr); // call wasi imports printHostAddress(globalStr); // call custom import // Stack allocation static const char strLiteral[] = "Stack data"; char stackStr[sizeof(strLiteral)]; strcpy(stackStr, strLiteral); printf("Stack str: %s, ptr: %p\n", stackStr, (void *)stackStr); // call wasi imports printHostAddress(stackStr); // call custom import // Heap allocation static const char strLiteral2[] = "Heap data"; char* heapStr = new char[sizeof(strLiteral2)]; strcpy(heapStr, strLiteral2); printf("Heap str: %s, ptr: %p\n", heapStr, (void *)heapStr); // call wasi imports printHostAddress(heapStr); // call custom import return 0; }
I've provided the cpp implementation for the runtime below. Most of it is boilerplate code, the bit of interest is the implementation of the
printHostAddress
import function, given to the example.wasm module (seeprintHostAddress_callback
)./* runtime.cpp To build, from wasmtime repo: 1. Build libwasmtime.a: cargo build --release -p wasmtime-c-api 2. Build runtime.cpp clang++ target/release/libwasmtime.a \ -I./crates/c-api/include/ \ -I./crates/c-api/wasm-c-api/include \ -o runtime runtime.cpp */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <wasm.h> #include <wasi.h> #include <wasmtime.h> static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) { fprintf(stderr, "error: %s\n", message); wasm_byte_vec_t error_message; if (error != NULL) { wasmtime_error_message(error, &error_message); wasmtime_error_delete(error); } else { wasm_trap_message(trap, &error_message); wasm_trap_delete(trap); } fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data); wasm_byte_vec_delete(&error_message); exit(1); } // Make memory a global to access it in the printHostAddress_callback function wasm_memory_t* g_memory; wasm_memory_t* get_export_memory(const wasm_extern_vec_t* exports, size_t i) { if (exports->size <= i || !wasm_extern_as_memory(exports->data[i])) { printf("> Error accessing memory export %zu!\n", i); exit(1); } return wasm_extern_as_memory(exports->data[i]); } /* LOOK HERE! */ static wasm_trap_t* printHostAddress_callback(const wasm_val_t args[], wasm_val_t results[]) { printf("> Print Host Address Callback: \n"); auto base_memory = wasm_memory_data(g_memory); printf("> Base memory: %p\n",base_memory); const char* str = reinterpret_cast<const char* >(args[0].of.i32 + base_memory); printf("> Received ptr: %p\n", (void *)str); printf("> String value: %s\n\n", str); return NULL; } ///////////// Boilerplate code starts here! int main(int argc, char *argv[]) { const char* wasm_module_file = argv[1]; // Set up our context wasm_engine_t* engine = wasm_engine_new(); assert(engine != NULL); wasm_store_t* store = wasm_store_new(engine); assert(store != NULL); wasm_byte_vec_t wasm; // Load our input file to parse it next FILE* file = fopen(wasm_module_file, "rb"); if (!file) { printf("> Error loading file %s!\n", wasm_module_file); exit(1); } fseek(file, 0L, SEEK_END); size_t file_size = ftell(file); wasm_byte_vec_new_uninitialized(&wasm, file_size); fseek(file, 0L, SEEK_SET); if (fread(wasm.data, file_size, 1, file) != 1) { printf("> Error loading module!\n"); exit(1); } fclose(file); // Compile our modules wasm_module_t* module = NULL; wasmtime_error_t* error = wasmtime_module_new(engine, &wasm, &module); if (!module) exit_with_error("failed to compile module", error, NULL); wasm_byte_vec_delete(&wasm); // Instantiate wasi wasi_config_t *wasi_config = wasi_config_new(); assert(wasi_config); wasi_config_inherit_argv(wasi_config); wasi_config_inherit_env(wasi_config); wasi_config_inherit_stdin(wasi_config); wasi_config_inherit_stdout(wasi_config); wasi_config_inherit_stderr(wasi_config); wasm_trap_t *trap = NULL; wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap); if (wasi == NULL) exit_with_error("failed to instantiate WASI", NULL, trap); // Create the linker wasmtime_linker_t* linker = wasmtime_linker_new(store); // Add wasi to the linker error = wasmtime_linker_define_wasi(linker, wasi); if (error != NULL) exit_with_error("failed to link wasi", error, NULL); // Define the printHostAddress import and register it with the linker wasm_functype_t* printHostAddress_callback_ty = wasm_functype_new_1_0( wasm_valtype_new_i32() ); wasm_extern_t* funcAsExt = wasm_func_as_extern( wasm_func_new(store, printHostAddress_callback_ty, printHostAddress_callback) ); wasm_name_t mod_name; wasm_name_new(&mod_name, strlen("env"), "env"); wasm_name_t func_name; wasm_name_new(&func_name, strlen("printHostAddress"), "printHostAddress"); error = wasmtime_linker_define(linker, &mod_name, &func_name, funcAsExt); if (error != NULL) { printf("Failed to register %s with wasmtime linker\n", "printHostAddress"); exit(1); }; wasm_name_t empty; wasm_name_new_from_string(&empty, ""); error = wasmtime_linker_module(linker, &empty, module); if (error != NULL) exit_with_error("failed to instantiate module", error, NULL); wasm_instance_t* wasm_instance; error = wasmtime_linker_instantiate(linker, module, &wasm_instance, &trap); if (error != NULL || trap != NULL) { printf("Failed to instantiate \n"); exit(1); } wasm_extern_vec_t exports; wasm_instance_exports(wasm_instance, &exports); size_t i = 0; g_memory = get_export_memory(&exports, i++); // Run it. wasm_func_t* func; wasmtime_linker_get_default(linker, &empty, &func); if (error != NULL) exit_with_error("failed to locate default export for module", error, NULL); error = wasmtime_func_call(func, NULL, 0, NULL, 0, &trap); if (error != NULL) exit_with_error("error calling default export", error, trap); // Clean up after ourselves at this point wasm_module_delete(module); wasm_store_delete(store); wasm_engine_delete(engine); return 0; }
The implementation of the
printHostAddress_callback
import function prints the base address of the wasm module's memory (for reference), the received pointer and the string accessed at that address.Compile the runtime, using libwasmatime.a:
$ cargo build --release -p wasmtime-c-api $ clang++ target/release/libwasmtime.a \ -I./crates/c-api/include/ \ -I./crates/c-api/wasm-c-api/include \ -o runtime runtime.cpp
Run the example runtime with the previously compiled wasm module:
$ ./runtime example.wasm
This is the output I get:
Global data str: Global data, ptr: 0x400 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d919400 > String value: Global data Stack str: Stack data, ptr: 0x114b1 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d92a4b1 > String value: Heap str: Heap data, ptr: 0x20010 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d939010 [1] 7734 bus error ./runtime example.wasm
The global data string has an expected memory offset of 0x400, and the string literal is retrieved succesfully.
The issue comes for the strings allocated on the stack and the heap. For the one allocated on the stack, the address is valid, but is clearly not the right one, as there is nothing at that address!
As for the one allocated on the he
[message truncated]
cfallin commented on Issue #2547:
Hypothesis: is the memory growing, and as a result, the pointer invalidated? (I don't know anything about the C API so I'm not sure why or how the lifetimes could cause this; @sunfishcode appears to mention a related lifetime issue over in WebAssembly/c-api#105.)
The reason I suspect this is that the second and third cases, where the data is not as expected, have pointers just above the first Wasm page (64K bytes, or 0x10000).
A way to check whether this is the case would be to call your host function (again) with the global pointer at the end of your Wasm module's
main()
. If it works at first but not the second time, then that means the memory storage will have been moved to a larger buffer.
cfallin edited a comment on Issue #2547:
Hypothesis: is the memory growing, and as a result, the pointer invalidated? (I don't know anything about the C API so I'm not sure why or how the lifetimes could cause this; @sunfishcode appears to mention a related lifetime issue over in WebAssembly/wasm-c-api#105.)
The reason I suspect this is that the second and third cases, where the data is not as expected, have pointers just above the first Wasm page (64K bytes, or 0x10000).
A way to check whether this is the case would be to call your host function (again) with the global pointer at the end of your Wasm module's
main()
. If it works at first but not the second time, then that means the memory storage will have been moved to a larger buffer.
peterhuene commented on Issue #2547:
Provided the return value of
wasm_memory_data
isn't reused between either execution of Wasm code or calls to explicitly grow the memory then data pointer shouldn't be invalidated.
peterhuene edited a comment on Issue #2547:
Provided the return value of
wasm_memory_data
isn't reused between either execution of Wasm code or calls to explicitly grow the memory then the data pointer shouldn't be invalidated.
peterhuene edited a comment on Issue #2547:
Provided the return value of
wasm_memory_data
isn't reused between either execution of Wasm code or calls to explicitly grow the memory then the data pointer shouldn't be invalidated.That doesn't seem to be the case here, so I have no hypothesis to offer without some debugging.
alexcrichton commented on Issue #2547:
I believe the issue here is in the usage of the
wasmtime_linker_t
type and the interaction betweenwasmtime_linker_get_default
andwasmtime_linker_instantiate
. The module you're defining in the linker is a "command" module which means that every time the function is called through the linker it will re-instantiate the module. This means that the instance returned fromwasmtime_linker_instantiate
is not the same instance as the one executing code fromwasmtime_linker_get_default
If you switch
wasmtime_linker_get_default
to instead execute the_start
export, or if you usewasmtime_caller_t
to extract the memory, I believe the example should work.
peterhuene commented on Issue #2547:
Ah good catch; that makes sense why the memory wasn't grown as it's the wrong memory being accessed.
JulietteCarter commented on Issue #2547:
Thank you very much indeed for your speedy replies!
I used the exportedstart
function instead ofwasmtime_linker_get_default
and it worked as expected! :DGlobal data str: Global data, ptr: 0x400 > Print Host Address Callback: > Base memory: 0x10ccb4000 > Received ptr: 0x10ccb4400 > String value: Global data Stack str: Stack data, ptr: 0x114b1 > Print Host Address Callback: > Base memory: 0x10ccb4000 > Received ptr: 0x10ccc54b1 > String value: Stack data Heap str: Heap data, ptr: 0x20010 > Print Host Address Callback: > Base memory: 0x10ccb4000 > Received ptr: 0x10ccd4010 > String value: Heap data
Is there any further documentation about "command mode" ?
Thanks again for all your quick responses, the wasmtime project is really fantastic and the community is doing great work!
alexcrichton commented on Issue #2547:
There's some more information here -- https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi and a brief description on
Linker::module
too. I'm gonna go ahead and close this because I think it's fixed, but feel free to ask any follow-ups here of course!
alexcrichton closed Issue #2547:
I am having issues understanding how to access data stored in the different parts of a wasm module's memory (data, stack and heap), from a runtime in C (using libwasmtime.a).
I'm trying to pass a C string literal pointer from a WebAssembly program to an imported function implemented in C in the wasm runtime. Adding the 'base memory pointer' to the guest pointer address doesn't yield a valid host address when strings are stored on the stack or in the heap. It only works for global data. I don't know what I'm doing wrong. How do I compute the host address for all global, stack and heap memory addresses?
I have constructed the following reproducible test case to illustrate the problem.
I have produced a small c++ example, which creates a static string (global data)globalStr
, a string allocated on the stackstackStr
, and string allocated on the heapheapStr
.For each of those 3 strings, I print the value of the string and its memory address. I then call the custom import
printHostAddress
(which I implement in the runtime - see later), which tries to access the memory at the address passed from the wasm module to the runtime.Produced from wasmtime main branch, commit hash
128c3bd74
.
Using clang++ version 10.0.0.Example below, compiled to wasm using the wasi-sdk.
/* example.cpp Compile to wasm: clang++ --target=wasm32-wasi -Wl,--allow-undefined -o example.wasm example.cpp */ #include <stdio.h> #include <string.h> extern "C" void printHostAddress(const char* str); // custom import int main() { // Global data static const char* globalStr = "Global data"; printf("Global data str: %s, ptr: %p\n", globalStr, (void *)globalStr); // call wasi imports printHostAddress(globalStr); // call custom import // Stack allocation static const char strLiteral[] = "Stack data"; char stackStr[sizeof(strLiteral)]; strcpy(stackStr, strLiteral); printf("Stack str: %s, ptr: %p\n", stackStr, (void *)stackStr); // call wasi imports printHostAddress(stackStr); // call custom import // Heap allocation static const char strLiteral2[] = "Heap data"; char* heapStr = new char[sizeof(strLiteral2)]; strcpy(heapStr, strLiteral2); printf("Heap str: %s, ptr: %p\n", heapStr, (void *)heapStr); // call wasi imports printHostAddress(heapStr); // call custom import return 0; }
I've provided the cpp implementation for the runtime below. Most of it is boilerplate code, the bit of interest is the implementation of the
printHostAddress
import function, given to the example.wasm module (seeprintHostAddress_callback
)./* runtime.cpp To build, from wasmtime repo: 1. Build libwasmtime.a: cargo build --release -p wasmtime-c-api 2. Build runtime.cpp clang++ target/release/libwasmtime.a \ -I./crates/c-api/include/ \ -I./crates/c-api/wasm-c-api/include \ -o runtime runtime.cpp */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <wasm.h> #include <wasi.h> #include <wasmtime.h> static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) { fprintf(stderr, "error: %s\n", message); wasm_byte_vec_t error_message; if (error != NULL) { wasmtime_error_message(error, &error_message); wasmtime_error_delete(error); } else { wasm_trap_message(trap, &error_message); wasm_trap_delete(trap); } fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data); wasm_byte_vec_delete(&error_message); exit(1); } // Make memory a global to access it in the printHostAddress_callback function wasm_memory_t* g_memory; wasm_memory_t* get_export_memory(const wasm_extern_vec_t* exports, size_t i) { if (exports->size <= i || !wasm_extern_as_memory(exports->data[i])) { printf("> Error accessing memory export %zu!\n", i); exit(1); } return wasm_extern_as_memory(exports->data[i]); } /* LOOK HERE! */ static wasm_trap_t* printHostAddress_callback(const wasm_val_t args[], wasm_val_t results[]) { printf("> Print Host Address Callback: \n"); auto base_memory = wasm_memory_data(g_memory); printf("> Base memory: %p\n",base_memory); const char* str = reinterpret_cast<const char* >(args[0].of.i32 + base_memory); printf("> Received ptr: %p\n", (void *)str); printf("> String value: %s\n\n", str); return NULL; } ///////////// Boilerplate code starts here! int main(int argc, char *argv[]) { const char* wasm_module_file = argv[1]; // Set up our context wasm_engine_t* engine = wasm_engine_new(); assert(engine != NULL); wasm_store_t* store = wasm_store_new(engine); assert(store != NULL); wasm_byte_vec_t wasm; // Load our input file to parse it next FILE* file = fopen(wasm_module_file, "rb"); if (!file) { printf("> Error loading file %s!\n", wasm_module_file); exit(1); } fseek(file, 0L, SEEK_END); size_t file_size = ftell(file); wasm_byte_vec_new_uninitialized(&wasm, file_size); fseek(file, 0L, SEEK_SET); if (fread(wasm.data, file_size, 1, file) != 1) { printf("> Error loading module!\n"); exit(1); } fclose(file); // Compile our modules wasm_module_t* module = NULL; wasmtime_error_t* error = wasmtime_module_new(engine, &wasm, &module); if (!module) exit_with_error("failed to compile module", error, NULL); wasm_byte_vec_delete(&wasm); // Instantiate wasi wasi_config_t *wasi_config = wasi_config_new(); assert(wasi_config); wasi_config_inherit_argv(wasi_config); wasi_config_inherit_env(wasi_config); wasi_config_inherit_stdin(wasi_config); wasi_config_inherit_stdout(wasi_config); wasi_config_inherit_stderr(wasi_config); wasm_trap_t *trap = NULL; wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap); if (wasi == NULL) exit_with_error("failed to instantiate WASI", NULL, trap); // Create the linker wasmtime_linker_t* linker = wasmtime_linker_new(store); // Add wasi to the linker error = wasmtime_linker_define_wasi(linker, wasi); if (error != NULL) exit_with_error("failed to link wasi", error, NULL); // Define the printHostAddress import and register it with the linker wasm_functype_t* printHostAddress_callback_ty = wasm_functype_new_1_0( wasm_valtype_new_i32() ); wasm_extern_t* funcAsExt = wasm_func_as_extern( wasm_func_new(store, printHostAddress_callback_ty, printHostAddress_callback) ); wasm_name_t mod_name; wasm_name_new(&mod_name, strlen("env"), "env"); wasm_name_t func_name; wasm_name_new(&func_name, strlen("printHostAddress"), "printHostAddress"); error = wasmtime_linker_define(linker, &mod_name, &func_name, funcAsExt); if (error != NULL) { printf("Failed to register %s with wasmtime linker\n", "printHostAddress"); exit(1); }; wasm_name_t empty; wasm_name_new_from_string(&empty, ""); error = wasmtime_linker_module(linker, &empty, module); if (error != NULL) exit_with_error("failed to instantiate module", error, NULL); wasm_instance_t* wasm_instance; error = wasmtime_linker_instantiate(linker, module, &wasm_instance, &trap); if (error != NULL || trap != NULL) { printf("Failed to instantiate \n"); exit(1); } wasm_extern_vec_t exports; wasm_instance_exports(wasm_instance, &exports); size_t i = 0; g_memory = get_export_memory(&exports, i++); // Run it. wasm_func_t* func; wasmtime_linker_get_default(linker, &empty, &func); if (error != NULL) exit_with_error("failed to locate default export for module", error, NULL); error = wasmtime_func_call(func, NULL, 0, NULL, 0, &trap); if (error != NULL) exit_with_error("error calling default export", error, trap); // Clean up after ourselves at this point wasm_module_delete(module); wasm_store_delete(store); wasm_engine_delete(engine); return 0; }
The implementation of the
printHostAddress_callback
import function prints the base address of the wasm module's memory (for reference), the received pointer and the string accessed at that address.Compile the runtime, using libwasmatime.a:
$ cargo build --release -p wasmtime-c-api $ clang++ target/release/libwasmtime.a \ -I./crates/c-api/include/ \ -I./crates/c-api/wasm-c-api/include \ -o runtime runtime.cpp
Run the example runtime with the previously compiled wasm module:
$ ./runtime example.wasm
This is the output I get:
Global data str: Global data, ptr: 0x400 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d919400 > String value: Global data Stack str: Stack data, ptr: 0x114b1 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d92a4b1 > String value: Heap str: Heap data, ptr: 0x20010 > Print Host Address Callback: > Base memory: 0x10d919000 > Received ptr: 0x10d939010 [1] 7734 bus error ./runtime example.wasm
The global data string has an expected memory offset of 0x400, and the string literal is retrieved succesfully.
The issue comes for the strings allocated on the stack and the heap. For the one allocated on the stack, the address is valid, but is clearly not the right one, as there is nothing at that address!
As for the one allocated on the heap
[message truncated]
Last updated: Dec 23 2024 at 13:07 UTC