Test Case
Attached: demo.wasm plus its source:
wasmtime-cache-bug-repro.zip
// demo.rs
fn add(a: i32, b: i32) -> i32 {
let c = a + b;
c
}
fn main() {
println!("{}", add(3, 4));
}
Steps to Reproduce
Step 1: rm -rf ~/Library/Caches/BytecodeAlliance.wasmtime (Linux: ~/.cache/wasmtime) so the cache is cold
Step 2: Debug the module under lldb with one breakpoint, the initial run compiles the module and populates the cache.
lldb --batch \
-o "settings set plugin.jit-loader.gdb.enable on" \
-o "breakpoint set --file demo.rs --line 2" \
-o run \
-o continue \
-- wasmtime run -D debug-info -O opt-level=0 demo.wasm
then echo $?
Step 3: Run the module again without debugger
wasmtime run -D debug-info -O opt-level=0 demo.wasm
then echo $?
Expected Results
Both runs print 7 and exit 0.
- the debugged run stops at the breakpoint, then continues to completion normally
- the plain run afterward is unaffected by the earlier debug session
Actual Results
The debugged run itself behaves normally, the breakpoint hits and the program works. But the next run of the module, with no debugger attached, is killed by SIGTRAP with no output (macOS: exit 133; Linux: Trace/breakpoint trap). Every run after that fails the same way until the cache is cleared, after which the same command prints 7 again.
Debugging against a warm cache does not corrupt it; the corruption only happens when the debugged process is the one that compiles the module and writes the cache entry.
Versions and Environment
Wasmtime version or commit: 46.0.1 (also reproduced on 45.0.0)
Operating system: MacOS 26.5.2 and Linux (Debian trixie); LLDB 17 (Xcode) and LLDB 19 (Debian)
Architecture: aarch64 (both)
Extra Info
What I checked while root-causing this:
-
The cache entry written by the debugged run differs from one written by an undebugged run of the same module/flags (sha256 mismatch, same cache key/filename).
-
Running the "plain" step under lldb instead: EXC_BREAKPOINT at exactly the address of the breakpoint from the first session.
-
Dumping the JIT-registered image from a process running off the bad entry shows 0xd4200000 (debugserver's arm64 breakpoint opcode) at the previously-breakpointed address. wasmtime compile + llvm-objdump of the same module/flags has an ordinary instruction there, and that encoding doesn't appear in the wasmtime tree
The mechanism appears to be in CodeBuilder::compile_cached (crates/wasmtime/src/compile/runtime.rs): the compute closure publishes the mmap (which triggers GDB-JIT registration, at which point the debugger writes its breakpoints into the mapping) and the serialize closure then reads code.mmap().to_vec() from that same mapping. I'd propose serializing from a copy captured before publish_mmap, unless you'd prefer a different approach.
Workaround until then: -C cache=n while debugging, plus clearing the cache once if already affected.
Test Case
Attached:
demo.wasmplus its source:wasmtime-cache-bug-repro.zip
Steps to Reproduce
Step 1: rm -rf ~/Library/Caches/BytecodeAlliance.wasmtime (Linux: ~/.cache/wasmtime) so the cache is cold
Step 2: Debug the module under lldb with one breakpoint, the initial run compiles the module and populates the cache.
then
echo $?Step 3: Run the module again without debugger
wasmtime run -D debug-info -O opt-level=0 demo.wasmthen
echo $?Expected Results
Both runs print 7 and exit 0.
Actual Results
The debugged run itself behaves normally, the breakpoint hits and the program works. But the next run of the module, with no debugger attached, is killed by SIGTRAP with no output (macOS: exit 133; Linux: Trace/breakpoint trap). Every run after that fails the same way until the cache is cleared, after which the same command prints 7 again.
Debugging against a warm cache does not corrupt it; the corruption only happens when the debugged process is the one that compiles the module and writes the cache entry.
Versions and Environment
Wasmtime version or commit: 46.0.1 (also reproduced on 45.0.0)
Operating system: MacOS 26.5.2 and Linux (Debian trixie); LLDB 17 (Xcode) and LLDB 19 (Debian)
Architecture: aarch64 (both)
Extra Info
What I checked while root-causing this:
The cache entry written by the debugged run differs from one written by an undebugged run of the same module/flags (sha256 mismatch, same cache key/filename).
Running the "plain" step under lldb instead: EXC_BREAKPOINT at exactly the address of the breakpoint from the first session.
Dumping the JIT-registered image from a process running off the bad entry shows 0xd4200000 (debugserver's arm64 breakpoint opcode) at the previously-breakpointed address. wasmtime compile + llvm-objdump of the same module/flags has an ordinary instruction there, and that encoding doesn't appear in the wasmtime tree
The mechanism appears to be in CodeBuilder::compile_cached (crates/wasmtime/src/compile/runtime.rs): the compute closure publishes the mmap (which triggers GDB-JIT registration, at which point the debugger writes its breakpoints into the mapping) and the serialize closure then reads code.mmap().to_vec() from that same mapping. I'd propose serializing from a copy captured before publish_mmap, unless you'd prefer a different approach.
Workaround until then: -C cache=n while debugging, plus clearing the cache once if already affected.