fix: safely write usePollingTraps by temporarily unprotecting frozen JSC config page

This commit is contained in:
Alistair Smith
2026-02-13 17:36:34 -08:00
parent 49c2ef2732
commit 4a9c54d03b
2 changed files with 24 additions and 2 deletions

View File

@@ -13,6 +13,9 @@
#include "BunInjectedScriptHost.h"
#include <JavaScriptCore/JSGlobalObjectInspectorController.h>
#include <sys/mman.h>
#include <unistd.h>
#include <JavaScriptCore/JSCConfig.h>
#include "InspectorLifecycleAgent.h"
#include "InspectorTestReporterAgent.h"
#include "InspectorBunFrontendDevServerAgent.h"
@@ -1003,10 +1006,27 @@ extern "C" void VM__cancelStop(JSC::VM* vm)
vm->cancelStop();
}
// Called from Zig when the event loop path activates the inspector.
// Ensures runtimeInspectorActivated is set so that connect() and
// Called from Zig and from the STW callback when the inspector activates.
// Sets runtimeInspectorActivated so that connect() and
// interruptForMessageDelivery() use STW-based message delivery.
// Also enables usePollingTraps for 100% reliable trap delivery in DFG/FTL.
// Since JSC::Options are mprotected read-only after init, we temporarily
// unprotect the config page, write, and re-protect.
extern "C" void Bun__activateRuntimeInspectorMode()
{
Bun::runtimeInspectorActivated.store(true);
// Enable polling traps so NeedDebuggerBreak is checked at every loop
// back-edge in DFG/FTL code. Without this, while(true){} can only be
// interrupted by signal-based traps (~94% reliable per attempt).
auto& options = g_jscConfig.options;
if (!options.usePollingTraps) {
// The config page is frozen (mprotect PROT_READ). Temporarily make it writable.
size_t pageSize = sysconf(_SC_PAGESIZE);
void* pageBase = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(&options) & ~(pageSize - 1));
size_t protectSize = (reinterpret_cast<uintptr_t>(&options) + sizeof(options) - reinterpret_cast<uintptr_t>(pageBase) + pageSize - 1) & ~(pageSize - 1);
mprotect(pageBase, protectSize, PROT_READ | PROT_WRITE);
options.usePollingTraps = true;
mprotect(pageBase, protectSize, PROT_READ);
}
}

View File

@@ -314,6 +314,8 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
// The tradeoff: signal-based trap delivery for requestStopAll (used by the
// runtime inspector via SIGUSR1) is ~94% reliable vs 100% with polling.
// We accept this for the inspector path since speed is the priority.
// IMPORTANT: JSC::Options are frozen (mprotected read-only) after init.
// Writing to usePollingTraps later crashes on Linux with SEGV at offset 0xB34.
JSC::Options::evalMode() = evalMode;
JSC::Options::heapGrowthSteepnessFactor() = 1.0;
JSC::Options::heapGrowthMaxIncrease() = 2.0;