diff --git a/llvm/include/llvm/Support/Signals.h b/llvm/include/llvm/Support/Signals.h --- a/llvm/include/llvm/Support/Signals.h +++ b/llvm/include/llvm/Support/Signals.h @@ -17,6 +17,10 @@ #include #include +#if defined(_AIX) +struct sigcontext; +#endif + namespace llvm { class StringRef; class raw_ostream; @@ -53,7 +57,9 @@ /// Print the stack trace using the given \c raw_ostream object. /// \param Depth refers to the number of stackframes to print. If not /// specified, the entire frame is printed. - void PrintStackTrace(raw_ostream &OS, int Depth = 0); + /// \param Cookie is a pointer to some context structure, currently used + /// to get the sigcontext on AIX. + void PrintStackTrace(raw_ostream &OS, int Depth = 0, void *Cookie = nullptr); // Run all registered signal handlers. void RunSignalHandlers(); @@ -65,6 +71,13 @@ /// instance of the handler it is. void AddSignalHandler(SignalHandlerCallback FnPtr, void *Cookie); + /// Normally the cookies are recorded at the time of inserting signal handlers + /// into the call-back list. This function allows the cookie to be overriden + /// *after* the signal handlers are inserted. This can be useful, for example + /// on AIX where we would like to pass the signal context at the time the + /// signal is caught. + void OverrideCookie(SignalHandlerCallback FnPtr, void *Cookie); + /// This function registers a function to be called when the user "interrupts" /// the program (typically by pressing ctrl-c). When the user interrupts the /// program, the specified interrupt function is called instead of the program @@ -123,6 +136,24 @@ void CleanupOnSignal(uintptr_t Context); void unregisterHandlers(); + +#if defined(_AIX) + /// On AIX, CrashRecovery and LLVMSupport both need to be able to register + /// this function, so we make it available. On AIX, \p Cookie contains the + /// signal context. + void PrintStackTraceSignalHandler(void *Cookie); + + /// A structure to hold the AIX signal context. + struct SignalContext_t { + // Define a magic number to be able to do a safety check and make sure the + // cookie passed to the handler is actually an AIX context. + static const unsigned int SC_AIXContextMagic = 131313; + unsigned MagicNumber; + siginfo_t *Info; + struct sigcontext *SigContextData; + }; +#endif + } // End sys namespace } // End llvm namespace diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp --- a/llvm/lib/Support/CrashRecoveryContext.cpp +++ b/llvm/lib/Support/CrashRecoveryContext.cpp @@ -390,11 +390,28 @@ const_cast(CRCI)->HandleCrash(RetCode, Signal); } +#if defined(_AIX) +static void +CrashRecoverySignalHandlerWithContext(int Signal, siginfo_t *Info, + struct sigcontext *SigContextData) { + llvm::sys::SignalContext_t Ctx = { + llvm::sys::SignalContext_t::SC_AIXContextMagic, Info, SigContextData}; + llvm::sys::OverrideCookie(llvm::sys::PrintStackTraceSignalHandler, &Ctx); + CrashRecoverySignalHandler(Signal); +} +#endif + static void installExceptionOrSignalHandlers() { // Setup the signal handler. struct sigaction Handler; +#if defined(_AIX) + Handler.sa_sigaction = + (void (*)(int, siginfo_t *, void *))CrashRecoverySignalHandlerWithContext; + Handler.sa_flags = SA_NODEFER | SA_SIGINFO; +#else Handler.sa_handler = CrashRecoverySignalHandler; Handler.sa_flags = 0; +#endif sigemptyset(&Handler.sa_mask); for (unsigned i = 0; i != NumSignals; ++i) { diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp --- a/llvm/lib/Support/Signals.cpp +++ b/llvm/lib/Support/Signals.cpp @@ -11,6 +11,10 @@ // //===----------------------------------------------------------------------===// +#if defined(_AIX) +#define _ALL_SOURCE // needed for sigcontext +#endif + #include "llvm/Support/Signals.h" #include "DebugOptions.h" @@ -123,6 +127,18 @@ report_fatal_error("too many signal callbacks already registered"); } +void sys::OverrideCookie(SignalHandlerCallback FnPtr, void *Cookie) { + for (size_t I = 0; I < MaxSignalHandlerCallbacks; ++I) { + auto &SetMe = CallBacksToRun()[I]; + if (SetMe.Callback == FnPtr) { + SetMe.Cookie = Cookie; + return; + } + } + report_fatal_error("A registered handler was not found, " + "or there are too many cookie overrides"); +} + static bool findModulesAndOffsets(void **StackTrace, int Depth, const char **Modules, intptr_t *Offsets, const char *MainExecutableName, diff --git a/llvm/lib/Support/Unix/AIX.inc b/llvm/lib/Support/Unix/AIX.inc new file mode 100644 --- /dev/null +++ b/llvm/lib/Support/Unix/AIX.inc @@ -0,0 +1,258 @@ +struct TBTableShort_t { + /* BYTE ONE */ + unsigned version : 8; /* traceback format version */ + + /* BYTE TWO */ + unsigned lang : 8; /* See language values below */ + + /* BYTE THREE */ + unsigned globallink : 1; /* Set if routine is global linkage */ + unsigned is_eprol : 1; /* Set if is out-of-line epilog/prologue */ + unsigned has_tboff : 1; /* Set if offset from start of proc stored */ + unsigned int_proc : 1; /* Set if routine is internal */ + unsigned has_ctl : 1; /* Set if routine involves controlled storage */ + unsigned tocless : 1; /* Set if routine contains no TOC */ + unsigned fp_present : 1; /* Set if routine performs FP operations */ + unsigned log_abort : 1; /* Set if routine logs or aborts FP ops */ + + /* BYTE FOUR */ + unsigned int_hndl : 1; /* Set if routine is interrupt handler */ + unsigned name_present : 1; /* Set if name is present in proc table */ + unsigned uses_alloca : 1; /* Set if alloca used to allocate storage */ + unsigned cl_dis_inv : 3; /* On-condition directives, see below */ + unsigned saves_cr : 1; /* Set if procedure saves the condition reg */ + unsigned saves_lr : 1; /* Set if procedure saves the link reg */ + + /* BYTE FIVE */ + unsigned stores_bc : 1; /* Set if procedure stores the backchain */ + unsigned fixup : 1; /* Set if code is generated fixup code. */ + unsigned fpr_saved : 6; /* Number of FPRs saved, max of 32 */ + + /* BYTE SIX */ + unsigned has_vec : 1; /* Set if optional vector info is present */ + unsigned longtbtable : 1; /* Set if xtbtable extension exists. */ + unsigned gpr_saved : 6; /* Number of GPRs saved, max of 32 */ + + /* BYTE SEVEN */ + unsigned fixedparms : 8; /* Number of fixed point parameters */ + + /* BYTE EIGHT */ + unsigned floatparms : 7; /* Number of floating point parameters */ + unsigned parmsonstk : 1; /* Set if all parameters placed on stack */ +}; + +/* + * Optional portions of procedure-end table. + * + * Optional portions exist in the following order independently, not as + * a structure or an union. Whether or not portions exist is determinable + * from bit-fields within the base procedure-end table. + * + * parminfo exists if fixedparms or floatparms != 0. + * tb_offset exists if has_tboff bit is set. + * hand_mask exists if int_hndl bit is set. + * ctl_info exists if has_ctl bit is set. + * ctl_info_disp exists if ctl_info exists. + * name_len exists if name_present bit is set. + * name exists if name_len exists. + * alloca_reg exists if uses_alloca bit is set. + */ +struct TBTableExt_t { + unsigned int parminfo; /* Order and type encoding of parameters: + * Left-justified bit-encoding as follows: + * '0' ==> fixed parameter + * '10' ==> single-precision float parameter + * '11' ==> double-precision float parameter */ + unsigned int tb_offset; /* Offset from start of code to tb table */ + int hand_mask; /* What interrupts are handled by */ + int ctl_info; /* Number of CTL anchors, followed by */ + int ctl_info_disp[1]; /* Actually ctl_info_disp[ctl_info] */ + /* Displacements into stack of each anchor */ + short name_len; /* Length of procedure name */ + char name[1]; /* Actually char[name_len] (no NULL) */ + char alloca_reg; /* Register for alloca automatic storage */ +}; + +struct TBTable_t { + struct TBTableShort_t tb; + struct TBTableExt_t tb_ext; +}; + +/* A structure used to describe the name of a procedure. */ +struct ProcName_t { + short Length; + char Name[1]; /* Actually name[len]. */ +}; + +/* This is the link area in the stack frame. */ + +struct Links_t { + struct Frame_t *FramePtr; + long CondRegister; + void *LinkRegister; + void *Buffer[2]; + int *TOC; +}; + +/* This is the link area + the callee's output parameter area in the + * stack frame. + */ + +struct Frame_t { + Links_t LinksArea; + int OutputArgs[8]; +}; + +struct Registers_t { + unsigned long ReturnAddress; // Return address + unsigned long StackPointer; // Stack pointer +}; + +/* Given a frame pointer, get the frame pointer of the caller. */ +#define getCallerFrame(CurrentFrame) CurrentFrame->LinksArea.FramePtr + +static TBTable_t *getTBTable(uintptr_t Addr) { + unsigned int *Ptr; + Ptr = (unsigned int *)Addr; + + /* + * Keep looking forward until a word of all 0's is found. The + * traceback table starts at the following word. + */ + while (*Ptr++) + ; + + if (((TBTable_t *)Ptr)->tb.version == 0x0) + return (TBTable_t *)Ptr; + else + return NULL; +} + +static int traceFunc(uintptr_t Base, TBTable_t *Tbtable, unsigned int *Offset, + char *ProcName) { + TBTableExt_t *TbtableExt; + char *TbtableExtPtr; + ProcName_t *PName; + + if ((!Tbtable->tb.has_tboff) || (!Tbtable->tb.name_present)) { + strcpy(ProcName, "**unknown**"); + return 0; + } + + TbtableExtPtr = (char *)&Tbtable->tb_ext; + + if (!(Tbtable->tb.fixedparms | Tbtable->tb.floatparms)) + TbtableExtPtr -= sizeof(TbtableExt->parminfo); + + TbtableExt = (TBTableExt_t *)TbtableExtPtr; + *Offset = Base - (uintptr_t)((char *)(((unsigned int *)Tbtable) - 1) - + (char *)(uintptr_t)(TbtableExt->tb_offset)); + + if (!Tbtable->tb.int_hndl) + TbtableExtPtr -= sizeof(TbtableExt->hand_mask); + + if (Tbtable->tb.has_ctl) { + TbtableExt = (TBTableExt_t *)TbtableExtPtr; + TbtableExtPtr += + ((TbtableExt->ctl_info) * sizeof(TbtableExt->ctl_info_disp[0])); + } else + TbtableExtPtr -= sizeof(TbtableExt->ctl_info); + + TbtableExtPtr -= sizeof(TbtableExt->ctl_info_disp); + TbtableExt = (TBTableExt_t *)TbtableExtPtr; + + PName = (ProcName_t *)&(TbtableExt->name_len); + if (PName) { + memcpy(ProcName, PName->Name, PName->Length); + ProcName[PName->Length] = '\0'; + } else + strcpy(ProcName, "**unknown**"); + + return 1; +} + +void printTracebackLine(unsigned int Offset, char *DemangledName, char *RawName, + raw_ostream &OS) { + char *ProcName; + ProcName = DemangledName != NULL ? DemangledName : RawName; + OS << " Offset " << format_hex(Offset, 10) << " " << ProcName << "\n"; +} + +static void printTraceback(Registers_t Regs, raw_ostream &OS) { + // Pointer to the stack frame of the routine in which the traceback starts + Frame_t *Frame = (Frame_t *)Regs.StackPointer; + // The absolute address of a point in the call chain. + uintptr_t Base = Regs.ReturnAddress; + + while (Base && (getCallerFrame(Frame) != NULL)) { + char ProcName[1024]; + char *DemangledName = NULL; + TBTable_t *Tbtable = getTBTable(Base); + unsigned int + Offset; // Offset from the start of a routine in the call chain. + if (Tbtable != NULL && traceFunc(Base, Tbtable, &Offset, ProcName)) { + DemangledName = itaniumDemangle(ProcName); + printTracebackLine(Offset, DemangledName, ProcName, OS); + free(DemangledName); + } else + OS << " Location " << format_hex(Base, 10) << "\n"; + + // Do not try to look for more traceback information if the traceback table + // was not valid, otherwise we may get a SIGSEV and make this unsafe during + // signal handling. This can happen if, for example, part of the call chain + // belongs to a shared library (which we currently don't support). + if (!Tbtable || !Tbtable->tb.stores_bc || !Tbtable->tb.saves_lr) + break; + + Frame = getCallerFrame(Frame); + Base = (uintptr_t)(Frame->LinksArea.LinkRegister); + } + + OS << "\n --- End of call chain ---\n"; +} + +bool getRegsFromSigcontext(Registers_t &Regs, void *Cookie) { + struct sigcontext *SigContextData = nullptr; + if (Cookie && ((llvm::sys::SignalContext_t *)Cookie)->MagicNumber == + llvm::sys::SignalContext_t::SC_AIXContextMagic) + SigContextData = ((llvm::sys::SignalContext_t *)Cookie)->SigContextData; + if (SigContextData) { + Regs.StackPointer = SigContextData->sc_jmpbuf.jmp_context.gpr[1]; + // In sigcontext, IAR is the address of instruction next to the call that + // generates the signal, e.g., kill(), so it is really serving as the + // return address. + Regs.ReturnAddress = SigContextData->sc_jmpbuf.jmp_context.iar; + return true; + } + return false; +} + +void getRegs(Registers_t *Regs) { +// Clang warns about the non-zero argument passed to __builtin_frame_address, +// while XL doesn't recognize the "-Wframe-address" option, so we need to +// disable two diagnostics for the following code to prevent warnings in +// bootstrap and non-bootstrap builds. +// FIXME: Once clang becomes the build compiler on all platforms, we can remove +// the first ignored diagnostic below. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wframe-address" + Regs->StackPointer = (unsigned long)__builtin_frame_address(1); +#pragma GCC diagnostic pop + Regs->ReturnAddress = (unsigned long)__builtin_return_address(0); +} + +static void SignalHandlerWithContext(int Sig, siginfo_t *Info, + struct sigcontext *SigContextData) { + // This should never happen, because we are careful to have signal-safe code + // in the signal handler, but just in case we want to make sure we don't + // handle signals caused by the signal handler + static int Nesting = 0; + if (Nesting++ > 0) + _exit(1); + + llvm::sys::SignalContext_t Ctx = { + llvm::sys::SignalContext_t::SC_AIXContextMagic, Info, SigContextData}; + llvm::sys::OverrideCookie(llvm::sys::PrintStackTraceSignalHandler, &Ctx); + SignalHandler(Sig); +} diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -85,6 +85,12 @@ static void SignalHandler(int Sig); // defined below. static void InfoSignalHandler(int Sig); // defined below. +#if defined(_AIX) +static void +SignalHandlerWithContext(int Sig, siginfo_t *info, + struct sigcontext *sigcontextdata); // defined below. +#endif + using SignalHandlerFunctionType = void (*)(); /// The function to call if ctrl-c is pressed. static std::atomic InterruptFunction = nullptr; @@ -301,7 +307,7 @@ // be able to reliably handle signals due to stack overflow. CreateSigAltStack(); - enum class SignalKind { IsKill, IsInfo }; + enum class SignalKind { IsKill, IsInfo, IsKillWithContext }; auto registerHandler = [&](int Signal, SignalKind Kind) { unsigned Index = NumRegisteredSignals.load(); assert(Index < std::size(RegisteredSignalInfo) && @@ -314,6 +320,13 @@ NewHandler.sa_handler = SignalHandler; NewHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK; break; + case SignalKind::IsKillWithContext: +#if defined(_AIX) + NewHandler.sa_sigaction = + (void (*)(int, siginfo_t *, void *))SignalHandlerWithContext; + NewHandler.sa_flags = SA_NODEFER | SA_SIGINFO; +#endif + break; case SignalKind::IsInfo: NewHandler.sa_handler = InfoSignalHandler; NewHandler.sa_flags = SA_ONSTACK; @@ -327,12 +340,17 @@ ++NumRegisteredSignals; }; +#if defined(_AIX) + SignalKind Kill = SignalKind::IsKillWithContext; +#else + SignalKind Kill = SignalKind::IsKill; +#endif for (auto S : IntSigs) - registerHandler(S, SignalKind::IsKill); + registerHandler(S, Kill); for (auto S : KillSigs) - registerHandler(S, SignalKind::IsKill); + registerHandler(S, Kill); if (OneShotPipeSignalFunction) - registerHandler(SIGPIPE, SignalKind::IsKill); + registerHandler(SIGPIPE, Kill); for (auto S : InfoSigs) registerHandler(S, SignalKind::IsInfo); } @@ -587,12 +605,16 @@ } #endif +#if defined(_AIX) +#include "AIX.inc" +#endif + // In the case of a program crash or fault, print out a stack trace so that the // user has an indication of why and where we died. // // On glibc systems we have the 'backtrace' function, which works nicely, but // doesn't demangle symbols. -void llvm::sys::PrintStackTrace(raw_ostream &OS, int Depth) { +void llvm::sys::PrintStackTrace(raw_ostream &OS, int Depth, void *Cookie) { #if ENABLE_BACKTRACES static void *StackTrace[256]; int depth = 0; @@ -607,6 +629,20 @@ depth = unwindBacktrace(StackTrace, static_cast(std::size(StackTrace))); #endif + +#if defined(_AIX) + if (!depth) { + // If the traceback is being invoked through a signal handler, we need to + // find the frame pointer and the return address from the signal context. + // This information is used to find the traceback table. If not invoked from + // a signal handler, we get this information from the current frame. + Registers_t Regs; + if (!getRegsFromSigcontext(Regs, Cookie)) + getRegs(&Regs); + printTraceback(Regs, OS); + return; + } +#endif if (!depth) return; // If "Depth" is not provided by the caller, use the return value of @@ -670,8 +706,12 @@ #endif } -static void PrintStackTraceSignalHandler(void *) { - sys::PrintStackTrace(llvm::errs()); +#if defined(_AIX) +void llvm::sys::PrintStackTraceSignalHandler(void *Cookie) { +#else +static void PrintStackTraceSignalHandler(void *Cookie) { +#endif + sys::PrintStackTrace(llvm::errs(), 0 /*Depth*/, Cookie); } void llvm::sys::DisableSystemDialogsOnCrash() {} @@ -682,7 +722,11 @@ bool DisableCrashReporting) { ::Argv0 = Argv0; +#if defined(_AIX) + AddSignalHandler(llvm::sys::PrintStackTraceSignalHandler, nullptr); +#else AddSignalHandler(PrintStackTraceSignalHandler, nullptr); +#endif #if defined(__APPLE__) && ENABLE_CRASH_OVERRIDES // Environment variable to disable any kind of crash dialog.