feat: now gets a functional dl_call

1. update examples
2. implements dl_call
3. add documentations
This commit is contained in:
Paul Pan 2023-02-11 23:59:58 +08:00
parent a471519ff7
commit 5c31d4e7ba
7 changed files with 190 additions and 281 deletions

View File

@ -1,15 +1,16 @@
#include <iostream>
#include <utility>
#define HIDE __attribute__((visibility("hidden")))
extern "C" {
int fib_recurse(int n);
int fib_iterate(int n);
int fib_tail_call(int n);
int fib_fast(int n);
int call_test(int a, long long b, float c, double d);
int call_test2(int a, long long b, int c, float d);
int my_cpy(char *dest, char *src, int len);
int fib_recurse(int n);
int fib_iterate(int n);
int fib_tail_call(int n);
int fib_fast(int n);
float call_test(int a, long long b, float c, double d);
double call_test2(int a, long long b, int c, float d);
int my_cpy(char *dest, char *src, int len);
}
int fib_recurse(int n) {
@ -51,15 +52,28 @@ HIDE inline std::pair<int, int> fib_fast_internal(int n) {
int fib_fast(int n) { return fib_fast_internal(n).first; }
int call_test(int a, long long b, float c, double d) { return a + b + c + d; }
float call_test(int a, long long b, float c, double d) {
float ans = a + b + c + d;
std::cout << "SimpleLib.cpp: call_test(" << a << ", " << b << ", " << c << ", " << d
<< ") = " << ans << std::endl;
return ans;
}
int call_test2(int a, long long b, int c, float d) { return a + b + c + d; }
double call_test2(int a, long long b, int c, float d) {
double ans = a + b + c + d;
std::cout << "SimpleLib.cpp: call_test2(" << a << ", " << b << ", " << c << ", " << d
<< ") = " << ans << std::endl;
return ans;
}
int my_cpy(char *dest, char *src, int len) {
printf("SimpleLib.cpp: my_cpy(dest=%p, src=%p)\n", dest, src);
char seed = 0x42;
for (int i = 0; i < len; i++) {
dest[i] = src[i];
seed ^= src[i];
}
std::cout << "SimpleLib.cpp: my_cpy(\"" << dest << "\", \"" << src << "\", " << len
<< ") = " << (int)seed << std::endl;
return seed;
}

View File

@ -1,15 +1,17 @@
#ifndef WRT_DL_H
#define WRT_DL_H
#include <stdint.h>
typedef enum {
WRT_OK = 0,
WRT_ERROR = -1,
} Status;
int dl_open(const char *filename, int flags);
int dl_sym(int handle, const char *symbol, const char *signature);
void *dl_call(int symbol, ...);
Status dl_close_sym(int handle, int symbol);
Status dl_close(int handle);
int dl_open(const char *filename, int flags);
int dl_sym(int handle, const char *symbol, const char *signature);
int64_t dl_call(int symbol, ...);
Status dl_close_sym(int symbol);
Status dl_close(int handle);
#endif // WRT_DL_H

View File

@ -4,62 +4,49 @@
unsigned fib(unsigned n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
void dl_test() {
int hnd, fib_iterate_sym, call_test_sym, call_test2_sym, my_cpy_sym;
Status status;
void *result;
int hnd, fib_iterate_sym, call_test_sym, call_test2_sym, my_cpy_sym;
int64_t result;
{ // Load library
hnd = dl_open("./SimpleLib.so", 1);
printf("hnd = %d\n", hnd);
}
{ // Load symbols
fib_iterate_sym = dl_sym(hnd, "fib_iterate", "(i)i");
printf("fib_iterate_sym = %d\n", fib_iterate_sym);
call_test_sym = dl_sym(hnd, "call_test", "(ilfd)i");
printf("call_test_sym = %d\n", call_test_sym);
call_test2_sym = dl_sym(hnd, "call_test2", "(ilid)i");
printf("call_test2_sym = %d\n", call_test2_sym);
my_cpy_sym = dl_sym(hnd, "my_cpy", "(ppi)i");
printf("my_cpy_sym = %d\n", my_cpy_sym);
call_test_sym = dl_sym(hnd, "call_test", "(ilfd)f");
call_test2_sym = dl_sym(hnd, "call_test2", "(ilid)d");
my_cpy_sym = dl_sym(hnd, "my_cpy", "(ppi)i");
}
{ // Call function
result = dl_call(fib_iterate_sym, 6);
printf("fib_iterate(6) = %d\n", (int)result);
printf("fib_iterate(6) = %lld\n", result);
result = dl_call(call_test_sym, 42, 0x12345678abcdef01, 3.1415926f, 2.718281828459045);
printf("call_test_sym = %d\n", (int)result);
float pi = 3.1415926f;
double e = 2.718281828459045;
long long ll = 0x12345678abcdef01;
result = dl_call(call_test2_sym, 42, 0x12345678abcdef01, 21, 3.1415926f);
printf("call_test2_sym = %d\n", (int)result);
result = dl_call(call_test_sym, 42, ll, pi, e);
printf("call_test(...) = %f\n", (float)result);
result = dl_call(call_test2_sym, 42, 21, 10, pi);
printf("call_test2(...) = %lf\n", (double)result);
volatile char src[16] = "Hello, World!";
volatile char dst[16] = "++++++++++++++++";
result = dl_call(my_cpy_sym, dst, src, 16);
printf("my_cpy = %d, src = %s , dst = %s\n", (int)result, src, dst);
volatile char dst[16] = "+++++++++++++";
result = dl_call(my_cpy_sym, dst, 16, src, 16, 16);
printf("my_cpy(...) = %lld, src = %s , dst = %s\n", result, src, dst);
}
{ // Close symbols
status = dl_close_sym(hnd, fib_iterate_sym);
printf("close sym status = %d\n", status);
status = dl_close_sym(hnd, call_test_sym);
printf("close sym status = %d\n", status);
status = dl_close_sym(hnd, call_test2_sym);
printf("close sym status = %d\n", status);
status = dl_close_sym(hnd, my_cpy_sym);
printf("close sym status = %d\n", status);
dl_close_sym(fib_iterate_sym);
dl_close_sym(call_test_sym);
dl_close_sym(call_test2_sym);
dl_close_sym(my_cpy_sym);
}
{ // Close library
status = dl_close(hnd);
printf("close lib status = %d\n", status);
dl_close(hnd);
}
}
@ -80,12 +67,12 @@ void evil_test() {
// 3. valid symbol, valid args
dl_call(my_cpy_sym, (void *)evil_test, (void *)evil_test, 1);
dl_close_sym(hnd, my_cpy_sym);
dl_close_sym(my_cpy_sym);
dl_close(hnd);
}
void entry() {
printf("Hello WASM\n");
dl_test();
evil_test();
// evil_test();
}

View File

@ -1,130 +0,0 @@
#include <string.h>
#include <stdio.h>
#include "utils/log.h"
#include "wrt/tinymap.h"
#define FOR(i) for (unsigned i = map->head[hash]; i; i = map->node[i].next)
static inline unsigned map_hash(int key) { return (key % BUK_SIZE + BUK_SIZE) % BUK_SIZE; }
void map_init(Map *map) {
map->count = 0;
memset(map->head, 0, sizeof(map->head));
memset(map->node, 0, sizeof(map->node));
}
void *map_get(Map *map, int key) {
if (key == 0) return NULL;
unsigned hash = map_hash(key);
FOR(i) {
if (map->node[i].key == key) return map->node[i].value;
}
return NULL;
}
bool map_set(Map *map, int key, void *value) {
if (key == 0) return false;
unsigned hash = map_hash(key);
FOR(i) {
if (map->node[i].key == key) {
LOG_DBG("map_set: upd key=%d, val=0x%llx, pos=%d", key, (unsigned long long)value, i);
map->node[i].value = value;
return true;
}
}
if (map->count >= NOD_SIZE) {
LOG_DBG("map_set: nod full, compact");
map_compact(map);
if (map->count == NOD_SIZE) return false;
}
map->node[++map->count] = (MapNode){key, value, map->head[hash]};
map->head[hash] = map->count;
LOG_DBG("map_set: ins key=%d, val=0x%llx, pos=%d", key, (unsigned long long)value, map->count);
return true;
}
void map_delete(Map *map, int key) {
if (key == 0) return;
unsigned hash = map_hash(key);
FOR(i) {
if (map->node[i].key == key) {
LOG_DBG("map_delete: del key=%d, pos=%d", key, i);
map->node[i].key = 0;
return;
}
}
}
void map_compact(Map *map) {
LOG_DBG("map_compact: before count=%d", map->count);
{
// compact map->node
// i = last empty node, j = current scanning node
// node[0] is undefined
unsigned i = 1, j = 2;
for (; j <= NOD_SIZE;) {
if (map->node[i].key) {
i++, j++;
continue;
}
if (map->node[j].key) {
LOG_DBG("map_compact: move node[%d] to node[%d]", j, i);
map->node[i] = map->node[j];
map->node[j].key = 0;
i++, j++;
continue;
}
j++;
}
map->count = i - 1;
LOG_DBG("map_compact: after count=%d", map->count);
}
{
// rebuild map->head
for (unsigned i = 0; i <= NOD_SIZE; i++) map->node[i].next = 0;
memset(map->head, 0, sizeof(map->head));
for (unsigned i = 1; i <= NOD_SIZE; i++) {
if (!map->node[i].key) break;
unsigned hash = map_hash(map->node[i].key);
if (map->head[hash] == 0) {
map->head[hash] = i;
} else {
unsigned j = map->head[hash];
map->head[hash] = i;
map->node[i].next = j;
}
}
}
}
void map_dbg(Map *map) {
for (int hash = 0; hash < BUK_SIZE; hash++) {
printf("[+] BUK[%2d]:\n", hash);
int spaces = 5;
FOR(i) {
for (int j = 0; j < spaces; j++) putchar(' ');
printf("└─ NOD[" __COLOR_GREEN "%2d" __COLOR_RESET "]: key=" __COLOR_YELLOW
"%d" __COLOR_RESET ", val=" __COLOR_CYAN "0x%llx" __COLOR_RESET
", nxt=" __COLOR_RED "%d" __COLOR_RESET "\n",
i, map->node[i].key, (unsigned long long)map->node[i].value, map->node[i].next);
spaces += 4;
}
}
}

View File

@ -1,32 +0,0 @@
#ifndef WRT_TINYMAP_H
#define WRT_TINYMAP_H
#include <stdbool.h>
#ifndef BUK_SIZE
#define BUK_SIZE 8
#endif
#ifndef NOD_SIZE
#define NOD_SIZE 32
#endif
typedef struct {
int key;
void *value;
unsigned next;
} MapNode;
typedef struct {
MapNode node[NOD_SIZE + 1];
unsigned head[BUK_SIZE];
unsigned count;
} Map;
void map_init(Map *map);
void *map_get(Map *map, int key);
bool map_set(Map *map, int key, void *value);
void map_delete(Map *map, int key);
void map_compact(Map *map);
#endif // WRT_TINYMAP_H

View File

@ -1,11 +1,25 @@
#include <string.h>
#include <dlfcn.h>
#include <alloca.h>
#include "utils/log.h"
#include "wrt/lib/dl.h"
#warning "TODO: Permission Check"
#define LOG_AND_QUIT(msg, ret) \
do { \
LOG_ERR(msg); \
wasm_runtime_set_exception(module_inst, msg); \
return ret; \
} while (0)
#define GET_CONTEXT(ret) \
wasm_module_inst_t module_inst = get_module_inst(exec_env); \
(void)module_inst; \
DLContext *ctx = wasm_runtime_get_function_attachment(exec_env); \
if (ctx == NULL) LOG_AND_QUIT("dl_get_context: NULL", ret);
Status dl_init(DLContext *ctx) {
memset(ctx->hnd, 0, sizeof(ctx->hnd));
memset(ctx->sym, 0, sizeof(ctx->sym));
@ -26,8 +40,8 @@ Status dl_init(DLContext *ctx) {
EXPORT(0, dl_open, "($i)i");
EXPORT(1, dl_sym, "(i$$)i");
EXPORT(2, dl_call, "(i*)i");
EXPORT(3, dl_close_sym, "(ii)i");
EXPORT(2, dl_call, "(i*)I");
EXPORT(3, dl_close_sym, "(i)i");
EXPORT(4, dl_close, "(i)i");
#undef EXPORT
@ -51,12 +65,6 @@ Status dl_free(DLContext *ctx) {
return WRT_OK;
}
#define GET_CONTEXT(ret) \
wasm_module_inst_t module_inst = get_module_inst(exec_env); \
(void)module_inst; \
DLContext *ctx = wasm_runtime_get_function_attachment(exec_env); \
if (ctx == NULL) return ret;
int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags) {
if (filename == NULL) return WRT_ERROR;
if (filename[0] == '\0') return WRT_ERROR;
@ -82,9 +90,10 @@ int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags) {
return WRT_ERROR;
}
// TODO: integrate it into create_ffi_cif
bool signature_check(const char *signature, int *arg_count, bool *has_ret) {
const static char allowed_chars[] = {'i', 'l', 'f', 'd', 'p'};
const static int allowed_chars_len = sizeof(allowed_chars) / sizeof(char);
const char allowed_chars[] = {'i', 'l', 'f', 'd', 'p'};
const int allowed_chars_len = sizeof(allowed_chars) / sizeof(char);
// tmpl: '(' ch* ')' ch?
@ -230,50 +239,47 @@ int dl_sym(wasm_exec_env_t exec_env, int handle, const char *symbol, const char
return WRT_ERROR;
}
void *dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args) {
GET_CONTEXT(NULL)
#define LOG_AND_QUIT(msg) \
do { \
LOG_ERR(msg); \
wasm_runtime_set_exception(module_inst, msg); \
return NULL; \
} while (0)
int64_t dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args) {
GET_CONTEXT(WRT_ERROR)
if (symbol < 1 || symbol > DL_MAX_SYMBOLS) {
LOG_AND_QUIT("dl_call: invalid symbol");
LOG_AND_QUIT("dl_call: invalid symbol", WRT_ERROR);
}
if (ctx->sym[symbol - 1].symbol == NULL) {
LOG_AND_QUIT("dl_call: symbol not found");
LOG_AND_QUIT("dl_call: symbol not found", WRT_ERROR);
}
if (!wasm_runtime_validate_native_addr(module_inst, va_args, sizeof(uint32_t))) {
LOG_AND_QUIT("dl_call: invalid va_args");
LOG_AND_QUIT("dl_call: invalid va_args", WRT_ERROR);
}
uint8_t *native_end_addr;
if (!wasm_runtime_get_native_addr_range(module_inst, (uint8_t *)va_args, NULL,
&native_end_addr)) {
LOG_AND_QUIT("dl_call: va_args out of bounds");
LOG_AND_QUIT("dl_call: va_args out of bounds", WRT_ERROR);
}
ffi_cif *cif = ctx->sym[symbol - 1].cif;
unsigned arg_count = cif->nargs;
void *args[arg_count];
void *tmp_buffer[arg_count]; // TODO: tmp_buffer only used for ptr args, which wastes memory
ffi_cif *cif = ctx->sym[symbol - 1].cif;
unsigned arg_count = cif->nargs, ext_ptr_count = 0, ext_float_count = 0;
for (int i = 0; i < arg_count; i++) {
if (cif->arg_types[i]->type == FFI_TYPE_POINTER) ext_ptr_count++;
if (cif->arg_types[i]->type == FFI_TYPE_FLOAT) ext_float_count++;
}
#define ALIGN(n, b) (((uintptr_t)(n) + b) & (uintptr_t)~b)
#define GET_ARG(ptr, type) (*(type *)((ptr += ALIGN(sizeof(type), 3)) - ALIGN(sizeof(type), 3)))
#define CHECK_VA_ARG(ptr, type) \
do { \
if ((uint8_t *)ptr + ALIGN(type, 3) > native_end_addr) { \
LOG_AND_QUIT("dl_call: func args out of bounds"); \
} \
#define CHECK_VA_ARG(ptr, type) \
do { \
if ((uint8_t *)ptr + ALIGN(type, 3) > native_end_addr) { \
LOG_AND_QUIT("dl_call: func args out of bounds", WRT_ERROR); \
} \
} while (0)
_va_list cur = va_args;
void **args = alloca(sizeof(void *) * arg_count);
void **ext_ptrs = alloca(sizeof(void *) * ext_ptr_count);
float *ext_floats = alloca(sizeof(float *) * ext_float_count);
_va_list cur = va_args;
for (int i = 0; i < arg_count; i++) {
// TODO: check wasm abi
// only support int, long long, float, double, pointer now
@ -288,6 +294,8 @@ void *dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args) {
case FFI_TYPE_POINTER: { // 32 bit
CHECK_VA_ARG(cur, int32_t);
uint32_t ptr = GET_ARG(cur, int32_t);
CHECK_VA_ARG(cur, int32_t);
uint32_t len = GET_ARG(cur, int32_t);
/* TODO: Security Warning !!
* - 3rd-party libraries could access native memory directly, which means a buffer
@ -298,12 +306,14 @@ void *dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args) {
* force memory alignment)
* - On embedded devices: maybe disable this feature? or embedded native symbols?
*/
if (!wasm_runtime_validate_app_addr(module_inst, ptr, 0)) {
LOG_AND_QUIT("dl_call: invalid ptr");
if (!wasm_runtime_validate_app_addr(module_inst, ptr, len)) {
LOG_AND_QUIT("dl_call: invalid ptr", WRT_ERROR);
}
tmp_buffer[i] = wasm_runtime_addr_app_to_native(module_inst, ptr);
args[i] = &tmp_buffer[i];
LOG_WARN("dl_call: ptr arg[%d] = 0x%lx", i, (uintptr_t)tmp_buffer[i]);
*ext_ptrs = wasm_runtime_addr_app_to_native(module_inst, ptr);
args[i] = ext_ptrs;
LOG_WARN("dl_call: ptr arg[%d] = 0x%lx, buf = %p", i, (uintptr_t)*ext_ptrs,
ext_ptrs);
ext_ptrs++;
break;
}
@ -313,53 +323,55 @@ void *dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args) {
args[i] = &GET_ARG(cur, int64_t);
break;
}
case FFI_TYPE_FLOAT:
case FFI_TYPE_DOUBLE: { // 64 bit: FLOAT and DOUBLE are the same in WASM
case FFI_TYPE_FLOAT: { // 64 bit in WASM
cur = (_va_list)ALIGN(cur, 7);
CHECK_VA_ARG(cur, double);
*ext_floats = (float)GET_ARG(cur, double);
args[i] = ext_floats;
ext_floats++;
break;
}
case FFI_TYPE_DOUBLE: { // 64 bit
cur = (_va_list)ALIGN(cur, 7);
CHECK_VA_ARG(cur, double);
args[i] = &GET_ARG(cur, double);
break;
}
default: {
LOG_AND_QUIT("dl_call: unsupported type");
LOG_AND_QUIT("dl_call: unsupported type", WRT_ERROR);
}
}
}
#undef CHECK_VA_ARG
#undef GET_ARG
#undef ALIGN
void *ret = NULL;
if (cif->rtype->type != FFI_TYPE_VOID) {
unsigned size = cif->rtype->size;
if (cif->rtype->type == FFI_TYPE_FLOAT) size = (&ffi_type_double)->size;
ret = ctx->mem.malloc(size);
ret = alloca(size);
}
unsigned short not_implemented[4] = {FFI_TYPE_POINTER, FFI_TYPE_SINT64, FFI_TYPE_DOUBLE,
FFI_TYPE_FLOAT};
for (int i = 0; i < 4; i++)
if (cif->rtype->type == not_implemented[i]) {
/* TODO:
* - FFI_TYPE_POINTER: copy the data, but how to free?(make a linked list?), how to
* determine the size?
* - Others: 64 bit, how to return? change signature to return int64? which suits ptr
* :)
*/
LOG_AND_QUIT("dl_call: unsupported return type (pointer)");
}
ffi_call(cif, FFI_FN(ctx->sym[symbol - 1].symbol), ret, args);
LOG_DBG("dl_call: symbol = %d, ret = 0x%x", symbol, *(unsigned *)ret);
int64_t ret_val;
switch (cif->rtype->type) {
case FFI_TYPE_SINT32: ret_val = *(int32_t *)ret; break;
case FFI_TYPE_SINT64: ret_val = *(int64_t *)ret; break;
case FFI_TYPE_FLOAT: ret_val = (int64_t)(double)(*(float *)ret); break;
case FFI_TYPE_DOUBLE: ret_val = (int64_t)(*(double *)ret); break;
case FFI_TYPE_VOID: ret_val = 0; break;
default: LOG_AND_QUIT("dl_call: unsupported type", WRT_ERROR);
}
return (void *)0x42;
LOG_DBG("dl_call: symbol = %d, ret = 0x%lx", symbol, ret_val);
#undef CHECK_VA_ARG
#undef GET_ARG
#undef ALIGN
#undef LOG_AND_QUIT
return ret_val;
}
Status dl_close_sym(wasm_exec_env_t exec_env, int handle, int symbol) {
if (handle < 1 || handle > DL_MAX_HANDLES) return WRT_ERROR;
Status dl_close_sym(wasm_exec_env_t exec_env, int symbol) {
if (symbol < 1 || symbol > DL_MAX_SYMBOLS) return WRT_ERROR;
GET_CONTEXT(WRT_ERROR)

View File

@ -23,12 +23,68 @@ typedef struct {
} sym[DL_MAX_SYMBOLS];
} DLContext;
/// Init DL environment
/// \param ctx DLContext to be initialized, ctx->mem.malloc and ctx->mem.free must be set before
/// \return WRT_OK, unless malloc failed
Status dl_init(DLContext *context);
/// Free DL environment
/// \param ctx DLContext to be freed
/// \return always WRT_OK
Status dl_free(DLContext *context);
int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags);
int dl_sym(wasm_exec_env_t exec_env, int handle, const char *symbol, const char *signature);
void *dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args);
Status dl_close_sym(wasm_exec_env_t exec_env, int handle, int symbol);
/// Open a dynamic library
/// \param exec_env WAMR execution environment
/// \param filename dynamic library file name
/// \param flags dlopen flags (man dlopen)
/// \return handle of the opened dynamic library, or WRT_ERROR if failed
int dl_open(wasm_exec_env_t exec_env, const char *filename, int flags);
/// Get symbol from a dynamic library
/// \param exec_env WAMR execution environment
/// \param handle handle of the dynamic library, returned by dl_open
/// \param symbol symbol name
/// \param signature function signature: \n
/// signature grammar: \n
/// -# `type_ret ::= 'i' | 'l' | 'f' | 'd'` \n
/// -# `type_arg ::= type_ret | 'p'` \n
/// -# `signature ::= '(' type_arg* ')' type_ret?` \n
/// 'i' for int(sint32), 'l' for long(sint64), 'f' for float, 'd' for double, 'p' for pointer \n
/// Examples: \n
/// -# `()` for function: `void func(void)` \n
/// -# `(i)` for function: `void func(int)` \n
/// -# `(ip)f` for function: `float func(int, void*)` \n
/// Several Notes: \n
/// -# Pointer type: \n
/// -# A int must be followed after the pointer indicating the length of the pointer \n
/// -# For example: \n
/// + if a native function is defined as `void func(int *p, int *q)` \n
/// + then, in WASM environment, the function should be defined as `void func(int *p, int
/// p_len, int *q, int q_len)` \n
/// -# We do not guarantee the same behavior when there are multiple pointers intervene with each
/// other \n
/// -# We do not support return value of type pointer, since we don't know how the data length \n
/// -# More types will be supported in the future \n
/// \return native function's output, or terminated if failed
int dl_sym(wasm_exec_env_t exec_env, int handle, const char *symbol, const char *signature);
/// Call a native function
/// \param exec_env WASM execution environment
/// \param symbol symbol handle, returned by dl_sym
/// \param va_args arguments, in WASM runtime, variadic arguments are pushed into stack, just like a
/// array \return function's return value
int64_t dl_call(wasm_exec_env_t exec_env, int symbol, _va_list va_args);
/// Destroy a symbol, it will free the cif structure
/// \param exec_env WAMR execution environment
/// \param symbol symbol handle, returned by dl_sym
/// \return WRT_OK, unless symbol is invalid
Status dl_close_sym(wasm_exec_env_t exec_env, int symbol);
/// Close a dynamic library
/// \param exec_env WASM execution environment
/// \param handle handle of the dynamic library, returned by dl_open
/// \return WRT_OK, unless handle is invalid or dlclose failed
Status dl_close(wasm_exec_env_t exec_env, int handle);
#endif // WRT_DL_H