cfallin opened PR #12791 from cfallin:lets-make-breakpoints-a-little-fuzzier-please to bytecodealliance:main:
LLDB, when instructed to
break main, looks at the DWARF metadata formainand finds its PC range, then sets a breakpoint at the first PC. This is reasonable behavior for native ISAs! That PC better be a real instruction!On Wasm, however, (i) toolchains typically emit the PC range as including the locals count, a leb128 value that precedes the first opcode and any types of locals; (ii) our gdbstub component that bridges LLDB to our debug APIs (#12771) only supports exact PCs for breakpoints, so when presented with a PC that does not actually point to an opcode, setting the breakpoint is effectively a no-op. There will always be a difference of at least 1 byte between the start-of-function offset and first-opcode offset (for a leb128 of
0for no locals), so a breakpoint "on" a function will never work.I initially prototyped a fix that adds a sequence point at the start of every function (which, again, is guaranteed to be distinct from the first opcode), and the branch is [here], but I didn't like the developer experience: this meant that when a breakpoint at a function start fired, LLDB had a weird interstitial state where no line-number applied.
The behavior that would be closer in line with "native" debug expectations is that we add a bit of fuzzy-ish matching: setting a breakpoint at function start should break at the first opcode, even if that's a few (or many) bytes later. There are two options here: special-case function start, or generally change the semantics of our breakpoint API so that "add breakpoint at
pc" means "add breakpoint at next opcode at or afterpc". I opted for the latter in this PR because it's more consistent.The logic is a little subtle because we're effectively defining an n-to-1 mapping with this "snap-to-next" behavior, so we have to refcount each breakpoint (consider setting a breakpoint at function start and at the first opcode, then deleting them, one at a time). I believe the result is self-consistent, even if a little more complicated now. And, importantly, with #12771 on top of this change, it produces the expected behavior for the (very simple!) debug script "
b main;continue".[here]: https://github.com/cfallin/wasmtime/tree/breakpoint-at-func-start
<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
cfallin requested dicej for a review on PR #12791.
cfallin requested wasmtime-core-reviewers for a review on PR #12791.
cfallin unassigned dicej from PR #12791 Debugging: allow breakpoints to be set at "function start" by slipping forward to first opcode..
cfallin requested alexcrichton for a review on PR #12791.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
Technically this is required from an lldb/gdbstub perspective I think anyway, right? In that if I set a breakpoint on a symbol and the same address and remove one the other should stay.
One option would be to return a
boolin Wasmtime if a breakpoint is set and push the refcounting up to the gdbstub itself, but I think it's fine to live in wasmtime too.
github-actions[bot] added the label wasmtime:api on PR #12791.
cfallin submitted PR review.
cfallin created PR review comment:
I'm actually not sure how much dedup lldb does but I believe it does track which addresses already have breakpoints -- otherwise even a simple gdbstub in say an embedded device that directly patches code would have to do refcounting, so in a "precise PC only" world it's likely not needed. In any case, it's needed now!
And yeah, I played around with where to put this but I think I prefer the complexity to live right where the breakpoint set is managed so we don't need to separately track it in every debug adapter we write.
cfallin added PR #12791 Debugging: allow breakpoints to be set at "function start" by slipping forward to first opcode. to the merge queue
cfallin merged PR #12791.
cfallin removed PR #12791 Debugging: allow breakpoints to be set at "function start" by slipping forward to first opcode. from the merge queue
Last updated: Mar 23 2026 at 16:19 UTC