From fe2bd12396580c60aabc08c60eba7e81d29d6cc8 Mon Sep 17 00:00:00 2001 From: Jacek Caban Date: Wed, 15 Nov 2023 12:35:45 +0100 Subject: [PATCH] [lld] Add support for EC code map. (#69101) --- lld/COFF/Chunks.cpp | 14 +++ lld/COFF/Chunks.h | 21 ++++ lld/COFF/Driver.cpp | 5 + lld/COFF/Writer.cpp | 61 ++++++++++ lld/test/COFF/Inputs/loadconfig-arm64ec.s | 4 +- lld/test/COFF/arm64ec-codemap.test | 132 +++++++++++++--------- 6 files changed, 182 insertions(+), 55 deletions(-) diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 4e845afa8947a..39f4575031be5 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -896,6 +896,20 @@ void RVAFlagTableChunk::writeTo(uint8_t *buf) const { "RVA tables should be de-duplicated"); } +size_t ECCodeMapChunk::getSize() const { + return map.size() * sizeof(chpe_range_entry); +} + +void ECCodeMapChunk::writeTo(uint8_t *buf) const { + auto table = reinterpret_cast(buf); + for (uint32_t i = 0; i < map.size(); i++) { + const ECCodeMapEntry &entry = map[i]; + uint32_t start = entry.first->getRVA(); + table[i].StartOffset = start | entry.type; + table[i].Length = entry.last->getRVA() + entry.last->getSize() - start; + } +} + // MinGW specific, for the "automatic import of variables from DLLs" feature. size_t PseudoRelocTableChunk::getSize() const { if (relocs.empty()) diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index 156e7a807cb8f..7b6bdeae4234e 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -703,6 +703,27 @@ class EmptyChunk : public NonSectionChunk { void writeTo(uint8_t *buf) const override {} }; +class ECCodeMapEntry { +public: + ECCodeMapEntry(Chunk *first, Chunk *last, chpe_range_type type) + : first(first), last(last), type(type) {} + Chunk *first; + Chunk *last; + chpe_range_type type; +}; + +// This is a chunk containing CHPE code map on EC targets. It's a table +// of address ranges and their types. +class ECCodeMapChunk : public NonSectionChunk { +public: + ECCodeMapChunk(std::vector &map) : map(map) {} + size_t getSize() const override; + void writeTo(uint8_t *buf) const override; + +private: + std::vector ↦ +}; + // MinGW specific, for the "automatic import of variables from DLLs" feature. // This provides the table of runtime pseudo relocations, for variable // references that turned out to need to be imported from a DLL even though diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index bbbaba194e83e..f5cb379c5a4bf 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -2360,6 +2360,11 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { ctx.symtab.addAbsolute(mangle("__guard_eh_cont_count"), 0); ctx.symtab.addAbsolute(mangle("__guard_eh_cont_table"), 0); + if (isArm64EC(config->machine)) { + ctx.symtab.addAbsolute("__hybrid_code_map", 0); + ctx.symtab.addAbsolute("__hybrid_code_map_count", 0); + } + if (config->pseudoRelocs) { ctx.symtab.addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0); ctx.symtab.addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0); diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index 59e7cf6c8337c..0477c094c0ac8 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -221,6 +221,7 @@ class Writer { uint16_t type, int margin); bool createThunks(OutputSection *os, int margin); bool verifyRanges(const std::vector chunks); + void createECCodeMap(); void finalizeAddresses(); void removeEmptySections(); void assignOutputSectionIndices(); @@ -229,6 +230,7 @@ class Writer { template void writeHeader(); void createSEHTable(); void createRuntimePseudoRelocs(); + void createECChunks(); void insertCtorDtorSymbols(); void markSymbolsWithRelocations(ObjFile *file, SymbolRVASet &usedSymbols); void createGuardCFTables(); @@ -272,6 +274,7 @@ class Writer { std::map partialSections; std::vector strtab; std::vector outputSymtab; + std::vector codeMap; IdataContents idata; Chunk *importTableStart = nullptr; uint64_t importTableSize = 0; @@ -537,6 +540,48 @@ bool Writer::createThunks(OutputSection *os, int margin) { return addressesChanged; } +// Create a code map for CHPE metadata. +void Writer::createECCodeMap() { + if (!isArm64EC(ctx.config.machine)) + return; + + // Clear the map in case we were're recomputing the map after adding + // a range extension thunk. + codeMap.clear(); + + std::optional lastType; + Chunk *first, *last; + + auto closeRange = [&]() { + if (lastType) { + codeMap.push_back({first, last, *lastType}); + lastType.reset(); + } + }; + + for (OutputSection *sec : ctx.outputSections) { + for (Chunk *c : sec->chunks) { + // Skip empty section chunks. MS link.exe does not seem to do that and + // generates empty code ranges in some cases. + if (isa(c) && !c->getSize()) + continue; + + std::optional chunkType = c->getArm64ECRangeType(); + if (chunkType != lastType) { + closeRange(); + first = c; + lastType = chunkType; + } + last = c; + } + } + + closeRange(); + + Symbol *tableCountSym = ctx.symtab.findUnderscore("__hybrid_code_map_count"); + cast(tableCountSym)->setVA(codeMap.size()); +} + // Verify that all relocations are in range, with no extra margin requirements. bool Writer::verifyRanges(const std::vector chunks) { for (Chunk *c : chunks) { @@ -1086,6 +1131,9 @@ void Writer::createMiscChunks() { if (config->guardCF != GuardCFLevel::Off) createGuardCFTables(); + if (isArm64EC(config->machine)) + createECChunks(); + if (config->autoImport) createRuntimePseudoRelocs(); @@ -1411,6 +1459,10 @@ void Writer::assignAddresses() { llvm::TimeTraceScope timeScope("Assign addresses"); Configuration *config = &ctx.config; + // We need to create EC code map so that ECCodeMapChunk knows its size. + // We do it here to make sure that we account for range extension chunks. + createECCodeMap(); + sizeOfHeaders = dosStubSize + sizeof(PEMagic) + sizeof(coff_file_header) + sizeof(data_directory) * numberOfDataDirectory + sizeof(coff_section) * ctx.outputSections.size(); @@ -1946,6 +1998,15 @@ void Writer::maybeAddRVATable(SymbolRVASet tableSymbols, StringRef tableSym, cast(c)->setVA(tableChunk->getSize() / (hasFlag ? 5 : 4)); } +// Create CHPE metadata chunks. +void Writer::createECChunks() { + auto codeMapChunk = make(codeMap); + rdataSec->addChunk(codeMapChunk); + Symbol *codeMapSym = ctx.symtab.findUnderscore("__hybrid_code_map"); + replaceSymbol(codeMapSym, codeMapSym->getName(), + codeMapChunk); +} + // MinGW specific. Gather all relocations that are imported from a DLL even // though the code didn't expect it to, produce the table that the runtime // uses for fixing them up, and provide the synthetic symbols that the diff --git a/lld/test/COFF/Inputs/loadconfig-arm64ec.s b/lld/test/COFF/Inputs/loadconfig-arm64ec.s index 1efd02406ca69..78ae594a21eff 100644 --- a/lld/test/COFF/Inputs/loadconfig-arm64ec.s +++ b/lld/test/COFF/Inputs/loadconfig-arm64ec.s @@ -64,8 +64,8 @@ __os_arm64x_helper8: .p2align 3, 0 __chpe_metadata: .word 1 - .rva code_map - .word code_map_count + .rva __hybrid_code_map + .word __hybrid_code_map_count .word 0 // __x64_code_ranges_to_entry_points .word 0 //__arm64x_redirection_metadata .rva __os_arm64x_dispatch_call_no_redirect diff --git a/lld/test/COFF/arm64ec-codemap.test b/lld/test/COFF/arm64ec-codemap.test index 24eedc6f8e96e..da3a4f9e47292 100644 --- a/lld/test/COFF/arm64ec-codemap.test +++ b/lld/test/COFF/arm64ec-codemap.test @@ -3,17 +3,17 @@ RUN: split-file %s %t.dir && cd %t.dir RUN: llvm-mc -filetype=obj -triple=arm64-windows arm64-func-sym.s -o arm64-func-sym.obj RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec-func-sym.s -o arm64ec-func-sym.obj +RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec-func-sym2.s -o arm64ec-func-sym2.obj RUN: llvm-mc -filetype=obj -triple=arm64ec-windows data-sec.s -o data-sec.obj +RUN: llvm-mc -filetype=obj -triple=arm64ec-windows empty-sec.s -o arm64ec-empty-sec.obj RUN: llvm-mc -filetype=obj -triple=x86_64-windows x86_64-func-sym.s -o x86_64-func-sym.obj -RUN: llvm-mc -filetype=obj -triple=arm64ec-windows codemap.s -o codemap.obj -RUN: llvm-mc -filetype=obj -triple=arm64ec-windows codemap2.s -o codemap2.obj -RUN: llvm-mc -filetype=obj -triple=arm64ec-windows codemap3.s -o codemap3.obj +RUN: llvm-mc -filetype=obj -triple=x86_64-windows empty-sec.s -o x86_64-empty-sec.obj RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj Link ARM64EC DLL and verify that the code is arranged as expected. RUN: lld-link -out:test.dll -machine:arm64ec arm64ec-func-sym.obj x86_64-func-sym.obj \ -RUN: codemap.obj loadconfig-arm64ec.obj -dll -noentry +RUN: loadconfig-arm64ec.obj -dll -noentry RUN: llvm-readobj --coff-load-config test.dll | FileCheck -check-prefix=CODEMAP %s CODEMAP: CodeMap [ @@ -45,12 +45,18 @@ DISASM-NEXT: 180006005: c3 retq Order of arguments doesn't matter in this case, chunks are sorted by target type anyway. RUN: lld-link -out:test2.dll -machine:arm64ec x86_64-func-sym.obj arm64ec-func-sym.obj \ -RUN: codemap.obj loadconfig-arm64ec.obj -dll -noentry +RUN: loadconfig-arm64ec.obj -dll -noentry RUN: llvm-readobj --coff-load-config test2.dll | FileCheck -check-prefix=CODEMAP %s RUN: llvm-objdump -d test2.dll | FileCheck -check-prefix=DISASM %s RUN: lld-link -out:testx.dll -machine:arm64x arm64-func-sym.obj arm64ec-func-sym.obj \ -RUN: x86_64-func-sym.obj codemap2.obj loadconfig-arm64ec.obj -dll -noentry +RUN: x86_64-func-sym.obj loadconfig-arm64ec.obj -dll -noentry + +Adding empty chunks does not affect code map ranges. + +RUN: lld-link -out:test3.dll -machine:arm64ec x86_64-empty-sec.obj arm64ec-empty-sec.obj \ +RUN: arm64ec-func-sym.obj x86_64-func-sym.obj loadconfig-arm64ec.obj -dll -noentry +RUN: llvm-readobj --coff-load-config test3.dll | FileCheck -check-prefix=CODEMAP %s Do the same with ARM64X target. @@ -88,7 +94,7 @@ DISASMX-NEXT: 180007005: c3 retq Test merged sections. RUN: lld-link -out:testm.dll -machine:arm64ec arm64ec-func-sym.obj x86_64-func-sym.obj \ -RUN: codemap3.obj loadconfig-arm64ec.obj -dll -noentry -merge:test=.text +RUN: loadconfig-arm64ec.obj -dll -noentry -merge:test=.text RUN: llvm-readobj --coff-load-config testm.dll | FileCheck -check-prefix=CODEMAPM %s CODEMAPM: CodeMap [ @@ -113,7 +119,7 @@ DISASMM-NEXT: 18000200d: c3 retq Merging data sections into code sections causes data to be separated from the code when sorting chunks. -RUN: lld-link -out:testdm.dll -machine:arm64ec arm64ec-func-sym.obj x86_64-func-sym.obj codemap.obj \ +RUN: lld-link -out:testdm.dll -machine:arm64ec arm64ec-func-sym.obj x86_64-func-sym.obj \ RUN: data-sec.obj loadconfig-arm64ec.obj -dll -noentry -merge:.testdata=.text -merge:.rdata=test RUN: llvm-readobj --coff-load-config testdm.dll | FileCheck -check-prefix=CODEMAPDM %s @@ -145,6 +151,59 @@ DISASMDM-NEXT: ... DISASMDM-NEXT: 180007000: b8 06 00 00 00 movl $0x6, %eax DISASMDM-NEXT: 180007005: c3 retq +Merging a code section into data section produces a valid code map. + +RUN: lld-link -out:testcm.dll -machine:arm64ec x86_64-func-sym.obj data-sec.obj \ +RUN: loadconfig-arm64ec.obj -dll -noentry -merge:test=.testdata -merge:.text=.testdata + +RUN: llvm-readobj --coff-load-config testcm.dll | FileCheck -check-prefix=CODEMAPCM %s +CODEMAPCM: CodeMap [ +CODEMAPCM-NEXT: 0x3008 - 0x3016 X64 +CODEMAPCM-NEXT: ] + +RUN: llvm-objdump -d testcm.dll | FileCheck -check-prefix=DISASMCM %s +DISASMCM: Disassembly of section .testdat: +DISASMCM-EMPTY: +DISASMCM-NEXT: 0000000180003000 <.testdat>: +DISASMCM-NEXT: 180003000: 00000001 udf #0x1 +DISASMCM-NEXT: 180003004: 00000000 udf #0x0 +DISASMCM-NEXT: 180003008: b8 03 00 00 00 movl $0x3, %eax +DISASMCM-NEXT: 18000300d: c3 retq +DISASMCM-NEXT: 18000300e: 00 00 addb %al, (%rax) +DISASMCM-NEXT: 180003010: b8 06 00 00 00 movl $0x6, %eax +DISASMCM-NEXT: 180003015: c3 + +Test code map range entry spanning over multiple sections. + +RUN: lld-link -out:testms.dll -machine:arm64ec arm64ec-func-sym.obj arm64ec-func-sym2.obj \ +RUN: loadconfig-arm64ec.obj -dll -noentry + +RUN: llvm-readobj --coff-load-config testms.dll | FileCheck -check-prefix=CODEMAPMS %s +CODEMAPMS: CodeMap [ +CODEMAPMS-NEXT: 0x1000 - 0x1008 ARM64EC +CODEMAPMS-NEXT: 0x4000 - 0x5008 ARM64EC +CODEMAPMS-NEXT: ] + +RUN: llvm-objdump -d testms.dll | FileCheck -check-prefix=DISASMMS %s +DISASMMS: Disassembly of section .text: +DISASMMS-EMPTY: +DISASMMS-NEXT: 0000000180001000 <.text>: +DISASMMS-NEXT: 180001000: 52800040 mov w0, #0x2 // =2 +DISASMMS-NEXT: 180001004: d65f03c0 ret +DISASMMS-EMPTY: +DISASMMS-NEXT: Disassembly of section test: +DISASMMS-EMPTY: +DISASMMS-NEXT: 0000000180004000 : +DISASMMS-NEXT: 180004000: 528000a0 mov w0, #0x5 // =5 +DISASMMS-NEXT: 180004004: d65f03c0 ret +DISASMMS-EMPTY: +DISASMMS-NEXT: Disassembly of section test2: +DISASMMS-EMPTY: +DISASMMS-NEXT: 0000000180005000 : +DISASMMS-NEXT: 180005000: 528000a0 mov w0, #0x5 // =5 +DISASMMS-NEXT: 180005004: d65f03c0 ret + + #--- arm64-func-sym.s .text .globl arm64_func_sym @@ -168,6 +227,14 @@ arm64ec_func_sym2: mov w0, #5 ret +#--- arm64ec-func-sym2.s + .section test2, "xr" + .globl arm64ec_func_sym3 + .p2align 2, 0x0 +arm64ec_func_sym3: + mov w0, #5 + ret + #--- x86_64-func-sym.s .text .globl x86_64_func_sym @@ -187,48 +254,7 @@ x86_64_func_sym2: .section .testdata, "rd" .xword 1 -#--- codemap.s - .section .rdata,"dr" - .globl code_map -code_map: - .rva arm64ec_func_sym + 1 - .word 8 - .rva x86_64_func_sym + 2 - .word 6 - .rva arm64ec_func_sym2 + 1 - .word 8 - .rva x86_64_func_sym2 + 2 - .word 6 - - .globl code_map_count -code_map_count = 4 - -#--- codemap2.s - .section .rdata,"dr" - .globl code_map -code_map: - .rva arm64_func_sym - .word 8 - .rva arm64ec_func_sym + 1 - .word 8 - .rva x86_64_func_sym + 2 - .word 6 - .rva arm64ec_func_sym2 + 1 - .word 8 - .rva x86_64_func_sym2 + 2 - .word 6 - - .globl code_map_count -code_map_count = 5 - -#--- codemap3.s - .section .rdata,"dr" - .globl code_map -code_map: - .rva arm64ec_func_sym + 1 - .word 16 - .rva x86_64_func_sym + 2 - .word 14 - - .globl code_map_count -code_map_count = 2 +#--- empty-sec.s + .section .empty1, "xr" + .section .empty2, "xr" + .section .empty3, "xr"