aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-19 11:11:20 +0000
committer_ov4 <ov4@ov4.org>2026-10-19 11:11:20 +0000
commit5bf9557b5ac1b26dd860a37026f817f85b24910d (patch)
tree1f5b5abd8623917cac19f57acf33156f8fe229ad /tools
parentcf0fb69b3d489cd0aebaa2d759b2d2c0c4e230b1 (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/driver/EmbeddedRunner.cc113
1 files changed, 74 insertions, 39 deletions
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 <algorithm>
+#include <cstring>
#include <limits>
#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<JITLinkError>(
+ "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<RUNTIME_FUNCTION> 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<JITLinkError>(
- "missing embedded __ImageBase for SEH registration");
- ExecutorAddr ImageBase = ImageBaseSym->getAddress();
- if (ImageBase.isNull())
- return make_error<JITLinkError>(
- "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<SPSArgList<SPSExecutorAddrRange,
- SPSExecutorAddr>>(
- ExecutorAddr::fromPtr(
- &cps_register_seh_table_alloc_action),
- TableRange, ImageBase);
- if (!RegisterAction)
- return RegisterAction.takeError();
- auto DeregisterAction =
- WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
- SPSExecutorAddr>>(
- 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<JITLinkError>(
+ "missing embedded __ImageBase for SEH registration");
+ ExecutorAddr ImageBase = ImageBaseSym->getAddress();
+ if (ImageBase.isNull())
+ return make_error<JITLinkError>("invalid embedded SEH image base");
+ auto RegisterAction =
+ WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
+ SPSExecutorAddr>>(
+ ExecutorAddr::fromPtr(&cps_register_seh_table_alloc_action),
+ TableRange, ImageBase);
+ if (!RegisterAction)
+ return RegisterAction.takeError();
+ auto DeregisterAction =
+ WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
+ SPSExecutorAddr>>(
+ 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;