diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h --- a/libunwind/include/__libunwind_config.h +++ b/libunwind/include/__libunwind_config.h @@ -58,7 +58,11 @@ # elif defined(__ppc__) # define _LIBUNWIND_TARGET_PPC 1 # define _LIBUNWIND_CONTEXT_SIZE 117 +#if defined(_AIX) && defined(__GNUC__) && !defined(__clang__) +#define _LIBUNWIND_CURSOR_SIZE 125 +#else # define _LIBUNWIND_CURSOR_SIZE 124 +#endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC # elif defined(__aarch64__) # define _LIBUNWIND_TARGET_AARCH64 1 diff --git a/libunwind/include/libunwind.h b/libunwind/include/libunwind.h --- a/libunwind/include/libunwind.h +++ b/libunwind/include/libunwind.h @@ -120,6 +120,9 @@ extern void unw_save_vfp_as_X(unw_cursor_t *) LIBUNWIND_AVAIL; #endif +#ifdef _AIX +extern uintptr_t unw_get_data_rel_base(unw_cursor_t *) LIBUNWIND_AVAIL; +#endif extern const char *unw_regname(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; extern int unw_get_proc_info(unw_cursor_t *, unw_proc_info_t *) LIBUNWIND_AVAIL; diff --git a/libunwind/src/AddressSpace.hpp b/libunwind/src/AddressSpace.hpp --- a/libunwind/src/AddressSpace.hpp +++ b/libunwind/src/AddressSpace.hpp @@ -24,11 +24,11 @@ #include "Registers.hpp" #ifndef _LIBUNWIND_USE_DLADDR - #if !defined(_LIBUNWIND_IS_BAREMETAL) && !defined(_WIN32) - #define _LIBUNWIND_USE_DLADDR 1 - #else - #define _LIBUNWIND_USE_DLADDR 0 - #endif +#if !(defined(_LIBUNWIND_IS_BAREMETAL) || defined(_WIN32) || defined(_AIX)) +#define _LIBUNWIND_USE_DLADDR 1 +#else +#define _LIBUNWIND_USE_DLADDR 0 +#endif #endif #if _LIBUNWIND_USE_DLADDR @@ -45,6 +45,12 @@ }; #endif +#if defined(_AIX) +namespace libunwind { +char *getFuncName(uintptr_t pc, uint16_t &NameLen, unw_word_t *offset); +} +#endif + #ifdef __APPLE__ struct dyld_unwind_sections @@ -580,6 +586,11 @@ (void)targetAddr; (void)info; return true; +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // The traceback table is used for unwinding. + (void)targetAddr; + (void)info; + return true; #elif defined(_LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX) int length = 0; info.arm_section = @@ -596,7 +607,6 @@ return false; } - inline bool LocalAddressSpace::findOtherFDE(pint_t targetAddr, pint_t &fde) { // TO DO: if OS has way to dynamically register FDEs, check that. (void)targetAddr; @@ -616,6 +626,13 @@ return true; } } +#elif defined(_AIX) + uint16_t nameLen; + char *funcName = getFuncName(addr, nameLen, offset); + if (funcName != NULL) { + snprintf(buf, bufLen, "%.*s", nameLen, funcName); + return true; + } #else (void)addr; (void)buf; diff --git a/libunwind/src/CMakeLists.txt b/libunwind/src/CMakeLists.txt --- a/libunwind/src/CMakeLists.txt +++ b/libunwind/src/CMakeLists.txt @@ -11,6 +11,12 @@ ) endif() +if(${CMAKE_SYSTEM_NAME} MATCHES "AIX") + list(APPEND LIBUNWIND_CXX_SOURCES + Unwind_AIXExtras.cpp + ) +endif() + set(LIBUNWIND_C_SOURCES UnwindLevel1.c UnwindLevel1-gcc-ext.c @@ -26,7 +32,11 @@ ) # See add_asm_sources() in compiler-rt for explanation of this workaround. -if((APPLE AND CMAKE_VERSION VERSION_LESS 3.19) OR (MINGW AND CMAKE_VERSION VERSION_LESS 3.17)) +# CMake doesn't work correctly with assembly on AIX. Workaround by compiling +# as C files as well. +if((APPLE AND CMAKE_VERSION VERSION_LESS 3.19) OR + (MINGW AND CMAKE_VERSION VERSION_LESS 3.17) OR + (${CMAKE_SYSTEM_NAME} MATCHES "AIX")) set_source_files_properties(${LIBUNWIND_ASM_SOURCES} PROPERTIES LANGUAGE C) endif() diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -590,6 +590,8 @@ void setSP(uint32_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint32_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint32_t value) { _registers.__cr = value; } private: struct ppc_thread_state_t { @@ -1156,6 +1158,8 @@ void setSP(uint64_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint64_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint64_t value) { _registers.__cr = value; } private: struct ppc64_thread_state_t { diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -23,6 +23,11 @@ #ifdef __APPLE__ #include #endif +#ifdef _AIX +#include +#include +#include +#endif #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) // Provide a definition for the DISPATCHER_CONTEXT struct for old (Win7 and @@ -449,6 +454,12 @@ #ifdef __arm__ virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); } #endif + +#ifdef _AIX + virtual uintptr_t getDataRelBase() { + _LIBUNWIND_ABORT("getDataRelBase not implemented"); + } +#endif }; #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32) @@ -901,6 +912,10 @@ virtual void saveVFPAsX(); #endif +#ifdef _AIX + virtual uintptr_t getDataRelBase(); +#endif + // libunwind does not and should not depend on C++ library which means that we // need our own defition of inline placement new. static void *operator new(size_t, UnwindCursor *p) { return p; } @@ -1192,6 +1207,16 @@ int stepWithSEHData() { /* FIXME: Implement */ return 0; } #endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + bool getInfoFromTBTable(pint_t pc, R ®isters); + int stepWithTBTable(pint_t pc, tbtable *TBTable, R ®isters, + bool &isSignalFrame); + int stepWithTBTableData() { + return stepWithTBTable((pint_t)this->getReg(UNW_REG_IP), + (tbtable *)_info.unwind_info, _registers, + _isSignalFrame); + } +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) A &_addressSpace; R _registers; @@ -1264,6 +1289,13 @@ } #endif +#ifdef _AIX +template +uintptr_t UnwindCursor::getDataRelBase() { + return (uintptr_t)_info.extra; +} +#endif + template const char *UnwindCursor::getRegisterName(int regNum) { return _registers.getRegisterName(regNum); @@ -1892,6 +1924,434 @@ } #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) +// The order of bits has_vec and longtbtable of the traceback table +// structure declared in system header is flipped in newer +// AIX levels. To allow building libunwind on AIX levels that do not have the +// fix, variable TBTableByte6 which points to the byte containing these bits +// in the traceback table and the following masks are used in functions +// getInfoFromTBTable() and stepWithTBTable() for checking these bits in the +// correct order. +#define _LIBUNWIND_TBTABLE_HAS_VEC_BIT 0x80 +#define _LIBUNWIND_TBTABLE_LONGTBTABLE_BIT 0x40 + +// Exception handling info is present if set. +#define _LIBUNWIND_TBTABLE_EH_INFO 0x08 + +enum frameType : unw_word_t { oldFrame = 0, clangFrame = 1 }; + +extern "C" { +typedef _Unwind_Reason_Code __xlcxx_personality_v0_t(int, _Unwind_Action, + uint64_t, + _Unwind_Exception *, + struct _Unwind_Context *); +__attribute__((__weak__)) _Unwind_Reason_Code +__xlcxx_personality_v0(int, _Unwind_Action, uint64_t, _Unwind_Exception *, + struct _Unwind_Context *); +} + +static __xlcxx_personality_v0_t *xlcPersonalityV0; +static RWMutex xlcPersonalityV0InitLock; + +template +bool UnwindCursor::getInfoFromTBTable(pint_t pc, R ®isters) { + uint32_t *p = (uint32_t *)pc; + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + ++p; + tbtable *TBTable = (tbtable *)(p + 1); + + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, (void *)TBTable); + } + + // If the traceback table does not contain necessary info, bypass this frame. + if (!TBTable->tb.has_tboff) + return false; + + // Structure tbtable_ext contains important data we are looking for. + p = (uint32_t *)&TBTable->tb_ext; + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // p now points to tb_offset, the offset from start of function to TB table. + unw_word_t start_ip = (unw_word_t)TBTable - *p - sizeof(uint32_t); + unw_word_t end_ip = (unw_word_t)TBTable; + ++p; + + _LIBUNWIND_TRACE_UNWINDING("start_ip=%p, end_ip=%p\n", (void *)start_ip, + (void *)end_ip); + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + ++p; + + // Pointer to the 6th byte of the traceback table. + uint8_t *TBTableByte6 = (uint8_t *)TBTable + 5; + + unw_word_t lsda = 0; + unw_word_t handler = 0; + unw_word_t flags = frameType::oldFrame; + + if (TBTable->tb.lang == TB_CPLUSPLUS && TBTable->tb.has_ctl) { + // State table info is available. The ctl_info field indicates the + // number of CTL anchors. There should be only one entry for the C++ + // state table. + assert(*p == 1 && "libunwind: there must be only one ctl_info entry"); + ++p; + // p points to the offset of the state table into the stack. + pint_t stateOffset = *p++; + + int framePointerReg; + + // Skip fields name_len and name if exist. + if (TBTable->tb.name_present) { + const uint16_t name_len = *((uint16_t *)p); + p = (uint32_t *)(((char *)p) + name_len + sizeof(name_len)); + } + + if (TBTable->tb.uses_alloca) + framePointerReg = *((char *)p); + else + framePointerReg = 1; // default frame pointer == SP + + _LIBUNWIND_TRACE_UNWINDING("framePointerReg=%d, framePointer=%p, " + "stateOffset=%#lx\n", + framePointerReg, + (void *)_registers.getRegister(framePointerReg), + stateOffset); + lsda = _registers.getRegister(framePointerReg) + stateOffset; + + // Resolve the function pointer to the state table personality if it has + // not already. + if (xlcPersonalityV0 == NULL) { + xlcPersonalityV0InitLock.lock(); + if (xlcPersonalityV0 == NULL) { + // If libc++abi is statically linked in, symbol __xlcxx_personality_v0 + // has been resolved at the link time. + xlcPersonalityV0 = &__xlcxx_personality_v0; + if (xlcPersonalityV0 == NULL) { + // libc++abi is dynamically linked. Resolve __xlcxx_personality_v0 + // using dlopen(). + const char libcxxabi[] = "libc++abi.a(libc++abi.so.1)"; + void *libHandle; + libHandle = dlopen(libcxxabi, RTLD_MEMBER | RTLD_NOW); + if (libHandle == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlopen() failed with errno=%d\n", + errno); + assert(0 && "dlopen() failed"); + } + xlcPersonalityV0 = reinterpret_cast<__xlcxx_personality_v0_t *>( + dlsym(libHandle, "__xlcxx_personality_v0")); + if (xlcPersonalityV0 == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlsym() failed with errno=%d\n", errno); + assert(0 && "dlsym() failed"); + } + dlclose(libHandle); + } + } + xlcPersonalityV0InitLock.unlock(); + } + handler = reinterpret_cast(xlcPersonalityV0); + _LIBUNWIND_TRACE_UNWINDING("State table: LSDA=%p, Personality=%p\n", + (void *)lsda, (void *)handler); + } else if (*TBTableByte6 & _LIBUNWIND_TBTABLE_LONGTBTABLE_BIT) { + // Check traceback table bit longtbtable. This is equivelent to + // "if (TBTable->tb.longtbtable)". If it is set, the range table + // info may be available. + + // p points to field name len. + uint8_t *charPtr = (uint8_t *)p; + + // Skip fields name_len and name if they exist. + if (TBTable->tb.name_present) + charPtr = charPtr + *((uint16_t *)charPtr) + sizeof(uint16_t); + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + // Check traceback table bit has_vec. This is equivalent to + // "if (TBTable->tb.has_vec)". Skip struct vec_ext if it is set. + if (*TBTableByte6 & _LIBUNWIND_TBTABLE_HAS_VEC_BIT) + charPtr += sizeof(struct vec_ext); + + // charPtr points to field xtbtable. + if (*charPtr & _LIBUNWIND_TBTABLE_EH_INFO) { + // eh_info is available. + charPtr++; + // The pointer is 4-byte aligned. + if ((uintptr_t)charPtr % 4) + charPtr += 4 - (uintptr_t)charPtr % 4; + uintptr_t *ehInfo = (uintptr_t *)*(uintptr_t *)(registers.getRegister(2) + + *(uintptr_t *)charPtr); + + // ehInfo points to structure en_info. The first member is version. + // Only version 0 is currently supported. + assert(*(uint32_t *)ehInfo == 0 && + "libunwind: ehInfo version other than 0 is not supported"); + + // Increment ehInfo to point to member lsda. + ++ehInfo; + lsda = *ehInfo++; + + // enInfo now points to member personality. + handler = *ehInfo; + + // Mark this frame has the new EH info. + flags = frameType::clangFrame; + + _LIBUNWIND_TRACE_UNWINDING("Range table: LSDA=%#lx, Personality=%#lx\n", + lsda, handler); + } + } + + _info.start_ip = start_ip; + _info.end_ip = end_ip; + _info.lsda = lsda; + _info.handler = handler; + _info.gp = 0; + _info.flags = flags; + _info.format = 0; + _info.unwind_info = (unw_word_t)TBTable; + _info.unwind_info_size = 0; + _info.extra = registers.getRegister(2); + + return true; +} + +// Step back up the stack following the frame back link. +template +int UnwindCursor::stepWithTBTable(pint_t pc, tbtable *TBTable, + R ®isters, bool &isSignalFrame) { + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, (void *)TBTable); + } + +#if defined(__powerpc64__) + // Instruction to reload TOC register "l r2,40(r1)" + const uint32_t loadTOCRegInst = 0xe8410028; + const int32_t unwPPCF0Index = UNW_PPC64_F0; + const int32_t unwPPCV0Index = UNW_PPC64_V0; +#else + // Instruction to reload TOC register "l r2,20(r1)" + const uint32_t loadTOCRegInst = 0x80410014; + const int32_t unwPPCF0Index = UNW_PPC_F0; + const int32_t unwPPCV0Index = UNW_PPC_V0; +#endif + + R newRegisters = registers; + + // lastStack points to the stack frame of the next routine up. + pint_t lastStack = *((pint_t *)registers.getSP()); + + // Return address is the address after call site instruction. + pint_t returnAddress; + + if (isSignalFrame) { + _LIBUNWIND_TRACE_UNWINDING("Possible signal handler frame: lastStack=%p", + (void *)lastStack); + + sigcontext *sigContext = (sigcontext *)(((char *)lastStack) + STKMIN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + + _LIBUNWIND_TRACE_UNWINDING("From sigContext=%p, returnAddress=%p\n", + (void *)sigContext, (void *)returnAddress); + + if (returnAddress < 0x10000000) { + // Try again using STKMINALIGN + sigContext = (sigcontext *)(((char *)lastStack) + STKMINALIGN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + if (returnAddress < 0x10000000) { + _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p\n", + (void *)returnAddress); + return UNW_EBADFRAME; + } else { + _LIBUNWIND_TRACE_UNWINDING("Tried again using STKMINALIGN: " + "sigContext=%p, returnAddress=%p. " + "Seems to be a valid address\n", + (void *)sigContext, (void *)returnAddress); + } + } + // Restore the condition register from sigcontext. + newRegisters.setCR(sigContext->sc_jmpbuf.jmp_context.cr); + + // Restore GPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setRegister(i, sigContext->sc_jmpbuf.jmp_context.gpr[i]); + + // Restore FPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setFloatRegister(i + unwPPCF0Index, + sigContext->sc_jmpbuf.jmp_context.fpr[i]); + + // Restore vector registers if there is an associated extended context + // structure. + if (sigContext->sc_jmpbuf.jmp_context.msr & __EXTCTX) { + ucontext_t *uContext = (ucontext_t *)sigContext; + if (uContext->__extctx->__extctx_magic == __EXTCTX_MAGIC) { + for (int i = 0; i < 32; ++i) + newRegisters.setVectorRegister( + i + unwPPCV0Index, + *(v128 *)(&(uContext->__extctx->__vmx.__vr[i]))); + } + } + } else { + // Step up a normal frame. + returnAddress = ((pint_t *)lastStack)[2]; + + _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, " + "returnAddress=%p\n", + (void *)lastStack, (void *)returnAddress); + _LIBUNWIND_TRACE_UNWINDING("fpr_regs=%d, gpr_regs=%d, saves_cr=%d\n", + TBTable->tb.fpr_saved, TBTable->tb.gpr_saved, + TBTable->tb.saves_cr); + + // Restore FP registers. + char *ptrToRegs = (char *)lastStack; + double *FPRegs = + (double *)(ptrToRegs - (TBTable->tb.fpr_saved * sizeof(double))); + for (int i = 0; i < TBTable->tb.fpr_saved; ++i) + newRegisters.setFloatRegister( + 32 - TBTable->tb.fpr_saved + i + unwPPCF0Index, FPRegs[i]); + + // Restore GP registers. + ptrToRegs = (char *)FPRegs; + uintptr_t *GPRegs = + (uintptr_t *)(ptrToRegs - (TBTable->tb.gpr_saved * sizeof(uintptr_t))); + for (int i = 0; i < TBTable->tb.gpr_saved; ++i) + newRegisters.setRegister(32 - TBTable->tb.gpr_saved + i, GPRegs[i]); + + // Restore Vector registers. + ptrToRegs = (char *)GPRegs; + + // Pointer to the 6th byte of the traceback table. + uint8_t *TBTableByte6 = (uint8_t *)TBTable + 5; + + // Restore vector registers only if this is a Clang frame. Also + // check if traceback table bit has_vec is set. If it is, structure + // vec_ext is available. This is equivelent to "if (TBTable->tb.has_vec)". + if (_info.flags == frameType::clangFrame && + (*TBTableByte6 & _LIBUNWIND_TBTABLE_HAS_VEC_BIT)) { + + // Get to the vec_ext structure to check if vector registers are saved. + uint32_t *p = (uint32_t *)&TBTable->tb_ext; + + // Skip field parminfo if exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // Skip field tb_offset if exists. + if (TBTable->tb.has_tboff) + ++p; + + // Skip field hand_mask if exists. + if (TBTable->tb.int_hndl) + ++p; + + // Skip fields ctl_info and ctl_info_disp if exist. + if (TBTable->tb.has_ctl) { + // Skip field ctl_info. + ++p; + // Skip field ctl_info_disp. + ++p; + } + + // Skip fields name_len and name if exist. + // p is supposed to point to field name_len now. + uint8_t *charPtr = (uint8_t *)p; + if (TBTable->tb.name_present) + charPtr = charPtr + *((uint16_t *)charPtr) + sizeof(uint16_t); + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + struct vec_ext *vec_ext = (struct vec_ext *)charPtr; + + _LIBUNWIND_TRACE_UNWINDING("vr_saved=%d\n", vec_ext->vr_saved); + + // Restore vector register(s) if saved on the stack. + if (vec_ext->vr_saved) { + // Saved vector registers are 16-byte aligned. + if ((uintptr_t)ptrToRegs % 16) + ptrToRegs += 16 - (uintptr_t)ptrToRegs % 16; + v128 *VecRegs = + (v128 *)(ptrToRegs - (vec_ext->vr_saved * sizeof(v128))); + for (int i = 0; i < vec_ext->vr_saved; ++i) { + newRegisters.setVectorRegister( + 32 - vec_ext->vr_saved + i + unwPPCV0Index, VecRegs[i]); + } + } + } + if (TBTable->tb.saves_cr) { + // Get the saved condition register. The condition register is only + // a single word. + newRegisters.setCR(*((uint32_t *)(lastStack + sizeof(uintptr_t)))); + } + + // Restore the SP. + newRegisters.setSP(lastStack); + + // The first instruction after return. + uint32_t firstInstruction = *(uint32_t *)returnAddress; + + // Do we need to set the TOC register? + _LIBUNWIND_TRACE_UNWINDING("Current gpr2=%p\n", + (void *)newRegisters.getRegister(2)); + if (firstInstruction == loadTOCRegInst) { + _LIBUNWIND_TRACE_UNWINDING("Set gpr2=%p from frame\n", + (void *)((pint_t *)lastStack)[5]); + newRegisters.setRegister(2, ((pint_t *)lastStack)[5]); + } + } + _LIBUNWIND_TRACE_UNWINDING("lastStack=%p, returnAddress=%p, pc=%p\n", + (void *)lastStack, (void *)returnAddress, + (void *)pc); + + // The return address is the address after call site instruction, so + // setting IP to that simualates a return. + newRegisters.setIP((uintptr_t)returnAddress); + + // Simulate the step by replacing the register set with the new ones. + registers = newRegisters; + + // Check if the next frame is a signal frame. + pint_t nextStack = *((pint_t *)registers.getSP()); + + // Return address is the address after call site instruction. + pint_t nextReturnAddress = ((pint_t *)nextStack)[2]; + + if (nextReturnAddress > 0x01 && nextReturnAddress < 0x10000) { + _LIBUNWIND_TRACE_UNWINDING("The next is a signal handler frame: " + "nextStack=%p, next return address=%p\n", + (void *)nextStack, (void *)nextReturnAddress); + isSignalFrame = true; + } else { + isSignalFrame = false; + } + + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) template void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { @@ -1918,7 +2378,14 @@ // To disambiguate this, back up the pc when we know it is a return // address. if (isReturnAddress) +#if defined(_AIX) + // PC needs to be a 4-byte aligned address to be able to look for a + // word of 0 that indicates the start of the traceback table at the end + // of a function on AIX. + pc -= 4; +#else --pc; +#endif // Ask address space object to find unwind sections for this pc. UnwindInfoSections sects; @@ -1952,6 +2419,12 @@ return; #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // If there is unwind info in the traceback table, look there next. + if (this->getInfoFromTBTable(pc, _registers)) + return; +#endif + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) // If there is dwarf unwind info, look there next. if (sects.dwarf_section != 0) { @@ -2086,6 +2559,8 @@ result = this->stepWithCompactEncoding(); #elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) result = this->stepWithSEHData(); +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + result = this->stepWithTBTableData(); #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) result = this->stepWithDwarfFDE(); #elif defined(_LIBUNWIND_ARM_EHABI) diff --git a/libunwind/src/UnwindLevel1-gcc-ext.c b/libunwind/src/UnwindLevel1-gcc-ext.c --- a/libunwind/src/UnwindLevel1-gcc-ext.c +++ b/libunwind/src/UnwindLevel1-gcc-ext.c @@ -68,9 +68,13 @@ /// relative encodings. _LIBUNWIND_EXPORT uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) { - (void)context; _LIBUNWIND_TRACE_API("_Unwind_GetDataRelBase(context=%p)", (void *)context); +#if defined(_AIX) + return unw_get_data_rel_base((unw_cursor_t *)context); +#else + (void)context; _LIBUNWIND_ABORT("_Unwind_GetDataRelBase() not implemented"); +#endif } diff --git a/libunwind/src/UnwindLevel1.c b/libunwind/src/UnwindLevel1.c --- a/libunwind/src/UnwindLevel1.c +++ b/libunwind/src/UnwindLevel1.c @@ -432,11 +432,13 @@ _LIBUNWIND_TRACE_API( "_Unwind_GetLanguageSpecificData(context=%p) => 0x%" PRIxPTR, (void *)context, result); +#if !defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) if (result != 0) { if (*((uint8_t *)result) != 0xFF) _LIBUNWIND_DEBUG_LOG("lsda at 0x%" PRIxPTR " does not start with 0xFF", result); } +#endif return result; } diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -8,7 +8,9 @@ #include "assembly.h" +#if !defined(_AIX) .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -217,27 +219,36 @@ PPC64_LVS(30) PPC64_LVS(31) - // use VRSAVE to conditionally restore the remaining VS regs, - // that are where the V regs are mapped +#define PPC64_CLVS_RESTORE(n) \ + addi 4, 3, PPC64_OFFS_FP + n * 16 ;\ + lxvd2x n, 0, 4 +#if !defined(_AIX) + // use VRSAVE to conditionally restore the remaining VS regs, that are + // where the V regs are mapped. In the AIX ABI, VRSAVE is not used. ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave cmpwi 5, 0 beq Lnovec // conditionally load VS -#define PPC64_CLVS_BOTTOM(n) \ - beq Ldone##n ;\ - addi 4, 3, PPC64_OFFS_FP + n * 16 ;\ - lxvd2x n, 0, 4 ;\ +#define PPC64_CLVSl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(47-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ +Ldone##n: + +#define PPC64_CLVSh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(63-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ Ldone##n: -#define PPC64_CLVSl(n) \ - andis. 0, 5, (1 PPC_LEFT_SHIFT(47-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#else -#define PPC64_CLVSh(n) \ - andi. 0, 5, (1 PPC_LEFT_SHIFT(63-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#define PPC64_CLVSl(n) PPC64_CLVS_RESTORE(n) +#define PPC64_CLVSh(n) PPC64_CLVS_RESTORE(n) + +#endif // !defined(_AIX) PPC64_CLVSl(32) PPC64_CLVSl(33) @@ -313,32 +324,44 @@ PPC64_LF(31) #if defined(__ALTIVEC__) - // restore vector registers if any are in use + +#define PPC64_CLV_UNALIGNED_RESTORE(n) \ + ld 0, (PPC64_OFFS_V + n * 16)(3) ;\ + std 0, 0(4) ;\ + ld 0, (PPC64_OFFS_V + n * 16 + 8)(3) ;\ + std 0, 8(4) ;\ + lvx n, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE is + // not used. ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave cmpwi 5, 0 beq Lnovec - subi 4, 1, 16 - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorScalarRegisters may not be 16-byte aligned - // so copy via red zone temp buffer +#define PPC64_CLV_UNALIGNEDl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ +Ldone ## n: -#define PPC64_CLV_UNALIGNED_BOTTOM(n) \ - beq Ldone##n ;\ - ld 0, (PPC64_OFFS_V + n * 16)(3) ;\ - std 0, 0(4) ;\ - ld 0, (PPC64_OFFS_V + n * 16 + 8)(3) ;\ - std 0, 8(4) ;\ - lvx n, 0, 4 ;\ +#define PPC64_CLV_UNALIGNEDh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ Ldone ## n: -#define PPC64_CLV_UNALIGNEDl(n) \ - andis. 0, 5, (1 PPC_LEFT_SHIFT(15-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) +#else + +#define PPC64_CLV_UNALIGNEDl(n) PPC64_CLV_UNALIGNED_RESTORE(n) +#define PPC64_CLV_UNALIGNEDh(n) PPC64_CLV_UNALIGNED_RESTORE(n) -#define PPC64_CLV_UNALIGNEDh(n) \ - andi. 0, 5, (1 PPC_LEFT_SHIFT(31-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) +#endif // !defined(_AIX) + + subi 4, 1, 16 + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorScalarRegisters may not be 16-byte aligned + // so copy via red zone temp buffer PPC64_CLV_UNALIGNEDl(0) PPC64_CLV_UNALIGNEDl(1) @@ -382,11 +405,23 @@ ld 0, PPC64_OFFS_SRR0(3) mtctr 0 +#if defined(_AIX) + // After setting GPR1 to a higher address, AIX wipes out the original + // stack space below that address invalidated by the new GPR1 value. Use + // GPR0 to save the value of GPR3 in the context before it is wiped out. + // This compromises the content of GPR0 which is a volatile register. + ld 0, (8 * (3 + 2))(3) +#else PPC64_LR(0) +#endif PPC64_LR(5) PPC64_LR(4) PPC64_LR(1) +#if defined(_AIX) + mr 3, 0 +#else PPC64_LR(3) +#endif bctr #elif defined(__ppc__) @@ -470,45 +505,48 @@ #endif #if defined(__ALTIVEC__) - // restore vector registers if any are in use + +#define LOAD_VECTOR_RESTORE(_index) \ + lwz 0, 424+_index*16(3) SEPARATOR \ + stw 0, 0(4) SEPARATOR \ + lwz 0, 424+_index*16+4(3) SEPARATOR \ + stw 0, 4(4) SEPARATOR \ + lwz 0, 424+_index*16+8(3) SEPARATOR \ + stw 0, 8(4) SEPARATOR \ + lwz 0, 424+_index*16+12(3) SEPARATOR \ + stw 0, 12(4) SEPARATOR \ + lvx _index, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE + // is not used. lwz 5, 156(3) // test VRsave cmpwi 5, 0 beq Lnovec - subi 4, 1, 16 - rlwinm 4, 4, 0, 0, 27 // mask low 4-bits - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer - - -#define LOAD_VECTOR_UNALIGNEDl(_index) \ - andis. 0, 5, (1 PPC_LEFT_SHIFT(15-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz 0, 424+_index*16(3) SEPARATOR \ - stw 0, 0(%r4) SEPARATOR \ - lwz 0, 424+_index*16+4(%r3) SEPARATOR \ - stw 0, 4(%r4) SEPARATOR \ - lwz 0, 424+_index*16+8(%r3) SEPARATOR \ - stw 0, 8(%r4) SEPARATOR \ - lwz 0, 424+_index*16+12(%r3) SEPARATOR \ - stw 0, 12(%r4) SEPARATOR \ - lvx _index, 0, 4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDl(_index) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: -#define LOAD_VECTOR_UNALIGNEDh(_index) \ - andi. 0, 5, (1 PPC_LEFT_SHIFT(31-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz 0, 424+_index*16(3) SEPARATOR \ - stw 0, 0(4) SEPARATOR \ - lwz 0, 424+_index*16+4(3) SEPARATOR \ - stw 0, 4(4) SEPARATOR \ - lwz 0, 424+_index*16+8(3) SEPARATOR \ - stw 0, 8(%r4) SEPARATOR \ - lwz 0, 424+_index*16+12(3) SEPARATOR \ - stw 0, 12(4) SEPARATOR \ - lvx _index, 0, 4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDh(_index) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: +#else + +#define LOAD_VECTOR_UNALIGNEDl(_index) LOAD_VECTOR_RESTORE(_index) +#define LOAD_VECTOR_UNALIGNEDh(_index) LOAD_VECTOR_RESTORE(_index) + +#endif // !defined(_AIX) + + subi 4, 1, 16 + rlwinm 4, 4, 0, 0, 27 // mask low 4-bits + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer LOAD_VECTOR_UNALIGNEDl(0) LOAD_VECTOR_UNALIGNEDl(1) diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -8,7 +8,9 @@ #include "assembly.h" +#if !defined(_AIX) .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -331,8 +333,11 @@ // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAKALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - +#endif // store register (GPR) #define PPC64_STR(n) \ std n, (8 * (n + 2))(3) @@ -564,7 +569,11 @@ // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAKALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) +#endif stw 0, 8(3) mflr 0 stw 0, 0(3) // store lr as ssr0 diff --git a/libunwind/src/Unwind_AIXExtras.cpp b/libunwind/src/Unwind_AIXExtras.cpp new file mode 100644 --- /dev/null +++ b/libunwind/src/Unwind_AIXExtras.cpp @@ -0,0 +1,61 @@ +//===--------------------- Unwind_AIXExtras.cpp -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//===----------------------------------------------------------------------===// + +// This file is only used for AIX. +#if defined(_AIX) + +#include "config.h" +#include "libunwind_ext.h" +#include + +namespace libunwind { +// getFuncName +// Get the function name from its traceback table. +char *getFuncName(uintptr_t Pc, uint16_t &NameLen, unw_word_t *Offset) { + uint32_t *p = (uint32_t *)Pc; + *Offset = 0; + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + p++; + tbtable *TBTable = (tbtable *)(p + 1); + + if (TBTable->tb.name_present) + return NULL; + + // Get to the name of the function. + p = (uint32_t *)&TBTable->tb_ext; + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + p++; + + // If the tb_offset field exisits, get the offset from the start of + // the function to pc. Skip the field. + if (TBTable->tb.has_tboff) { + unw_word_t StartIp = (uintptr_t)TBTable - *p - sizeof(uint32_t); + *Offset = Pc - StartIp; + p++; + } + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + p++; + + // Skip fields ctl_info and ctl_info_disp if they exist. + if (TBTable->tb.has_ctl) { + p += 1 + *p; + } + + NameLen = *((uint16_t *)p); + return (char *)p + sizeof(uint16_t); +} +} // namespace libunwind +#endif // defined(_AIX) diff --git a/libunwind/src/assembly.h b/libunwind/src/assembly.h --- a/libunwind/src/assembly.h +++ b/libunwind/src/assembly.h @@ -185,12 +185,52 @@ #elif defined(__sparc__) +#elif defined(_AIX) + +#if defined(__powerpc64__) +#define PPC_RELOC_EXPR_LENGTH .llong +#elif defined(__ppc__) +#define PPC_RELOC_EXPR_LENGTH .long +#endif + +// clang-format off +#define DEFINE_LIBUNWIND_FUNCTION_AND_WEAKALIAS(name, aliasname) \ + .toc SEPARATOR \ + .globl name SEPARATOR \ + .globl .name SEPARATOR \ + .csect name[DS] SEPARATOR \ +name: \ + .weak aliasname[DS] SEPARATOR \ + .weak .aliasname SEPARATOR \ +aliasname: \ + PPC_RELOC_EXPR_LENGTH .name, TOC[tc0], 0 SEPARATOR \ + .csect .text[PR] SEPARATOR \ +.name: \ +.aliasname: + +#define WEAK_ALIAS(name, aliasname) +#define NO_EXEC_STACK_DIRECTIVE + +// clang-format on #else #error Unsupported target #endif +#if defined(_AIX) + // clang-format off +#define DEFINE_LIBUNWIND_FUNCTION(name) \ + .toc SEPARATOR \ + .globl name SEPARATOR \ + .globl .name SEPARATOR \ + .csect name[DS] SEPARATOR \ +name: \ + PPC_RELOC_EXPR_LENGTH .name, TOC[tc0], 0 SEPARATOR \ + .csect .text[PR] SEPARATOR \ +.name: + // clang-format on +#else #define DEFINE_LIBUNWIND_FUNCTION(name) \ .globl SYMBOL_NAME(name) SEPARATOR \ HIDDEN_SYMBOL(SYMBOL_NAME(name)) SEPARATOR \ @@ -199,6 +239,7 @@ SYMBOL_NAME(name): \ PPC64_OPD2 \ AARCH64_BTI +#endif #if defined(__arm__) #if !defined(__ARM_ARCH) diff --git a/libunwind/src/config.h b/libunwind/src/config.h --- a/libunwind/src/config.h +++ b/libunwind/src/config.h @@ -43,6 +43,9 @@ // For ARM EHABI, Bionic didn't implement dl_iterate_phdr until API 21. After // API 21, dl_iterate_phdr exists, but dl_unwind_find_exidx is much faster. #define _LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX 1 +#elif defined(_AIX) +// The traceback table at the end of each function is used for unwinding. +#define _LIBUNWIND_SUPPORT_TBTAB_UNWIND 1 #else // Assume an ELF system with a dl_iterate_phdr function. #define _LIBUNWIND_USE_DL_ITERATE_PHDR 1 @@ -57,13 +60,13 @@ #define _LIBUNWIND_EXPORT #define _LIBUNWIND_HIDDEN #else - #if !defined(__ELF__) && !defined(__MACH__) - #define _LIBUNWIND_EXPORT __declspec(dllexport) - #define _LIBUNWIND_HIDDEN - #else - #define _LIBUNWIND_EXPORT __attribute__((visibility("default"))) - #define _LIBUNWIND_HIDDEN __attribute__((visibility("hidden"))) - #endif +#if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) +#define _LIBUNWIND_EXPORT __declspec(dllexport) +#define _LIBUNWIND_HIDDEN +#else +#define _LIBUNWIND_EXPORT __attribute__((visibility("default"))) +#define _LIBUNWIND_HIDDEN __attribute__((visibility("hidden"))) +#endif #endif #define STR(a) #a @@ -80,7 +83,7 @@ __asm__(".globl " SYMBOL_NAME(aliasname)); \ __asm__(SYMBOL_NAME(aliasname) " = " SYMBOL_NAME(name)); \ _LIBUNWIND_ALIAS_VISIBILITY(SYMBOL_NAME(aliasname)); -#elif defined(__ELF__) +#elif defined(__ELF__) || defined(_AIX) #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ __attribute__((weak, alias(#name))); diff --git a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp --- a/libunwind/src/libunwind.cpp +++ b/libunwind/src/libunwind.cpp @@ -232,6 +232,16 @@ } _LIBUNWIND_WEAK_ALIAS(__unw_is_signal_frame, unw_is_signal_frame) +#ifdef _AIX +_LIBUNWIND_EXPORT uintptr_t __unw_get_data_rel_base(unw_cursor_t *cursor) { + _LIBUNWIND_TRACE_API("unw_get_data_rel_base(cursor=%p)", + static_cast(cursor)); + AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; + return co->getDataRelBase(); +} +_LIBUNWIND_WEAK_ALIAS(__unw_get_data_rel_base, unw_get_data_rel_base) +#endif + #ifdef __arm__ // Save VFP registers d0-d15 using FSTMIADX instead of FSTMIADD _LIBUNWIND_HIDDEN void __unw_save_vfp_as_X(unw_cursor_t *cursor) { diff --git a/libunwind/src/libunwind_ext.h b/libunwind/src/libunwind_ext.h --- a/libunwind/src/libunwind_ext.h +++ b/libunwind/src/libunwind_ext.h @@ -43,6 +43,10 @@ extern int __unw_is_signal_frame(unw_cursor_t *); extern int __unw_get_proc_name(unw_cursor_t *, char *, size_t, unw_word_t *); +#if defined(_AIX) +extern uintptr_t __unw_get_data_rel_base(unw_cursor_t *); +#endif + // SPI extern void __unw_iterate_dwarf_unwind_cache(void (*func)( unw_word_t ip_start, unw_word_t ip_end, unw_word_t fde, unw_word_t mh));