aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-18 08:30:39 +0000
committer_ov4 <ov4@ov4.org>2026-10-18 08:30:39 +0000
commit20a6c049f93e14e9c4faaa9d66e657732f86adb3 (patch)
tree9cc4ebe738971d89f6e43c6323c2a94ddf42f4f9 /tools
parent14c03b7c26ff06835fca631eed6a3e308946321a (diff)
fix(driver): normalize MinGW unwind COMDATs before JIT linking
Associate symbol-less MinGW unwind sections with their code before graph construction, including objects loaded from runtime archives. Handle suffixed unwind sections in personality stubs and SEH table registration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/driver/EmbeddedRunner.cc113
-rw-r--r--tools/driver/MinGWCOFF.h99
2 files changed, 169 insertions, 43 deletions
diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc
index 3eedf8f..8950251 100644
--- a/tools/driver/EmbeddedRunner.cc
+++ b/tools/driver/EmbeddedRunner.cc
@@ -27,6 +27,7 @@
#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
#include "llvm/Support/Endian.h"
+#include "MinGWCOFF.h"
#endif
namespace {
@@ -59,7 +60,8 @@ static llvm::Error addCOFFJumpStubs(llvm::jitlink::LinkGraph &G) {
if (!Target.isExternal())
continue;
bool Personality = false;
- if (B->getSection().getName() == ".xdata" && Target.hasName()) {
+ if (isCOFFUnwindSection(B->getSection().getName(), ".xdata") &&
+ Target.hasName()) {
StringRef Name = *Target.getName();
Personality = Name == "__gxx_personality_seh0" ||
Name == "__gxx_personality_sj0" ||
@@ -168,7 +170,8 @@ public:
// SEH table liveness relies on the COFF backend's default
// SEHFrameKeepAlivePass(".pdata"), which keeps .pdata alive from its
- // referenced function/unwind blocks. No custom PrePrune pass here.
+ // referenced function/unwind blocks. Normalized .pdata$/.xdata$
+ // sections are kept alive by their associative COMDAT code edges.
// COFF unwind tables address the personality by 32-bit RVA, so it
// must live near JIT code. Retarget .xdata personality references
// to this graph's anonymous pointer jump stubs; the stub pointer
@@ -209,48 +212,49 @@ public:
// 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 and the graph-local image base.
+ // non-empty .pdata/.pdata$ sections and the graph-local image base.
Config.PostFixupPasses.push_back(
[](LinkGraph &G) -> Error {
using namespace llvm::orc::shared;
- auto *PDataSec = G.findSectionByName(".pdata");
- if (!PDataSec)
- return Error::success();
- jitlink::SectionRange Range(*PDataSec);
- if (Range.empty())
- return Error::success();
- 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 &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)});
+ }
return Error::success();
});
(void)MR;
@@ -269,6 +273,24 @@ public:
llvm::orc::ResourceKey,
llvm::orc::ResourceKey) override {}
};
+
+// Both user objects and lazily extracted runtime archive members pass through
+// this layer, before JITLink builds their graphs or runs plugin passes.
+class MinGWObjectLinkingLayer final : public llvm::orc::ObjectLinkingLayer {
+public:
+ using ObjectLinkingLayer::ObjectLinkingLayer;
+
+ void emit(std::unique_ptr<llvm::orc::MaterializationResponsibility> R,
+ std::unique_ptr<llvm::MemoryBuffer> O) override {
+ auto Normalized = normalizeMinGWUnwindSections(std::move(O));
+ if (!Normalized) {
+ getExecutionSession().reportError(Normalized.takeError());
+ R->failMaterialization();
+ return;
+ }
+ ObjectLinkingLayer::emit(std::move(R), std::move(*Normalized));
+ }
+};
#endif
// Embedded JITLink object layer. Created before LLJIT takes ownership so
@@ -278,8 +300,13 @@ static std::unique_ptr<llvm::orc::ObjectLinkingLayer>
createEmbeddedObjectLayer(llvm::orc::ExecutionSession &ES,
llvm::jitlink::JITLinkMemoryManager &MM,
const llvm::Triple &TT) {
- auto Layer =
- std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, MM);
+ std::unique_ptr<llvm::orc::ObjectLinkingLayer> Layer;
+#if defined(_WIN32) && defined(__x86_64__)
+ if (TT.isOSBinFormatCOFF() && TT.getArch() == llvm::Triple::x86_64)
+ Layer = std::make_unique<MinGWObjectLinkingLayer>(ES, MM);
+ else
+#endif
+ Layer = std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, MM);
if (TT.isOSBinFormatCOFF()) {
Layer->setOverrideObjectFlagsWithResponsibilityFlags(true);
Layer->setAutoClaimResponsibilityForObjectSymbols(true);
diff --git a/tools/driver/MinGWCOFF.h b/tools/driver/MinGWCOFF.h
new file mode 100644
index 0000000..22b8fb2
--- /dev/null
+++ b/tools/driver/MinGWCOFF.h
@@ -0,0 +1,99 @@
+#ifndef CPSC_DRIVER_MINGWCOFF_H
+#define CPSC_DRIVER_MINGWCOFF_H
+
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Object/COFF.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <cstring>
+
+namespace {
+
+inline bool isCOFFUnwindSection(llvm::StringRef Name, llvm::StringRef Base) {
+ return Name == Base || (Name.starts_with(Base) &&
+ Name.drop_front(Base.size()).starts_with("$"));
+}
+
+// MinGW emits .xdata$foo/.pdata$foo as SELECT_ANY without an external
+// COMDAT symbol. JITLink waits for that symbol and never creates the section
+// symbol used by relocations. Express their implicit association with .text$foo
+// as ordinary associative COMDATs in a private copy of the object instead.
+inline llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
+normalizeMinGWUnwindSections(std::unique_ptr<llvm::MemoryBuffer> Buffer) {
+ using namespace llvm;
+ auto Object = object::ObjectFile::createObjectFile(Buffer->getMemBufferRef());
+ if (!Object)
+ return Object.takeError();
+ auto *COFF = dyn_cast<object::COFFObjectFile>(Object->get());
+ if (!COFF || COFF->getMachine() != COFF::IMAGE_FILE_MACHINE_AMD64)
+ return std::move(Buffer);
+
+ StringMap<int32_t> CodeSections;
+ DenseSet<int32_t> ExternalSections;
+ for (const auto &Section : COFF->sections()) {
+ auto Name = Section.getName();
+ if (!Name)
+ return Name.takeError();
+ if (Name->starts_with(".text$") &&
+ (COFF->getCOFFSection(Section)->Characteristics &
+ COFF::IMAGE_SCN_LNK_COMDAT))
+ CodeSections[*Name] = COFF->getSectionID(Section);
+ }
+ for (const auto &Symbol : COFF->symbols()) {
+ auto Sym = COFF->getCOFFSymbol(Symbol);
+ if (Sym.isExternal() && Sym.getSectionNumber() > 0)
+ ExternalSections.insert(Sym.getSectionNumber());
+ }
+
+ struct Association { size_t Offset; int32_t Parent; };
+ SmallVector<Association, 16> Associations;
+ for (const auto &Symbol : COFF->symbols()) {
+ auto Sym = COFF->getCOFFSymbol(Symbol);
+ auto *Def = Sym.getSectionDefinition();
+ if (!Def || Def->Selection != COFF::IMAGE_COMDAT_SELECT_ANY ||
+ Sym.getSectionNumber() <= 0 ||
+ ExternalSections.contains(Sym.getSectionNumber()))
+ continue;
+ auto Section = COFF->getSection(Sym.getSectionNumber());
+ if (!Section)
+ return Section.takeError();
+ if (!((*Section)->Characteristics & COFF::IMAGE_SCN_LNK_COMDAT))
+ continue;
+ auto Name = COFF->getSectionName(*Section);
+ if (!Name)
+ return Name.takeError();
+ if (!Name->starts_with(".xdata$") && !Name->starts_with(".pdata$"))
+ continue;
+ std::string CodeName = (".text$" + Name->drop_front(7)).str();
+ auto Parent = CodeSections.find(CodeName);
+ if (Parent == CodeSections.end())
+ return make_error<StringError>(
+ "missing MinGW unwind code section '" + CodeName + "' in '" +
+ Buffer->getBufferIdentifier().str() + "'",
+ inconvertibleErrorCode());
+ Associations.push_back({static_cast<size_t>(
+ reinterpret_cast<const char *>(Def) -
+ Buffer->getBufferStart()),
+ Parent->second});
+ }
+ if (Associations.empty())
+ return std::move(Buffer);
+
+ auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(
+ Buffer->getBufferSize(), Buffer->getBufferIdentifier());
+ std::memcpy(Copy->getBufferStart(), Buffer->getBufferStart(),
+ Buffer->getBufferSize());
+ for (const auto &A : Associations) {
+ auto *Def = reinterpret_cast<object::coff_aux_section_definition *>(
+ Copy->getBufferStart() + A.Offset);
+ Def->Selection = COFF::IMAGE_COMDAT_SELECT_ASSOCIATIVE;
+ Def->NumberLowPart = A.Parent & 0xffff;
+ Def->NumberHighPart = static_cast<uint32_t>(A.Parent) >> 16;
+ }
+ return std::unique_ptr<MemoryBuffer>(std::move(Copy));
+}
+
+} // namespace
+
+#endif