From 20a6c049f93e14e9c4faaa9d66e657732f86adb3 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sun, 18 Oct 2026 08:30:39 +0000 Subject: 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 --- tools/driver/EmbeddedRunner.cc | 113 +++++++++++++++++++++++++---------------- tools/driver/MinGWCOFF.h | 99 ++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 tools/driver/MinGWCOFF.h (limited to 'tools') 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( - "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 &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)}); + } 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 R, + std::unique_ptr 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 createEmbeddedObjectLayer(llvm::orc::ExecutionSession &ES, llvm::jitlink::JITLinkMemoryManager &MM, const llvm::Triple &TT) { - auto Layer = - std::make_unique(ES, MM); + std::unique_ptr Layer; +#if defined(_WIN32) && defined(__x86_64__) + if (TT.isOSBinFormatCOFF() && TT.getArch() == llvm::Triple::x86_64) + Layer = std::make_unique(ES, MM); + else +#endif + Layer = std::make_unique(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 + +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> +normalizeMinGWUnwindSections(std::unique_ptr Buffer) { + using namespace llvm; + auto Object = object::ObjectFile::createObjectFile(Buffer->getMemBufferRef()); + if (!Object) + return Object.takeError(); + auto *COFF = dyn_cast(Object->get()); + if (!COFF || COFF->getMachine() != COFF::IMAGE_FILE_MACHINE_AMD64) + return std::move(Buffer); + + StringMap CodeSections; + DenseSet 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 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( + "missing MinGW unwind code section '" + CodeName + "' in '" + + Buffer->getBufferIdentifier().str() + "'", + inconvertibleErrorCode()); + Associations.push_back({static_cast( + reinterpret_cast(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( + Copy->getBufferStart() + A.Offset); + Def->Selection = COFF::IMAGE_COMDAT_SELECT_ASSOCIATIVE; + Def->NumberLowPart = A.Parent & 0xffff; + Def->NumberHighPart = static_cast(A.Parent) >> 16; + } + return std::unique_ptr(std::move(Copy)); +} + +} // namespace + +#endif -- cgit v1.3.1-4-g156e