Index: COFF/Chunks.h =================================================================== --- COFF/Chunks.h +++ COFF/Chunks.h @@ -320,17 +320,41 @@ 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::getEmptyKey(), 0}; + } + static ChunkAndOffset getTombstoneKey() { + return {llvm::DenseMapInfo::getTombstoneKey(), 0}; + } + static unsigned getHashValue(const ChunkAndOffset &CO) { + return llvm::DenseMapInfo>::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; + +// Table which contains symbol RVAs. Used for /safeseh and /guard:cf. +class RVATableChunk : public Chunk { public: - explicit SEHTableChunk(std::set 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 Syms; + SymbolRVASet Syms; }; // Windows-specific. @@ -362,4 +386,10 @@ } // namespace coff } // namespace lld +namespace llvm { +template <> +struct DenseMapInfo + : lld::coff::ChunkAndOffset::DenseMapInfo {}; +} + #endif Index: COFF/Chunks.cpp =================================================================== --- COFF/Chunks.cpp +++ COFF/Chunks.cpp @@ -453,12 +453,14 @@ } } -void SEHTableChunk::writeTo(uint8_t *Buf) const { +void RVATableChunk::writeTo(uint8_t *Buf) const { ulittle32_t *Begin = reinterpret_cast(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. Index: COFF/Config.h =================================================================== --- COFF/Config.h +++ COFF/Config.h @@ -112,6 +112,9 @@ bool SaveTemps = false; + // /guard:cf + bool GuardCF; + // Used for SafeSEH. Symbol *SEHTable = nullptr; Symbol *SEHCount = nullptr; Index: COFF/Driver.h =================================================================== --- COFF/Driver.h +++ COFF/Driver.h @@ -145,6 +145,8 @@ // Parses a string in the form of "[,]". void parseNumbers(StringRef Arg, uint64_t *Addr, uint64_t *Size = nullptr); +void parseGuard(StringRef Arg); + // Parses a string in the form of "[.]". // Minor's default value is 0. void parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor); Index: COFF/Driver.cpp =================================================================== --- COFF/Driver.cpp +++ COFF/Driver.cpp @@ -983,6 +983,10 @@ 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 @@ 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 @@ // 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; Index: COFF/DriverUtils.cpp =================================================================== --- COFF/DriverUtils.cpp +++ COFF/DriverUtils.cpp @@ -128,6 +128,15 @@ 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 "[,[.]]". void parseSubsystem(StringRef Arg, WindowsSubsystem *Sys, uint32_t *Major, uint32_t *Minor) { Index: COFF/InputFiles.h =================================================================== --- COFF/InputFiles.h +++ COFF/InputFiles.h @@ -110,6 +110,8 @@ MachineTypes getMachineType() override; ArrayRef getChunks() { return Chunks; } ArrayRef getDebugChunks() { return DebugChunks; } + ArrayRef getSXDataChunks() { return SXDataChunks; } + ArrayRef getGuardFidChunks() { return GuardFidChunks; } ArrayRef getSymbols() { return Symbols; } // Returns a Symbol object for the SymbolIndex'th symbol in the @@ -123,13 +125,17 @@ static std::vector Instances; - // True if this object file is compatible with SEH. - // COFF-specific and x86-only. - bool SEHCompat = false; - - // The symbol table indexes of the safe exception handlers. - // COFF-specific and x86-only. - ArrayRef SXData; + // 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; + + // 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 @@ // CodeView debug info sections. std::vector DebugChunks; + // Chunks containing symbol table indices of exception handlers. Only used for + // 32-bit x86. + std::vector 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 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. Index: COFF/InputFiles.cpp =================================================================== --- COFF/InputFiles.cpp +++ COFF/InputFiles.cpp @@ -151,15 +151,7 @@ if (auto EC = COFFObj->getSectionName(Sec, Name)) fatal("getSectionName failed: #" + Twine(SectionNumber) + ": " + EC.message()); - if (Name == ".sxdata") { - ArrayRef Data; - COFFObj->getSectionContents(Sec, Data); - if (Data.size() % 4 != 0) - fatal(".sxdata must be an array of symbol table indices"); - SXData = {reinterpret_cast(Data.data()), - Data.size() / 4}; - return nullptr; - } + if (Name == ".drectve") { ArrayRef Data; COFFObj->getSectionContents(Sec, Data); @@ -191,6 +183,10 @@ // 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 @@ // 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()) Index: COFF/Options.td =================================================================== --- COFF/Options.td +++ COFF/Options.td @@ -28,6 +28,7 @@ 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">; Index: COFF/Writer.cpp =================================================================== --- COFF/Writer.cpp +++ 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 #include @@ -123,6 +124,12 @@ void openFile(StringRef OutputPath); template void writeHeader(); void createSEHTable(OutputSection *RData); + void createGFIDTable(OutputSection *RData); + void markSymbolsForRVATable(ObjFile *File, + ArrayRef SymIdxChunks, + SymbolRVASet &TableSymbols); + void maybeAddRVATable(OutputSection *RData, SymbolRVASet TableSymbols, + StringRef TableSym, StringRef CountSym); void setSectionPermissions(); void writeSections(); void writeBuildId(); @@ -146,7 +153,8 @@ IdataContents Idata; DelayLoadContents DelayIdata; EdataContents Edata; - SEHTableChunk *SEHTable = nullptr; + RVATableChunk *GuardFidsTable = nullptr; + RVATableChunk *SEHTable = nullptr; Chunk *DebugDirectory = nullptr; std::vector DebugRecords; @@ -428,7 +436,13 @@ 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 @@ 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::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; - std::set Handlers; + markSymbolsForRVATable(File, File->getSXDataChunks(), 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(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(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(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(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); + } + + // Mark the image entry as address-taken. + if (Config->Entry) + addSymbolToRVASet(AddressTakenSyms, cast(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(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 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 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 SymIndices( + reinterpret_cast(Data.data()), Data.size() / 4); + ArrayRef 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(S)); + } + } } +} - if (Handlers.empty()) +// 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(Handlers); - RData->addChunk(SEHTable); + RVATableChunk *TableChunk = make(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(T, T->getName(), SEHTable); - cast(C)->setVA(SEHTable->getSize() / 4); + Symbol *T = Symtab->findUnderscore(TableSym); + Symbol *C = Symtab->findUnderscore(CountSym); + replaceSymbol(T, T->getName(), TableChunk); + cast(C)->setVA(TableChunk->getSize() / 4); } // Handles /section options to allow users to overwrite Index: test/COFF/gfids-corrupt.s =================================================================== --- test/COFF/gfids-corrupt.s +++ 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 Index: test/COFF/gfids-fallback.s =================================================================== --- test/COFF/gfids-fallback.s +++ 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 Index: test/COFF/gfids-gc.s =================================================================== --- test/COFF/gfids-gc.s +++ 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 Index: test/COFF/gfids-icf.s =================================================================== --- test/COFF/gfids-icf.s +++ 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