/* * nanowasm, a tiny WebAssembly/Wasm interpreter * Copyright (C) 2023-2025 Xavier Del Campo Romero * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include struct inst { long retval; size_t n_files; struct file { FILE *f; int fd; } *files; struct mem { void *p; size_t n; } stack, linear, global; }; static int io_read(void *const buf, const size_t n, void *const user) { FILE *const f = user; const size_t r = fread(buf, 1, 1, f); if (!r && ferror(f)) { fprintf(stderr, "ferror\n"); return -1; } return r; } static enum nw_state io_seek(const long offset, void *const user) { if (fseek(user, offset, SEEK_SET)) { fprintf(stderr, "fseek(3): %s\n", strerror(errno)); return NW_FATAL; } return NW_OK; } static enum nw_state io_tell(long *const out, void *const user) { const long offset = ftell(user); if (offset < 0) { fprintf(stderr, "ftell(3): %s\n", strerror(errno)); return NW_FATAL; } *out = offset; return NW_OK; } static int io_eof(void *const user) { FILE *const f = user; return feof(f); } static int push(const void *const src, const size_t n, void *const user) { struct inst *const inst = user; struct mem *const s = &inst->stack; char *const p = realloc(s->p, s->n + n); if (!p) { fprintf(stderr, "realloc(3): %s\n", strerror(errno)); return -1; } fprintf(stderr, "pushed %zu bytes: {", n); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)src)[i]); if (i + 1 < n) fputs(", ", stderr); } memcpy(&p[s->n], src, n); s->p = p; s->n += n; fprintf(stderr, "}, stack size=%zu\n", s->n); return n; } static int pop(void *const dst, const size_t n, void *const user) { struct inst *const inst = user; struct mem *const s = &inst->stack; if (s->n < n) { fprintf(stderr, "stack underflow\n"); return -1; } const char *const src = (const char *)s->p + (s->n - n); fprintf(stderr, "popped %zu bytes: {", n); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)src)[i]); if (i + 1 < n) fputs(", ", stderr); } if (dst) memcpy(dst, src, n); if (s->n == n) { free(s->p); s->p = NULL; } else { char *const p = realloc(s->p, s->n - n); if (!p) { fprintf(stderr, "realloc(3): %s\n", strerror(errno)); return -1; } s->p = p; } s->n -= n; fprintf(stderr, "}, stack size=%zu\n", s->n); return n; } static size_t ptr(void *const user) { return ((const struct inst *)user)->stack.n; } static int load(const nw_varuint32 offset, void *const dst, const size_t n, const struct mem *const m) { if (n > m->n || offset > m->n - n) { fprintf(stderr, "out-of-bounds access, offset=%lu, n=%zu\n", (unsigned long)offset, (unsigned long)n); return -1; } fprintf(stderr, "loaded %zu bytes from offset %lu: {", (unsigned long)n, (unsigned long)offset); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]); if (i + 1 < n) fputs(", ", stderr); } fputs("}\n", stderr); memcpy(dst, (const char *)m->p + offset, n); return n; } static int store(const nw_varuint32 offset, const void *const src, const size_t n, const struct mem *const m) { if (n > m->n || offset > m->n - n) { fprintf(stderr, "out-of-bounds access, offset=%lu, n=%lu\n", (unsigned long)offset, (unsigned long)n); return -1; } fprintf(stderr, "stored %lu bytes into offset %lu: {", (unsigned long)n, (unsigned long)offset); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)src)[i]); if (i + 1 < n) fputs(", ", stderr); } fputs("}\n", stderr); memcpy((char *)m->p + offset, src, n); return n; } static int stread(const size_t offset, void *const dst, const size_t n, void *const user) { const struct inst *const inst = user; return load(offset, dst, n, &inst->stack); } static int stwrite(const size_t offset, const void *const src, const size_t n, void *const user) { const struct inst *const inst = user; return store(offset, src, n, &inst->stack); } static int ensure_linear(struct mem *const m, const unsigned long offset, const size_t n) { void *const p = realloc(m->p, offset + n); if (!p) { fprintf(stderr, "realloc(3): %s\n", strerror(errno)); return -1; } m->p = p; m->n = offset + n; memset((char *)m->p + offset, 0, n); return 0; } static int lnload(const unsigned long offset, void *const dst, const size_t n, void *const user) { struct inst *const i = user; struct mem *const m = &i->linear; if (offset >= m->n && ensure_linear(m, offset, n)) { fprintf(stderr, "ensure_linear failed\n"); return -1; } fprintf(stderr, "loaded %lu bytes from offset %lu: {", (unsigned long)n, (unsigned long)offset); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)m->p)[offset + i]); if (i + 1 < n) fputs(", ", stderr); } fputs("}\n", stderr); memcpy(dst, (const char *)m->p + offset, n); return n; } static int lnstore(const unsigned long offset, const void *const src, const size_t n, void *const user) { struct inst *const i = user; struct mem *const m = &i->linear; if (offset >= m->n && ensure_linear(m, offset, n)) { fprintf(stderr, "ensure_linear failed\n"); return -1; } fprintf(stderr, "stored %lu bytes into offset %lu: {", (unsigned long)n, (unsigned long)offset); for (size_t i = 0; i < n; i++) { fprintf(stderr, "%hhu", ((const char *)src)[i]); if (i + 1 < n) fputs(", ", stderr); } fputs("}\n", stderr); memcpy((char *)m->p + offset, src, n); return n; } static int glload(const unsigned long offset, void *const dst, const size_t n, void *const user) { const struct inst *const i = user; return load(offset, dst, n, &i->global); } static int glstore(const unsigned long offset, const void *const src, const size_t n, void *const user) { const struct inst *const i = user; return store(offset, src, n, &i->global); } static enum nw_state wasi_exit(const union nw_value *const params, union nw_value *const ret, void *user) { struct inst *const i = user; i->retval = params->i32; return NW_OK; } static enum nw_state wasi_fd_close(const union nw_value *const params, union nw_value *const ret, void *user) { return NW_FATAL; } static enum nw_state wasi_fd_fdstat_get(const union nw_value *const params, union nw_value *const ret, void *user) { return NW_FATAL; } static enum nw_state wasi_fd_prestat_get(const union nw_value *const params, union nw_value *const ret, void *user) { /* * From wasi/phases/old/snapshot_0/docs.md: * * fd_prestat_get(fd: fd) -> (errno, prestat) * Return a description of the given preopened file descriptor. * Params * fd: fd * Results * error: errno * buf: prestat The buffer where the description is stored. * * Offset: 0 * prestat: Union * Information about a pre-opened capability. * Size: 8 * Alignment: 4 * Union Layout * tag_size: 1 * tag_align: 1 * contents_offset: 4 * contents_size: 4 * contents_align: 4 */ const long fd = params[0].i32; const unsigned long prestat = params[1].i32; const struct file *fl = NULL; struct inst *const inst = user; fprintf(stderr, "fd=%lu, prestat=%#lx\n", fd, prestat); for (size_t i = 0; i < inst->n_files; i++) { const struct file *const f = &inst->files[i]; if (fd == (unsigned long)f->fd) { fl = f; break; } } if (!fl) { /* __WASI_EBADF */ ret->i32 = 8; return NW_OK; } return NW_OK; } static enum nw_state wasi_fd_prestat_dir_name( const union nw_value *const params, union nw_value *const ret, void *user) { return NW_FATAL; } static enum nw_state wasi_fd_read( const union nw_value *const params, union nw_value *const ret, void *user) { const long fd = params[0].i32; const unsigned long iovec = params[1].i32, n = params[2].i32, nread = params[3].i32; fprintf(stderr, "fd=%ld, iovec=%#lx, n=%lu, nread=%#lx\n", fd, iovec, n, nread); return NW_FATAL; } static enum nw_state wasi_path_open( const union nw_value *const params, union nw_value *const ret, void *user) { return NW_FATAL; } int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; FILE *f = NULL; struct inst ins = {0}; if (argc != 2) { fprintf(stderr, "%s \n", *argv); goto end; } const char *const path = argv[1]; if (!(f = fopen(path, "rb"))) { fprintf(stderr, "fopen(3) %s: %s\n", path, strerror(errno)); goto end; } static const struct nw_import imports[] = { { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "proc_exit", .u.function = { .fn = wasi_exit, .signature = "(i)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "fd_close", .u.function = { .fn = wasi_fd_close, .signature = "i(i)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "fd_fdstat_get", .u.function = { .fn = wasi_fd_fdstat_get, .signature = "i(ii)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "fd_prestat_get", .u.function = { .fn = wasi_fd_prestat_get, .signature = "i(ii)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "fd_prestat_dir_name", .u.function = { .fn = wasi_fd_prestat_dir_name, .signature = "i(ii)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "fd_read", .u.function = { .fn = wasi_fd_read, .signature = "i(iiii)" } }, { .kind = NW_KIND_FUNCTION, .module = "wasi_snapshot_preview1", .field = "path_open", .u.function = { .fn = wasi_path_open, .signature = "i(iiiiiIIii)" } }, }; const struct nw_io_cfg io = { .read = io_read, .seek = io_seek, .tell = io_tell, .eof = io_eof, .user = f }; enum {N_IMPORTS = sizeof imports / sizeof *imports}; const struct nw_mod_cfg cfg = { .imports = imports, .imp_indexes = (struct nw_import_index[N_IMPORTS]){{0}}, .n_imports = N_IMPORTS, .io = io }; struct nw_mod m; struct nw_mod_out mout; nw_init(&m, &cfg); again: switch (nw_load(&m, &mout)) { case NW_OK: break; case NW_AGAIN: goto again; case NW_FATAL: fprintf(stderr, "nw_load failed\n"); goto end; } if (!(ins.global.p = malloc(ins.global.n = mout.global))) { fprintf(stderr, "malloc(3): %s\n", strerror(errno)); goto end; } enum {ARGS = 10}; const struct nw_inst_cfg icfg = { .entry = "_start", .interp_cfg = { .io = io, .m = &m, .user = &ins, .args = (union nw_value[ARGS]){{0}}, .n_args = ARGS, .stack = { .push = push, .pop = pop, .ptr = ptr, .read = stread, .write = stwrite }, .linear = { .load = lnload, .store = lnstore }, .global = { .load = glload, .store = glstore } } }; struct nw_inst inst; if (nw_start(&inst, &icfg)) { fprintf(stderr, "nw_start failed\n"); goto end; } again2: switch (nw_run(&inst)) { case NW_OK: break; case NW_AGAIN: if (ins.retval) { ret = ins.retval; fprintf(stderr, "instance exited with status %d\n", ret); goto end; } goto again2; case NW_FATAL: fprintf(stderr, "nw_run failed: %s\n", nw_rexc(&inst)); goto end; } ret = EXIT_SUCCESS; end: for (size_t i = 0; i < ins.n_files; i++) { const struct file *const f = &ins.files[i]; if (fclose(f->f)) { fprintf(stderr, "fclose(3) [%lu]: %s\n", (unsigned long)i, strerror(errno)); ret = EXIT_FAILURE; } } if (f && fclose(f)) { fprintf(stderr, "fclose(3) %s: %s\n", path, strerror(errno)); goto end; } free(ins.files); free(ins.stack.p); free(ins.linear.p); free(ins.global.p); return ret; }