diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -211,6 +211,7 @@ uint32_t functionPadMin = 0; bool dynamicBase = true; bool allowBind = true; + bool cetCompat = false; bool nxCompat = true; bool allowIsolation = true; bool terminalServerAware = true; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -1549,6 +1549,7 @@ !args.hasArg(OPT_profile)); config->integrityCheck = args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false); + config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false); config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true); for (auto *arg : args.filtered(OPT_swaprun)) parseSwaprun(arg->getValue()); diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -148,6 +148,8 @@ defm appcontainer : B<"appcontainer", "Image can only be run in an app container", "Image can run outside an app container (default)">; +defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack", + "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">; defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)", "Disable ASLR (default when /fixed)">; defm fixed : B<"fixed", "Disable base relocations", diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -91,7 +91,8 @@ class DebugDirectoryChunk : public NonSectionChunk { public: - DebugDirectoryChunk(const std::vector &r, bool writeRepro) + DebugDirectoryChunk(const std::vector> &r, + bool writeRepro) : records(r), writeRepro(writeRepro) {} size_t getSize() const override { @@ -101,11 +102,11 @@ void writeTo(uint8_t *b) const override { auto *d = reinterpret_cast(b); - for (const Chunk *record : records) { - OutputSection *os = record->getOutputSection(); - uint64_t offs = os->getFileOff() + (record->getRVA() - os->getRVA()); - fillEntry(d, COFF::IMAGE_DEBUG_TYPE_CODEVIEW, record->getSize(), - record->getRVA(), offs); + for (const std::pair record : records) { + Chunk *c = record.second; + OutputSection *os = c->getOutputSection(); + uint64_t offs = os->getFileOff() + (c->getRVA() - os->getRVA()); + fillEntry(d, record.first, c->getSize(), c->getRVA(), offs); ++d; } @@ -140,7 +141,7 @@ } mutable std::vector timeDateStamps; - const std::vector &records; + const std::vector> &records; bool writeRepro; }; @@ -165,6 +166,17 @@ mutable codeview::DebugInfo *buildId = nullptr; }; +class ExtendedDllCharacteristicsChunk : public NonSectionChunk { +public: + ExtendedDllCharacteristicsChunk(uint32_t c) : characteristics(c) {} + + size_t getSize() const override { return 4; } + + void writeTo(uint8_t *buf) const override { write32le(buf, characteristics); } + + uint32_t characteristics = 0; +}; + // PartialSection represents a group of chunks that contribute to an // OutputSection. Collating a collection of PartialSections of same name and // characteristics constitutes the OutputSection. @@ -250,7 +262,7 @@ bool setNoSEHCharacteristic = false; DebugDirectoryChunk *debugDirectory = nullptr; - std::vector debugRecords; + std::vector> debugRecords; CVDebugRecordChunk *buildId = nullptr; ArrayRef sectionTable; @@ -920,8 +932,9 @@ // Create Debug Information Chunks OutputSection *debugInfoSec = config->mingw ? buildidSec : rdataSec; - if (config->debug || config->repro) { + if (config->debug || config->repro || config->cetCompat) { debugDirectory = make(debugRecords, config->repro); + debugDirectory->setAlignment(4); debugInfoSec->addChunk(debugDirectory); } @@ -931,10 +944,20 @@ // allowing a debugger to match a PDB and an executable. So we need it even // if we're ultimately not going to write CodeView data to the PDB. buildId = make(); - debugRecords.push_back(buildId); + debugRecords.push_back({COFF::IMAGE_DEBUG_TYPE_CODEVIEW, buildId}); + } + + if (config->cetCompat) { + ExtendedDllCharacteristicsChunk *extendedDllChars = + make( + IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT); + debugRecords.push_back( + {COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, extendedDllChars}); + } - for (Chunk *c : debugRecords) - debugInfoSec->addChunk(c); + if (debugRecords.size() > 0) { + for (std::pair r : debugRecords) + debugInfoSec->addChunk(r.second); } // Create SEH table. x86-only. diff --git a/lld/test/COFF/options.test b/lld/test/COFF/options.test --- a/lld/test/COFF/options.test +++ b/lld/test/COFF/options.test @@ -50,6 +50,16 @@ # RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=NONXCOMPAT %s NONXCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_NX_COMPAT +# RUN: lld-link /out:%t.exe /entry:main /cetcompat %t.obj +# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPAT %s +CETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT + +# RUN: lld-link /out:%t.exe /entry:main %t.obj +# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s +# RUN: lld-link /out:%t.exe /entry:main /cetcompat:no %t.obj +# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s +NONCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT + # RUN: lld-link /out:%t.exe /entry:main /swaprun:CD %t.obj # RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=SWAPCD %s # RUN: lld-link /out:%t.exe /entry:main /swaprun:cd,net %t.obj diff --git a/llvm/include/llvm/BinaryFormat/COFF.h b/llvm/include/llvm/BinaryFormat/COFF.h --- a/llvm/include/llvm/BinaryFormat/COFF.h +++ b/llvm/include/llvm/BinaryFormat/COFF.h @@ -642,6 +642,11 @@ IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 }; +enum ExtendedDLLCharacteristics : unsigned { + /// Image is CET compatible + IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001 +}; + enum DebugType : unsigned { IMAGE_DEBUG_TYPE_UNKNOWN = 0, IMAGE_DEBUG_TYPE_COFF = 1, @@ -660,6 +665,7 @@ IMAGE_DEBUG_TYPE_ILTCG = 14, IMAGE_DEBUG_TYPE_MPX = 15, IMAGE_DEBUG_TYPE_REPRO = 16, + IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS = 20, }; enum BaseRelocationType : unsigned { diff --git a/llvm/test/tools/llvm-readobj/COFF/Inputs/has-cet.exe b/llvm/test/tools/llvm-readobj/COFF/Inputs/has-cet.exe new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ has-cet.c +# $ cl has-cet.c /link /cetcompat +RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cet.exe | FileCheck %s + +CHECK: DebugEntry { +CHECK: Characteristics: 0x0 +CHECK: Type: ExtendedDLLCharacteristics (0x14) +CHECK: ExtendedCharacteristics [ (0x1) +CHECK: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT (0x1) +CHECK: ] +CHECK: RawData ( +CHECK: 0000: 01000000 |....| +CHECK: ) +CHECK: } + diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp --- a/llvm/tools/llvm-readobj/COFFDumper.cpp +++ b/llvm/tools/llvm-readobj/COFFDumper.cpp @@ -104,6 +104,7 @@ bool GHash) override; void printStackMap() const override; void printAddrsig() override; + private: void printSymbols() override; void printDynamicSymbols() override; @@ -409,6 +410,11 @@ LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE), }; +static const EnumEntry + PEExtendedDLLCharacteristics[] = { + LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT), +}; + static const EnumEntry ImageSectionCharacteristics[] = { LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_SCN_TYPE_NOLOAD ), @@ -516,23 +522,25 @@ }; static const EnumEntry ImageDebugType[] = { - { "Unknown" , COFF::IMAGE_DEBUG_TYPE_UNKNOWN }, - { "COFF" , COFF::IMAGE_DEBUG_TYPE_COFF }, - { "CodeView" , COFF::IMAGE_DEBUG_TYPE_CODEVIEW }, - { "FPO" , COFF::IMAGE_DEBUG_TYPE_FPO }, - { "Misc" , COFF::IMAGE_DEBUG_TYPE_MISC }, - { "Exception" , COFF::IMAGE_DEBUG_TYPE_EXCEPTION }, - { "Fixup" , COFF::IMAGE_DEBUG_TYPE_FIXUP }, - { "OmapToSrc" , COFF::IMAGE_DEBUG_TYPE_OMAP_TO_SRC }, - { "OmapFromSrc", COFF::IMAGE_DEBUG_TYPE_OMAP_FROM_SRC }, - { "Borland" , COFF::IMAGE_DEBUG_TYPE_BORLAND }, - { "Reserved10" , COFF::IMAGE_DEBUG_TYPE_RESERVED10 }, - { "CLSID" , COFF::IMAGE_DEBUG_TYPE_CLSID }, - { "VCFeature" , COFF::IMAGE_DEBUG_TYPE_VC_FEATURE }, - { "POGO" , COFF::IMAGE_DEBUG_TYPE_POGO }, - { "ILTCG" , COFF::IMAGE_DEBUG_TYPE_ILTCG }, - { "MPX" , COFF::IMAGE_DEBUG_TYPE_MPX }, - { "Repro" , COFF::IMAGE_DEBUG_TYPE_REPRO }, + {"Unknown", COFF::IMAGE_DEBUG_TYPE_UNKNOWN}, + {"COFF", COFF::IMAGE_DEBUG_TYPE_COFF}, + {"CodeView", COFF::IMAGE_DEBUG_TYPE_CODEVIEW}, + {"FPO", COFF::IMAGE_DEBUG_TYPE_FPO}, + {"Misc", COFF::IMAGE_DEBUG_TYPE_MISC}, + {"Exception", COFF::IMAGE_DEBUG_TYPE_EXCEPTION}, + {"Fixup", COFF::IMAGE_DEBUG_TYPE_FIXUP}, + {"OmapToSrc", COFF::IMAGE_DEBUG_TYPE_OMAP_TO_SRC}, + {"OmapFromSrc", COFF::IMAGE_DEBUG_TYPE_OMAP_FROM_SRC}, + {"Borland", COFF::IMAGE_DEBUG_TYPE_BORLAND}, + {"Reserved10", COFF::IMAGE_DEBUG_TYPE_RESERVED10}, + {"CLSID", COFF::IMAGE_DEBUG_TYPE_CLSID}, + {"VCFeature", COFF::IMAGE_DEBUG_TYPE_VC_FEATURE}, + {"POGO", COFF::IMAGE_DEBUG_TYPE_POGO}, + {"ILTCG", COFF::IMAGE_DEBUG_TYPE_ILTCG}, + {"MPX", COFF::IMAGE_DEBUG_TYPE_MPX}, + {"Repro", COFF::IMAGE_DEBUG_TYPE_REPRO}, + {"ExtendedDLLCharacteristics", + COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS}, }; static const EnumEntry @@ -736,12 +744,19 @@ W.printString("PDBFileName", PDBFileName); } } else if (D.SizeOfData != 0) { - // FIXME: Type values of 12 and 13 are commonly observed but are not in - // the documented type enum. Figure out what they mean. + // FIXME: Data visualization for IMAGE_DEBUG_TYPE_VC_FEATURE and + // IMAGE_DEBUG_TYPE_POGO? ArrayRef RawData; if (std::error_code EC = Obj->getRvaAndSizeAsBytes(D.AddressOfRawData, D.SizeOfData, RawData)) reportError(errorCodeToError(EC), Obj->getFileName()); + if (D.Type == COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS) { + // FIXME right now the only possible value would fit in 8 bits, + // but that might change in the future + uint16_t Characteristics = RawData[0]; + W.printFlags("ExtendedCharacteristics", Characteristics, + makeArrayRef(PEExtendedDLLCharacteristics)); + } W.printBinaryBlock("RawData", RawData); } }