[libunwind][arm64e] Mach-O Support unwinding PAuth_LR frames (#202772)
Under PAuth_LR, compact unwind entries marked UNWIND_ARM64_MODE_FRAME_PAUTH_LR are signed with the PC of the pacibsppc at the start of the range. The compiler may emit those entries with UNWIND_IS_NOT_FUNCTION_START when the pacibsppc is not the first instruction in the function; the unwinder must scan backwards past such entries to find the actual function start (and its LSDA pointer). When unwinding a PAuth_LR frame, authenticate and re-sign using setIPPAuthLR(), which calls __builtin_ptrauth_auth_with_pc_and_resign when available, falling back to inline asm (autib171615 + pacib) for compilers that lack the builtin. To reduce the size of Compact Unwind entries for shrinkwrapped frames, new offset bits encode the distance in multiples of 4 bytes from the range start to the pacibsppc. The compiler emits paired FRAMELESS/FRAMELESS+PAUTH_LR entries that the linker merges into a single FRAME_PAUTH_LR | (offset/4) << 12 encoding. rdar://161907302&169746432&171118419
This commit is contained in:
parent
8ba8c16cde
commit
8e34b2cbb0
5 changed files with 170 additions and 58 deletions
|
|
@ -39,16 +39,14 @@
|
|||
//
|
||||
typedef uint32_t compact_unwind_encoding_t;
|
||||
|
||||
|
||||
// architecture independent bits
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_IS_NOT_FUNCTION_START = 0x80000000,
|
||||
UNWIND_HAS_LSDA = 0x40000000,
|
||||
UNWIND_PERSONALITY_MASK = 0x30000000,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// clang-format on
|
||||
|
||||
//
|
||||
// x86
|
||||
|
|
@ -67,6 +65,7 @@ enum {
|
|||
// 3-bits register count
|
||||
// 10-bits register permutation
|
||||
//
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_X86_MODE_MASK = 0x0F000000,
|
||||
UNWIND_X86_MODE_EBP_FRAME = 0x01000000,
|
||||
|
|
@ -84,7 +83,9 @@ enum {
|
|||
|
||||
UNWIND_X86_DWARF_SECTION_OFFSET = 0x00FFFFFF,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_X86_REG_NONE = 0,
|
||||
UNWIND_X86_REG_EBX = 1,
|
||||
|
|
@ -94,6 +95,7 @@ enum {
|
|||
UNWIND_X86_REG_ESI = 5,
|
||||
UNWIND_X86_REG_EBP = 6,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
//
|
||||
// For x86 there are four modes for the compact unwind encoding:
|
||||
|
|
@ -182,9 +184,6 @@ enum {
|
|||
//}
|
||||
//
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
// x86_64
|
||||
//
|
||||
|
|
@ -202,6 +201,7 @@ enum {
|
|||
// 3-bits register count
|
||||
// 10-bits register permutation
|
||||
//
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_X86_64_MODE_MASK = 0x0F000000,
|
||||
UNWIND_X86_64_MODE_RBP_FRAME = 0x01000000,
|
||||
|
|
@ -219,7 +219,9 @@ enum {
|
|||
|
||||
UNWIND_X86_64_DWARF_SECTION_OFFSET = 0x00FFFFFF,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_X86_64_REG_NONE = 0,
|
||||
UNWIND_X86_64_REG_RBX = 1,
|
||||
|
|
@ -229,6 +231,8 @@ enum {
|
|||
UNWIND_X86_64_REG_R15 = 5,
|
||||
UNWIND_X86_64_REG_RBP = 6,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
//
|
||||
// For x86_64 there are four modes for the compact unwind encoding:
|
||||
// UNWIND_X86_64_MODE_RBP_FRAME:
|
||||
|
|
@ -267,7 +271,6 @@ enum {
|
|||
// function.
|
||||
//
|
||||
|
||||
|
||||
// ARM64
|
||||
//
|
||||
// 1-bit: start
|
||||
|
|
@ -283,11 +286,16 @@ enum {
|
|||
// DWARF:
|
||||
// 24-bits offset of DWARF FDE in __eh_frame section
|
||||
//
|
||||
// clang-format off
|
||||
enum {
|
||||
UNWIND_ARM64_MODE_MASK = 0x0F000000,
|
||||
UNWIND_ARM64_MODE_FRAMELESS = 0x02000000,
|
||||
UNWIND_ARM64_MODE_DWARF = 0x03000000,
|
||||
UNWIND_ARM64_MODE_FRAME = 0x04000000,
|
||||
UNWIND_ARM64_MODE_FRAME_PAUTH_LR = 0x05000000,
|
||||
|
||||
UNWIND_ARM64_PAUTH_LR_OFFSET_MASK = 0x00FFF000,
|
||||
UNWIND_ARM64_PAUTH_LR_OFFSET_SHIFT = 12,
|
||||
|
||||
UNWIND_ARM64_FRAME_X19_X20_PAIR = 0x00000001,
|
||||
UNWIND_ARM64_FRAME_X21_X22_PAIR = 0x00000002,
|
||||
|
|
@ -302,6 +310,8 @@ enum {
|
|||
UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK = 0x00FFF000,
|
||||
UNWIND_ARM64_DWARF_SECTION_OFFSET = 0x00FFFFFF,
|
||||
};
|
||||
// clang-format on
|
||||
//
|
||||
// For arm64 there are three modes for the compact unwind encoding:
|
||||
// UNWIND_ARM64_MODE_FRAME:
|
||||
// This is a standard arm64 prolog where FP/LR are immediately pushed on the
|
||||
|
|
@ -323,11 +333,13 @@ enum {
|
|||
// This mode is never used in object files. It is only generated by the
|
||||
// linker in final linked images which have only DWARF unwind info for a
|
||||
// function.
|
||||
//
|
||||
|
||||
|
||||
|
||||
|
||||
// For arm64e there is one more mode for compact unwind encoding:
|
||||
// UNWIND_ARM64_MODE_FRAME_PAUTH_LR:
|
||||
// An arm64e prologue, where LR is signed in the range with a `pacibsppc`.
|
||||
// The UNWIND_ARM64_PAUTH_LR_OFFSET_MASK bits specify an unsigned positive
|
||||
// offset (encoded as a multiple of 4 bytes) from the beginning of the range
|
||||
// to the `pacibsppc`, with 0 indicating that the `pacibsppc` is the first
|
||||
// instruction in the range.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ int CompactUnwinder_arm64<A>::stepWithCompactEncoding(
|
|||
A &addressSpace, Registers_arm64 ®isters) {
|
||||
switch (compactEncoding & UNWIND_ARM64_MODE_MASK) {
|
||||
case UNWIND_ARM64_MODE_FRAME:
|
||||
case UNWIND_ARM64_MODE_FRAME_PAUTH_LR:
|
||||
return stepWithCompactEncodingFrame(compactEncoding, functionStart,
|
||||
addressSpace, registers);
|
||||
case UNWIND_ARM64_MODE_FRAMELESS:
|
||||
|
|
@ -619,7 +620,7 @@ int CompactUnwinder_arm64<A>::stepWithCompactEncodingFrameless(
|
|||
|
||||
template <typename A>
|
||||
int CompactUnwinder_arm64<A>::stepWithCompactEncodingFrame(
|
||||
compact_unwind_encoding_t encoding, uint64_t, A &addressSpace,
|
||||
compact_unwind_encoding_t encoding, uint64_t functionStart, A &addressSpace,
|
||||
Registers_arm64 ®isters) {
|
||||
Registers_arm64::reg_t savedRegisterLoc = registers.getFP() - 8;
|
||||
|
||||
|
|
@ -698,7 +699,21 @@ int CompactUnwinder_arm64<A>::stepWithCompactEncodingFrame(
|
|||
registers.setSP(fp + 16);
|
||||
|
||||
// pop return address into pc
|
||||
registers.setIP(addressSpace.get64(fp + 8));
|
||||
Registers_arm64::reg_t linkRegister = addressSpace.get64(fp + 8);
|
||||
|
||||
// authenticate lr, if needed
|
||||
if ((encoding & UNWIND_ARM64_MODE_MASK) == UNWIND_ARM64_MODE_FRAME_PAUTH_LR) {
|
||||
// Offset from the beginning of the range to the pacibsppc, in words.
|
||||
uint32_t offset = (encoding & UNWIND_ARM64_PAUTH_LR_OFFSET_MASK) >>
|
||||
UNWIND_ARM64_PAUTH_LR_OFFSET_SHIFT;
|
||||
|
||||
// Address of the pacibsppc
|
||||
uint64_t pacibsppcAddr = functionStart + offset * 4;
|
||||
|
||||
registers.setIPPAuthLR(linkRegister, pacibsppcAddr);
|
||||
} else {
|
||||
registers.setIP(linkRegister);
|
||||
}
|
||||
|
||||
return UNW_STEP_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,16 +300,28 @@ int DwarfInstructions<A, R>::stepWithDwarf(
|
|||
|
||||
isSignalFrame = cieInfo.isSignalFrame;
|
||||
|
||||
#if defined(_LIBUNWIND_TARGET_AARCH64) && \
|
||||
!defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING)
|
||||
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
||||
// There are two ways of return address signing: pac-ret (enabled via
|
||||
// -mbranch-protection=pac-ret) and ptrauth-returns (enabled as part of
|
||||
// Apple's arm64e or experimental pauthtest ABI on Linux). The code
|
||||
// below handles signed RA for pac-ret, while ptrauth-returns uses
|
||||
// different logic.
|
||||
// below handles signed RA for ptrauth-returns, while pac-ret uses pacm
|
||||
// instructions from the hint space.
|
||||
//
|
||||
// TODO: unify logic for both cases, see
|
||||
// https://github.com/llvm/llvm-project/issues/160110
|
||||
//
|
||||
#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING)
|
||||
if (getReturnAddressSignStatus(addressSpace, registers, cfa, prolog) ==
|
||||
RASignedWithPC) {
|
||||
newRegisters.setIPPAuthLR(returnAddress, prolog.ptrAuthDiversifier);
|
||||
} else {
|
||||
newRegisters.setIP(returnAddress);
|
||||
}
|
||||
|
||||
// Simulate the step by replacing the register set with the new ones.
|
||||
registers = newRegisters;
|
||||
|
||||
return UNW_STEP_SUCCESS;
|
||||
#else
|
||||
// If the target is aarch64 then the return address may have been signed
|
||||
// using the v8.3 pointer authentication extensions. The original
|
||||
// return address needs to be authenticated before the return address is
|
||||
|
|
@ -352,6 +364,7 @@ int DwarfInstructions<A, R>::stepWithDwarf(
|
|||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_LIBUNWIND_IS_NATIVE_ONLY) && defined(_LIBUNWIND_TARGET_ARM) && \
|
||||
defined(__ARM_FEATURE_PAUTH)
|
||||
|
|
|
|||
|
|
@ -1896,11 +1896,9 @@ public:
|
|||
#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING)
|
||||
// Note the value of the PC was signed to its address in the register state
|
||||
// but everyone else expects it to be signed by the SP, so convert on return.
|
||||
value = (uint64_t)ptrauth_auth_and_resign((void *)_registers.__pc,
|
||||
ptrauth_key_return_address,
|
||||
&_registers.__pc,
|
||||
ptrauth_key_return_address,
|
||||
getSP());
|
||||
value = (uint64_t)ptrauth_auth_and_resign(
|
||||
(void *)_registers.__pc, ptrauth_key_return_address, &_registers.__pc,
|
||||
ptrauth_key_return_address, _registers.__sp);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
|
@ -1909,14 +1907,37 @@ public:
|
|||
// Note the value which was set should have been signed with the SP.
|
||||
// We then resign with the slot we are being stored in to so that both SP
|
||||
// and LR can't be spoofed at the same time.
|
||||
value = (uint64_t)ptrauth_auth_and_resign((void *)value,
|
||||
ptrauth_key_return_address,
|
||||
getSP(),
|
||||
ptrauth_key_return_address,
|
||||
&_registers.__pc);
|
||||
value = (uint64_t)ptrauth_auth_and_resign(
|
||||
(void *)value, ptrauth_key_return_address, _registers.__sp,
|
||||
ptrauth_key_return_address, &_registers.__pc);
|
||||
#endif
|
||||
_registers.__pc = value;
|
||||
}
|
||||
__attribute__((target("pauth-lr"))) void setIPPAuthLR(uint64_t value,
|
||||
uint64_t signing_pc) {
|
||||
#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING)
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
#if __has_builtin(__builtin_ptrauth_auth_with_pc_and_resign)
|
||||
value = (uint64_t)ptrauth_auth_with_pc_and_resign(
|
||||
(void *)value, ptrauth_key_return_address, _registers.__sp, signing_pc,
|
||||
ptrauth_key_return_address, &_registers.__pc);
|
||||
#else
|
||||
register uint64_t x17 __asm("x17") = value;
|
||||
register uint64_t x16 __asm("x16") = _registers.__sp;
|
||||
register uint64_t x15 __asm("x15") = signing_pc;
|
||||
uint64_t resignDisc = (uint64_t)&_registers.__pc;
|
||||
asm("autib171615\n\t"
|
||||
"pacib %0, %3"
|
||||
: "+r"(x17)
|
||||
: "r"(x16), "r"(x15), "r"(resignDisc));
|
||||
value = x17;
|
||||
#endif
|
||||
#endif
|
||||
_registers.__pc = value;
|
||||
}
|
||||
|
||||
uint64_t getFP() const { return _registers.__fp; }
|
||||
void setFP(uint64_t value) { _registers.__fp = value; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1911,6 +1911,7 @@ bool UnwindCursor<A, R>::getInfoFromCompactEncodingSection(
|
|||
// do a binary search of second level page index
|
||||
uint32_t encoding = 0;
|
||||
pint_t funcStart = 0;
|
||||
pint_t rangeStart = 0;
|
||||
pint_t funcEnd = 0;
|
||||
pint_t lsda = 0;
|
||||
pint_t personality = 0;
|
||||
|
|
@ -1950,21 +1951,39 @@ bool UnwindCursor<A, R>::getInfoFromCompactEncodingSection(
|
|||
}
|
||||
}
|
||||
encoding = pageIndex.encoding(low);
|
||||
funcStart = pageIndex.functionOffset(low) + sects.dso_base;
|
||||
rangeStart = pageIndex.functionOffset(low) + sects.dso_base;
|
||||
|
||||
// If UNWIND_IS_NOT_FUNCTION_START is set, walk backwards to find the actual
|
||||
// function start.
|
||||
funcStart = rangeStart;
|
||||
if (encoding & UNWIND_IS_NOT_FUNCTION_START) {
|
||||
assert(low != 0 &&
|
||||
"UNWIND_IS_NOT_FUNCTION_START comact unwind must be preceded by a "
|
||||
"~UNWIND_IS_NOT_FUNCTION_START entry for the same function");
|
||||
uint32_t backIndex = low;
|
||||
do {
|
||||
--backIndex;
|
||||
} while (backIndex > 0 &&
|
||||
(pageIndex.encoding(backIndex) & UNWIND_IS_NOT_FUNCTION_START));
|
||||
funcStart = pageIndex.functionOffset(backIndex) + sects.dso_base;
|
||||
}
|
||||
|
||||
if (pc < funcStart) {
|
||||
if (log)
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tpc not in table, pc=0x%llX, funcStart=0x%llX, funcEnd=0x%llX\n",
|
||||
(uint64_t) pc, (uint64_t) funcStart, (uint64_t) funcEnd);
|
||||
fprintf(stderr,
|
||||
"\tpc not in table, pc=0x%llX, funcStart=0x%llX, "
|
||||
"rangeStart=0x%llX, funcEnd=0x%llX\n",
|
||||
(uint64_t)pc, (uint64_t)funcStart, (uint64_t)rangeStart,
|
||||
(uint64_t)funcEnd);
|
||||
return false;
|
||||
}
|
||||
if (pc > funcEnd) {
|
||||
if (log)
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tpc not in table, pc=0x%llX, funcStart=0x%llX, funcEnd=0x%llX\n",
|
||||
(uint64_t) pc, (uint64_t) funcStart, (uint64_t) funcEnd);
|
||||
fprintf(stderr,
|
||||
"\tpc not in table, pc=0x%llX, funcStart=0x%llX, "
|
||||
"rangeStart=0x%llX, funcEnd=0x%llX\n",
|
||||
(uint64_t)pc, (uint64_t)funcStart, (uint64_t)rangeStart,
|
||||
(uint64_t)funcEnd);
|
||||
return false;
|
||||
}
|
||||
} else if (pageKind == UNWIND_SECOND_LEVEL_COMPRESSED) {
|
||||
|
|
@ -1998,19 +2017,55 @@ bool UnwindCursor<A, R>::getInfoFromCompactEncodingSection(
|
|||
high = mid;
|
||||
}
|
||||
}
|
||||
funcStart = pageIndex.functionOffset(low) + firstLevelFunctionOffset
|
||||
+ sects.dso_base;
|
||||
rangeStart = pageIndex.functionOffset(low) + firstLevelFunctionOffset +
|
||||
sects.dso_base;
|
||||
if (low < last)
|
||||
funcEnd =
|
||||
pageIndex.functionOffset(low + 1) + firstLevelFunctionOffset
|
||||
+ sects.dso_base;
|
||||
else
|
||||
funcEnd = firstLevelNextPageFunctionOffset + sects.dso_base;
|
||||
|
||||
auto encodingAtIndex = [&](uint32_t idx) -> uint32_t {
|
||||
uint16_t encIdx = pageIndex.encodingIndex(idx);
|
||||
if (encIdx < sectionHeader.commonEncodingsArrayCount()) {
|
||||
return _addressSpace.get32(
|
||||
sects.compact_unwind_section +
|
||||
sectionHeader.commonEncodingsArraySectionOffset() +
|
||||
encIdx * sizeof(uint32_t));
|
||||
} else {
|
||||
uint16_t pageEncIdx =
|
||||
encIdx - (uint16_t)sectionHeader.commonEncodingsArrayCount();
|
||||
return _addressSpace.get32(secondLevelAddr +
|
||||
pageHeader.encodingsPageOffset() +
|
||||
pageEncIdx * sizeof(uint32_t));
|
||||
}
|
||||
};
|
||||
|
||||
encoding = encodingAtIndex(low);
|
||||
|
||||
// If UNWIND_IS_NOT_FUNCTION_START is set, walk backwards to find the actual
|
||||
// function start.
|
||||
funcStart = rangeStart;
|
||||
if (encoding & UNWIND_IS_NOT_FUNCTION_START) {
|
||||
assert(low != 0 &&
|
||||
"UNWIND_IS_NOT_FUNCTION_START comact unwind must be preceded by a "
|
||||
"~UNWIND_IS_NOT_FUNCTION_START entry for the same function.");
|
||||
uint32_t backIndex = low;
|
||||
do {
|
||||
--backIndex;
|
||||
} while (backIndex > 0 &&
|
||||
(encodingAtIndex(backIndex) & UNWIND_IS_NOT_FUNCTION_START));
|
||||
funcStart = pageIndex.functionOffset(backIndex) +
|
||||
firstLevelFunctionOffset + sects.dso_base;
|
||||
}
|
||||
|
||||
if (pc < funcStart) {
|
||||
_LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX "
|
||||
"not in second level compressed unwind table. "
|
||||
"funcStart=0x%llX",
|
||||
(uint64_t) pc, (uint64_t) funcStart);
|
||||
"funcStart=0x%llX, rangeStart=0x%llX",
|
||||
(uint64_t)pc, (uint64_t)funcStart,
|
||||
(uint64_t)rangeStart);
|
||||
return false;
|
||||
}
|
||||
if (pc > funcEnd) {
|
||||
|
|
@ -2020,21 +2075,6 @@ bool UnwindCursor<A, R>::getInfoFromCompactEncodingSection(
|
|||
(uint64_t) pc, (uint64_t) funcEnd);
|
||||
return false;
|
||||
}
|
||||
uint16_t encodingIndex = pageIndex.encodingIndex(low);
|
||||
if (encodingIndex < sectionHeader.commonEncodingsArrayCount()) {
|
||||
// encoding is in common table in section header
|
||||
encoding = _addressSpace.get32(
|
||||
sects.compact_unwind_section +
|
||||
sectionHeader.commonEncodingsArraySectionOffset() +
|
||||
encodingIndex * sizeof(uint32_t));
|
||||
} else {
|
||||
// encoding is in page specific table
|
||||
uint16_t pageEncodingIndex =
|
||||
encodingIndex - (uint16_t)sectionHeader.commonEncodingsArrayCount();
|
||||
encoding = _addressSpace.get32(secondLevelAddr +
|
||||
pageHeader.encodingsPageOffset() +
|
||||
pageEncodingIndex * sizeof(uint32_t));
|
||||
}
|
||||
} else {
|
||||
_LIBUNWIND_DEBUG_LOG(
|
||||
"malformed __unwind_info at 0x%0llX bad second level page",
|
||||
|
|
@ -2111,7 +2151,18 @@ bool UnwindCursor<A, R>::getInfoFromCompactEncodingSection(
|
|||
fprintf(stderr, "getInfoFromCompactEncodingSection(pc=0x%llX), "
|
||||
"encoding=0x%08X, lsda=0x%08llX for funcStart=0x%llX\n",
|
||||
(uint64_t) pc, encoding, (uint64_t) lsda, (uint64_t) funcStart);
|
||||
|
||||
// For ARM64 PAuth_LR frames, start_ip should be the pacibsppc address
|
||||
// (rangeStart). For all other cases, start_ip should be the function start.
|
||||
#if defined(_LIBUNWIND_TARGET_AARCH64)
|
||||
if ((encoding & UNWIND_ARM64_MODE_MASK) == UNWIND_ARM64_MODE_FRAME_PAUTH_LR) {
|
||||
_info.start_ip = rangeStart;
|
||||
} else {
|
||||
_info.start_ip = funcStart;
|
||||
}
|
||||
#else
|
||||
_info.start_ip = funcStart;
|
||||
#endif
|
||||
_info.end_ip = funcEnd;
|
||||
_info.lsda = lsda;
|
||||
// We use memmove to copy the personality function as we have already manually
|
||||
|
|
|
|||
Loading…
Reference in a new issue