Skip to content

Latest commit

 

History

History
203 lines (154 loc) · 5.71 KB

export_native_api.md

File metadata and controls

203 lines (154 loc) · 5.71 KB

Export native API to WASM application

The basic working flow for WASM application calling into the native API is shown in the following diagram:

WAMR WASM API ext diagram

WAMR provides the macro EXPORT_WASM_API to enable users to export a native API to a WASM application. WAMR has implemented a base API for the timer and messaging by using EXPORT_WASM_API. This can be a point of reference for extending your own library.

static NativeSymbol extended_native_symbol_defs[] = {
    EXPORT_WASM_API(wasm_register_resource),
    EXPORT_WASM_API(wasm_response_send),
    EXPORT_WASM_API(wasm_post_request),
    EXPORT_WASM_API(wasm_sub_event),
    EXPORT_WASM_API(wasm_create_timer),
    EXPORT_WASM_API(wasm_timer_set_interval),
    EXPORT_WASM_API(wasm_timer_cancel),
    EXPORT_WASM_API(wasm_timer_restart)
};

Security attention: A WebAssembly application should only have access to its own memory space. As a result, the integrator should carefully design the native function to ensure that the memory accesses are safe. The native API to be exported to the WASM application must:

  • Only use 32 bits number for parameters
  • Should not pass data to the structure pointer (do data serialization instead)
  • Should do the pointer address conversion in the native API
  • Should not pass function pointer as callback

Below is a sample of a library extension. All code invoked across WASM and native world must be serialized and de-serialized, and the native world must do a boundary check for every incoming address from the WASM world.

In wasm world:

void api_send_request(request_t * request, response_handler_f response_handler,
        void * user_data)
{
    int size;
    char *buffer;
    transaction_t *trans;

    if ((trans = (transaction_t *) malloc(sizeof(transaction_t))) == NULL) {
        printf(
                "send request: allocate memory for request transaction failed!\n");
        return;
    }

    memset(trans, 0, sizeof(transaction_t));
    trans->handler = response_handler;
    trans->mid = request->mid;
    trans->time = wasm_get_sys_tick_ms();
    trans->user_data = user_data;

    // pack request
    if ((buffer = pack_request(request, &size)) == NULL) {
        printf("send request: pack request failed!\n");
        free(trans);
        return;
    }

    transaction_add(trans);

    /* if the trans is the 1st one, start the timer */
    if (trans == g_transactions) {
        /* assert(g_trans_timer == NULL); */
        if (g_trans_timer == NULL) {
            g_trans_timer = api_timer_create(TRANSACTION_TIMEOUT_MS,
            false,
            true, transaction_timeout_handler);
        }
    }

    // call native API
    wasm_post_request(buffer, size);

    free_req_resp_packet(buffer);
}

In native world:

void
wasm_post_request(wasm_exec_env_t exec_env,
                  int32 buffer_offset, int size)
{
    wasm_module_inst_t module_inst = get_module_inst(exec_env);
    char *buffer = NULL;

    // do boundary check
    if (!validate_app_addr(buffer_offset, size))
        return;

    // do address conversion
    buffer = addr_app_to_native(buffer_offset);

    if (buffer != NULL) {
        request_t req[1];

        // De-serialize data
        if (!unpack_request(buffer, size, req))
            return;

        // set sender to help dispatch the response to the sender app later
        unsigned int mod_id = app_manager_get_module_id(Module_WASM_App,
                                                        module_inst);
        bh_assert(mod_id != ID_NONE);
        req->sender = mod_id;

        if (req->action == COAP_EVENT) {
            am_publish_event(req);
            return;
        }

        am_dispatch_request(req);
    }
}

Steps for exporting native API

WAMR implemented a framework for developers to export API's. Below is the procedure to expose the platform API's in three steps:

Step 1: Define the native API for exporting

Define the function example_native_func in your source file, namely example.c here:

int example_native_func(wasm_exec_env_t exec_env,
                        int arg1, int arg2)
{
    // Your implementation here
}

The first function argument must be defined using type wasm_exec_env_t which is the WAMR calling convention for native API exporting.

The function prototype should also be declared in a header file so the wasm application can include it.

#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_
#ifdef __cplusplus
extern "C" {
#endif

void example_native_func(int arg1, int arg2);

#ifdef __cplusplus
}
#endif
#endif

Step 2: Declare the native API exporting

Declare the function example_native_func with macro EXPORT_WASM_API in your .inl file, namely example.inl in this sample.

EXPORT_WASM_API(example_native_func),

Then include the file example.inl in definition of array extended_native_symbol_defs in the ext_lib_export.c.

static NativeSymbol extended_native_symbol_defs[] = {
   #include "example.inl"
};

#include "ext_lib_export.h"

Step 3: Compile the runtime product

Add the source file example.c and ext_lib_export.c into the CMakeList.txt for building runtime with the exported API's:

set (EXT_API_SOURCE example.c)

add_executable (sample
    # other source files
    # ......
    ${EXT_API_SOURCE}
    ext_lib_export.c
)

Use exported API in wasm application

We can call the exported native API example_native_func in wasm application like this:

#include <stdio.h>
#include "example.h"

int main(int argc, char **argv)
{
    int a = 0, b = 1;

    example_native_func(a, b);
    return 0;
}