Skip to content

Commit e208208

Browse files
committed
[ELF][AArch64] Support for BTI and PAC
Branch Target Identification (BTI) and Pointer Authentication (PAC) are architecture features introduced in v8.5a and 8.3a respectively. The new instructions have been added in the hint space so that binaries take advantage of support where it exists yet still run on older hardware. The impact of each feature is: BTI: For executable pages that have been guarded, all indirect branches must have a destination that is a BTI instruction of the appropriate type. For the static linker, this means that PLT entries must have a "BTI c" as the first instruction in the sequence. BTI is an all or nothing property for a link unit, any indirect branch not landing on a valid destination will cause a Branch Target Exception. PAC: The dynamic loader encodes with PACIA the address of the destination that the PLT entry will load from the .plt.got, placing the result in a subset of the top-bits that are not valid virtual addresses. The PLT entry may authenticate these top-bits using the AUTIA instruction before branching to the destination. Use of PAC in PLT sequences is a contract between the dynamic loader and the static linker, it is independent of whether the relocatable objects use PAC. BTI and PAC are independent features that can be combined. So we can have several combinations of PLT: - Standard with no BTI or PAC - BTI PLT with "BTI c" as first instruction. - PAC PLT with "AUTIA1716" before the indirect branch to X17. - BTIPAC PLT with "BTI c" as first instruction and "AUTIA1716" before the first indirect branch to X17. The use of BTI and PAC in relocatable object files are encoded by feature bits in the .note.gnu.property section in a similar way to Intel CET. There is one AArch64 specific program property GNU_PROPERTY_AARCH64_FEATURE_1_AND and two target feature bits defined: - GNU_PROPERTY_AARCH64_FEATURE_1_BTI -- All executable sections are compatible with BTI. - GNU_PROPERTY_AARCH64_FEATURE_1_PAC -- All executable sections have return address signing enabled. Due to the properties of FEATURE_1_AND the static linker can tell when all input relocatable objects have the BTI and PAC feature bits set. The static linker uses this to enable the appropriate PLT sequence. Neither -> standard PLT GNU_PROPERTY_AARCH64_FEATURE_1_BTI -> BTI PLT GNU_PROPERTY_AARCH64_FEATURE_1_PAC -> PAC PLT Both properties -> BTIPAC PLT In addition to the .note.gnu.properties there are two new command line options: --force-bti : Act as if all relocatable inputs had GNU_PROPERTY_AARCH64_FEATURE_1_BTI and warn for every relocatable object that does not. --pac-plt : Act as if all relocatable inputs had GNU_PROPERTY_AARCH64_FEATURE_1_PAC. As PAC is a contract between the loader and static linker no warning is given if it is not present in an input. Two processor specific dynamic tags are used to communicate that a non standard PLT sequence is being used. DTI_AARCH64_BTI_PLT and DTI_AARCH64_BTI_PAC. Differential Revision: https://reviews.llvm.org/D62609 llvm-svn: 362793
1 parent f2ddd60 commit e208208

24 files changed

+961
-12
lines changed

lld/ELF/Arch/AArch64.cpp

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ uint64_t elf::getAArch64Page(uint64_t Expr) {
2828
}
2929

3030
namespace {
31-
class AArch64 final : public TargetInfo {
31+
class AArch64 : public TargetInfo {
3232
public:
3333
AArch64();
3434
RelExpr getRelExpr(RelType Type, const Symbol &S,
@@ -431,7 +431,157 @@ void AArch64::relaxTlsIeToLe(uint8_t *Loc, RelType Type, uint64_t Val) const {
431431
llvm_unreachable("invalid relocation for TLS IE to LE relaxation");
432432
}
433433

434-
TargetInfo *elf::getAArch64TargetInfo() {
435-
static AArch64 Target;
436-
return &Target;
434+
// AArch64 may use security features in variant PLT sequences. These are:
435+
// Pointer Authentication (PAC), introduced in armv8.3-a and Branch Target
436+
// Indicator (BTI) introduced in armv8.5-a. The additional instructions used
437+
// in the variant Plt sequences are encoded in the Hint space so they can be
438+
// deployed on older architectures, which treat the instructions as a nop.
439+
// PAC and BTI can be combined leading to the following combinations:
440+
// writePltHeader
441+
// writePltHeaderBti (no PAC Header needed)
442+
// writePlt
443+
// writePltBti (BTI only)
444+
// writePltPac (PAC only)
445+
// writePltBtiPac (BTI and PAC)
446+
//
447+
// When PAC is enabled the dynamic loader encrypts the address that it places
448+
// in the .got.plt using the pacia1716 instruction which encrypts the value in
449+
// x17 using the modifier in x16. The static linker places autia1716 before the
450+
// indirect branch to x17 to authenticate the address in x17 with the modifier
451+
// in x16. This makes it more difficult for an attacker to modify the value in
452+
// the .got.plt.
453+
//
454+
// When BTI is enabled all indirect branches must land on a bti instruction.
455+
// The static linker must place a bti instruction at the start of any PLT entry
456+
// that may be the target of an indirect branch. As the PLT entries call the
457+
// lazy resolver indirectly this must have a bti instruction at start. In
458+
// general a bti instruction is not needed for a PLT entry as indirect calls
459+
// are resolved to the function address and not the PLT entry for the function.
460+
// There are a small number of cases where the PLT address can escape, such as
461+
// taking the address of a function or ifunc via a non got-generating
462+
// relocation, and a shared library refers to that symbol.
463+
//
464+
// We use the bti c variant of the instruction which permits indirect branches
465+
// (br) via x16/x17 and indirect function calls (blr) via any register. The ABI
466+
// guarantees that all indirect branches from code requiring BTI protection
467+
// will go via x16/x17
468+
469+
namespace {
470+
class AArch64BtiPac final : public AArch64 {
471+
public:
472+
AArch64BtiPac();
473+
void writePltHeader(uint8_t *Buf) const override;
474+
void writePlt(uint8_t *Buf, uint64_t GotPltEntryAddr, uint64_t PltEntryAddr,
475+
int32_t Index, unsigned RelOff) const override;
476+
477+
private:
478+
bool BtiHeader; // bti instruction needed in PLT Header
479+
bool BtiEntry; // bti instruction needed in PLT Entry
480+
bool PacEntry; // autia1716 instruction needed in PLT Entry
481+
};
482+
} // namespace
483+
484+
AArch64BtiPac::AArch64BtiPac() {
485+
BtiHeader = (Config->AndFeatures & GNU_PROPERTY_AARCH64_FEATURE_1_BTI);
486+
// A BTI (Branch Target Indicator) Plt Entry is only required if the
487+
// address of the PLT entry can be taken by the program, which permits an
488+
// indirect jump to the PLT entry. This can happen when the address
489+
// of the PLT entry for a function is canonicalised due to the address of
490+
// the function in an executable being taken by a shared library.
491+
// FIXME: There is a potential optimization to omit the BTI if we detect
492+
// that the address of the PLT entry isn't taken.
493+
BtiEntry = BtiHeader && !Config->Shared;
494+
PacEntry = (Config->AndFeatures & GNU_PROPERTY_AARCH64_FEATURE_1_PAC);
495+
496+
if (BtiEntry || PacEntry)
497+
PltEntrySize = 24;
437498
}
499+
500+
void AArch64BtiPac::writePltHeader(uint8_t *Buf) const {
501+
const uint8_t BtiData[] = { 0x5f, 0x24, 0x03, 0xd5 }; // bti c
502+
const uint8_t PltData[] = {
503+
0xf0, 0x7b, 0xbf, 0xa9, // stp x16, x30, [sp,#-16]!
504+
0x10, 0x00, 0x00, 0x90, // adrp x16, Page(&(.plt.got[2]))
505+
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, Offset(&(.plt.got[2]))]
506+
0x10, 0x02, 0x00, 0x91, // add x16, x16, Offset(&(.plt.got[2]))
507+
0x20, 0x02, 0x1f, 0xd6, // br x17
508+
0x1f, 0x20, 0x03, 0xd5, // nop
509+
0x1f, 0x20, 0x03, 0xd5 // nop
510+
};
511+
const uint8_t NopData[] = { 0x1f, 0x20, 0x03, 0xd5 }; // nop
512+
513+
uint64_t Got = In.GotPlt->getVA();
514+
uint64_t Plt = In.Plt->getVA();
515+
516+
if (BtiHeader) {
517+
// PltHeader is called indirectly by Plt[N]. Prefix PltData with a BTI C
518+
// instruction.
519+
memcpy(Buf, BtiData, sizeof(BtiData));
520+
Buf += sizeof(BtiData);
521+
Plt += sizeof(BtiData);
522+
}
523+
memcpy(Buf, PltData, sizeof(PltData));
524+
525+
relocateOne(Buf + 4, R_AARCH64_ADR_PREL_PG_HI21,
526+
getAArch64Page(Got + 16) - getAArch64Page(Plt + 8));
527+
relocateOne(Buf + 8, R_AARCH64_LDST64_ABS_LO12_NC, Got + 16);
528+
relocateOne(Buf + 12, R_AARCH64_ADD_ABS_LO12_NC, Got + 16);
529+
if (!BtiHeader)
530+
// We didn't add the BTI c instruction so round out size with NOP.
531+
memcpy(Buf + sizeof(PltData), NopData, sizeof(NopData));
532+
}
533+
534+
void AArch64BtiPac::writePlt(uint8_t *Buf, uint64_t GotPltEntryAddr,
535+
uint64_t PltEntryAddr, int32_t Index,
536+
unsigned RelOff) const {
537+
// The PLT entry is of the form:
538+
// [BtiData] AddrInst (PacBr | StdBr) [NopData]
539+
const uint8_t BtiData[] = { 0x5f, 0x24, 0x03, 0xd5 }; // bti c
540+
const uint8_t AddrInst[] = {
541+
0x10, 0x00, 0x00, 0x90, // adrp x16, Page(&(.plt.got[n]))
542+
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, Offset(&(.plt.got[n]))]
543+
0x10, 0x02, 0x00, 0x91 // add x16, x16, Offset(&(.plt.got[n]))
544+
};
545+
const uint8_t PacBr[] = {
546+
0x9f, 0x21, 0x03, 0xd5, // autia1716
547+
0x20, 0x02, 0x1f, 0xd6 // br x17
548+
};
549+
const uint8_t StdBr[] = {
550+
0x20, 0x02, 0x1f, 0xd6, // br x17
551+
0x1f, 0x20, 0x03, 0xd5 // nop
552+
};
553+
const uint8_t NopData[] = { 0x1f, 0x20, 0x03, 0xd5 }; // nop
554+
555+
if (BtiEntry) {
556+
memcpy(Buf, BtiData, sizeof(BtiData));
557+
Buf += sizeof(BtiData);
558+
PltEntryAddr += sizeof(BtiData);
559+
}
560+
561+
memcpy(Buf, AddrInst, sizeof(AddrInst));
562+
relocateOne(Buf, R_AARCH64_ADR_PREL_PG_HI21,
563+
getAArch64Page(GotPltEntryAddr) -
564+
getAArch64Page(PltEntryAddr));
565+
relocateOne(Buf + 4, R_AARCH64_LDST64_ABS_LO12_NC, GotPltEntryAddr);
566+
relocateOne(Buf + 8, R_AARCH64_ADD_ABS_LO12_NC, GotPltEntryAddr);
567+
568+
if (PacEntry)
569+
memcpy(Buf + sizeof(AddrInst), PacBr, sizeof(PacBr));
570+
else
571+
memcpy(Buf + sizeof(AddrInst), StdBr, sizeof(StdBr));
572+
if (!BtiEntry)
573+
// We didn't add the BTI c instruction so round out size with NOP.
574+
memcpy(Buf + sizeof(AddrInst) + sizeof(StdBr), NopData, sizeof(NopData));
575+
}
576+
577+
static TargetInfo *getTargetInfo() {
578+
if (Config->AndFeatures & (GNU_PROPERTY_AARCH64_FEATURE_1_BTI |
579+
GNU_PROPERTY_AARCH64_FEATURE_1_PAC)) {
580+
static AArch64BtiPac T;
581+
return &T;
582+
}
583+
static AArch64 T;
584+
return &T;
585+
}
586+
587+
TargetInfo *elf::getAArch64TargetInfo() { return getTargetInfo(); }

lld/ELF/Config.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ struct Configuration {
147147
bool ExecuteOnly;
148148
bool ExportDynamic;
149149
bool FixCortexA53Errata843419;
150+
bool ForceBTI;
150151
bool FormatBinary = false;
151152
bool RequireCET;
152153
bool GcSections;
@@ -168,6 +169,7 @@ struct Configuration {
168169
bool OFormatBinary;
169170
bool Omagic;
170171
bool OptRemarksWithHotness;
172+
bool PacPlt;
171173
bool PicThunk;
172174
bool Pie;
173175
bool PrintGcSections;

lld/ELF/Driver.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,13 @@ static void checkOptions() {
337337

338338
if (Config->ZRetpolineplt && Config->RequireCET)
339339
error("--require-cet may not be used with -z retpolineplt");
340+
341+
if (Config->EMachine != EM_AARCH64) {
342+
if (Config->PacPlt)
343+
error("--pac-plt only supported on AArch64");
344+
if (Config->ForceBTI)
345+
error("--force-bti only supported on AArch64");
346+
}
340347
}
341348

342349
static const char *getReproduceOption(opt::InputArgList &Args) {
@@ -816,6 +823,7 @@ static void readConfigs(opt::InputArgList &Args) {
816823
Config->FilterList = args::getStrings(Args, OPT_filter);
817824
Config->Fini = Args.getLastArgValue(OPT_fini, "_fini");
818825
Config->FixCortexA53Errata843419 = Args.hasArg(OPT_fix_cortex_a53_843419);
826+
Config->ForceBTI = Args.hasArg(OPT_force_bti);
819827
Config->RequireCET = Args.hasArg(OPT_require_cet);
820828
Config->GcSections = Args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false);
821829
Config->GnuUnique = Args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true);
@@ -851,6 +859,7 @@ static void readConfigs(opt::InputArgList &Args) {
851859
Config->Optimize = args::getInteger(Args, OPT_O, 1);
852860
Config->OrphanHandling = getOrphanHandling(Args);
853861
Config->OutputFile = Args.getLastArgValue(OPT_o);
862+
Config->PacPlt = Args.hasArg(OPT_pac_plt);
854863
Config->Pie = Args.hasFlag(OPT_pie, OPT_no_pie, false);
855864
Config->PrintIcfSections =
856865
Args.hasFlag(OPT_print_icf_sections, OPT_no_print_icf_sections, false);
@@ -1594,20 +1603,32 @@ static void wrapSymbols(ArrayRef<WrappedSymbol> Wrapped) {
15941603
// with CET.
15951604
//
15961605
// This function returns the merged feature flags. If 0, we cannot enable CET.
1606+
// This is also the case with AARCH64's BTI and PAC which use the similar
1607+
// GNU_PROPERTY_AARCH64_FEATURE_1_AND mechanism.
15971608
//
15981609
// Note that the CET-aware PLT is not implemented yet. We do error
15991610
// check only.
16001611
template <class ELFT> static uint32_t getAndFeatures() {
1601-
if (Config->EMachine != EM_386 && Config->EMachine != EM_X86_64)
1612+
if (Config->EMachine != EM_386 && Config->EMachine != EM_X86_64 &&
1613+
Config->EMachine != EM_AARCH64)
16021614
return 0;
16031615

16041616
uint32_t Ret = -1;
16051617
for (InputFile *F : ObjectFiles) {
16061618
uint32_t Features = cast<ObjFile<ELFT>>(F)->AndFeatures;
1607-
if (!Features && Config->RequireCET)
1619+
if (Config->ForceBTI && !(Features & GNU_PROPERTY_AARCH64_FEATURE_1_BTI)) {
1620+
warn(toString(F) + ": --force-bti: file does not have BTI property");
1621+
Features |= GNU_PROPERTY_AARCH64_FEATURE_1_BTI;
1622+
} else if (!Features && Config->RequireCET)
16081623
error(toString(F) + ": --require-cet: file is not compatible with CET");
16091624
Ret &= Features;
16101625
}
1626+
1627+
// Force enable pointer authentication Plt, we don't warn in this case as
1628+
// this does not require support in the object for correctness.
1629+
if (Config->PacPlt)
1630+
Ret |= GNU_PROPERTY_AARCH64_FEATURE_1_PAC;
1631+
16111632
return Ret;
16121633
}
16131634

@@ -1793,6 +1814,11 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &Args) {
17931814
// contain a hint to tweak linker's and loader's behaviors.
17941815
Config->AndFeatures = getAndFeatures<ELFT>();
17951816

1817+
// The Target instance handles target-specific stuff, such as applying
1818+
// relocations or writing a PLT section. It also contains target-dependent
1819+
// values such as a default image base address.
1820+
Target = getTarget();
1821+
17961822
Config->EFlags = Target->calcEFlags();
17971823
// MaxPageSize (sometimes called abi page size) is the maximum page size that
17981824
// the output can be run on. For example if the OS can use 4k or 64k page

lld/ELF/InputFiles.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,10 @@ static uint32_t readAndFeatures(ObjFile<ELFT> *Obj, ArrayRef<uint8_t> Data) {
787787
continue;
788788
}
789789

790+
uint32_t FeatureAndType = Config->EMachine == EM_AARCH64
791+
? GNU_PROPERTY_AARCH64_FEATURE_1_AND
792+
: GNU_PROPERTY_X86_FEATURE_1_AND;
793+
790794
// Read a body of a NOTE record, which consists of type-length-value fields.
791795
ArrayRef<uint8_t> Desc = Note.getDesc();
792796
while (!Desc.empty()) {
@@ -796,7 +800,7 @@ static uint32_t readAndFeatures(ObjFile<ELFT> *Obj, ArrayRef<uint8_t> Data) {
796800
uint32_t Type = read32le(Desc.data());
797801
uint32_t Size = read32le(Desc.data() + 4);
798802

799-
if (Type == GNU_PROPERTY_X86_FEATURE_1_AND) {
803+
if (Type == FeatureAndType) {
800804
// We found a FEATURE_1_AND field. There may be more than one of these
801805
// in a .note.gnu.propery section, for a relocatable object we
802806
// accumulate the bits set.
@@ -966,8 +970,9 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(const Elf_Shdr &Sec) {
966970
if (Name == ".note.GNU-stack")
967971
return &InputSection::Discarded;
968972

969-
// If an object file is compatible with Intel Control-Flow Enforcement
970-
// Technology (CET), it has a .note.gnu.property section containing the
973+
// Object files that use processor features such as Intel Control-Flow
974+
// Enforcement (CET) or AArch64 Branch Target Identification BTI, use a
975+
// .note.gnu.property section containing a bitfield of feature bits like the
971976
// GNU_PROPERTY_X86_FEATURE_1_IBT flag. Read a bitmap containing the flag.
972977
//
973978
// Since we merge bitmaps from multiple object files to create a new

lld/ELF/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def fix_cortex_a53_843419: F<"fix-cortex-a53-843419">,
175175
// is not complete.
176176
def require_cet: F<"require-cet">;
177177

178+
def force_bti: F<"force-bti">,
179+
HelpText<"Force enable AArch64 BTI in PLT, warn if Input ELF file does not have GNU_PROPERTY_AARCH64_FEATURE_1_BTI property">;
180+
178181
defm format: Eq<"format", "Change the input format of the inputs following this option">,
179182
MetaVarName<"[default,elf,binary]">;
180183

@@ -269,6 +272,9 @@ defm pack_dyn_relocs:
269272
Eq<"pack-dyn-relocs", "Pack dynamic relocations in the given format">,
270273
MetaVarName<"[none,android,relr,android+relr]">;
271274

275+
def pac_plt: F<"pac-plt">,
276+
HelpText<"AArch64 only, use pointer authentication in PLT">;
277+
272278
defm use_android_relr_tags: B<"use-android-relr-tags",
273279
"Use SHT_ANDROID_RELR / DT_ANDROID_RELR* tags instead of SHT_RELR / DT_RELR*",
274280
"Use SHT_RELR / DT_RELR* tags (default)">;

lld/ELF/SyntheticSections.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,9 @@ static size_t getHashSize() {
290290

291291
// This class represents a linker-synthesized .note.gnu.property section.
292292
//
293-
// In x86, object files may contain feature flags indicating the features that
294-
// they are using. The flags are stored in a .note.gnu.property section.
293+
// In x86 and AArch64, object files may contain feature flags indicating the
294+
// features that they have used. The flags are stored in a .note.gnu.property
295+
// section.
295296
//
296297
// lld reads the sections from input files and merges them by computing AND of
297298
// the flags. The result is written as a new .note.gnu.property section.
@@ -304,11 +305,15 @@ GnuPropertySection::GnuPropertySection()
304305
".note.gnu.property") {}
305306

306307
void GnuPropertySection::writeTo(uint8_t *Buf) {
308+
uint32_t FeatureAndType = Config->EMachine == EM_AARCH64
309+
? GNU_PROPERTY_AARCH64_FEATURE_1_AND
310+
: GNU_PROPERTY_X86_FEATURE_1_AND;
311+
307312
write32(Buf, 4); // Name size
308313
write32(Buf + 4, Config->Is64 ? 16 : 12); // Content size
309314
write32(Buf + 8, NT_GNU_PROPERTY_TYPE_0); // Type
310315
memcpy(Buf + 12, "GNU", 4); // Name string
311-
write32(Buf + 16, GNU_PROPERTY_X86_FEATURE_1_AND); // Feature type
316+
write32(Buf + 16, FeatureAndType); // Feature type
312317
write32(Buf + 20, 4); // Feature size
313318
write32(Buf + 24, Config->AndFeatures); // Feature flags
314319
if (Config->Is64)
@@ -1340,6 +1345,13 @@ template <class ELFT> void DynamicSection<ELFT>::finalizeContents() {
13401345
addInt(DT_PLTREL, Config->IsRela ? DT_RELA : DT_REL);
13411346
}
13421347

1348+
if (Config->EMachine == EM_AARCH64) {
1349+
if (Config->AndFeatures & GNU_PROPERTY_AARCH64_FEATURE_1_BTI)
1350+
addInt(DT_AARCH64_BTI_PLT, 0);
1351+
if (Config->AndFeatures & GNU_PROPERTY_AARCH64_FEATURE_1_PAC)
1352+
addInt(DT_AARCH64_PAC_PLT, 0);
1353+
}
1354+
13431355
addInSec(DT_SYMTAB, In.DynSymTab);
13441356
addInt(DT_SYMENT, sizeof(Elf_Sym));
13451357
addInSec(DT_STRTAB, In.DynStrTab);

lld/docs/ld.lld.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ Set the
182182
field to the specified value.
183183
.It Fl -fini Ns = Ns Ar symbol
184184
Specify a finalizer function.
185+
.It Fl -force-bti
186+
Force enable AArch64 BTI instruction in PLT, warn if Input ELF file does not have GNU_PROPERTY_AARCH64_FEATURE_1_BTI property.
185187
.It Fl -format Ns = Ns Ar input-format , Fl b Ar input-format
186188
Specify the format of the inputs following this option.
187189
.Ar input-format
@@ -382,6 +384,8 @@ is the default. If
382384
.Fl -use-android-relr-tags
383385
is specified, use SHT_ANDROID_RELR instead of SHT_RELR.
384386
.Pp
387+
.It Fl -pac-plt
388+
AArch64 only, use pointer authentication in PLT.
385389
.It Fl -pic-veneer
386390
Always generate position independent thunks.
387391
.It Fl -pie , Fl -pic-executable
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.text
2+
.globl myfunc
3+
.globl func1
4+
.type func1, %function
5+
func1:
6+
adrp x8, :got: myfunc
7+
ldr x8, [x8, :got_lo12: myfunc]
8+
ret

0 commit comments

Comments
 (0)