Stream: ComponentizeJS

Topic: Enable debugger for components that don't target wasi/http


view this post on Zulip Tomasz Andrzejak (Jul 01 2025 at 14:49):

Hey! I'm trying to enable debugging for StarlingMonkey components that don't target WASI/HTTP. Based on my understanding this should be possible by:

  1. Removing debugger session initialization from the StarlingMonkey fetch-event handler wrapper at https://github.com/bytecodealliance/StarlingMonkey/blob/main/builtins/web/fetch/fetch_event.cpp#L547
  2. Calling the debugger initialization function from the componentize-js builtin on the first call to an exported function at https://github.com/bytecodealliance/ComponentizeJS/blob/main/embedding/embedding.cpp#L150

The problem is that after calling content_debugger::maybe_init_debugger from step 2, the function cannot access the environment variables needed for debugger configuration (such as DEBUGGER_PORT) that I pass to the wasmtime run command.

I see that the environment is deinitialized after wizening, which makes sense to avoid polluting variables from the wizening pass. I tried to call __wasilibc_initialize_environ again within the debugger initialization function, but the environment list remains empty.

Does anyone know why the environment variables aren't being set? Why would this work when we have http handler installed but not for an arbitrary entrypoint?

With those changes I would also hope to fix the implicit dependency on wasi/environement by moving this call to debugger initialization so that it is gated behind debugger opt-in.

my config

package component:greeter;

interface greeter {
    hello-user: func(user: string);
}

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 greeter;
}
export const greeter = {
  helloUser(user) {
    console.log("Hello", user);
  }
}
componentize-js --aot --wit wit --world-name example --runtime-args "--enable-script-debugging" -o out.wasm index.js
wasmtime run -S cli,tcp,http --dir /home/tandr/workspace/sm-debug/::/ --invoke 'hello-user("Gordon Shumway")' out.wasm --env STARLINGMONKEY_CONFIG="--verbose -d" --env DEBUGGER_PORT=64373
The StarlingMonkey JS runtime. Contribute to bytecodealliance/StarlingMonkey development by creating an account on GitHub.
The StarlingMonkey JS runtime. Contribute to bytecodealliance/StarlingMonkey development by creating an account on GitHub.
JS -> WebAssembly Component. Contribute to bytecodealliance/ComponentizeJS development by creating an account on GitHub.

view this post on Zulip Tomasz Andrzejak (Jul 01 2025 at 15:01):

The ComponentizeJS diff looks like this:

diff --git a/embedding/embedding.cpp b/embedding/embedding.cpp
index 585f3ed..60ba219 100644
--- a/embedding/embedding.cpp
+++ b/embedding/embedding.cpp
@@ -1,4 +1,5 @@
 #include "embedding.h"
+#include "debugger.h"
 #include "builtins/web/performance.h"

 namespace builtins::web::console {
@@ -147,6 +148,7 @@ cabi_realloc(void *ptr, size_t orig_size, size_t org_align, size_t new_size) {
 __attribute__((export_name("call"))) uint32_t call(uint32_t fn_idx,
                                                    void *argptr) {
   if (Runtime.first_call) {
+    content_debugger::maybe_init_debugger(Runtime.engine, true);
     js::ResetMathRandomSeed(Runtime.cx);
     Runtime.first_call = false;
     if (Runtime.clocks) {

The StarlingMonkey diff:

diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp
index e87bd9e..e33ec36 100644
--- a/host-apis/wasi-0.2.0/host_api.cpp
+++ b/host-apis/wasi-0.2.0/host_api.cpp
@@ -2,8 +2,6 @@
 #include "bindings/bindings.h"
 #include "handles.h"

-#include <wasi/libc-environ.h>
-
 static std::optional<wasi_clocks_monotonic_clock_own_pollable_t> immediately_ready;

 size_t poll_handles(vector<WASIHandle<host_api::Pollable>::Borrowed> handles) {
@@ -1032,9 +1030,6 @@ void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request reque
   // that it properly initializes the runtime and installs a request handler.
   if (!REQUEST_HANDLER) {
     init_from_environment();
-  } else {
-    // Resuming a wizer snapshot, so we have to ensure that the environment is reset.
-    __wasilibc_initialize_environ();
   }
   MOZ_ASSERT(REQUEST_HANDLER);

diff --git a/runtime/debugger.cpp b/runtime/debugger.cpp
index e81dcaa..dececee 100644
--- a/runtime/debugger.cpp
+++ b/runtime/debugger.cpp
@@ -4,6 +4,8 @@
 #include <encode.h>
 #include <js/CompilationAndEvaluation.h>
 #include <js/SourceText.h>
+#include <wasi/libc-environ.h>
+
 #include <string_view>

 mozilla::Maybe<std::string> main_path;
@@ -278,6 +280,10 @@ void content_debugger::maybe_init_debugger(api::Engine * engine, bool content_al
     return;
   }
   debugger_initialized = true;
+
+  // Resuming a wizer snapshot, so we have to ensure that the environment is reset.
+  __wasilibc_initialize_environ();
+
   auto port_str = std::getenv("DEBUGGER_PORT");
   if (port_str) {
     uint32_t port = std::stoi(port_str);

view this post on Zulip Tomasz Andrzejak (Jul 02 2025 at 12:25):

I think I've found what was the issue:

wasmtime run command is interpreting everything after positional argument as an input to WASM module, or when using invoke option, an input to specified function as per trailing_var_arg = true setting:

https://github.com/bytecodealliance/wasmtime/blob/main/src/commands/run.rs#L80

The debugger plugin is appending the --env options to componentRuntime , so when I switch the debugger command from serve to run all appended env variables are being parsed as an module input not actual wasmtime options.

As a side note, I think requiring -- to separate wasmtime options from WASM arguments could be beneficial.

A lightweight WebAssembly runtime that is fast, secure, and standards-compliant - bytecodealliance/wasmtime

Last updated: Dec 06 2025 at 07:03 UTC