diff options
-rw-r--r-- | lld/COFF/Chunks.cpp | 8 | ||||
-rw-r--r-- | lld/COFF/Chunks.h | 42 | ||||
-rw-r--r-- | lld/COFF/Config.h | 3 | ||||
-rw-r--r-- | lld/COFF/Driver.cpp | 10 | ||||
-rw-r--r-- | lld/COFF/Driver.h | 2 | ||||
-rw-r--r-- | lld/COFF/DriverUtils.cpp | 9 | ||||
-rw-r--r-- | lld/COFF/InputFiles.cpp | 18 | ||||
-rw-r--r-- | lld/COFF/InputFiles.h | 26 | ||||
-rw-r--r-- | lld/COFF/Options.td | 1 | ||||
-rw-r--r-- | lld/COFF/Writer.cpp | 172 | ||||
-rw-r--r-- | lld/test/COFF/gfids-corrupt.s | 83 | ||||
-rw-r--r-- | lld/test/COFF/gfids-fallback.s | 96 | ||||
-rw-r--r-- | lld/test/COFF/gfids-gc.s | 128 | ||||
-rw-r--r-- | lld/test/COFF/gfids-icf.s | 87 |
14 files changed, 632 insertions, 53 deletions
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 557b0265442..76e207566c1 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -453,12 +453,14 @@ void LocalImportChunk::writeTo(uint8_t *Buf) const { } } -void SEHTableChunk::writeTo(uint8_t *Buf) const { +void RVATableChunk::writeTo(uint8_t *Buf) const { ulittle32_t *Begin = reinterpret_cast<ulittle32_t *>(Buf + OutputSectionOff); size_t Cnt = 0; - for (Defined *D : Syms) - Begin[Cnt++] = D->getRVA(); + for (const ChunkAndOffset &CO : Syms) + Begin[Cnt++] = CO.InputChunk->getRVA() + CO.Offset; std::sort(Begin, Begin + Cnt); + assert(std::unique(Begin, Begin + Cnt) == Begin + Cnt && + "RVA tables should be de-duplicated"); } // Windows-specific. This class represents a block in .reloc section. diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index 381527ee6ef..b4b22997fea 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -320,17 +320,41 @@ private: Defined *Sym; }; -// Windows-specific. -// A chunk for SEH table which contains RVAs of safe exception handler -// functions. x86-only. -class SEHTableChunk : public Chunk { +// Duplicate RVAs are not allowed in RVA tables, so unique symbols by chunk and +// offset into the chunk. Order does not matter as the RVA table will be sorted +// later. +struct ChunkAndOffset { + Chunk *InputChunk; + uint32_t Offset; + + struct DenseMapInfo { + static ChunkAndOffset getEmptyKey() { + return {llvm::DenseMapInfo<Chunk *>::getEmptyKey(), 0}; + } + static ChunkAndOffset getTombstoneKey() { + return {llvm::DenseMapInfo<Chunk *>::getTombstoneKey(), 0}; + } + static unsigned getHashValue(const ChunkAndOffset &CO) { + return llvm::DenseMapInfo<std::pair<Chunk *, uint32_t>>::getHashValue( + {CO.InputChunk, CO.Offset}); + } + static bool isEqual(const ChunkAndOffset &LHS, const ChunkAndOffset &RHS) { + return LHS.InputChunk == RHS.InputChunk && LHS.Offset == RHS.Offset; + } + }; +}; + +using SymbolRVASet = llvm::DenseSet<ChunkAndOffset>; + +// Table which contains symbol RVAs. Used for /safeseh and /guard:cf. +class RVATableChunk : public Chunk { public: - explicit SEHTableChunk(std::set<Defined *> S) : Syms(std::move(S)) {} + explicit RVATableChunk(SymbolRVASet S) : Syms(std::move(S)) {} size_t getSize() const override { return Syms.size() * 4; } void writeTo(uint8_t *Buf) const override; private: - std::set<Defined *> Syms; + SymbolRVASet Syms; }; // Windows-specific. @@ -362,4 +386,10 @@ void applyBranch24T(uint8_t *Off, int32_t V); } // namespace coff } // namespace lld +namespace llvm { +template <> +struct DenseMapInfo<lld::coff::ChunkAndOffset> + : lld::coff::ChunkAndOffset::DenseMapInfo {}; +} + #endif diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 10bc5728ddc..4a4d6d189e1 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -112,6 +112,9 @@ struct Configuration { bool SaveTemps = false; + // /guard:cf + bool GuardCF; + // Used for SafeSEH. Symbol *SEHTable = nullptr; Symbol *SEHCount = nullptr; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 79a9d66ef2b..093c9241e47 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -983,6 +983,10 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) { if (auto *Arg = Args.getLastArg(OPT_stack)) parseNumbers(Arg->getValue(), &Config->StackReserve, &Config->StackCommit); + // Handle /guard:cf + if (auto *Arg = Args.getLastArg(OPT_guard)) + parseGuard(Arg->getValue()); + // Handle /heap if (auto *Arg = Args.getLastArg(OPT_heap)) parseNumbers(Arg->getValue(), &Config->HeapReserve, &Config->HeapCommit); @@ -1285,11 +1289,9 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) { Symtab->addAbsolute("___safe_se_handler_count", 0); } - // We do not support /guard:cf (control flow protection) yet. - // Define CFG symbols anyway so that we can link MSVC 2015 CRT. Symtab->addAbsolute(mangle("__guard_fids_count"), 0); Symtab->addAbsolute(mangle("__guard_fids_table"), 0); - Symtab->addAbsolute(mangle("__guard_flags"), 0x100); + Symtab->addAbsolute(mangle("__guard_flags"), 0); Symtab->addAbsolute(mangle("__guard_iat_count"), 0); Symtab->addAbsolute(mangle("__guard_iat_table"), 0); Symtab->addAbsolute(mangle("__guard_longjmp_count"), 0); @@ -1364,7 +1366,7 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) { // Handle /safeseh. if (Args.hasFlag(OPT_safeseh, OPT_safeseh_no, false)) { for (ObjFile *File : ObjFile::Instances) - if (!File->SEHCompat) + if (!File->hasSafeSEH()) error("/safeseh: " + File->getName() + " is not compatible with SEH"); if (errorCount()) return; diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index bbb6cc97532..02b24269049 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -145,6 +145,8 @@ StringRef machineToStr(MachineTypes MT); // Parses a string in the form of "<integer>[,<integer>]". void parseNumbers(StringRef Arg, uint64_t *Addr, uint64_t *Size = nullptr); +void parseGuard(StringRef Arg); + // Parses a string in the form of "<integer>[.<integer>]". // Minor's default value is 0. void parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor); diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index f2a9da510a8..950bce88ea8 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -128,6 +128,15 @@ void parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor) { fatal("invalid number: " + S2); } +void parseGuard(StringRef Arg) { + if (Arg.equals_lower("no")) + Config->GuardCF = false; + else if (Arg.equals_lower("cf")) + Config->GuardCF = true; + else + fatal("invalid argument to /GUARD: " + Arg); +} + // Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]". void parseSubsystem(StringRef Arg, WindowsSubsystem *Sys, uint32_t *Major, uint32_t *Minor) { diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp index a8f52e0391f..5839b832992 100644 --- a/lld/COFF/InputFiles.cpp +++ b/lld/COFF/InputFiles.cpp @@ -151,15 +151,7 @@ SectionChunk *ObjFile::readSection(uint32_t SectionNumber, if (auto EC = COFFObj->getSectionName(Sec, Name)) fatal("getSectionName failed: #" + Twine(SectionNumber) + ": " + EC.message()); - if (Name == ".sxdata") { - ArrayRef<uint8_t> Data; - COFFObj->getSectionContents(Sec, Data); - if (Data.size() % 4 != 0) - fatal(".sxdata must be an array of symbol table indices"); - SXData = {reinterpret_cast<const ulittle32_t *>(Data.data()), - Data.size() / 4}; - return nullptr; - } + if (Name == ".drectve") { ArrayRef<uint8_t> Data; COFFObj->getSectionContents(Sec, Data); @@ -191,6 +183,10 @@ SectionChunk *ObjFile::readSection(uint32_t SectionNumber, // linked in the regular manner. if (C->isCodeView()) DebugChunks.push_back(C); + else if (Config->GuardCF && Name == ".gfids$y") + GuardFidChunks.push_back(C); + else if (Name == ".sxdata") + SXDataChunks.push_back(C); else Chunks.push_back(C); @@ -308,10 +304,8 @@ Optional<Symbol *> ObjFile::createDefined( // Skip special symbols. if (Name == "@comp.id") return nullptr; - // COFF spec 5.10.1. The .sxdata section. if (Name == "@feat.00") { - if (Sym.getValue() & 1) - SEHCompat = true; + Feat00Flags = Sym.getValue(); return nullptr; } if (Sym.isExternal()) diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h index adedbc2ad7a..3d7b83ead55 100644 --- a/lld/COFF/InputFiles.h +++ b/lld/COFF/InputFiles.h @@ -110,6 +110,8 @@ public: MachineTypes getMachineType() override; ArrayRef<Chunk *> getChunks() { return Chunks; } ArrayRef<SectionChunk *> getDebugChunks() { return DebugChunks; } + ArrayRef<SectionChunk *> getSXDataChunks() { return SXDataChunks; } + ArrayRef<SectionChunk *> getGuardFidChunks() { return GuardFidChunks; } ArrayRef<Symbol *> getSymbols() { return Symbols; } // Returns a Symbol object for the SymbolIndex'th symbol in the @@ -123,13 +125,17 @@ public: static std::vector<ObjFile *> Instances; - // True if this object file is compatible with SEH. - // COFF-specific and x86-only. - bool SEHCompat = false; + // Flags in the absolute @feat.00 symbol if it is present. These usually + // indicate if an object was compiled with certain security features enabled + // like stack guard, safeseh, /guard:cf, or other things. + uint32_t Feat00Flags = 0; - // The symbol table indexes of the safe exception handlers. - // COFF-specific and x86-only. - ArrayRef<llvm::support::ulittle32_t> SXData; + // True if this object file is compatible with SEH. COFF-specific and + // x86-only. COFF spec 5.10.1. The .sxdata section. + bool hasSafeSEH() { return Feat00Flags & 0x1; } + + // True if this file was compiled with /guard:cf. + bool hasGuardCF() { return Feat00Flags & 0x800; } // Pointer to the PDB module descriptor builder. Various debug info records // will reference object files by "module index", which is here. Things like @@ -165,6 +171,14 @@ private: // CodeView debug info sections. std::vector<SectionChunk *> DebugChunks; + // Chunks containing symbol table indices of exception handlers. Only used for + // 32-bit x86. + std::vector<SectionChunk *> SXDataChunks; + + // Chunks containing symbol table indices of address taken symbols. These are + // not linked into the final binary when /guard:cf is set. + std::vector<SectionChunk *> GuardFidChunks; + // This vector contains the same chunks as Chunks, but they are // indexed such that you can get a SectionChunk by section index. // Nonexistent section indices are filled with null pointers. diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index 2b68af9f9ec..d9531b5557d 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -28,6 +28,7 @@ def errorlimit : P<"errorlimit", def export : P<"export", "Export a function">; // No help text because /failifmismatch is not intended to be used by the user. def failifmismatch : P<"failifmismatch", "">; +def guard : P<"guard", "Control flow guard">; def heap : P<"heap", "Size of the heap">; def ignore : P<"ignore", "Specify warning codes to ignore">; def implib : P<"implib", "Import library name">; diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 9b5b0c301f1..6f035ae27ab 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -26,6 +26,7 @@ #include "llvm/Support/Endian.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/Parallel.h" +#include "llvm/Support/Path.h" #include "llvm/Support/RandomNumberGenerator.h" #include <algorithm> #include <cstdio> @@ -123,6 +124,12 @@ private: void openFile(StringRef OutputPath); template <typename PEHeaderTy> void writeHeader(); void createSEHTable(OutputSection *RData); + void createGFIDTable(OutputSection *RData); + void markSymbolsForRVATable(ObjFile *File, + ArrayRef<SectionChunk *> SymIdxChunks, + SymbolRVASet &TableSymbols); + void maybeAddRVATable(OutputSection *RData, SymbolRVASet TableSymbols, + StringRef TableSym, StringRef CountSym); void setSectionPermissions(); void writeSections(); void writeBuildId(); @@ -146,7 +153,8 @@ private: IdataContents Idata; DelayLoadContents DelayIdata; EdataContents Edata; - SEHTableChunk *SEHTable = nullptr; + RVATableChunk *GuardFidsTable = nullptr; + RVATableChunk *SEHTable = nullptr; Chunk *DebugDirectory = nullptr; std::vector<Chunk *> DebugRecords; @@ -428,7 +436,13 @@ void Writer::createMiscChunks() { RData->addChunk(C); } - createSEHTable(RData); + // Create SEH table. x86-only. + if (Config->Machine == I386) + createSEHTable(RData); + + // Create the guard function id table if requested. + if (Config->GuardCF) + createGFIDTable(RData); } // Create .idata section for the DLL-imported symbol table. @@ -720,6 +734,8 @@ template <typename PEHeaderTy> void Writer::writeHeader() { PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_NX_COMPAT; if (!Config->AllowIsolation) PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION; + if (Config->GuardCF) + PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_GUARD_CF; if (Config->Machine == I386 && !SEHTable && !Symtab->findUnderscore("_load_config_used")) PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_NO_SEH; @@ -825,34 +841,146 @@ void Writer::openFile(StringRef Path) { } void Writer::createSEHTable(OutputSection *RData) { - // Create SEH table. x86-only. - if (Config->Machine != I386) - return; + SymbolRVASet Handlers; + for (ObjFile *File : ObjFile::Instances) { + // FIXME: We should error here instead of earlier unless /safeseh:no was + // passed. + if (!File->hasSafeSEH()) + return; + + markSymbolsForRVATable(File, File->getSXDataChunks(), Handlers); + } - std::set<Defined *> Handlers; + maybeAddRVATable(RData, std::move(Handlers), "__safe_se_handler_table", + "__safe_se_handler_count"); +} + +// Add a symbol to an RVA set. Two symbols may have the same RVA, but an RVA set +// cannot contain duplicates. Therefore, the set is uniqued by Chunk and the +// symbol's offset into that Chunk. +static void addSymbolToRVASet(SymbolRVASet &RVASet, Defined *S) { + Chunk *C = S->getChunk(); + if (auto *SC = dyn_cast<SectionChunk>(C)) + C = SC->Repl; // Look through ICF replacement. + uint32_t Off = S->getRVA() - (C ? C->getRVA() : 0); + RVASet.insert({C, Off}); +} + +// Visit all relocations from all section contributions of this object file and +// mark the relocation target as address-taken. +static void markSymbolsWithRelocations(ObjFile *File, + SymbolRVASet &UsedSymbols) { + for (Chunk *C : File->getChunks()) { + // We only care about live section chunks. Common chunks and other chunks + // don't generally contain relocations. + SectionChunk *SC = dyn_cast<SectionChunk>(C); + if (!SC || !SC->isLive()) + continue; + + // Look for relocations in this section against symbols in executable output + // sections. + for (Symbol *Ref : SC->symbols()) { + // FIXME: Do further testing to see if the relocation type matters, + // especially for 32-bit where taking the address of something usually + // uses an absolute relocation instead of a relative one. + if (auto *D = dyn_cast_or_null<Defined>(Ref)) { + Chunk *RefChunk = D->getChunk(); + OutputSection *OS = RefChunk ? RefChunk->getOutputSection() : nullptr; + if (OS && OS->getPermissions() & IMAGE_SCN_MEM_EXECUTE) + addSymbolToRVASet(UsedSymbols, D); + } + } + } +} +// Create the guard function id table. This is a table of RVAs of all +// address-taken functions. It is sorted and uniqued, just like the safe SEH +// table. +void Writer::createGFIDTable(OutputSection *RData) { + SymbolRVASet AddressTakenSyms; for (ObjFile *File : ObjFile::Instances) { - if (!File->SEHCompat) - return; - for (uint32_t I : File->SXData) - if (Symbol *B = File->getSymbol(I)) - if (B->isLive()) - Handlers.insert(cast<Defined>(B)); + // If the object was compiled with /guard:cf, the address taken symbols are + // in the .gfids$y sections. Otherwise, we approximate the set of address + // taken symbols by checking which symbols were used by relocations in live + // sections. + if (File->hasGuardCF()) + markSymbolsForRVATable(File, File->getGuardFidChunks(), AddressTakenSyms); + else + markSymbolsWithRelocations(File, AddressTakenSyms); } - if (Handlers.empty()) + // Mark the image entry as address-taken. + if (Config->Entry) + addSymbolToRVASet(AddressTakenSyms, cast<Defined>(Config->Entry)); + + maybeAddRVATable(RData, std::move(AddressTakenSyms), "__guard_fids_table", + "__guard_fids_count"); + + // Set __guard_flags, which will be used in the load config to indicate that + // /guard:cf was enabled. + uint32_t GuardFlags = uint32_t(coff_guard_flags::CFInstrumented) | + uint32_t(coff_guard_flags::HasFidTable); + Symbol *FlagSym = Symtab->findUnderscore("__guard_flags"); + cast<DefinedAbsolute>(FlagSym)->setVA(GuardFlags); +} + +// Take a list of input sections containing symbol table indices and add those +// symbols to an RVA table. The challenge is that symbol RVAs are not known and +// depend on the table size, so we can't directly build a set of integers. +void Writer::markSymbolsForRVATable(ObjFile *File, + ArrayRef<SectionChunk *> SymIdxChunks, + SymbolRVASet &TableSymbols) { + for (SectionChunk *C : SymIdxChunks) { + // Skip sections discarded by linker GC. This comes up when a .gfids section + // is associated with something like a vtable and the vtable is discarded. + // In this case, the associated gfids section is discarded, and we don't + // mark the virtual member functions as address-taken by the vtable. + if (!C->isLive()) + continue; + + // Validate that the contents look like symbol table indices. + ArrayRef<uint8_t> Data = C->getContents(); + if (Data.size() % 4 != 0) { + warn("ignoring " + C->getSectionName() + + " symbol table index section in object " + toString(File)); + continue; + } + + // Read each symbol table index and check if that symbol was included in the + // final link. If so, add it to the table symbol set. + ArrayRef<ulittle32_t> SymIndices( + reinterpret_cast<const ulittle32_t *>(Data.data()), Data.size() / 4); + ArrayRef<Symbol *> ObjSymbols = File->getSymbols(); + for (uint32_t SymIndex : SymIndices) { + if (SymIndex >= ObjSymbols.size()) { + warn("ignoring invalid symbol table index in section " + + C->getSectionName() + " in object " + toString(File)); + continue; + } + if (Symbol *S = ObjSymbols[SymIndex]) { + if (S->isLive()) + addSymbolToRVASet(TableSymbols, cast<Defined>(S)); + } + } + } +} + +// Replace the absolute table symbol with a synthetic symbol pointing to +// TableChunk so that we can emit base relocations for it and resolve section +// relative relocations. +void Writer::maybeAddRVATable(OutputSection *RData, + SymbolRVASet TableSymbols, + StringRef TableSym, StringRef CountSym) { + if (TableSymbols.empty()) return; - SEHTable = make<SEHTableChunk>(Handlers); - RData->addChunk(SEHTable); + RVATableChunk *TableChunk = make<RVATableChunk>(std::move(TableSymbols)); + RData->addChunk(TableChunk); - // Replace the absolute table symbol with a synthetic symbol pointing to the - // SEHTable chunk so that we can emit base relocations for it and resolve - // section relative relocations. - Symbol *T = Symtab->find("___safe_se_handler_table"); - Symbol *C = Symtab->find("___safe_se_handler_count"); - replaceSymbol<DefinedSynthetic>(T, T->getName(), SEHTable); - cast<DefinedAbsolute>(C)->setVA(SEHTable->getSize() / 4); + Symbol *T = Symtab->findUnderscore(TableSym); + Symbol *C = Symtab->findUnderscore(CountSym); + replaceSymbol<DefinedSynthetic>(T, T->getName(), TableChunk); + cast<DefinedAbsolute>(C)->setVA(TableChunk->getSize() / 4); } // Handles /section options to allow users to overwrite diff --git a/lld/test/COFF/gfids-corrupt.s b/lld/test/COFF/gfids-corrupt.s new file mode 100644 index 00000000000..4636a2d3fef --- /dev/null +++ b/lld/test/COFF/gfids-corrupt.s @@ -0,0 +1,83 @@ +# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj +# RUN: lld-link %t.obj -opt:noref -guard:cf -out:%t.exe -entry:main 2>&1 | FileCheck %s --check-prefix=ERRS +# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s + +# ERRS: warning: ignoring .gfids$y symbol table index section in object {{.*}}gfids-corrupt{{.*}} +# ERRS: warning: ignoring invalid symbol table index in section .gfids$y in object {{.*}}gfids-corrupt{{.*}} + +# The table is arbitrary, really. +# CHECK: ImageBase: 0x140000000 +# CHECK: LoadConfig [ +# CHECK: SEHandlerTable: 0x0 +# CHECK: SEHandlerCount: 0 +# CHECK: GuardCFCheckFunction: 0x0 +# CHECK: GuardCFCheckDispatch: 0x0 +# CHECK: GuardCFFunctionTable: 0x14000{{.*}} +# CHECK: GuardCFFunctionCount: 2 +# CHECK: GuardFlags: 0x500 +# CHECK: GuardAddressTakenIatEntryTable: 0x0 +# CHECK: GuardAddressTakenIatEntryCount: 0 +# CHECK: GuardLongJumpTargetTable: 0x0 +# CHECK: GuardLongJumpTargetCount: 0 +# CHECK: ] +# CHECK: GuardFidTable [ +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: ] + + +# Indicate that gfids are present. + .def @feat.00; .scl 3; .type 0; .endef + .globl @feat.00 +@feat.00 = 0x800 + + .def f1; .scl 2; .type 32; .endef + .section .text,"xr",one_only,f1 + .global f1 +f1: + movl $42, %eax + retq + + .def f2; .scl 2; .type 32; .endef + .section .text,"xr",one_only,f2 + .global f2 +f2: + movl $13, %eax + retq + + .section .data,"dw",one_only,fp1 + .globl fp1 +fp1: + .quad f1 + + .section .data,"dw",one_only,fp2 + .globl fp2 +fp2: + .quad f2 + + .section .gfids$y,"dr",associative,fp1 + .symidx f1 + .byte 0 + + .section .gfids$y,"dr",associative,fp2 + .symidx f2 + .long 0x400 + + .def main; .scl 2; .type 32; .endef + .section .text,"xr",one_only,main + .globl main +main: + callq *fp1(%rip) + callq *fp2(%rip) + xor %eax, %eax + retq + + .section .rdata,"dr" +.globl _load_config_used +_load_config_used: + .long 256 + .fill 124, 1, 0 + .quad __guard_fids_table + .quad __guard_fids_count + .long __guard_flags + .fill 128, 1, 0 diff --git a/lld/test/COFF/gfids-fallback.s b/lld/test/COFF/gfids-fallback.s new file mode 100644 index 00000000000..e82adc6919b --- /dev/null +++ b/lld/test/COFF/gfids-fallback.s @@ -0,0 +1,96 @@ +# RUN: grep -B99999 [S]PLITMARKER %s | llvm-mc -triple x86_64-windows-msvc -filetype=obj -o %t1.obj +# RUN: grep -A99999 [S]PLITMARKER %s | llvm-mc -triple x86_64-windows-msvc -filetype=obj -o %t2.obj +# RUN: lld-link %t1.obj %t2.obj -guard:cf -out:%t.exe -entry:main -opt:noref +# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s + +# CHECK: ImageBase: 0x140000000 +# CHECK: LoadConfig [ +# CHECK: SEHandlerTable: 0x0 +# CHECK: SEHandlerCount: 0 +# CHECK: GuardCFCheckFunction: 0x0 +# CHECK: GuardCFCheckDispatch: 0x0 +# CHECK: GuardCFFunctionTable: 0x14000{{.*}} +# CHECK: GuardCFFunctionCount: 3 +# CHECK: GuardFlags: 0x500 +# CHECK: GuardAddressTakenIatEntryTable: 0x0 +# CHECK: GuardAddressTakenIatEntryCount: 0 +# CHECK: GuardLongJumpTargetTable: 0x0 +# CHECK: GuardLongJumpTargetCount: 0 +# CHECK: ] +# CHECK: GuardFidTable [ +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: ] + + +# Indicate that no gfids are present. All symbols used by relocations in this +# file will be considered address-taken. + .def @feat.00; .scl 3; .type 0; .endef + .globl @feat.00 +@feat.00 = 0 + + .def main; .scl 2; .type 32; .endef + .section .text,"xr",one_only,main + .globl main +main: + subq $8, %rsp + leaq foo(%rip), %rdx + callq bar + movl $0, %eax + addq $8, %rsp + retq + +# Should not appear in gfids table. + .def baz; .scl 2; .type 32; .endef + .section .text,"xr",one_only,baz + .globl baz +baz: + mov $1, %eax + retq + + .def qux; .scl 2; .type 32; .endef + .section .text,"xr",one_only,qux + .globl qux +qux: + mov $2, %eax + retq + + .def quxx; .scl 2; .type 32; .endef + .section .text,"xr",one_only,quxx + .globl quxx +quxx: + mov $3, %eax + retq + +# Load config. + .section .rdata,"dr" +.globl _load_config_used +_load_config_used: + .long 256 + .fill 124, 1, 0 + .quad __guard_fids_table + .quad __guard_fids_count + .long __guard_flags + .fill 128, 1, 0 + +# SPLITMARKER + +# Indicate that gfids are present. This file does not take any addresses. + .def @feat.00; .scl 3; .type 0; .endef + .globl @feat.00 +@feat.00 = 0x800 + + .def foo; .scl 2; .type 32; .endef + .section .text,"xr",one_only,foo + .global foo +foo: + movl $42, %eax + retq + + .def bar; .scl 2; .type 32; .endef + .section .text,"xr",one_only,bar + .global bar +bar: + movl $13, %eax + retq diff --git a/lld/test/COFF/gfids-gc.s b/lld/test/COFF/gfids-gc.s new file mode 100644 index 00000000000..4f67a84a570 --- /dev/null +++ b/lld/test/COFF/gfids-gc.s @@ -0,0 +1,128 @@ +# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj +# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:noref -entry:main +# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK-NOGC +# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:ref -entry:main +# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK-GC + +# This assembly is meant to mimic what CL emits for this kind of C code when +# /Gw (-fdata-sections) is enabled: +# int f() { return 42; } +# int g() { return 13; } +# int (*fp1)() = &f; +# int (*fp2)() = &g; +# int main() { +# return fp1(); +# } +# Compile with 'cl -c -guard:cf -Gw -O1' and note the two associative .gfids$y +# sections. + +# Expect 3 entries: main, f, and g. + +# CHECK-NOGC: ImageBase: 0x140000000 +# CHECK-NOGC: LoadConfig [ +# CHECK-NOGC: SEHandlerTable: 0x0 +# CHECK-NOGC: SEHandlerCount: 0 +# CHECK-NOGC: GuardCFCheckFunction: 0x0 +# CHECK-NOGC: GuardCFCheckDispatch: 0x0 +# CHECK-NOGC: GuardCFFunctionTable: 0x14000{{.*}} +# CHECK-NOGC: GuardCFFunctionCount: 3 +# CHECK-NOGC: GuardFlags: 0x500 +# CHECK-NOGC: GuardAddressTakenIatEntryTable: 0x0 +# CHECK-NOGC: GuardAddressTakenIatEntryCount: 0 +# CHECK-NOGC: GuardLongJumpTargetTable: 0x0 +# CHECK-NOGC: GuardLongJumpTargetCount: 0 +# CHECK-NOGC: ] +# CHECK-NOGC: GuardFidTable [ +# CHECK-NOGC-NEXT: 0x14000{{.*}} +# CHECK-NOGC-NEXT: 0x14000{{.*}} +# CHECK-NOGC-NEXT: 0x14000{{.*}} +# CHECK-NOGC-NEXT: ] + +# Expect 2 entries: main and f. fp2 was discarded, so g was only used as a +# direct call target. + +# CHECK-GC: ImageBase: 0x140000000 +# CHECK-GC: LoadConfig [ +# CHECK-GC: SEHandlerTable: 0x0 +# CHECK-GC: SEHandlerCount: 0 +# CHECK-GC: GuardCFCheckFunction: 0x0 +# CHECK-GC: GuardCFCheckDispatch: 0x0 +# CHECK-GC: GuardCFFunctionTable: 0x14000{{.*}} +# CHECK-GC: GuardCFFunctionCount: 2 +# CHECK-GC: GuardFlags: 0x500 +# CHECK-GC: GuardAddressTakenIatEntryTable: 0x0 +# CHECK-GC: GuardAddressTakenIatEntryCount: 0 +# CHECK-GC: GuardLongJumpTargetTable: 0x0 +# CHECK-GC: GuardLongJumpTargetCount: 0 +# CHECK-GC: ] +# CHECK-GC: GuardFidTable [ +# CHECK-GC-NEXT: 0x14000{{.*}} +# CHECK-GC-NEXT: 0x14000{{.*}} +# CHECK-GC-NEXT: ] + + +# We need @feat.00 to have 0x800 to indicate .gfids are present. + .def @feat.00; + .scl 3; + .type 0; + .endef + .globl @feat.00 +@feat.00 = 0x801 + + .def main; + .scl 2; + .type 32; + .endef + .section .text,"xr",one_only,main + .globl main +main: + # Call g directly so that it is not dead stripped. + callq g + rex64 jmpq *fp1(%rip) + + .def f; + .scl 3; + .type 32; + .endef + .section .text,"xr",one_only,f +f: + movl $42, %eax + retq + + .section .data,"dw",one_only,fp1 + .globl fp1 +fp1: + .quad f + + .section .gfids$y,"dr",associative,fp1 + .symidx f + +# Section GC will remove the following, so 'g' should not be present in the +# guard fid table. + + .def g; + .scl 3; + .type 32; + .endef + .section .text,"xr",one_only,g +g: + movl $13, %eax + retq + + .section .data,"dw",one_only,fp2 + .globl fp2 +fp2: + .quad g + + .section .gfids$y,"dr",associative,fp2 + .symidx g + + .section .rdata,"dr" +.globl _load_config_used +_load_config_used: + .long 256 + .fill 124, 1, 0 + .quad __guard_fids_table + .quad __guard_fids_count + .long __guard_flags + .fill 128, 1, 0 diff --git a/lld/test/COFF/gfids-icf.s b/lld/test/COFF/gfids-icf.s new file mode 100644 index 00000000000..52d7d7af430 --- /dev/null +++ b/lld/test/COFF/gfids-icf.s @@ -0,0 +1,87 @@ +# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj +# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:icf -entry:main +# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK + +# This assembly is meant to mimic what CL emits for this kind of C code: +# int icf1() { return 42; } +# int icf2() { return 42; } +# int (*fp1)() = &icf1; +# int (*fp2)() = &icf2; +# int main() { +# return fp1(); +# return fp2(); +# } + +# 'icf1' and 'icf2' are address taken, but should be merged into one entry. +# There are two entries in the table because 'main' is included. + +# CHECK: ImageBase: 0x140000000 +# CHECK: LoadConfig [ +# CHECK: SEHandlerTable: 0x0 +# CHECK: SEHandlerCount: 0 +# CHECK: GuardCFCheckFunction: 0x0 +# CHECK: GuardCFCheckDispatch: 0x0 +# CHECK: GuardCFFunctionTable: 0x14000{{.*}} +# CHECK: GuardCFFunctionCount: 2 +# CHECK: GuardFlags: 0x500 +# CHECK: GuardAddressTakenIatEntryTable: 0x0 +# CHECK: GuardAddressTakenIatEntryCount: 0 +# CHECK: GuardLongJumpTargetTable: 0x0 +# CHECK: GuardLongJumpTargetCount: 0 +# CHECK: ] +# CHECK: GuardFidTable [ +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: 0x14000{{.*}} +# CHECK-NEXT: ] + + +# Indicate that gfids are present. + .def @feat.00; .scl 3; .type 0; .endef + .globl @feat.00 +@feat.00 = 0x800 + + .def icf1; .scl 2; .type 32; .endef + .section .text,"xr",one_only,icf1 + .global icf1 +icf1: + movl $42, %eax + retq + + .def icf2; .scl 2; .type 32; .endef + .section .text,"xr",one_only,icf2 + .global icf2 +icf2: + movl $42, %eax + retq + +# Take their two addresses. + .data + .globl fp1 +fp1: + .quad icf1 + .globl fp2 +fp2: + .quad icf2 + + .section .gfids$y,"dr" + .symidx icf1 + .symidx icf2 + + .def main; .scl 2; .type 32; .endef + .section .text,"xr",one_only,main + .globl main +main: + callq *fp1(%rip) + callq *fp2(%rip) + xor %eax, %eax + retq + + .section .rdata,"dr" +.globl _load_config_used +_load_config_used: + .long 256 + .fill 124, 1, 0 + .quad __guard_fids_table + .quad __guard_fids_count + .long __guard_flags + .fill 128, 1, 0 |