Skip to content

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

Description

@Aristide021

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIncorrect behavior in the current implementation that needs fixing

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions