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 @@ -1543,6 +1543,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 @@ -147,6 +147,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 @@ -92,7 +92,7 @@ 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 { @@ -102,11 +102,12 @@ 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; } @@ -141,7 +142,7 @@ } mutable std::vector timeDateStamps; - const std::vector &records; + const std::vector> &records; bool writeRepro; }; @@ -166,6 +167,22 @@ mutable codeview::DebugInfo *buildId = nullptr; }; +class ExtendedDllCharacteristicsChunk : public NonSectionChunk { +public: + ExtendedDllCharacteristicsChunk(unsigned C) + : Characteristics(C) {} + + size_t getSize() const override { + return sizeof(Characteristics); + } + + void writeTo(uint8_t *B) const override { + memcpy(B, &Characteristics, sizeof(ExtendedDLLCharacteristics)); + } + + 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. @@ -251,7 +268,7 @@ bool setNoSEHCharacteristic = false; DebugDirectoryChunk *debugDirectory = nullptr; - std::vector debugRecords; + std::vector> debugRecords; CVDebugRecordChunk *buildId = nullptr; ArrayRef sectionTable; @@ -921,8 +938,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); } @@ -932,10 +950,17 @@ // 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/MinGW/Options.td b/lld/MinGW/Options.td --- a/lld/MinGW/Options.td +++ b/lld/MinGW/Options.td @@ -102,6 +102,7 @@ def: S<"minor-image-version">; def: F<"no-seh">; def: F<"nxcompat">; +def: F<"cetcompat">; def: F<"pic-executable">; def: S<"plugin">; def: J<"plugin=">; 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/Inputs/has_cet.exe b/llvm/test/tools/llvm-readobj/Inputs/has_cet.exe new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ 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 +520,24 @@ }; 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 +741,17 @@ 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); } }