bytecodealliance / bytecodealliance/wasmtime

Debugger breakpoints leak into compilation cache entries; later runs crash with SIGTRAP

Open
#13,865 1 comment 0 reactions 0 assignees View on GitHub
bug wasmtime:debugging
Dominant language
Rust
Stars
18.6k
Forks
1.8k
Avg merge
1d 19h
Merged PRs (30d)
121

Description

### Test Case

Attached: `demo.wasm` plus its source:

[wasmtime-cache-bug-repro.zip](https://github.com/user-attachments/files/29925276/wasmtime-cache-bug-repro.zip)

```rust
// 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.