diff --git a/libunwind/include/mach-o/compact_unwind_encoding.h b/libunwind/include/mach-o/compact_unwind_encoding.h index c495b83c40b9..2bfa4dd7b13a 100644 --- a/libunwind/include/mach-o/compact_unwind_encoding.h +++ b/libunwind/include/mach-o/compact_unwind_encoding.h @@ -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. //////////////////////////////////////////////////////////////////////////////// // diff --git a/libunwind/src/CompactUnwinder.hpp b/libunwind/src/CompactUnwinder.hpp index 929f540b662b..bc9c569a8ccb 100644 --- a/libunwind/src/CompactUnwinder.hpp +++ b/libunwind/src/CompactUnwinder.hpp @@ -520,6 +520,7 @@ int CompactUnwinder_arm64::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::stepWithCompactEncodingFrameless( template int CompactUnwinder_arm64::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::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; } diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp index be8bcf1c9c93..8868932d6821 100644 --- a/libunwind/src/DwarfInstructions.hpp +++ b/libunwind/src/DwarfInstructions.hpp @@ -300,16 +300,28 @@ int DwarfInstructions::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::stepWithDwarf( #endif } #endif +#endif #if defined(_LIBUNWIND_IS_NATIVE_ONLY) && defined(_LIBUNWIND_TARGET_ARM) && \ defined(__ARM_FEATURE_PAUTH) diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index c88422a6d5f4..d79d836d6925 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -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; } diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp index 537dbd83f4f7..ad110359e26d 100644 --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -1911,6 +1911,7 @@ bool UnwindCursor::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::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::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::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::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