From f668331f0ffe5769e9221dc6c00edaa0d752e30b Mon Sep 17 00:00:00 2001 From: Charlie Fenton Date: Tue, 20 Sep 2005 12:58:29 +0000 Subject: [PATCH] *** empty log message *** svn path=/trunk/boinc/; revision=8053 --- checkin_notes | 13 + lib/mac_backtrace.C | 908 ++++++++++++++++++++++++++++++++++++++++++++ lib/mac_backtrace.h | 156 ++++++++ 3 files changed, 1077 insertions(+) create mode 100644 lib/mac_backtrace.C create mode 100644 lib/mac_backtrace.h diff --git a/checkin_notes b/checkin_notes index f36fe6125f..8a96b8c4f4 100755 --- a/checkin_notes +++ b/checkin_notes @@ -11888,3 +11888,16 @@ David 19 Sept 2005 html/ops/ bbcode_convert.php + +Charlie 19 Sept 2005 + - Mac: Trim garbage off end of symbols in backtrace. + - Fixed backtracing through signal handler. This required + determining the correct offset within sigtramp's stack + frame of the pointer to the next stack frame; this varies + among major versions of the macintosh OS. I'm confident + I have the correct value for OS 10.3, but three different + offsets all contained the correct value in my tests on + OS 10.4, so I'm less confident there. + + lib/ + mac_backtrace.C, .h diff --git a/lib/mac_backtrace.C b/lib/mac_backtrace.C new file mode 100644 index 0000000000..30c9477bdf --- /dev/null +++ b/lib/mac_backtrace.C @@ -0,0 +1,908 @@ +// Berkeley Open Infrastructure for Network Computing +// http://boinc.berkeley.edu +// Copyright (C) 2005 University of California +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; +// either version 2.1 of the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// To view the GNU Lesser General Public License visit +// http://www.gnu.org/copyleft/lesser.html +// or write to the Free Software Foundation, Inc., +// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +/* + * mac_backtrace.C + * + */ + +/* This is a rudimentary backtrace generator for boinc project applications. +* +* It is adapted from Apple Developer Technical Support Sample Code +* MoreisBetter / MoreDebugging / MoreBacktraceTest +* The symbols it displays are not always clean. This code assumes +* +* This code handles Mac OS X 10.2.x through 10.4.2. It may require some +* adjustment for future OS versions; see the discussion of _sigtramp and +* PowerPC Signal Stack Frames below. +* +* For useful tips on using backtrace information, see Apple Tech Note 2123: +* http://developer.apple.com/technotes/tn2004/tn2123.html#SECNOSYMBOLS +* +* To convert addresses to correct symbols, use the atos command-line tool: +* atos -o path/to/executable/with/symbols address +* Note: if address 1a23 is hex, use 0x1a23. +* +* To demangle mangled C++ symbols, use the c++filt command-line tool. +* You may need to prefix C++ symbols with an additonal underscore before +* passing them to c++filt (so they begin with two underscore characters). +* +* Flags in backtrace: +* F this frame pointer is bad +* P this PC is bad +* S this frame is a signal handler +* +*/ + +#include +#include +#include +#include + +#include +#include // for getpid() +#include +#include +#include + +#include "mac_backtrace.h" + +extern void * _sigtramp; + +enum { + kFrameCount = 200 +}; + +static void PrintNameOfThisApp(void); +static void PrintOSVersion(char *minorVersion); +static int OutputFrames(const MacBTPPCFrame *frameArray, unsigned long frameCount, unsigned char lookupSymbolNames); + + +void PrintBacktrace(void) { + int err; + MacBTPPCFrame frames[kFrameCount]; + unsigned long frameCount; + unsigned long validFrames; + char OSMinorVersion; + + PrintNameOfThisApp(); + PrintOSVersion(&OSMinorVersion); + + frameCount = sizeof(frames) / sizeof(*frames); + err = MacBacktracePPCMachSelf(0, 0, frames, frameCount, &validFrames, OSMinorVersion); + if (err == 0) { + if (validFrames > frameCount) { + validFrames = frameCount; + } + err = OutputFrames(frames, validFrames, true); + } +} + + +static char * PersistentFGets(char *buf, size_t buflen, FILE *f) { + char *p = buf; + size_t len = buflen; + size_t datalen = 0; + + *buf = '\0'; + while (datalen < (buflen - 1)) { + fgets(p, len, f); + if (feof(f)) break; + if (ferror(f) && (errno != EINTR)) break; + if (strchr(buf, '\n')) break; + datalen = strlen(buf); + p = buf + datalen; + len -= datalen; + } + return (buf[0] ? buf : NULL); +} + + +static void PrintNameOfThisApp() { + FILE *f; + char buf[64], nameBuf[1024]; + pid_t myPID = getpid(); + int i; + + nameBuf[0] = 0; // in case of failure + + sprintf(buf, "ps -p %d -c -o command", myPID); + f = popen(buf, "r"); + if (!f) + return; + PersistentFGets(nameBuf, sizeof(nameBuf), f); // Skip over line of column headings + nameBuf[0] = 0; + PersistentFGets(nameBuf, sizeof(nameBuf), f); // Get the UNIX command which ran us + fclose(f); + + for (i=strlen(nameBuf)-1; i>=0; --i) { + if (nameBuf[i] <= ' ') + nameBuf[i] = 0; // Strip off trailing spaces, newlines, etc. + else + break; + } + + if (nameBuf[0]) + fprintf(stderr, "\nExecutable name: %s\n", nameBuf); +} + + +// This is an alternative to using Gestalt(gestaltSystemVersion,..) so +// we don't need the Carbon Framework +static void PrintOSVersion(char *OSMinorVersion) { + char buf[1024], *p1 = NULL, *p2 = NULL, *p3; + FILE *f; + int n; + + f = fopen("/System/Library/CoreServices/SystemVersion.plist", "r"); + if (!f) + return; + + n = fread(buf, 1, sizeof(buf)-1, f); + buf[n] = '\0'; + p1 = strstr(buf, "ProductUserVisibleVersion"); + if (p1) { + p1 = strstr(p1, "") + 8; + p2 = strstr(p1, ""); + if (p2) { + // Extract the minor system version number character + p3 = strchr(p2, '.'); + *OSMinorVersion = *(p3+1); // Pass minor version number back to caller + // Now print the full OS version string + fputs("System version: Macintosh OS ", stderr); + while (p1 < p2) { + fputc(*p1++, stderr); + } + } + } + + if (p2) { + p2 = NULL; + p1 = strstr(buf, "ProductBuildVersion"); + if (p1) { + p1 = strstr(p1, "") + 8; + p2 = strstr(p1, ""); + if (p2) { + fputs(" build ", stderr); + while (p1 < p2) { + fputc(*p1++, stderr); + } + } + } + fputc('\n', stderr); + } + + fclose(f); +} + +static void ReplaceSymbolIfBetter(MacAToSSymInfo *existingSymbol, MacAToSSymbolType symbolType, const char * symbolName, unsigned long symbolOffset) { + unsigned char replace; + + if (existingSymbol->symbolType == kMacAToSNoSymbol) { + replace = true; + } else { + replace = (symbolOffset < existingSymbol->symbolOffset); + } + + if (replace) { + existingSymbol->symbolType = symbolType; + strncpy(existingSymbol->symbolName, symbolName, sizeof(existingSymbol->symbolName)-1); + existingSymbol->symbolName[sizeof(existingSymbol->symbolName)-1] = '\0'; + existingSymbol->symbolOffset = symbolOffset; + } +} + + +// FindOwnerOfPC and GetFunctionName countesy of Ed Wynne. + +static const struct mach_header *FindOwnerOfPC(unsigned int pc) { + unsigned int count,index,offset,cmdex; + struct segment_command *seg; + struct load_command *cmd; + struct mach_header *header; + + count = _dyld_image_count(); + for (index = 0;index < count;index += 1) + { + header = (struct mach_header*)_dyld_get_image_header(index); + offset = _dyld_get_image_vmaddr_slide(index); + cmd = (struct load_command*)((char*)header + sizeof(struct mach_header)); + for (cmdex = 0;cmdex < header->ncmds;cmdex += 1,cmd = (struct load_command*)((char*)cmd + cmd->cmdsize)) + { + switch(cmd->cmd) + { + case LC_SEGMENT: + seg = (struct segment_command*)cmd; + if ((pc >= (seg->vmaddr + offset)) && (pc < (seg->vmaddr + offset + seg->vmsize))) + return header; + break; + } + } + } + + return NULL; +} + +static const char *GetFunctionName(unsigned int pc,unsigned int *offset, unsigned char *publicSymbol) { + struct segment_command *seg_linkedit = NULL; + struct segment_command *seg_text = NULL; + struct symtab_command *symtab = NULL; + struct load_command *cmd; + const struct mach_header*header; + unsigned int vm_slide,file_slide; + struct nlist *sym,*symbase; + char *strings,*name; + unsigned int base,index; + + header = FindOwnerOfPC(pc); + if (header != NULL) + { + cmd = (struct load_command*)((char*)header + sizeof(struct mach_header)); + for (index = 0;index < header->ncmds;index += 1,cmd = (struct load_command*)((char*)cmd + cmd->cmdsize)) + { + switch(cmd->cmd) + { + case LC_SEGMENT: + if (!strcmp(((struct segment_command*)cmd)->segname,SEG_TEXT)) + seg_text = (struct segment_command*)cmd; + else if (!strcmp(((struct segment_command*)cmd)->segname,SEG_LINKEDIT)) + seg_linkedit = (struct segment_command*)cmd; + break; + + case LC_SYMTAB: + symtab = (struct symtab_command*)cmd; + break; + } + } + + if ((seg_text == NULL) || (seg_linkedit == NULL) || (symtab == NULL)) + { + *offset = 0; + return NULL; + } + + vm_slide = (unsigned long)header - (unsigned long)seg_text->vmaddr; + file_slide = ((unsigned long)seg_linkedit->vmaddr - (unsigned long)seg_text->vmaddr) - seg_linkedit->fileoff; + symbase = (struct nlist*)((unsigned long)header + (symtab->symoff + file_slide)); + strings = (char*)((unsigned long)header + (symtab->stroff + file_slide)); + + // Look for a global symbol. + for (index = 0,sym = symbase;index < symtab->nsyms;index += 1,sym += 1) + { + if (sym->n_type != N_FUN) + continue; + + name = sym->n_un.n_strx ? (strings + sym->n_un.n_strx) : NULL; + base = sym->n_value + vm_slide; + + for (index += 1,sym += 1;index < symtab->nsyms;index += 1,sym += 1) + if (sym->n_type == N_FUN) + break; + + if ((pc >= base) && (pc <= (base + sym->n_value)) && (name != NULL) && (strlen(name) > 0)) + { + *offset = pc - base; + *publicSymbol = true; + return strdup(name); + } + } + + // Look for a reasonably close private symbol. + for (name = NULL,base = 0xFFFFFFFF,index = 0,sym = symbase;index < symtab->nsyms;index += 1,sym += 1) + { + if ((sym->n_type & 0x0E) != 0x0E) + continue; + + if ((sym->n_value + vm_slide) > pc) + continue; + + if ((base != 0xFFFFFFFF) && ((pc - (sym->n_value + vm_slide)) >= (pc - base))) + continue; + + name = sym->n_un.n_strx ? (strings + sym->n_un.n_strx) : NULL; + base = sym->n_value + vm_slide; + } + + *offset = pc - base; + *publicSymbol = false; + return (name != NULL) ? strdup(name) : NULL; + } + + *offset = 0; + return NULL; +} + +static int AToSCopySymbolNameUsingDyld(MacAToSAddr address, MacAToSSymInfo *symbol) { + const char * thisSymbol; + char * colonPtr; + unsigned int thisSymbolOffset; + unsigned char thisSymbolPublic; + MacAToSSymbolType thisSymbolType; + int err = 0; + + thisSymbol = NULL; + if (address != NULL) { // NULL is never a useful symbol + thisSymbol = GetFunctionName( (unsigned int) address, &thisSymbolOffset, &thisSymbolPublic); + } + if (thisSymbol != NULL) { + // Symbols are sometimes followed by a colon and other characters. + // If there is a colon, replace it with a null terminator + colonPtr = strchr(thisSymbol, ':'); + if (colonPtr) + *colonPtr = '\0'; + + if (thisSymbolPublic) { + thisSymbolType = kMacAToSDyldPubliSymbol; + } else { + thisSymbolType = kMacAToSDyldPrivateSymbol; + } + + if (err == 0) { + ReplaceSymbolIfBetter(symbol, thisSymbolType, thisSymbol, (unsigned long) thisSymbolOffset); + } + } else { + symbol->symbolName[0] = '\0'; + } + + free( (void *) thisSymbol); + + return err; +} + + + +static int OutputFrames(const MacBTPPCFrame *frameArray, unsigned long frameCount, unsigned char lookupSymbolNames) { + // Output a textual description of frameCount frames from frameArray. + // we look up the symbol names of the PCs of each of the frames. + + int err; + unsigned long frameIndex; + MacAToSSymInfo symbol; + MacAToSAddr address; + + err = 0; + + fputs("Stack Frame backtrace:\n # Flags Frame Addr Caller PC Symbol\n" + "=== === ========== ========== ==========\n", stderr); + + for (frameIndex = 0; frameIndex < frameCount; frameIndex++) { + + fprintf(stderr, "%3ld %c%c%c 0x%08lx 0x%08lx ", + frameIndex, + (frameArray[frameIndex].flags & kMacBTFrameBadMask) ? 'F' : '-', + (frameArray[frameIndex].flags & kMacBTPCBadMask) ? 'P' : '-', + (frameArray[frameIndex].flags & kMacBTSignalHandlerMask) ? 'S' : '-', + frameArray[frameIndex].sp, + frameArray[frameIndex].pc); + + if (frameArray[frameIndex].flags & kMacBTPCBadMask) { + address = NULL; + } else { + address = (MacAToSAddr) frameArray[frameIndex].pc; + } + + symbol.symbolName[0] = '\0'; + symbol.symbolType = kMacAToSNoSymbol; + symbol.symbolOffset = 0; + + err = AToSCopySymbolNameUsingDyld(address, &symbol); + + if (symbol.symbolName[0]) { + fprintf(stderr, "%s + 0x%lx", + symbol.symbolName, symbol.symbolOffset); + } + + fputs("\n", stderr); + } + + return err; +} + +#pragma mark ***** PowerPC Backtrace Core + +/* PowerPC Stack Frame Basics + -------------------------- + + Size Purpose + ---- ------- + low memory -> 0x004 pointer to next frame + 0x004 place to save CR + 0x004 place to save LR + 0x008 reserved + high memory -> 0x004 place to save TOC (CFM only) + + To get from one frame to the next, you have to indirect + through an offset of 0 (kMacBTPPCOffsetToSP). To + extract the return address from a frame, you have to + indirect an offset of 8 (kMacBTPPCOffsetToLR). +*/ + +enum { + kMacBTPPCOffsetToSP = 0, + kMacBTPPCOffsetToLR = 8 +}; + +/* PowerPC Signal Stack Frames + --------------------------- + In the current Mac OS X architecture, there is no guaranteed reliable + way to backtrace a signal stack frame. The problem is that the kernel + pushes a variable amount of data on to the stack when it invokes the + user space signal trampoline (_sigtramp), and the only handle to the + information about how much data was pushed is passed in a register + parameter to _sigtramp. _sigtramp stashes that value away in a + non-volatile register. So, when _sigtramp calls the user-supplied + signal handler, there's no way to work out where that register + ends up being saved. + + Thus, we devolve into guesswork. It turns out that the offset from + the stack of the kernel data to the information we need (the place + where the interrupted thread's SP was stored) is a constant for any + given system release. So, we can just simply add the appropriate + offset to the frame pointer and grab the data we need. + + The problem is that this constant varies from release to release. + This code handles Mac OS X 10.2.x through 10.4.x. There's no + guarantee that this offset won't change again in the future. + + When the kernel invokes the user space signal trampoline, it pushes + the following items on to the stack. + + Mac OS X 10.2.x + --------------- + Size Purpose + ---- ------- + low memory -> 0x030 bytes for C linkage + 0x040 bytes for saving PowerPC parameters + 0x008 alignment padding + 0x408 struct mcontext, comprised of: + 0x020 ppc_exception_state_t + 0x0A0 ppc_thread_state_t + 0x108 ppc_float_state_t + 0x240 ppc_vector_state_t + 0x040 siginfo_t + 0x020 ucontext + high memory -> 0x0e0 red zone + + The previous frame's SP is at offset 0x00C within + ppc_thread_state_t, which makes + kMacBTPPCOffsetToSignalSPTenTwo equal to + 0x030 + 0x040 + 0x008 + 0x020 + 0x00C, or 0x0A4. +*/ + +enum { + kMacBTPPCOffsetToSignalSPTenOne = 0x07C, + kMacBTPPCOffsetToSignalSPTenTwo = 0x0A4, + // We determined the offsets for OS 10.3 and 10.4 heuristically by + // examining the contents of the stack + kMacBTPPCOffsetToSignalSPTenThree = 0x09C, + // The same offset seems to work for OS 10.4 as for OS 10.3. Other + // possible values for the offset in 10.4 are 0x0a8 and 0x0c8. (In + // our tests on OS 10.4.2, the same correct frame pointer appeared + // at all three of these offsets.) + kMacBTPPCOffsetToSignalSPTenFour = 0x09C +}; + +/* PowerPC Signal Stack Frames (cont) + ---------------------------------- + The only remotely reliable way to detect a signal stack frame is to + look at the return address to see whether it points within the + _sigtramp routine. I can find the address of this routine via + the dynamic linker, but I don't have an easy way to determine it's + length. So I just guess! Fortunately, this is rarely a problem. + And here's the number I chose. +*/ + +enum { + kMacBTPPCSigTrampSize = 256 +}; + +typedef struct MacBTPPCContext MacBTPPCContext; + +typedef int (*MacBTReadBytesProc)(MacBTPPCContext *context, MacBTPPCAddr src, void *dst, unsigned long size); + // This function pointer is called by the core backtrace code + // when it needs to read memory. The callback should do a safe + // read of size bytes from src into the buffer specified by + // dst. By "safe" we mean that the routine should return an error + // if the read can't be done (typically because src is a pointer to + // unmapped memory). + +// The MacBTPPCContext structure is used by the core backtrace code +// to maintain its state. + +struct MacBTPPCContext { + + // Internal parameters that are set up by the caller + // of the core backtrace code. + + unsigned long offsetToSignalSP; + MacBTPPCAddr sigTrampLowerBound; + MacBTPPCAddr sigTrampUpperBound; + MacBTReadBytesProc readBytes; + void * refCon; + + // Parameters from client. + + MacBTPPCAddr pc; + MacBTPPCAddr r0; // see MacBTPPCCheckLeaf + MacBTPPCAddr sp; + MacBTPPCAddr lr; + MacBTPPCAddr stackBottom; + MacBTPPCAddr stackTop; + MacBTPPCFrame *frameArray; // array contents filled out by core + unsigned long frameArrayCount; + unsigned long frameCountOut; // returned by core +}; + +static int ReadPPCAddr(MacBTPPCContext *context, MacBTPPCAddr addr, MacBTPPCAddr *value) { + // Reads a PowerPC address (ie a pointer) from the target task, + // returning an error if the memory is unmapped. + + return context->readBytes(context, addr, value, sizeof(*value)); +} + +static int ReadPPCInst(MacBTPPCContext *context, MacBTPPCAddr addr, MacBTPPCInst *value) { + // Reads a PowerPC instruction from the target task, + // returning an error if the memory is unmapped. + + return context->readBytes(context, addr, value, sizeof(*value)); +} + +static int MacBTPPCCheckLeaf(MacBTPPCContext *context) { + // The top most frame may be in a weird state because of the + // possible variations in the routine prologue. There are a + // variety of combinations, such as: + // + // 1. a normal routine, with its return address stored in + // its caller's stack frame + // + // 2. a system call routine, which is a leaf routine with + // no frame and the return address is in LR + // + // 3. a leaf routine with no frame, where the return address + // is in LR + // + // 4. a leaf routine with no frame that accesses a global, where + // the return address is in R0 + // + // 5. a normal routine that was stopped midway through + // constructing its prolog, where the return address is + // typically in R0 + // + // Of these, 1 and 2 are most common, and they're the cases I + // handle. General support for all of the cases requires the + // ability to accurately determine the start of the routine + // which is not something that I can do with my current + // infrastructure. + // + // Note that don't handle any cases where the return address is + // in R0, although I do have a variable for R0 in the context + // in case I add that handling in the future. + + int err; + unsigned char isSystemCall; + MacBTPPCInst inst; + MacBTPPCInst pc; + int count; + + // Using the PC from the top frame (frame[0]), walk back through + // the code stream for 3 instructions looking for a "sc" instruction. + // If we find one, it's almost certain that we're in a system call + // frameless leaf routine. + + isSystemCall = false; + count = 0; + pc = context->pc; + do { + err = ReadPPCInst(context, pc, &inst); + if (err == 0) { + isSystemCall = (inst == 0x44000002); // PPC "sc" instruction + } + if ( (err == 0) && ! isSystemCall ) { + count += 1; + pc -= sizeof(MacBTPPCInst); + } + } while ( (err == 0) && ! isSystemCall && (count < 3) ); + err = 0; + + // If we find that we're in a system call frameless leaf routine, + // te add a dummy stack frame (with no frame, because the frame actually + // belows to frameArray[1]). + + if (isSystemCall) { + if ( (context->frameArray != NULL) && (context->frameCountOut < context->frameArrayCount) ) { + MacBTPPCFrame * frameOutPtr; + + frameOutPtr = &context->frameArray[context->frameCountOut]; + frameOutPtr->pc = context->pc; + frameOutPtr->sp = 0; + frameOutPtr->flags = kMacBTFrameBadMask; + } + context->frameCountOut += 1; + + context->pc = context->lr; + } + + return err; +} + +static int MacBacktracePPCCore(MacBTPPCContext *context) { + // The core backtrace code. This routine is called by all of the various + // exported routines. It implements the core backtrace functionality. + // All of the parameters to this routine are contained within + // the context. This routine traces back through the stack (using the + // readBytes callback in the context to actually read memory) creating + // a backtrace. + + int err; + MacBTPPCAddr thisPC; + MacBTPPCAddr thisFrame; + MacBTPPCAddr lowerBound; + MacBTPPCAddr upperBound; + unsigned char stopNow; + + lowerBound = context->stackBottom; + upperBound = context->stackTop; + if (upperBound == 0) { + upperBound = (MacBTPPCAddr) -1; + } + + // Check the current PC and add a dummy frame if it points to + // a frameless leaf routine. + + context->frameCountOut = 0; + err = MacBTPPCCheckLeaf(context); + + // Handle the normal frames. + + if (err == 0) { + thisPC = context->pc; + thisFrame = context->sp; + + stopNow = false; + do { + MacBTPPCFrame * frameOutPtr; + MacBTPPCFrame tmpFrameOut; + MacBTPPCAddr nextFrame; + MacBTPPCAddr nextPC; + MacBTPPCInst junkInst; + + // Output to a tmpFrameOut unless the client has supplied + // a buffer and there's sufficient space left in it. + + if ( (context->frameArray != NULL) && (context->frameCountOut < context->frameArrayCount) ) { + frameOutPtr = &context->frameArray[context->frameCountOut]; + } else { + frameOutPtr = &tmpFrameOut; + } + context->frameCountOut += 1; + + // Record this entry. + + frameOutPtr->pc = thisPC; + frameOutPtr->sp = thisFrame; + frameOutPtr->flags = 0; + + // Now set the flags to indicate the validity of specific information. + + // Check the validity of the PC. Don't set the err here; a bad PC value + // does not cause us to quit the backtrace. + + if ( (((int) thisPC) & 0x03) || (ReadPPCInst(context, thisPC, &junkInst) != 0) ) { + frameOutPtr->flags |= kMacBTPCBadMask; + } + + // Check the validity of the frame pointer. A bad frame pointer *does* + // cause us to stop tracing. + + if ( (thisFrame == 0L) || (((int) thisFrame) & 0x03) || (thisFrame < lowerBound) || (thisFrame >= upperBound) ) { + frameOutPtr->flags |= kMacBTFrameBadMask; + stopNow = true; + } + + if (err == 0 && ! stopNow) { + + // Read the next frame pointer. Again, a failure here causes us to quit + // backtracing. Note that we set kMacBTFrameBadMask in frameOutPtr + // because, if we can't read the contents of the frame pointer, the + // frame pointer itself must be bad. + + err = ReadPPCAddr(context, thisFrame + kMacBTPPCOffsetToSP, &nextFrame); + if (err != 0) { + frameOutPtr->flags |= kMacBTFrameBadMask; + // No need to set stopNow because err != 0 will + // terminate loop. + } + + // If the next frame pointer indicates that this frame was called + // as a signal handler, handle the discontinuity in the stack. + + if (err == 0) { + // Extract the LR from the stack frame. Note that we have to do + // this before we check for a signal frame because the PC of + // the frame that was interrupted by the signal is stored + // in this nextFrame, not in the one we'll get by delving + // into the signal handler stack block. + + if ( ReadPPCAddr(context, nextFrame + kMacBTPPCOffsetToLR, &nextPC) != 0 ) { + nextPC = (MacBTPPCAddr) -1; // an odd value, to trigger above check on next iteration + } + + // delving into the signal handler stack block. + + if ( !(frameOutPtr->flags & kMacBTPCBadMask) + && ( frameOutPtr->pc >= context->sigTrampLowerBound ) + && ( frameOutPtr->pc < context->sigTrampUpperBound ) ) { + frameOutPtr->flags |= kMacBTSignalHandlerMask; +#if 0 + // This code allows us to examine the stack to find the correct + // value for offsetToSignalSP for new releases of OS X. We + // don't use it in production + for (long index=0;index<256;index+=4) { + MacBTPPCAddr value; + ReadPPCAddr(context, nextFrame + index, &value); + fprintf(stderr, "offset %lx: value = %lx\n", index, value); + } +#endif + err = ReadPPCAddr(context, nextFrame + context->offsetToSignalSP, &nextFrame); + } + } + + // Set up for the next iteration. + + if (err == 0) { + lowerBound = thisFrame; + thisPC = nextPC; + thisFrame = nextFrame; + } + } + } while ( (err == 0) && ! stopNow ); + } + + return err; +} + +static int InitMacBTPPCContext(MacBTPPCContext *context, + MacBTPPCAddr stackBottom, MacBTPPCAddr stackTop, + MacBTPPCFrame *frameArray, unsigned long frameArrayCount, + char OSMinorVersion) { + // Initialises a MacBTPPCContext to appropriate default values. + int err; + + memset(context, 0, sizeof(context)); + + // We don't check the input parameters here. Instead the + // check is done by the backtrace core. + + context->stackBottom = stackBottom; + context->stackTop = stackTop; + context->frameArray = frameArray; + context->frameArrayCount = frameArrayCount; + + // Some system version specific parameters: + // + // o _sigtramp is irrelevant on traditional Mac OS. + // o We don't support Mac OS X 10.0.x. + // o offsetToSignalSP changed between 10.1.x and 10.2.x. + + err = 0; + + if (OSMinorVersion == '3') + context->offsetToSignalSP = kMacBTPPCOffsetToSignalSPTenThree; + else + context->offsetToSignalSP = kMacBTPPCOffsetToSignalSPTenFour; + + context->sigTrampLowerBound = (MacBTPPCAddr) & _sigtramp; + + // We can't actually determine the size of _sigtramp with our current + // technology, so we just guess at the upper bound. + context->sigTrampUpperBound = context->sigTrampLowerBound + kMacBTPPCSigTrampSize; + return err; +} + +///////////////////////////////////////////////////////////////// +#pragma mark ***** Mach Interface + +// The Mach interface works accesses all backtrace memory via +// Mach VM calls, and thus there's the potential for it to execute +// on a instruction set architecture other than the one being +// backtraced. Hence, there's no requirement for TARGET_CPU_PPC here. + +static int MacBTReadBytesMach(MacBTPPCContext *context, MacBTPPCAddr src, void *dst, unsigned long size) { + // A memory read callback for Mach (see MacBTReadBytesProc). + // This simply calls through to the Mach vm_read_overwrite + // primitive, which does exactly what we want. + int err; + vm_size_t sizeRead; + + sizeRead = size; + err = vm_read_overwrite( (thread_t) context->refCon, (vm_address_t) src, size, (vm_address_t) dst, &sizeRead); + if ( (err == 0) && (sizeRead != size) ) { + err = -1; + } + return err; +} + +int MacBacktracePPCMach(task_t task, MacBTPPCAddr pc, MacBTPPCAddr sp, + MacBTPPCAddr stackBottom, MacBTPPCAddr stackTop, + MacBTPPCFrame *frameArray, unsigned long frameArrayCount, + unsigned long *frameCount, char OSMinorVersion) { + // See comments in header. + int err; + MacBTPPCContext context; + + err = InitMacBTPPCContext(&context, stackBottom, stackTop, frameArray, frameArrayCount, OSMinorVersion); + if (err == 0) { + context.pc = pc; + context.sp = sp; + context.readBytes = MacBTReadBytesMach; + context.refCon = (void *) task; + + err = MacBacktracePPCCore(&context); + } + if (frameCount != NULL) { + *frameCount = context.frameCountOut; + } + return err; +} + +///////////////////////////////////////////////////////////////// +#pragma mark ***** Assembly Stuff + +// The following is an inline assembly abstraction layer that isolates +// the rest of the code from the specific compiler's flavour of assembly. +// Note that this is not as clean as I'd like it to be. Specifically, +// MacBTPPCGetProgramCounter is a nice function definition, but +// MacBTPPCGetStackPointer is a macro. If I define MacBTPPCGetStackPointer +// as a function, I have problems because GCC insists on building a +// stack from in non-optimised builds but no stack frame in optimised +// builds. That makes things tricky. So, instead, I use a macro that +// expands inside the function itself. This causes other issues. +// For example, in MacBacktracePPCCarbonSelf I had to name the local +// variable "mySP" and not "sp", because otherwise the CodeWarrior +// inline assembler can't distinguish between the variable and the +// register. Also, mySP has to qualified as "register" for the sake +// of the CodeWarrior inline assembler. Fortunately, these changes +// do not cause problems for GCC. + +// Obviously the "Self" calls require TARGET_CPU_PPC. + + +#define MacBTPPCGetStackPointer(result) \ + __asm__ volatile("mr %0,r1" : "=r" (result)); + +static MacBTPPCAddr MacBTPPCGetProgramCounter(void) { + MacBTPPCAddr result; + __asm__ volatile("mflr %0" : "=r" (result)); + return result; +} + +int MacBacktracePPCMachSelf(MacBTPPCAddr stackBottom, MacBTPPCAddr stackTop, + MacBTPPCFrame *frameArray, unsigned long frameArrayCount, + unsigned long *frameCount, char OSMinorVersion) { + // See comments in header. + register MacBTPPCAddr mySP; + MacBTPPCAddr myPC; + + // For Mac information about these inline assembly routines, + // see the "***** Assembly Stuff" comment above. + + MacBTPPCGetStackPointer(mySP); + myPC = MacBTPPCGetProgramCounter(); + + return MacBacktracePPCMach(mach_task_self(), myPC, mySP, stackBottom, stackTop, frameArray, frameArrayCount, frameCount, OSMinorVersion); +} diff --git a/lib/mac_backtrace.h b/lib/mac_backtrace.h new file mode 100644 index 0000000000..5eb7e2894c --- /dev/null +++ b/lib/mac_backtrace.h @@ -0,0 +1,156 @@ +// Berkeley Open Infrastructure for Network Computing +// http://boinc.berkeley.edu +// Copyright (C) 2005 University of California +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; +// either version 2.1 of the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// To view the GNU Lesser General Public License visit +// http://www.gnu.org/copyleft/lesser.html +// or write to the Free Software Foundation, Inc., +// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +/* + * mac_backtrace.h + * + */ + +/* This is a rudimentary backtrace generator for boinc project applications. +* +* It is adapted from Apple Developer Technical Support Sample Code +* MoreisBetter / MoreDebugging / MoreBacktraceTest +* The symbols it displays are not always clean. +* +* For useful tips on using backtrace information, see Apple Tech Note 2123: +* http://developer.apple.com/technotes/tn2004/tn2123.html#SECNOSYMBOLS +* +* To convert addresses to correct symbols, use the atos command-line tool: +* atos -o path/to/executable/with/symbols address +* Note: if address 1a23 is hex, use 0x1a23. +* +* To demangle mangled C++ symbols, use the c++filt command-line tool. +* You may need to prefix C++ symbols with an additonal underscore before +* passing them to c++filt (so they begin with two underscore characters). +* +* Flags in backtrace: +* F this frame pointer is bad +* P this PC is bad +* S this frame is a signal handler +* +*/ + +#ifndef _BOINC_BACKTRACE_ +#define _BOINC_BACKTRACE_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Overview + -------- + This module implements a number of PowerPC backtrace routines. + All of the routines are implemented in terms of a common core. + The code is structured in a very generic way. For example, if + you were running on a version of Mach that support inter-machine + messaging, it would be feasible to do a backtrace of a PowerPC + program from program executing a completely different instruction + set architecture (ISA). + + Backtraces are inherently processor-specific. As Mac OS X only + runs on PowerPC, I haven't attempted to make this code ISA + independent. Even if I did this work, there would be no way + to test it, and I don't believe it shipping untested code. + Similarly, I don't have any way to test this with 64-bit PowerPC + code. + + If you're curious about how PowerPC stack frames work, check out + the comments in the implementation file. The comments in the + header focus on how you use these routines. +*/ + +// These definitions isolate the backtrace algorithm from the specifics +// of the instruction set architecture that it's being compiled for. + +typedef unsigned long MacBTPPCInst; +typedef unsigned long MacBTPPCAddr; + +// The end result of a backtrace is an array of MacBTPPCFrame +// structures. + +struct MacBTPPCFrame { + MacBTPPCAddr sp; // frame pointer for this function invocation + MacBTPPCAddr pc; // PC for this function invocation + unsigned long flags; // various flags, see below +}; +typedef struct MacBTPPCFrame MacBTPPCFrame; + +enum { + kMacBTFrameBadMask = 0x0001, // this frame pointer is bad + kMacBTPCBadMask = 0x0002, // this PC is bad + kMacBTSignalHandlerMask = 0x0004 // this frame is a signal handler +}; + +/* Common Parameters + ----------------- + All of the backtrace routines accept certain common parameters. + + o pc and sp -- For non "Self" routines, these parameters supply + the initial program counter and stack pointer for the backtrace. + + o stackBottom and stackTop -- These define the extent of the stack + which you are tracing. If this information isn't handy, supply + 0 for both. Supplying meaningful values can reduce the number + of bogus frames reported if the stack is corrupt. + + o frameArray and frameArrayCount -- These define an array of stack + frames that the routines fill out. You can supply NULL and 0 + (respectively) if you're not interested in getting the actual + frame data (typically you do this to get the count of the number + of frames via frameCount). The routines do not fail if this + buffer is exhausted. Instead they simply return as many frames + as they can and continue tracing, returning an accurate value + for frameCount. + + o frameCount -- You can use this to get back an accurate count of + the number of frames in the stack. If you're not interested + in this information, you can pass NULL. +*/ + +void PrintBacktrace(void); +int MacBacktracePPCMachSelf(MacBTPPCAddr stackBottom, MacBTPPCAddr stackTop, + MacBTPPCFrame *frameArray, unsigned long frameArrayCount, + unsigned long *frameCount, char OSMinorVersion); + +#if defined(__cplusplus) +} +#endif + + +typedef const void *MacAToSAddr; + +enum { + kMacAToSNoSymbol = 0, + kMacAToSDyldPubliSymbol, // supported + kMacAToSDyldPrivateSymbol, // supported + kMacAToSCFMSymbol, // not yet implemented + kMacAToSTracebackTableSymbol // not yet implemented +}; +typedef signed long MacAToSSymbolType; + +struct MacAToSSymInfo { + MacAToSSymbolType symbolType; + char symbolName[63]; + unsigned long symbolOffset; +}; +typedef struct MacAToSSymInfo MacAToSSymInfo; + +#endif // _BOINC_BACKTRACE_ \ No newline at end of file