Skip to content

Commit fd4f952

Browse files
ahmedbougachaFrancis Visoiu Mistrih
andauthored
[AArch64][MachO] Add ptrauth ABI version to arm64e cpusubtype. (#104650)
In a mach_header, the cpusubtype is a 32-bit field, but it's split in 2 subfields: - the low 24 bits containing the cpu subtype proper, (e.g., CPU_SUBTYPE_ARM64E 2) - the high 8 bits containing a capability field used for additional feature flags. Notably, it's only the subtype subfield that participates in fat file slice discrimination: the caps are ignored. arm64e uses the caps subfield to encode a ptrauth ABI version: - 0x80 (CPU_SUBTYPE_PTRAUTH_ABI) denotes a versioned binary - 0x40 denotes a kernel-ABI binary - 0x00-0x0F holds the ptrauth ABI version This teaches the basic obj tools to decode that (or ignore it when unneeded). It also teaches the MachO writer to default to emitting versioned binaries, but with a version of 0 (and without the kernel ABI flag). Modern arm64e requires versioned binaries: a binary with 0x00 caps in cpusubtype is now rejected by the linker and everything after. We can live without the sophistication of specifying the version and kernel ABI for now. Co-authored-by: Francis Visoiu Mistrih <[email protected]>
1 parent f73050e commit fd4f952

File tree

13 files changed

+191
-8
lines changed

13 files changed

+191
-8
lines changed

llvm/include/llvm/BinaryFormat/MachO.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,8 +1641,39 @@ enum CPUSubTypeARM64 {
16411641
CPU_SUBTYPE_ARM64_ALL = 0,
16421642
CPU_SUBTYPE_ARM64_V8 = 1,
16431643
CPU_SUBTYPE_ARM64E = 2,
1644+
1645+
// arm64e uses the capability bits to encode ptrauth ABI information.
1646+
// Bit 63 marks the binary as Versioned.
1647+
CPU_SUBTYPE_ARM64E_VERSIONED_PTRAUTH_ABI_MASK = 0x80000000,
1648+
// Bit 62 marks the binary as using a kernel ABI.
1649+
CPU_SUBTYPE_ARM64E_KERNEL_PTRAUTH_ABI_MASK = 0x40000000,
1650+
// Bits [59:56] hold the 4-bit ptrauth ABI version.
1651+
CPU_SUBTYPE_ARM64E_PTRAUTH_MASK = 0x0f000000,
16441652
};
16451653

1654+
inline int CPU_SUBTYPE_ARM64E_PTRAUTH_VERSION(unsigned ST) {
1655+
return (ST & CPU_SUBTYPE_ARM64E_PTRAUTH_MASK) >> 24;
1656+
}
1657+
1658+
inline unsigned
1659+
CPU_SUBTYPE_ARM64E_WITH_PTRAUTH_VERSION(unsigned PtrAuthABIVersion,
1660+
bool PtrAuthKernelABIVersion) {
1661+
assert((PtrAuthABIVersion <= 0xF) &&
1662+
"ptrauth abi version must fit in 4 bits");
1663+
return CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_ARM64E_VERSIONED_PTRAUTH_ABI_MASK |
1664+
(PtrAuthKernelABIVersion ? CPU_SUBTYPE_ARM64E_KERNEL_PTRAUTH_ABI_MASK
1665+
: 0) |
1666+
(PtrAuthABIVersion << 24);
1667+
}
1668+
1669+
inline unsigned CPU_SUBTYPE_ARM64E_IS_VERSIONED_PTRAUTH_ABI(unsigned ST) {
1670+
return ST & CPU_SUBTYPE_ARM64E_VERSIONED_PTRAUTH_ABI_MASK;
1671+
}
1672+
1673+
inline unsigned CPU_SUBTYPE_ARM64E_IS_KERNEL_PTRAUTH_ABI(unsigned ST) {
1674+
return ST & CPU_SUBTYPE_ARM64E_KERNEL_PTRAUTH_ABI_MASK;
1675+
}
1676+
16461677
enum CPUSubTypeARM64_32 { CPU_SUBTYPE_ARM64_32_V8 = 1 };
16471678

16481679
enum CPUSubTypeSPARC { CPU_SUBTYPE_SPARC_ALL = 0 };
@@ -1668,6 +1699,8 @@ enum CPUSubTypePowerPC {
16681699

16691700
Expected<uint32_t> getCPUType(const Triple &T);
16701701
Expected<uint32_t> getCPUSubType(const Triple &T);
1702+
Expected<uint32_t> getCPUSubType(const Triple &T, unsigned PtrAuthABIVersion,
1703+
bool PtrAuthKernelABIVersion);
16711704

16721705
struct x86_thread_state32_t {
16731706
uint32_t eax;

llvm/lib/BinaryFormat/MachO.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,21 @@ Expected<uint32_t> MachO::getCPUSubType(const Triple &T) {
105105
return getPowerPCSubType(T);
106106
return unsupported("subtype", T);
107107
}
108+
109+
Expected<uint32_t> MachO::getCPUSubType(const Triple &T,
110+
unsigned PtrAuthABIVersion,
111+
bool PtrAuthKernelABIVersion) {
112+
Expected<uint32_t> Result = MachO::getCPUSubType(T);
113+
if (!Result)
114+
return Result.takeError();
115+
if (*Result != MachO::CPU_SUBTYPE_ARM64E)
116+
return createStringError(
117+
std::errc::invalid_argument,
118+
"ptrauth ABI version is only supported on arm64e.");
119+
if (PtrAuthABIVersion > 0xF)
120+
return createStringError(
121+
std::errc::invalid_argument,
122+
"The ptrauth ABI version needs to fit within 4 bits.");
123+
return CPU_SUBTYPE_ARM64E_WITH_PTRAUTH_VERSION(PtrAuthABIVersion,
124+
PtrAuthKernelABIVersion);
125+
}

llvm/lib/MC/MachObjectWriter.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,18 @@ void MachObjectWriter::writeHeader(MachO::HeaderFileType Type,
191191
W.write<uint32_t>(is64Bit() ? MachO::MH_MAGIC_64 : MachO::MH_MAGIC);
192192

193193
W.write<uint32_t>(TargetObjectWriter->getCPUType());
194-
W.write<uint32_t>(TargetObjectWriter->getCPUSubtype());
194+
195+
uint32_t Cpusubtype = TargetObjectWriter->getCPUSubtype();
196+
197+
// Promote arm64e subtypes to always be ptrauth-ABI-versioned, at version 0.
198+
// We never need to emit unversioned binaries.
199+
// And we don't support arbitrary ABI versions (or the kernel flag) yet.
200+
if (TargetObjectWriter->getCPUType() == MachO::CPU_TYPE_ARM64 &&
201+
Cpusubtype == MachO::CPU_SUBTYPE_ARM64E)
202+
Cpusubtype = MachO::CPU_SUBTYPE_ARM64E_WITH_PTRAUTH_VERSION(
203+
/*PtrAuthABIVersion=*/0, /*PtrAuthKernelABIVersion=*/false);
204+
205+
W.write<uint32_t>(Cpusubtype);
195206

196207
W.write<uint32_t>(Type);
197208
W.write<uint32_t>(NumLoadCommands);

llvm/lib/Object/MachOObjectFile.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ static unsigned getCPUType(const MachOObjectFile &O) {
134134
}
135135

136136
static unsigned getCPUSubType(const MachOObjectFile &O) {
137-
return O.getHeader().cpusubtype;
137+
return O.getHeader().cpusubtype & ~MachO::CPU_SUBTYPE_MASK;
138138
}
139139

140140
static uint32_t

llvm/test/MC/AArch64/arm64e-subtype.s

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
; CHECK: 0: c0 03 5f d6 ret
55

66
; CHECK: Mach header
7-
; CHECK: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
8-
; CHECK: MH_MAGIC_64 ARM64 E 0x00 OBJECT 3 256 0x00000000
7+
; CHECK: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
8+
; CHECK: MH_MAGIC_64 ARM64 E PAC00 OBJECT 3 256 0x00000000
99

1010
.globl _foo
1111
_foo:
Binary file not shown.
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
RUN: llvm-objdump %p/Inputs/fat.macho-arm64e-kernel-ptrauth-abi -m -p --universal-headers | FileCheck %s
2+
3+
CHECK: Fat headers
4+
CHECK: fat_magic FAT_MAGIC
5+
CHECK: nfat_arch 1
6+
CHECK: architecture arm64e
7+
CHECK: cputype CPU_TYPE_ARM64
8+
CHECK: cpusubtype CPU_SUBTYPE_ARM64E
9+
CHECK: capabilities CPU_SUBTYPE_ARM64E_KERNEL_PTRAUTH_VERSION 5
10+
11+
CHECK: Mach header
12+
CHECK: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
13+
CHECK: MH_MAGIC_64 ARM64 E PAK05 OBJECT 0 0 0x00000000
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
RUN: llvm-objdump %p/Inputs/fat.macho-arm64e-ptrauth-abi -m -p --universal-headers | FileCheck %s
2+
3+
CHECK: Fat headers
4+
CHECK: fat_magic FAT_MAGIC
5+
CHECK: nfat_arch 1
6+
CHECK: architecture arm64e
7+
CHECK: cputype CPU_TYPE_ARM64
8+
CHECK: cpusubtype CPU_SUBTYPE_ARM64E
9+
CHECK: capabilities CPU_SUBTYPE_ARM64E_PTRAUTH_VERSION 5
10+
11+
CHECK: Mach header
12+
CHECK: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
13+
CHECK: MH_MAGIC_64 ARM64 E PAC05 OBJECT 0 0 0x00000000
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#RUN: sed -e "s,SRC_CPUSUBTYPE,0x80000002,g" %s | yaml2obj -o -| llvm-objdump --macho -p - 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=V0
2+
#RUN: sed -e "s,SRC_CPUSUBTYPE,0x81000002,g" %s | yaml2obj -o -| llvm-objdump --macho -p - 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=V1
3+
#RUN: sed -e "s,SRC_CPUSUBTYPE,0xc1000002,g" %s | yaml2obj -o -| llvm-objdump --macho -p - 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=V1K
4+
#RUN: sed -e "s,SRC_CPUSUBTYPE,0x00000002,g" %s | yaml2obj -o -| llvm-objdump --macho -p - 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=VNONE
5+
6+
# CHECK: Mach header
7+
# CHECK: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
8+
# V0: MH_MAGIC_64 ARM64 E PAC00 OBJECT 0 0 0x00000000
9+
# V1: MH_MAGIC_64 ARM64 E PAC01 OBJECT 0 0 0x00000000
10+
# V1K: MH_MAGIC_64 ARM64 E PAK01 OBJECT 0 0 0x00000000
11+
# VNONE: MH_MAGIC_64 ARM64 E 0x00 OBJECT 0 0 0x00000000
12+
13+
--- !mach-o
14+
FileHeader:
15+
magic: 0xFEEDFACF
16+
cputype: 0x0100000C
17+
cpusubtype: SRC_CPUSUBTYPE
18+
filetype: 0x00000001
19+
ncmds: 0
20+
sizeofcmds: 0
21+
flags: 0x00000000
22+
reserved: 0x00000000
23+
...

llvm/tools/dsymutil/MachOUtils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,8 @@ bool generateDsymCompanion(
523523
SymtabStart = alignTo(SymtabStart, 0x1000);
524524

525525
// We gathered all the information we need, start emitting the output file.
526-
Writer.writeHeader(MachO::MH_DSYM, NumLoadCommands, LoadCommandSize, false);
526+
Writer.writeHeader(MachO::MH_DSYM, NumLoadCommands, LoadCommandSize,
527+
/*SubsectionsViaSymbols=*/false);
527528

528529
// Write the load commands.
529530
assert(OutFile.tell() == HeaderSize);

llvm/tools/llvm-objdump/MachODump.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,8 +2394,16 @@ static void printMachOUniversalHeaders(const object::MachOUniversalBinary *UB,
23942394
outs() << " cpusubtype " << (cpusubtype & ~MachO::CPU_SUBTYPE_MASK)
23952395
<< "\n";
23962396
}
2397-
if (verbose &&
2398-
(cpusubtype & MachO::CPU_SUBTYPE_MASK) == MachO::CPU_SUBTYPE_LIB64)
2397+
if (verbose && cputype == MachO::CPU_TYPE_ARM64 &&
2398+
MachO::CPU_SUBTYPE_ARM64E_IS_VERSIONED_PTRAUTH_ABI(cpusubtype)) {
2399+
outs() << " capabilities CPU_SUBTYPE_ARM64E_";
2400+
if (MachO::CPU_SUBTYPE_ARM64E_IS_KERNEL_PTRAUTH_ABI(cpusubtype))
2401+
outs() << "KERNEL_";
2402+
outs() << format("PTRAUTH_VERSION %d",
2403+
MachO::CPU_SUBTYPE_ARM64E_PTRAUTH_VERSION(cpusubtype))
2404+
<< "\n";
2405+
} else if (verbose && (cpusubtype & MachO::CPU_SUBTYPE_MASK) ==
2406+
MachO::CPU_SUBTYPE_LIB64)
23992407
outs() << " capabilities CPU_SUBTYPE_LIB64\n";
24002408
else
24012409
outs() << " capabilities "
@@ -8368,7 +8376,17 @@ static void PrintMachHeader(uint32_t magic, uint32_t cputype,
83688376
outs() << format(" %10d", cpusubtype & ~MachO::CPU_SUBTYPE_MASK);
83698377
break;
83708378
}
8371-
if ((cpusubtype & MachO::CPU_SUBTYPE_MASK) == MachO::CPU_SUBTYPE_LIB64) {
8379+
8380+
if (cputype == MachO::CPU_TYPE_ARM64 &&
8381+
MachO::CPU_SUBTYPE_ARM64E_IS_VERSIONED_PTRAUTH_ABI(cpusubtype)) {
8382+
const char *Format =
8383+
MachO::CPU_SUBTYPE_ARM64E_IS_KERNEL_PTRAUTH_ABI(cpusubtype)
8384+
? " PAK%02d"
8385+
: " PAC%02d";
8386+
outs() << format(Format,
8387+
MachO::CPU_SUBTYPE_ARM64E_PTRAUTH_VERSION(cpusubtype));
8388+
} else if ((cpusubtype & MachO::CPU_SUBTYPE_MASK) ==
8389+
MachO::CPU_SUBTYPE_LIB64) {
83728390
outs() << " LIB64";
83738391
} else {
83748392
outs() << format(" 0x%02" PRIx32,

llvm/unittests/BinaryFormat/MachOTest.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,56 @@ TEST(MachOTest, CPUSubType) {
125125
}
126126
#undef CHECK_CPUSUBTYPE
127127
}
128+
129+
TEST(MachOTest, CPUSubTypePtrAuthABI) {
130+
{
131+
Expected<uint32_t> Type = MachO::getCPUSubType(
132+
Triple("x86_64-apple-darwin"), /*PtrAuthABIVersion=*/5,
133+
/*PtrAuthKernelABIVersion=*/false);
134+
ASSERT_EQ(toString(Type.takeError()),
135+
"ptrauth ABI version is only supported on arm64e.");
136+
}
137+
{
138+
Expected<uint32_t> Type = MachO::getCPUSubType(
139+
Triple("arm64e-apple-darwin"), /*PtrAuthABIVersion=*/0x10,
140+
/*PtrAuthKernelABIVersion=*/false);
141+
ASSERT_EQ(toString(Type.takeError()),
142+
"The ptrauth ABI version needs to fit within 4 bits.");
143+
}
144+
{
145+
uint32_t Type = cantFail(MachO::getCPUSubType(
146+
Triple("arm64e-apple-darwin"),
147+
/*PtrAuthABIVersion=*/5, /*PtrAuthKernelABIVersion=*/false));
148+
ASSERT_EQ(Type, 0x85000002U);
149+
}
150+
{
151+
uint32_t Type = cantFail(MachO::getCPUSubType(
152+
Triple("arm64e-apple-darwin"),
153+
/*PtrAuthABIVersion=*/5, /*PtrAuthKernelABIVersion=*/true));
154+
ASSERT_EQ(Type, 0xC5000002U);
155+
}
156+
{
157+
uint32_t Type = cantFail(MachO::getCPUSubType(
158+
Triple("arm64e-apple-darwin"),
159+
/*PtrAuthABIVersion=*/0xF, /*PtrAuthKernelABIVersion=*/false));
160+
ASSERT_EQ(Type, 0x8F000002U);
161+
}
162+
{
163+
uint32_t Type = cantFail(MachO::getCPUSubType(
164+
Triple("arm64e-apple-darwin"),
165+
/*PtrAuthABIVersion=*/0xF, /*PtrAuthKernelABIVersion=*/true));
166+
ASSERT_EQ(Type, 0xCF000002U);
167+
}
168+
{
169+
uint32_t Type = cantFail(MachO::getCPUSubType(
170+
Triple("arm64e-apple-darwin"),
171+
/*PtrAuthABIVersion=*/0, /*PtrAuthKernelABIVersion=*/false));
172+
ASSERT_EQ(Type, 0x80000002U);
173+
}
174+
{
175+
uint32_t Type = cantFail(MachO::getCPUSubType(
176+
Triple("arm64e-apple-darwin"),
177+
/*PtrAuthABIVersion=*/0, /*PtrAuthKernelABIVersion=*/true));
178+
ASSERT_EQ(Type, 0xC0000002U);
179+
}
180+
}

0 commit comments

Comments
 (0)