mirror of https://github.com/BOINC/boinc.git
918 lines
34 KiB
C
918 lines
34 KiB
C
// 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 handles Mac OS X 10.3.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 <mach-o/dyld.h>
|
|
#include <mach-o/loader.h>
|
|
#include <mach-o/nlist.h>
|
|
#include <mach-o/stab.h>
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h> // for getpid()
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "mac_backtrace.h"
|
|
|
|
extern void * _sigtramp;
|
|
|
|
enum {
|
|
kFrameCount = 200
|
|
};
|
|
|
|
#define SKIPFRAME 4 /* Number frames overhead for signal handler and backtrace */
|
|
|
|
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, "\nCrashed executable name: %s\n", nameBuf);
|
|
|
|
#ifdef BOINC_VERSION_STRING
|
|
fprintf(stderr, "built using BOINC library version %s\n", BOINC_VERSION_STRING);
|
|
#endif
|
|
}
|
|
|
|
|
|
// 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, "<key>ProductUserVisibleVersion</key>");
|
|
if (p1) {
|
|
p1 = strstr(p1, "<string>") + 8;
|
|
p2 = strstr(p1, "</string>");
|
|
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, "<key>ProductBuildVersion</key>");
|
|
if (p1) {
|
|
p1 = strstr(p1, "<string>") + 8;
|
|
p2 = strstr(p1, "</string>");
|
|
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, skipframe = 0;
|
|
MacAToSSymInfo symbol;
|
|
MacAToSAddr address;
|
|
|
|
err = 0;
|
|
|
|
if ((frameCount >= SKIPFRAME) && frameArray[SKIPFRAME-1].flags & kMacBTSignalHandlerMask)
|
|
skipframe = SKIPFRAME;
|
|
|
|
fputs("Stack Frame backtrace:\n # Flags Frame Addr Caller PC Symbol\n"
|
|
"=== === ========== ========== ==========\n", stderr);
|
|
|
|
for (frameIndex = skipframe; frameIndex < frameCount; frameIndex++) {
|
|
|
|
fprintf(stderr, "%3ld %c%c%c 0x%08lx 0x%08lx ",
|
|
frameIndex - skipframe + 1,
|
|
(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);
|
|
}
|