Fixed, verified end-to-end (guest dlerror() text included), and deployed to production.
Root cause
The HWJS_PARK_DLOPEN service arm in src/worker.mjs wrapped serviceDlopen in catch (e) { ... dlret = -12; } — every host exception, whatever its class, became -ENOMEM, and the guest had no channel for the real message at all (musl/glibc could only format the errno).
Fix (hardwarejs 7c16a44)
1. Classification — classifyDlopenFailure (exported from src/dynLoader.mjs, used by the service arm):
| host failure |
errno |
class |
WebAssembly.LinkError (import-type mismatch — your case 1) |
-ELIBBAD (80) |
link |
WebAssembly.CompileError / not-wasm (bad magic) |
-ENOEXEC (8) |
compile |
RangeError (engine grow OOM), the loader's never-silent mmap-OOM throw, shared-table exhaustion |
-ENOMEM (12) |
alloc |
hwjs-dlopen: load-policy refusals (manifest skew, PIC holes, sp-fabrication, unresolved imports) |
-ELIBBAD (80) |
policy |
| anything unrecognized |
-EINVAL (22) |
unknown |
Only genuine allocation failure keeps ENOMEM — your case 2 (and linux-in-the-browser#70's real OOM) still reads errno 12, now with the OOM text attached.
2. The dlerror side channel — new SYS_wasm_dlerror (1043, synchronous host intercept like dlsym; added to the asm/hwjs_dlopen.h shared ABI record): the service latches the real failure text on every failure path (classified throws AND the synchronous refusal errnos: ENOSYS/no-staging, static image, kernel staging errno), cleared at each SYS_wasm_dlopen latch. musl (src/ldso/wasm32/dlopen.c) and glibc (sysdeps/wasm32/dl-open.c) fetch it on a failed cookie and feed it to dlerror().
3. Host-side attribution — the user-report now carries class -> errno + full message, plus a guaranteed console.error copy, so a failing dlopen is diagnosable from the page console even without the guest half.
Before / after (guest stdout, real boots)
RED (pre-fix arm, import-type-mismatch .so):
dlopen failed: dlopen("/suspder2.so") failed (errno 12)
GREEN (final artifacts):
dlopen failed: dlopen("/suspder2.so") failed (errno 80): [link -> ELIBBAD] WebAssembly.Instance(): Import #17 "env" "read": imported function does not match the expected type
Simulated genuine OOM (HWJS_RED_DLOPEN_OOM=1, drives the real allocMemory throw):
dlopen failed: dlopen("/suspder2.so") failed (errno 12): [alloc -> ENOMEM] hwjs-dlopen: SYS_mmap for the .so data region (36B) returned 0 (never-silent OOM)
Gate
test/d1-dlopen-errno.test.ts — 8 unit classification arms + 2 e2e kernel boots: (a) d1-dlopen-ebad cpio ships a byte-patched import-type-mismatch .so (new distros/common/dlopen-fixtures/patch-import-type.mjs; GOOD manifest, still validates, LinkError at instantiate) asserting errno 80 + the verbatim engine text + the attributed host report; (b) the OOM knob asserting ENOMEM is preserved. The RED arm genuinely flips: run against the pre-fix catch-all, (a) fails with the observed errno 12 and (b) fails because the knob doesn't exist. Full d1-dlopen* + dlopen-loader* suites green (74 tests) on the rebuilt vmlinux + rebuilt musl fixtures.
SHAs
- hardwarejs
7c16a44 (service + classifier + side channel + gate)
- musl
33f83995 (dlopen fetches the host text; libc/musl-bc/fixture mains rebuilt)
- glibc
a2f08bf792e (_dl_open ditto; pool recompiled — elf_dl-open.bc verified to carry the 1043 call. Honest caveat: there is no glibc guest dlopen gate yet, so the glibc text half is compile-verified only; the errno half needs no guest rebuild and is live for existing binaries)
- linux
9f2e14926374 (shared ABI record; vmlinux rebuilt)
- superproject
704341e (gitlinks + the ebad fixture rig)
Deployed
make web && make web-sync + sst deploy --stage production — https://brintos.io/hardwarejs/worker.mjs and dynLoader.mjs verified on the live URLs to carry SYS_WASM_DLERROR/classifyDlopenFailure.
This un-masks the diagnostics behind linux-in-the-browser#70/#71/#73: #71's import-type refusal now surfaces its exact engine message in dlerror() (and on the page console), #70's genuine ENOMEM still reads ENOMEM (now with the allocation site named), and anything mis-attributed to memory pressure by the old collapse will now show its true class. Note existing guest binaries get the faithful errno immediately (host-side change); picking up the dlerror text requires a relink against the updated musl/glibc.