Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions Source/JavaScriptCore/jit/ExecutableAllocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,20 @@ struct JITReservation {
// RegisterExecutableMemory (js/src/jit/ProcessExecutableMemory.cpp).
// RtlAddGrowableFunctionTable rather than RtlAddFunctionTable so that
// out-of-process stack walkers (ETW, WPA, WinDbg) see the entry too.
//
// The offlineasm code linked into the image (LLInt, vmEntryToJavaScript and
// the other vmEntry* trampolines) has the same frame shape but cannot be
// covered by a dynamic table: for a PC inside a loaded module the unwinder
// consults only that module's static .pdata. LowLevelInterpreter.cpp therefore
// assembles a static .pdata/.xdata record with the same unwind codes over
// jsc_llint_begin..jsc_llint_end (as V8 does for its embedded builtins in
// platform-embedded-file-writer-win.cc). Being in the image, that record can
// name jscJITSEHHandler directly, which is why the handler has a C name.

static Atomic<void*> g_jitSEHFunctionTable { nullptr };
static Atomic<JITExceptionHandlerWin> g_jitSEHCallback { nullptr };

static EXCEPTION_DISPOSITION jscJITSEHHandler(PEXCEPTION_RECORD exceptionRecord, PVOID establisherFrame, PCONTEXT contextRecord, PDISPATCHER_CONTEXT dispatcherContext)
extern "C" EXCEPTION_DISPOSITION jscJITSEHHandler(PEXCEPTION_RECORD exceptionRecord, PVOID establisherFrame, PCONTEXT contextRecord, PDISPATCHER_CONTEXT dispatcherContext)
{
if (auto callback = g_jitSEHCallback.loadRelaxed())
return static_cast<EXCEPTION_DISPOSITION>(callback(exceptionRecord, establisherFrame, contextRecord, dispatcherContext));
Expand Down Expand Up @@ -561,14 +570,6 @@ static void registerJITUnwindInfo(PageReservation& pageReservation, void*& base,

#endif

// LLInt / vmEntryToJavaScript live in image .text with no .pdata of their own
// (offlineasm emits no .seh_* directives). A dynamic function table cannot
// cover those: RtlLookupFunctionEntry for a PC inside a loaded module consults
// only that module's static .pdata. V8 solves this at build time by emitting
// .pdata/.xdata for its embedded builtins (platform-embedded-file-writer-
// win.cc); the JSC equivalent is offlineasm emitting .seh_* directives, which
// is a separate change.

#endif // OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64))

WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
Expand Down
3 changes: 2 additions & 1 deletion Source/JavaScriptCore/jit/ExecutableAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ JS_EXPORT_PRIVATE void* NODELETE endOfFixedExecutableMemoryPoolImpl();

#if OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64))
// Set the language-specific SEH handler invoked when exception dispatch
// reaches a JIT frame. See registerJITUnwindInfo in ExecutableAllocator.cpp.
// reaches a JIT frame or an LLInt / vmEntry* frame. See registerJITUnwindInfo
// in ExecutableAllocator.cpp and the .pdata record in LowLevelInterpreter.cpp.
// Signature: EXCEPTION_DISPOSITION(PEXCEPTION_RECORD, PVOID establisherFrame,
// PCONTEXT, PDISPATCHER_CONTEXT).
using JITExceptionHandlerWin = long(__cdecl*)(void*, void*, void*, void*);
Expand Down
86 changes: 86 additions & 0 deletions Source/JavaScriptCore/llint/LowLevelInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,92 @@ __asm__(
#endif
#endif

#if OS(WINDOWS) && ENABLE(JIT) && (CPU(X86_64) || CPU(ARM64))
// The Windows unwinder (RtlLookupFunctionEntry, and through it RtlVirtualUnwind,
// RtlCaptureStackBackTrace, SEH dispatch, ETW and debuggers) only knows code that has
// a RUNTIME_FUNCTION in the image's .pdata: the DWARF CFI emitted above on ARM64 means
// nothing to it, and the dynamic function table that registerJITUnwindInfo
// (ExecutableAllocator.cpp) registers for the JIT pool cannot cover code inside the
// image. offlineasm emits no .seh_* directives, so without the record below every
// walk stops or derails at the first LLInt or vmEntry* frame.
//
// Everything between jsc_llint_begin and jsc_llint_end keeps the frame pointer on a
// CallFrame whose first two slots are the caller's frame pointer and the return
// address (functionPrologue), the frame shape registerJITUnwindInfo describes for the
// JIT pool. So one RUNTIME_FUNCTION over the whole range, carrying the unwind info
// registerJITUnwindInfo hand-encodes, unwinds all of it, and its language-specific
// handler gives a fault under these frames the same catch point a fault under a JIT
// frame has (hence ENABLE(JIT): the handler is defined in ExecutableAllocator.cpp).
// The range starts with the jsc_llint_begin and llintPCRangeStart trap instructions,
// so no real PC falls inside the prologue the record describes.
//
// The record is written out by hand rather than with .seh_* directives: on ARM64 LLVM
// needs the function length when it reaches .seh_endproc and cannot compute it across
// the alignment directives in the offlineasm output ("Failed to evaluate function
// length in SEH unwind info", llvm/llvm-project#47432, the bug the -fno-unwind-tables
// in CMakeLists.txt works around), whereas the data fixups below are resolved after
// layout. The RVAs are spelled ".long symbol@IMGREL" rather than ".rva symbol" (the
// two assemble to the same IMAGE_REL_*_ADDR32NB relocation) because LTO's scan of the
// module asm for the symbols it references does not see .rva operands: with .rva, a
// -flto build internalizes jscJITSEHHandler and the link fails with an undefined
// symbol referenced from .xdata.
#if CPU(X86_64)
__asm__(
".section .xdata,\"dr\"\n"
".p2align 2\n"
LOCAL_LABEL_STRING(jsc_llint_unwind_info) ":\n"
// UNWIND_INFO (https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64):
// the bytes of JITUnwindRecord::unwindInfo in ExecutableAllocator.cpp, except that
// the handler is addressed directly instead of through a thunk in the pool.
".byte 0x09\n" // Version 1, UNW_FLAG_EHANDLER
".byte 4\n" // SizeOfProlog: push rbp (1 byte); mov rbp, rsp (3 bytes)
".byte 2\n" // CountOfCodes
".byte 0x05\n" // FrameRegister rbp, FrameOffset 0
".byte 4, 0x03\n" // offset 4: UWOP_SET_FPREG
".byte 1, 0x50\n" // offset 1: UWOP_PUSH_NONVOL rbp
".long " SYMBOL_STRING(jscJITSEHHandler) "@IMGREL\n"

".section .pdata,\"dr\"\n"
".p2align 2\n"
".long " SYMBOL_STRING(jsc_llint_begin) "@IMGREL\n"
".long " SYMBOL_STRING(jsc_llint_end) "@IMGREL\n"
".long " LOCAL_LABEL_STRING(jsc_llint_unwind_info) "@IMGREL\n"

".text\n"
);
#elif CPU(ARM64)
__asm__(
// The .xdata header holds the function length as 18 bits of instructions, and the
// .long below would silently overflow into the flag bits once the range outgrows
// that. ADR reaches exactly 2^18 instructions back, so this instruction, which
// nothing executes (it sits after the trap that precedes jsc_llint_end), assembled
// right after jsc_llint_end and aimed one instruction before jsc_llint_begin, fails
// to assemble ("fixup value out of range") as soon as the range no longer fits.
".text\n"
"adr xzr, " SYMBOL_STRING(jsc_llint_begin) " - 4\n"

".section .xdata,\"dr\"\n"
".p2align 2\n"
LOCAL_LABEL_STRING(jsc_llint_unwind_info) ":\n"
// .xdata record (https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling):
// the layout and codes of JITUnwindHeader::unwindInfoFull in ExecutableAllocator.cpp
// (arm64XdataHeader / arm64JITUnwindCodes), with the real length and the handler
// addressed directly instead of through a thunk in the pool. Header fields:
// FunctionLength:18 | Version:2 = 0 | X:1 = 1 (handler present) | E:1 = 0 | EpilogCount:5 = 0 | CodeWords:5 = 1
".long ((" SYMBOL_STRING(jsc_llint_end) " - " SYMBOL_STRING(jsc_llint_begin) ") >> 2) | (1 << 20) | (1 << 27)\n"
".byte 0xE1, 0x81, 0xE4, 0xE3\n" // set_fp; save_fplr_x 16; end; nop (padding)
".long " SYMBOL_STRING(jscJITSEHHandler) "@IMGREL\n"

".section .pdata,\"dr\"\n"
".p2align 2\n"
".long " SYMBOL_STRING(jsc_llint_begin) "@IMGREL\n"
".long " LOCAL_LABEL_STRING(jsc_llint_unwind_info) "@IMGREL\n"

".text\n"
);
#endif
#endif // OS(WINDOWS) && ENABLE(JIT) && (CPU(X86_64) || CPU(ARM64))

DEBUGGER_ANNOTATION_MARKER(after_llint_asm)

#endif // ENABLE(C_LOOP)
Loading