Stream: git-wasmtime

Topic: wasmtime / Issue #2547 Wasmtime host address calculation ...


view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 19:24):

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 stack stackStr, and string allocated on the heap heapStr.

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 (see printHostAddress_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]

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 20:04):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 20:05):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 20:48):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 20:48):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 20:49):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 21:38):

alexcrichton commented on Issue #2547:

I believe the issue here is in the usage of the wasmtime_linker_t type and the interaction between wasmtime_linker_get_default and wasmtime_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 from wasmtime_linker_instantiate is not the same instance as the one executing code from wasmtime_linker_get_default

If you switch wasmtime_linker_get_default to instead execute the _start export, or if you use wasmtime_caller_t to extract the memory, I believe the example should work.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2021 at 21:55):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 06 2021 at 11:17):

JulietteCarter commented on Issue #2547:

Thank you very much indeed for your speedy replies!
I used the exported start function instead of wasmtime_linker_get_default and it worked as expected! :D

Global 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!

view this post on Zulip Wasmtime GitHub notifications bot (Jan 06 2021 at 15:03):

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!

view this post on Zulip Wasmtime GitHub notifications bot (Jan 06 2021 at 15:03):

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 stack stackStr, and string allocated on the heap heapStr.

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 (see printHostAddress_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