Skip to content

Conversation

@tschneidereit
Copy link
Member

This commit introduces a substantial new feature: debugging support via the Debug Adapter Protocol.

While for now the debugger is experimental and not quite ready for production use, it already covers a decent amount of functionality: it supports breakpoints, single-stepping into and over function calls, stack and (local and global) scope inspection, and modifying locals and object properties.

Building on the platform support added in #218, the extension has two key components:

  1. a DAP implementation as a Node.js script that starts StarlingMonkey, starts a server for the debug session, and translates between the messages StarlingMonkey sends/receives and the DAP
  2. a VS Code extension providing launch configurations and settings for the debugger, and initiates the debug session

This commit introduces a substantial new feature: debugging support via the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/).

While for now the debugger is experimental and not quite ready for production use, it already covers a decent amount of functionality: it supports breakpoints, single-stepping into and over function calls, stack and (local and global) scope inspection, and modifying locals and object properties.

Building on the platform support added in bytecodealliance#218, the extension has two key components:
1. a DAP implementation as a Node.js script that starts StarlingMonkey, starts a server for the debug session, and translates between the messages StarlingMonkey sends/receives and the DAP
2. a VS Code extension providing launch configurations and settings for the debugger, and initiates the debug session
@tschneidereit tschneidereit force-pushed the vscode-debugging-extension branch from 7cbb804 to 3b7954f Compare March 22, 2025 14:38
Signed-off-by: Till Schneidereit <till@tillschneidereit.net>
@itowlson
Copy link
Contributor

itowlson commented Jun 2, 2025

@tschneidereit I've been having a tinker with this and I'm struggling to understand this behaviour:

  • I load the sample workspace and hit F5
  • VS Code goes into "debug mode" (shows debug UI, etc.)
  • I make a request to my debuggee component (e.g. curl). Breakpoints are hit, etc.
  • The request returns.
  • VS Code goes out of "debug mode."
    • wasmtime serve is still running and can still accept requests
    • Breakpoints are not hit
    • Requests cause the debug log to report No debugging session active, telling runtime to continue

There is a trace in the extension debug log disconnectRequest suspend: undefined, terminate: undefined which comes from the override of LoggingDebugSession.disconnectRequest.

So it can only handle one request per debug session. My expectation was that the debugger would remain connected to the debuggee until told to stop (at which point the wasmtime process would exit). And I can't find what is telling it to disconnect and terminate the debug session.

Do you have any pointers on why this is happening?

(I had a vague guess that it was something to do with the debugger.ts script running in the SM instance and that getting torn down at the end of the request, but I don't think that makes sense, because the debugger is clearly running before the request starts...?)

(Edit: it seems like something is hanging up the other end of the StarlingMonkeyRuntime._socket (triggering its end event) but I'm not sure if that's a symptom or a cause.)

@tschneidereit
Copy link
Member Author

So it can only handle one request per debug session. My expectation was that the debugger would remain connected to the debuggee until told to stop (at which point the wasmtime process would exit). And I can't find what is telling it to disconnect and terminate the debug session.

That's by design, though I'm very much up for discussing whether it's a good design.

The idea is that you only get debugging when you're requesting it, so you don't get weird phenomena such as your IDE taking you random places in your code because something sent a request to a server. Instead, you have to start a new debugging session if you want to debug again.

So why keep the server running, then? Because starting it might take time, and this way you always get quick turnaround after the first session.

At the very least it'd make sense to make this configurable, of course.

@squillace
Copy link

FWIW, I'd expect a "server" process to continue in debug mode until directly stopped, rather than have debug mode stop after each request. Sometimes it takes several iterations of walking through to realize precisely what the problem is.

@andreiltd
Copy link
Member

Hey @tschneidereit I'm curious about your thoughts on how do you envision running the debugger for scripts that don't target the incoming-handler?

FWICT, we currently initialize the debugger on incoming request. I'm wondering -- would it be possible to initialize it during engine startup instead (wizer resume perhaps) to allow debug components regardless of whether they have an incoming HTTP handler installed?

@tschneidereit
Copy link
Member Author

@andreiltd I'm a bit wary of unconditionally activating the debugger, since it might be the case that the logic for handling an inbound call might require some amount of setup before the debugger is started.

What we could certainly do though is to make it very easy to activate with a single call, and to document that properly.

@squillace
Copy link

Yes, probably we should have a way to a) configure whether debug is unconditionally started or not and b) figure out whether the specific solution there would require specifying an api call to start on? Wide open as to what the most flexible method would be here. We have some engineering time to burn here as well....

@tschneidereit
Copy link
Member Author

configure whether debug is unconditionally started or not

I don't think we need this part, as in a way it doesn't entirely make sense: we can only start debugging if a socket port to connect to has been provided during wasmtime serve as part of telling StarlingMonkey to enable debugging.

Basically, I think all that's needed is to add this call to the handler for incoming calls. I can't immediately see a reason why this couldn't be moved to here, which would take it out of the builtins implementation and into the runtime. That's probably the better place for it, and something we could readily document

@andreiltd
Copy link
Member

@tschneidereit thanks for the insight, this all makes sense. I was thinking if we could also make the init_debugger function visible from JS. I would imagine something like this should be possible with componentize-js?

wit:

package component:debugger;

interface debugger {
    initDbg: func();
}

world example {
    import wasi:cli/environment@0.2.3;
    import wasi:sockets/network@0.2.3;
    import wasi:sockets/instance-network@0.2.3;
    import wasi:sockets/tcp@0.2.3;
    import wasi:sockets/tcp-create-socket@0.2.3;
    
    export debugger;
}

index.js:

const debugger {
  function initDbg() {
    globalThis.maybeInitDebugger()
  }
}

run:

wasmtime run -S sockets --invoke 'initDbg' debugger.wasm

I would imagine socket.connect would block waiting for wasmtime providing a connection. We would probably need to skip the initialization when wizening.

@andreiltd
Copy link
Member

andreiltd commented Jun 25, 2025

We just briefly talked about this on the community meeting and I'm going to put a PR in componentize-js that will enable this automatically without need to expose config to wit.

@itowlson itowlson mentioned this pull request Jun 26, 2025
@tschneidereit
Copy link
Member Author

Superseded by #248, as mentioned there by @itowlson. I'll review the parts changed by Ivan, and figure out how best to get the parts reviewed that I wrote :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants