From 5bf9557b5ac1b26dd860a37026f817f85b24910d Mon Sep 17 00:00:00 2001 From: _ov4 Date: Mon, 19 Oct 2026 11:11:20 +0000 Subject: fix(driver): register one sorted SEH table per JIT object Separate .pdata and .pdata$ registrations share an image base and shadow other functions during unwind lookup. Combine their relocated entries in one sorted, allocation-owned table so C++ catches and stack restoration work across the entire object. Preserve allocation-action cleanup. Verified the exp-string JIT reproduction under Wine: exit 5 before the change, exit 0 with exact expected output afterward. All 164 Linux tests also pass. Co-Authored-By: Claude Opus 5.5 --- tools/driver/EmbeddedRunner.cc | 113 +++++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 39 deletions(-) (limited to 'tools') diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc index c8c3fcd..66af215 100644 --- a/tools/driver/EmbeddedRunner.cc +++ b/tools/driver/EmbeddedRunner.cc @@ -21,6 +21,8 @@ #ifndef NOMINMAX #define NOMINMAX #endif +#include +#include #include #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/ExecutionEngine/JITLink/x86_64.h" @@ -50,6 +52,8 @@ namespace { // actions. Exception propagation still uses the existing C++ ABI, LLVM // exception instructions and platform unwinder. +static constexpr llvm::StringLiteral COFFSEHTableSection = "$__cpsc_seh"; + static llvm::Error addCOFFJumpStubs(llvm::jitlink::LinkGraph &G) { using namespace llvm; using namespace llvm::jitlink; @@ -187,6 +191,29 @@ public: // The same target reuses the same stub, including personality uses. Config.PostPrunePasses.push_back(addCOFFJumpStubs); + // Tables sharing an image base have overlapping lookup ranges in the + // Windows unwinder. Reserve one table for all retained .pdata sections + // in this graph, then fill and sort its entries after relocations. + Config.PostPrunePasses.push_back([](LinkGraph &G) -> Error { + size_t Size = 0; + for (auto &Sec : G.sections()) { + if (!isCOFFUnwindSection(Sec.getName(), ".pdata")) + continue; + for (auto *B : Sec.blocks()) { + if (B->isZeroFill() || B->getSize() % sizeof(RUNTIME_FUNCTION)) + return make_error( + "invalid embedded SEH function table"); + Size += B->getSize(); + } + } + if (Size) { + auto &Sec = G.createSection(COFFSEHTableSection, MemProt::Read); + G.createMutableContentBlock(Sec, Size, ExecutorAddr(), + alignof(RUNTIME_FUNCTION), 0); + } + return Error::success(); + }); + // COFF unwind tables use 32-bit image-relative addresses, so the // image base must sit at or below the lowest JIT address. Alias the // external __ImageBase to the lowest defined block once addresses @@ -215,52 +242,60 @@ public: return Error::success(); }); - // After relocations are applied, register the function table with - // the system unwinder via allocation actions. Table contents are - // validated in the registration callback; here only locate the - // non-empty .pdata/.pdata$ sections and the graph-local image base. + // Copy fixed-up entries into one sorted, allocation-owned table. + // Register it once per graph, with deregistration on link failure or + // teardown before the table and its code are released. Config.PostFixupPasses.push_back( [](LinkGraph &G) -> Error { using namespace llvm::orc::shared; + auto *TableSec = G.findSectionByName(COFFSEHTableSection); + if (!TableSec) + return Error::success(); + auto *Table = *TableSec->blocks().begin(); + std::vector Entries; + Entries.reserve(Table->getSize() / sizeof(RUNTIME_FUNCTION)); for (auto &Sec : G.sections()) { if (!isCOFFUnwindSection(Sec.getName(), ".pdata")) continue; - jitlink::SectionRange Range(Sec); - if (Range.empty()) - continue; - auto TableRange = Range.getRange(); - // Resolve the graph-local image base established above. - auto *ImageBaseSym = - G.findDefinedSymbolByName(G.intern("__ImageBase")); - if (!ImageBaseSym) - return make_error( - "missing embedded __ImageBase for SEH registration"); - ExecutorAddr ImageBase = ImageBaseSym->getAddress(); - if (ImageBase.isNull()) - return make_error( - "invalid embedded SEH image base"); - // Registration failure must prevent entry to JIT code; the - // deregistration action also covers link-failure cleanup and - // normal teardown before the memory is released. - auto RegisterAction = - WrapperFunctionCall::Create>( - ExecutorAddr::fromPtr( - &cps_register_seh_table_alloc_action), - TableRange, ImageBase); - if (!RegisterAction) - return RegisterAction.takeError(); - auto DeregisterAction = - WrapperFunctionCall::Create>( - ExecutorAddr::fromPtr( - &cps_deregister_seh_table_alloc_action), - TableRange, ImageBase); - if (!DeregisterAction) - return DeregisterAction.takeError(); - G.allocActions().push_back( - {std::move(*RegisterAction), std::move(*DeregisterAction)}); + for (auto *B : Sec.blocks()) { + size_t Start = Entries.size(); + Entries.resize(Start + B->getSize() / sizeof(RUNTIME_FUNCTION)); + if (B->getSize()) + std::memcpy(Entries.data() + Start, B->getContent().data(), + B->getSize()); + } } + std::sort(Entries.begin(), Entries.end(), + [](const RUNTIME_FUNCTION &L, const RUNTIME_FUNCTION &R) { + return L.BeginAddress < R.BeginAddress; + }); + std::memcpy(Table->getAlreadyMutableContent().data(), Entries.data(), + Table->getSize()); + ExecutorAddrRange TableRange(Table->getAddress(), Table->getSize()); + auto *ImageBaseSym = + G.findDefinedSymbolByName(G.intern("__ImageBase")); + if (!ImageBaseSym) + return make_error( + "missing embedded __ImageBase for SEH registration"); + ExecutorAddr ImageBase = ImageBaseSym->getAddress(); + if (ImageBase.isNull()) + return make_error("invalid embedded SEH image base"); + auto RegisterAction = + WrapperFunctionCall::Create>( + ExecutorAddr::fromPtr(&cps_register_seh_table_alloc_action), + TableRange, ImageBase); + if (!RegisterAction) + return RegisterAction.takeError(); + auto DeregisterAction = + WrapperFunctionCall::Create>( + ExecutorAddr::fromPtr(&cps_deregister_seh_table_alloc_action), + TableRange, ImageBase); + if (!DeregisterAction) + return DeregisterAction.takeError(); + G.allocActions().push_back( + {std::move(*RegisterAction), std::move(*DeregisterAction)}); return Error::success(); }); (void)MR; -- cgit v1.3.1-4-g156e