commit 174bfd1d46c747e4faccc1ad8e4e37880d01cd02 Author: hrigar Date: Thu Mar 19 23:56:55 2026 +0530 wasm file loader and header validation diff --git a/src/wasm.asm b/src/wasm.asm new file mode 100644 index 0000000..435c083 --- /dev/null +++ b/src/wasm.asm @@ -0,0 +1,210 @@ +format ELF executable 3 +entry start + +SYS_EXIT = 1 +SYS_READ = 3 +SYS_WRITE = 4 +SYS_OPEN = 5 +SYS_CLOSE = 6 + +STDOUT = 1 +O_RDONLY = 0 + +WASM_MAGIC = 0x6D736100 ; "\0asm" +WASM_VERSION = 0x00000001 +WASM_HEADER_SZ = 8 +FILE_BUF_SIZE = 4 * 1024 * 1024 + +segment readable executable + +start: + mov eax, [esp] + cmp eax, 2 + jl .no_arg + + mov eax, SYS_OPEN + mov ebx, [esp + 8] + mov ecx, O_RDONLY + xor edx, edx + int 0x80 + test eax, eax + js .open_err + mov [file_fd], eax + + mov eax, SYS_READ + mov ebx, [file_fd] + mov ecx, file_buf + mov edx, FILE_BUF_SIZE + int 0x80 + test eax, eax + js .read_err + mov [file_size], eax + + mov eax, SYS_CLOSE + mov ebx, [file_fd] + int 0x80 + + cmp dword [file_size], WASM_HEADER_SZ + jl .too_small + + mov eax, dword [file_buf] + cmp eax, WASM_MAGIC + jne .bad_magic + + mov eax, dword [file_buf + 4] + cmp eax, WASM_VERSION + jne .bad_version + + mov esi, msg_ok + call print_str + mov esi, msg_size + call print_str + mov eax, [file_size] + call print_dec + mov esi, msg_bytes + call print_str + + xor ebx, ebx + jmp .exit + +.no_arg: + mov esi, msg_usage + call print_str + mov ebx, 1 + jmp .exit + +.open_err: + mov esi, msg_open_err + call print_str + mov ebx, 1 + jmp .exit + +.read_err: + mov esi, msg_read_err + call print_str + mov ebx, 1 + jmp .exit + +.too_small: + mov esi, msg_too_small + call print_str + mov ebx, 1 + jmp .exit + +.bad_magic: + mov esi, msg_bad_magic + call print_str + mov ebx, 1 + jmp .exit + +.bad_version: + mov esi, msg_bad_ver + call print_str + mov esi, msg_got_ver + call print_str + mov eax, dword [file_buf + 4] + call print_hex32 + mov esi, msg_newline + call print_str + mov ebx, 1 + jmp .exit + +.exit: + mov eax, SYS_EXIT + int 0x80 + +; esi = null-terminated string +print_str: + push eax ebx ecx edx esi + mov edx, esi +@@: + cmp byte [edx], 0 + je @f + inc edx + jmp @b +@@: + sub edx, esi + mov ecx, esi + mov ebx, STDOUT + mov eax, SYS_WRITE + int 0x80 + pop esi edx ecx ebx eax + ret + +; eax = unsigned 32-bit integer +print_dec: + push eax ebx ecx edx esi + mov ecx, 10 + lea esi, [dec_buf + 11] + mov byte [esi], 0 +.loop: + dec esi + xor edx, edx + div ecx + add dl, '0' + mov [esi], dl + test eax, eax + jnz .loop + + push eax ebx ecx edx + mov edx, esi +@@: + cmp byte [edx], 0 + je @f + inc edx + jmp @b +@@: + sub edx, esi + mov ecx, esi + mov ebx, STDOUT + mov eax, SYS_WRITE + int 0x80 + pop edx ecx ebx eax + pop esi edx ecx ebx eax + ret + +; eax = 32-bit value printed as 0xHHHHHHHH +print_hex32: + push eax ebx ecx edx esi + lea esi, [hex_buf] + mov byte [esi], '0' + mov byte [esi+1], 'x' + mov ecx, 8 + add esi, 2 +.hex_loop: + rol eax, 4 + mov edx, eax + and edx, 0x0F + mov dl, [hex_chars + edx] + mov [esi], dl + inc esi + dec ecx + jnz .hex_loop + mov byte [esi], 0 + lea esi, [hex_buf] + call print_str + pop esi edx ecx ebx eax + ret + +segment readable writeable + +msg_usage db 'Usage: wasm ', 10, 0 +msg_open_err db 'Error: cannot open file', 10, 0 +msg_read_err db 'Error: cannot read file', 10, 0 +msg_too_small db 'Error: file too small (less than 8 bytes)', 10, 0 +msg_bad_magic db 'Error: invalid wasm magic number (expected \0asm)', 10, 0 +msg_bad_ver db 'Error: unsupported wasm version', 10, 0 +msg_got_ver db ' got version: ', 0 +msg_ok db 'Valid wasm file', 10, 0 +msg_size db 'File size: ', 0 +msg_bytes db ' bytes', 10, 0 +msg_newline db 10, 0 + +hex_chars db '0123456789ABCDEF' +hex_buf rb 12 +dec_buf rb 12 + +file_fd dd 0 +file_size dd 0 + +file_buf rb FILE_BUF_SIZE diff --git a/test/build_wasm.sh b/test/build_wasm.sh new file mode 100755 index 0000000..7eafc85 --- /dev/null +++ b/test/build_wasm.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Compile all .wat files to .wasm +# Run from the test/ directory + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +WAT_DIR="$SCRIPT_DIR/wat" +WASM_DIR="$SCRIPT_DIR/wasm" + +mkdir -p "$WASM_DIR" + +for f in "$WAT_DIR"/*.wat; do + base=$(basename "$f" .wat) + wat2wasm "$f" -o "$WASM_DIR/$base.wasm" + echo " $base.wat -> $base.wasm" +done + +# edge cases +/usr/bin/printf '\x00asm\x02\x00\x00\x00' > "$WASM_DIR/badver.wasm" +/usr/bin/printf '\x00bad\x01\x00\x00\x00' > "$WASM_DIR/badmagic.wasm" + diff --git a/test/run_tests.sh b/test/run_tests.sh new file mode 100755 index 0000000..89add93 --- /dev/null +++ b/test/run_tests.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# Run from the repo root + +WASM=./src/wasm +PASS=0 +FAIL=0 +TESTS=0 + +check() { + TESTS=$((TESTS + 1)) + desc="$1"; shift + expected="$1"; shift + output=$("$@" 2>&1) + if echo "$output" | grep -q "$expected"; then + PASS=$((PASS + 1)) + echo " OK $desc" + else + FAIL=$((FAIL + 1)) + echo " FAIL $desc" + echo " expected: $expected" + echo " got: $output" + fi +} + +echo "=== wasm runtime tests ===" + +check "no arguments" "Usage:" $WASM +check "valid wasm" "Valid wasm file" $WASM test/wasm/valid.wasm +check "bad magic" "invalid wasm magic" $WASM test/wasm/badmagic.wasm +check "bad version" "unsupported wasm" $WASM test/wasm/badver.wasm + +echo "" +echo "Results: $PASS/$TESTS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] && exit 0 || exit 1 diff --git a/test/wasm/badmagic.wasm b/test/wasm/badmagic.wasm new file mode 100644 index 0000000..ec8e74b Binary files /dev/null and b/test/wasm/badmagic.wasm differ diff --git a/test/wasm/badver.wasm b/test/wasm/badver.wasm new file mode 100644 index 0000000..09c7ef4 Binary files /dev/null and b/test/wasm/badver.wasm differ diff --git a/test/wasm/valid.wasm b/test/wasm/valid.wasm new file mode 100644 index 0000000..f2fb7ee Binary files /dev/null and b/test/wasm/valid.wasm differ diff --git a/test/wat/valid.wat b/test/wat/valid.wat new file mode 100644 index 0000000..76f7506 --- /dev/null +++ b/test/wat/valid.wat @@ -0,0 +1,5 @@ +(module + (func (export "getSomething") (result i32) + i32.const 42 + ) +)