From 6e457c20016ae1ed7249dd28ce4b3c7993a91275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Csan=C3=A1d=20Hajd=C3=BA?= Date: Fri, 21 Feb 2025 18:01:38 +0100 Subject: [PATCH] [LLD][ELF][AArch64] Add support for SHF_AARCH64_PURECODE ELF section flag (3/3) (#125689) Add support for the new SHF_AARCH64_PURECODE ELF section flag: https://github.com/ARM-software/abi-aa/pull/304 The general implementation follows the existing one for ARM targets. The output section only has the `SHF_AARCH64_PURECODE` flag set if all input sections have it set. Related PRs: * LLVM: https://github.com/llvm/llvm-project/pull/125687 * Clang: https://github.com/llvm/llvm-project/pull/125688 --- lld/ELF/OutputSections.cpp | 12 ++- lld/ELF/ScriptParser.cpp | 1 + lld/ELF/Thunks.cpp | 48 +++++++++++- lld/test/ELF/aarch64-execute-only.s | 32 ++++++++ lld/test/ELF/aarch64-thunk-bti-execute-only.s | 75 +++++++++++++++++++ lld/test/ELF/aarch64-thunk-execute-only.s | 22 ++++++ lld/test/ELF/input-section-flags.s | 3 +- 7 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 lld/test/ELF/aarch64-execute-only.s create mode 100644 lld/test/ELF/aarch64-thunk-bti-execute-only.s create mode 100644 lld/test/ELF/aarch64-thunk-execute-only.s diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp index a2da5543d586..1020dd9f2569 100644 --- a/lld/ELF/OutputSections.cpp +++ b/lld/ELF/OutputSections.cpp @@ -42,7 +42,10 @@ using namespace lld::elf; uint32_t OutputSection::getPhdrFlags() const { uint32_t ret = 0; - if (ctx.arg.emachine != EM_ARM || !(flags & SHF_ARM_PURECODE)) + bool purecode = + (ctx.arg.emachine == EM_ARM && (flags & SHF_ARM_PURECODE)) || + (ctx.arg.emachine == EM_AARCH64 && (flags & SHF_AARCH64_PURECODE)); + if (!purecode) ret |= PF_R; if (flags & SHF_WRITE) ret |= PF_W; @@ -161,8 +164,11 @@ void OutputSection::commitSection(InputSection *isec) { } isec->parent = this; - uint64_t andMask = - ctx.arg.emachine == EM_ARM ? (uint64_t)SHF_ARM_PURECODE : 0; + uint64_t andMask = 0; + if (ctx.arg.emachine == EM_ARM) + andMask |= (uint64_t)SHF_ARM_PURECODE; + if (ctx.arg.emachine == EM_AARCH64) + andMask |= (uint64_t)SHF_AARCH64_PURECODE; uint64_t orMask = ~andMask; uint64_t andFlags = (flags & isec->flags) & andMask; uint64_t orFlags = (flags | isec->flags) & orMask; diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp index a10af9565a1d..671a5ec4decc 100644 --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -1411,6 +1411,7 @@ static std::optional parseFlag(StringRef tok) { .Case(CASE_ENT(SHF_COMPRESSED)) .Case(CASE_ENT(SHF_EXCLUDE)) .Case(CASE_ENT(SHF_ARM_PURECODE)) + .Case(CASE_ENT(SHF_AARCH64_PURECODE)) .Default(std::nullopt); #undef CASE_ENT } diff --git a/lld/ELF/Thunks.cpp b/lld/ELF/Thunks.cpp index 4e4e0684a3f5..0008ee3a0de6 100644 --- a/lld/ELF/Thunks.cpp +++ b/lld/ELF/Thunks.cpp @@ -91,6 +91,19 @@ private: ThunkSection *tsec = nullptr; }; +// AArch64 long range Thunks compatible with execute-only code. +class AArch64ABSXOLongThunk final : public AArch64Thunk { +public: + AArch64ABSXOLongThunk(Ctx &ctx, Symbol &dest, int64_t addend, + bool mayNeedLandingPad) + : AArch64Thunk(ctx, dest, addend, mayNeedLandingPad) {} + uint32_t size() override { return getMayUseShortThunk() ? 4 : 20; } + void addSymbols(ThunkSection &sec) override; + +private: + void writeLong(uint8_t *buf) override; +}; + class AArch64ADRPThunk final : public AArch64Thunk { public: AArch64ADRPThunk(Ctx &ctx, Symbol &dest, int64_t addend, @@ -663,6 +676,33 @@ void AArch64ABSLongThunk::addLongMapSyms() { addSymbol("$d", STT_NOTYPE, 8, *tsec); } +void AArch64ABSXOLongThunk::writeLong(uint8_t *buf) { + const uint8_t data[] = { + 0x10, 0x00, 0x80, 0xd2, // movz x16, :abs_g0_nc:S, lsl #0 + 0x10, 0x00, 0xa0, 0xf2, // movk x16, :abs_g1_nc:S, lsl #16 + 0x10, 0x00, 0xc0, 0xf2, // movk x16, :abs_g2_nc:S, lsl #32 + 0x10, 0x00, 0xe0, 0xf2, // movk x16, :abs_g3:S, lsl #48 + 0x00, 0x02, 0x1f, 0xd6, // br x16 + }; + // If mayNeedLandingPad is true then destination is an + // AArch64BTILandingPadThunk that defines landingPad. + assert(!mayNeedLandingPad || landingPad != nullptr); + uint64_t s = mayNeedLandingPad + ? landingPad->getVA(ctx, 0) + : getAArch64ThunkDestVA(ctx, destination, addend); + memcpy(buf, data, sizeof(data)); + ctx.target->relocateNoSym(buf + 0, R_AARCH64_MOVW_UABS_G0_NC, s); + ctx.target->relocateNoSym(buf + 4, R_AARCH64_MOVW_UABS_G1_NC, s); + ctx.target->relocateNoSym(buf + 8, R_AARCH64_MOVW_UABS_G2_NC, s); + ctx.target->relocateNoSym(buf + 12, R_AARCH64_MOVW_UABS_G3, s); +} + +void AArch64ABSXOLongThunk::addSymbols(ThunkSection &sec) { + addSymbol(ctx.saver.save("__AArch64AbsXOLongThunk_" + destination.getName()), + STT_FUNC, 0, sec); + addSymbol("$x", STT_NOTYPE, 0, sec); +} + // This Thunk has a maximum range of 4Gb, this is sufficient for all programs // using the small code model, including pc-relative ones. At time of writing // clang and gcc do not support the large code model for position independent @@ -1482,7 +1522,8 @@ Thunk::Thunk(Ctx &ctx, Symbol &d, int64_t a) Thunk::~Thunk() = default; -static std::unique_ptr addThunkAArch64(Ctx &ctx, RelType type, Symbol &s, +static std::unique_ptr addThunkAArch64(Ctx &ctx, const InputSection &sec, + RelType type, Symbol &s, int64_t a) { assert(is_contained({R_AARCH64_CALL26, R_AARCH64_JUMP26, R_AARCH64_PLT32}, type)); @@ -1491,6 +1532,9 @@ static std::unique_ptr addThunkAArch64(Ctx &ctx, RelType type, Symbol &s, !isAArch64BTILandingPad(ctx, s, a); if (ctx.arg.picThunk) return std::make_unique(ctx, s, a, mayNeedLandingPad); + if (sec.getParent()->flags & SHF_AARCH64_PURECODE) + return std::make_unique(ctx, s, a, + mayNeedLandingPad); return std::make_unique(ctx, s, a, mayNeedLandingPad); } @@ -1702,7 +1746,7 @@ std::unique_ptr elf::addThunk(Ctx &ctx, const InputSection &isec, switch (ctx.arg.emachine) { case EM_AARCH64: - return addThunkAArch64(ctx, rel.type, s, a); + return addThunkAArch64(ctx, isec, rel.type, s, a); case EM_ARM: return addThunkArm(ctx, isec, rel.type, s, a); case EM_AVR: diff --git a/lld/test/ELF/aarch64-execute-only.s b/lld/test/ELF/aarch64-execute-only.s new file mode 100644 index 000000000000..20908ba9f754 --- /dev/null +++ b/lld/test/ELF/aarch64-execute-only.s @@ -0,0 +1,32 @@ +// REQUIRES: aarch64 + +// RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t.o +// RUN: ld.lld %t.o -o %t.so -shared +// RUN: llvm-readelf -l %t.so | FileCheck --implicit-check-not=LOAD %s + +// RUN: echo ".section .foo,\"ax\"; ret" > %t.s +// RUN: llvm-mc -filetype=obj -triple=aarch64 %t.s -o %t2.o +// RUN: ld.lld %t.o %t2.o -o %t.so -shared +// RUN: llvm-readelf -l %t.so | FileCheck --check-prefix=DIFF --implicit-check-not=LOAD %s + +// CHECK: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x000245 0x000245 R 0x10000 +// CHECK-NEXT: LOAD 0x000248 0x0000000000010248 0x0000000000010248 0x{{.*}} 0x{{.*}} R E 0x10000 +// CHECK-NEXT: LOAD 0x00024c 0x000000000002024c 0x000000000002024c 0x{{.*}} 0x{{.*}} E 0x10000 +// CHECK-NEXT: LOAD 0x000250 0x0000000000030250 0x0000000000030250 0x000070 0x000db0 RW 0x10000 + +// CHECK: 01 .dynsym .gnu.hash .hash .dynstr +// CHECK-NEXT: 02 .text +// CHECK-NEXT: 03 .foo +// CHECK-NEXT: 04 .dynamic + +// DIFF: LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00020d 0x00020d R 0x10000 +// DIFF-NEXT: LOAD 0x000210 0x0000000000010210 0x0000000000010210 0x00000c 0x00000c R E 0x10000 +// DIFF-NEXT: LOAD 0x000220 0x0000000000020220 0x0000000000020220 0x000070 0x000de0 RW 0x10000 + +// DIFF: 01 .dynsym .gnu.hash .hash .dynstr +// DIFF-NEXT: 02 .text .foo +// DIFF-NEXT: 03 .dynamic + + ret + .section .foo,"axy" + ret diff --git a/lld/test/ELF/aarch64-thunk-bti-execute-only.s b/lld/test/ELF/aarch64-thunk-bti-execute-only.s new file mode 100644 index 000000000000..4c5bb2ae8436 --- /dev/null +++ b/lld/test/ELF/aarch64-thunk-bti-execute-only.s @@ -0,0 +1,75 @@ +// REQUIRES: aarch64 +// RUN: rm -rf %t && split-file %s %t && cd %t +// RUN: llvm-mc -filetype=obj -triple=aarch64 asm -o a.o +// RUN: ld.lld --script=lds a.o -o exe --defsym absolute=0xf0000000 +// RUN: llvm-objdump -d --no-show-raw-insn exe | FileCheck %s + +//--- asm +.section ".note.gnu.property", "a" +.p2align 3 +.long 4 +.long 0x10 +.long 0x5 +.asciz "GNU" + +/// Enable BTI. +.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND. +.long 4 +.long 1 // GNU_PROPERTY_AARCH64_FEATURE_1_BTI. +.long 0 + +.section .text.0,"axy",@progbits +.global _start +.type _start,@function +_start: +/// Expect thunk to target a linker generated entry point with BTI landing pad. +/// Two calls to make sure only one landing pad is created. + bl fn1 + b fn1 +/// No BTI landing pad is added for absolute symbols. + bl absolute + +/// This function does not have a BTI compatible landing pad. Expect a linker +/// generated landing pad for indirect branch thunks. +.section .text.1,"axy",@progbits +.hidden fn1 +.type fn1,@function +fn1: + ret + +// CHECK-LABEL: <_start>: +// CHECK-NEXT: 18001000: bl 0x1800100c <__AArch64AbsXOLongThunk_> +// CHECK-NEXT: b 0x1800100c <__AArch64AbsXOLongThunk_> +// CHECK-NEXT: bl 0x18001020 <__AArch64AbsXOLongThunk_absolute> + +// CHECK-LABEL: <__AArch64AbsXOLongThunk_>: +// CHECK-NEXT: 1800100c: mov x16, #0x0 +// CHECK-NEXT: movk x16, #0x3000, lsl #16 +// CHECK-NEXT: movk x16, #0x0, lsl #32 +// CHECK-NEXT: movk x16, #0x0, lsl #48 +// CHECK-NEXT: br x16 + +// CHECK-LABEL: <__AArch64AbsXOLongThunk_absolute>: +// CHECK-NEXT: 18001020: mov x16, #0x0 +// CHECK-NEXT: movk x16, #0xf000, lsl #16 +// CHECK-NEXT: movk x16, #0x0, lsl #32 +// CHECK-NEXT: movk x16, #0x0, lsl #48 +// CHECK-NEXT: br x16 + +// CHECK-LABEL: <__AArch64BTIThunk_>: +// CHECK-NEXT: 30000000: bti c + +// CHECK-LABEL: : +// CHECK-NEXT: 30000004: ret + +//--- lds +PHDRS { + low PT_LOAD FLAGS(0x1 | 0x4); + mid PT_LOAD FLAGS(0x1 | 0x4); + high PT_LOAD FLAGS(0x1 | 0x4); +} +SECTIONS { + .rodata 0x10000000 : { *(.note.gnu.property) } :low + .text 0x18001000 : { *(.text.0) } :mid + .text_high 0x30000000 : { *(.text.*) } :high +} diff --git a/lld/test/ELF/aarch64-thunk-execute-only.s b/lld/test/ELF/aarch64-thunk-execute-only.s new file mode 100644 index 000000000000..6591354938e3 --- /dev/null +++ b/lld/test/ELF/aarch64-thunk-execute-only.s @@ -0,0 +1,22 @@ +// REQUIRES: aarch64 +// RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t.o +// RUN: ld.lld %t.o --defsym big=0x1111222233334444 -o %t +// RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck %s + +.section .text,"axy",@progbits,unique,0 +.globl _start +_start: + bl big + b big + +// CHECK: Disassembly of section .text: +// CHECK-EMPTY: +// CHECK-LABEL: <_start>: +// CHECK-NEXT: bl {{.*}} <__AArch64AbsXOLongThunk_big> +// CHECK-NEXT: b {{.*}} <__AArch64AbsXOLongThunk_big> +// CHECK-LABEL: <__AArch64AbsXOLongThunk_big>: +// CHECK-NEXT: mov x16, #0x4444 +// CHECK-NEXT: movk x16, #0x3333, lsl #16 +// CHECK-NEXT: movk x16, #0x2222, lsl #32 +// CHECK-NEXT: movk x16, #0x1111, lsl #48 +// CHECK-NEXT: br x16 diff --git a/lld/test/ELF/input-section-flags.s b/lld/test/ELF/input-section-flags.s index f848d55e6fdd..c4496597ad53 100644 --- a/lld/test/ELF/input-section-flags.s +++ b/lld/test/ELF/input-section-flags.s @@ -15,7 +15,8 @@ # RUN: .outsec3 : { INPUT_SECTION_FLAGS(SHF_WRITE) *(.sec.*) } \ # RUN: .outsec4 : { INPUT_SECTION_FLAGS(SHF_MERGE & !SHF_STRINGS) *(.sec.*) } \ # RUN: .outsec5 : { INPUT_SECTION_FLAGS(SHF_STRINGS) *(.sec.*) } \ -# RUN: .outsec6 : { INPUT_SECTION_FLAGS(!SHF_TLS & !SHF_EXCLUDE & !SHF_COMPRESSED & !SHF_ARM_PURECODE) *(.sec.*) } \ +# RUN: .outsec6 : { INPUT_SECTION_FLAGS(!SHF_TLS & !SHF_EXCLUDE & !SHF_COMPRESSED & \ +# RUN: !SHF_ARM_PURECODE & !SHF_AARCH64_PURECODE) *(.sec.*) } \ # RUN: } " > %t.script # RUN: ld.lld -o %t1 --script %t.script %t.o # RUN: llvm-readobj --symbols %t1 | FileCheck %s