Hello @esoriaAsobo,
Thanks for looking into this. I’ve done extensive investigation and I think we probably found the root cause of the double mkdir/stat crash.
The key condition to reproduce: chdir must be called before the failing calls.
If your WASM module doesn’t call chdir, you won’t see the crash. That’s why a simple test with just two mkdir calls may work fine on your end.
Minimal reproducer:
MSFS_CALLBACK void module_init(void) {
chdir("/work"); // this activates the broken code path
mkdir("./work/test1", 0755); // works
mkdir("./work/test1", 0755); // 0xC0000005 crash
}
Remove the chdir call → no crash.
Add it back → crash on second mkdir (or stat, open, fopen, etc.).
If it can be helpful, here are my detailed findings about this subtle issue, with a hypothesis of the underlying cause:
Bug details
Root cause: compiler bug in the MSFS WASM toolchain’s clang
The MSFS SDK ships a custom LLVM/Clang fork (from AsoboStudio/innative-llvm.git, clang 15.0.1). This compiler seems to have a bug in how it handles static __thread variables in static (file-scope) functions when targeting WASM.
In wasi-libc’s posix.c, the internal function find_relpath has two static variables:
static int find_relpath(const char *path, char **relative) {
static __thread char *relative_buf = NULL;
static __thread size_t relative_buf_len = 0;
// ...passes &relative_buf and &relative_buf_len to __wasilibc_find_relpath_alloc
}
The compiler inlines find_relpath into every POSIX function that calls it (mkdir, stat, open, etc.). After inlining, it miscompiles the storage:
relative_buf (the char*) → demoted to a stack local (zeroed every call)
relative_buf_len (the size_t) → correctly placed in .data (persists between calls)
I verified this with llvm-nm on the posix.o inside the SDK’s libc.a:
Asobo’s libc.a (from MSFS SDK wasi-sysroot):
00000000 d find_relpath.relative_buf_len
find_relpath.relative_buf is completely absent — demoted to stack.
Official wasi-sdk-17 libc.a (compiled by upstream clang 15):
00000000 d find_relpath.relative_buf
00000004 d find_relpath.relative_buf_len
Both correctly in .data.
The crash sequence:
These two variables are passed by address to __wasilibc_find_relpath_alloc (defined in wasi-libc’s chdir.c), which manages a reusable path buffer:
-
First call: *relative_buf_len is 0, so _alloc does realloc(NULL, needed) → allocates buffer, stores pointer in *relative_buf (stack slot), sets *relative_buf_len (.data). strcpy(*relative_buf, rel) succeeds. Function returns, stack frame is destroyed, buffer pointer is lost.
-
Second call: Fresh stack frame → *relative_buf is NULL. But *relative_buf_len still has the old value from .data. _alloc sees len >= needed, skips realloc, does strcpy(*relative_buf, rel) → strcpy(NULL, ...) → access violation 0xC0000005.
Why chdir is the trigger:
__wasilibc_find_relpath_alloc is defined in chdir.o as a weak symbol. Without chdir linked into the binary, chdir.o isn’t pulled in, _alloc stays NULL, and wasi-libc’s find_relpath2 takes a fallback path through __wasilibc_find_relpath → __wasilibc_find_abspath. That fallback path simply sets a pointer to an existing string in the preopens table — no strcpy, no buffer management — so the broken statics are harmless.
Once chdir is linked (by calling it), _alloc becomes non-NULL, find_relpath2 takes the _alloc path, and the mismatched statics become fatal.
For now, I’m using a workaround for this, using linker’s –wrap option to intercept __wasilibc_find_relpath_alloc and wrapping it with correct buffers