boinc/lib/mac/QCrashReport.c

1614 lines
59 KiB
C

// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC 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 3 of the License, or (at your option) any later version.
//
// BOINC 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.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
/*
* QCrashReport.c
*
*/
/* This is part of a backtrace generator for boinc project applications.
*
* Adapted from Apple Developer Technical Support Sample Code QCrashReport
*
* This code handles Mac OS X 10.3.x through 10.4.9. It may require some
* adjustment for future OS versions; see the discussion of _sigtramp and
* PowerPC Signal Stack Frames in file QBacktrace.c.
*
* 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).
*
* A very useful shell script to add symbols to a crash dump can be found at:
* http://developer.apple.com/tools/xcode/symbolizingcrashdumps.html
* Pipe the output of the shell script through c++filt to demangle C++ symbols.
*/
/*
File: QCrashReport.c
Contains: Code for generating crash reports.
Written by: DTS
Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or
redistribution of this Apple software constitutes acceptance of
these terms. If you do not agree with these terms, please do
not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following
terms, and subject to these terms, Apple grants you a personal,
non-exclusive license, under Apple's copyrights in this
original Apple software (the "Apple Software"), to use,
reproduce, modify and redistribute the Apple Software, with or
without modifications, in source and/or binary forms; provided
that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the
following text and disclaimers in all such redistributions of
the Apple Software. Neither the name, trademarks, service marks
or logos of Apple Inc. may be used to endorse or promote
products derived from the Apple Software without specific prior
written permission from Apple. Except as expressly stated in
this notice, no other rights or licenses, express or implied,
are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or
by other works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis.
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Change History (most recent first):
$Log: QCrashReport.c,v $
Revision 1.1 2007/03/02 12:19:57
First checked in.
*/
/////////////////////////////////////////////////////////////////
// Compatibility with OS 10.5 SDK and later
#ifdef __DARWIN_UNIX03
#undef __DARWIN_UNIX03
#endif
#define _NONSTD_SOURCE 1
#define __DARWIN_UNIX03 0
#include "config.h"
#include "QCrashReport.h"
#include "mac_backtrace.h"
#include "filesys.h"
// System interfaces
#include <TargetConditionals.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
// Put Mach includes inside extern "C" guards for the C++ build
// because the Mach header files don't always have them.
#if defined(__cplusplus)
extern "C" {
#endif
#include <servers/bootstrap.h>
// We want both PowerPC and Intel thread state information.
// By default, the system only gives us the one that's appropriate
// for our machine. So we include both here.
#if TARGET_CPU_PPC
#include <mach/ppc/thread_status.h>
#endif
#include <mach/i386/thread_status.h>
#if defined(__cplusplus)
}
#endif
/////////////////////////////////////////////////////////////////
#pragma mark ***** Crash Report Object
// Each crash report maintains an array of QCRThread records, one per thread
// in the target task.
struct QCRThread {
thread_t thread; // name of send right for thread control port
bool stateOverridden; // true if thread state was explicitly set via QCRSetThreadStateAtIndex
thread_state_flavor_t stateFlavor; // flavor of thread state
void * state; // thread state, may be NULL if not yet been fetched
size_t stateSize; // size of thread state
QBTFrame * frames; // backtrace associated with thread state, may be NULL if not yet fetched
size_t frameCount; // number of elements of frames array
};
typedef struct QCRThread QCRThread;
// QCrashReport represents the crash report itself. It's the backing for
// the exported QCrashReportRef type.
struct QCrashReport {
task_t task; // target task
cpu_type_t requestedCPUType; // taken from cputype parameter of QCRCreateFromTask
cpu_type_t actualCPUType; // CPU type from main executable of target task
QSymbolsRef symRef; // symbols object for target task
QMOImageRef executable; // main executable of target task
size_t threadCount; // number of elements of threads array
QCRThread * threads; // threads array, one element per thread
size_t crashedThreadIndex; // index of crashed thread, kQCRNoThread
};
typedef struct QCrashReport QCrashReport;
#if ! defined(NDEBUG)
static bool QCRIsValid(QCrashReportRef crRef)
// Returns true if crRef looks like a valid crash report object.
{
bool success;
size_t threadIndex;
success = true
&& (crRef != NULL)
&& (crRef->task != MACH_PORT_NULL)
&& (crRef->actualCPUType != CPU_TYPE_ANY)
&& (crRef->symRef != NULL)
&& (crRef->executable != NULL)
&& (crRef->threadCount > 0)
&& (crRef->threads != NULL)
&& ((crRef->crashedThreadIndex == kQCRNoThread) || (crRef->crashedThreadIndex < crRef->threadCount))
;
if (success) {
for (threadIndex = 0; threadIndex < crRef->threadCount; threadIndex++) {
const QCRThread * thisThread;
// Note: A frame count of 0 can happen with a NULL backtrace (we
// haven't attempted the backtrace yet) or with a non-NULL backtrace
// (we did it and it returned zero frames).
thisThread = &crRef->threads[threadIndex];
success = true
&& (thisThread->thread != MACH_PORT_NULL)
&& ( (thisThread->state == NULL) == (thisThread->stateSize == 0) )
&& ((thisThread->frameCount == 0) || (thisThread->frames != NULL))
;
}
}
return success;
}
#endif
#pragma mark - Create and Destroy
extern int QCRCreateFromTask(
task_t task,
bool suspend,
thread_t crashedThread,
cpu_type_t cputype,
QCrashReportRef * crRefPtr
)
// See comment in header.
//
// This does less than you might think. A lot of the work, like getting the
// state and backtrace for each thread, is done lazily. The goal is to make
// it relatively cheap to create a crash report object, only paying the
// penality for things like getting the backtrace if you actually need them.
{
int err;
kern_return_t kr;
kern_return_t krJunk;
QCrashReportRef crRef;
size_t threadIndex;
assert(task != MACH_PORT_NULL);
// crashedThread may be MACH_PORT_NULL
assert( crRefPtr != NULL);
assert(*crRefPtr == NULL);
// Allocate the memory and start filling it out.
err = 0;
crRef = (QCrashReportRef) calloc(1, sizeof(*crRef));
if (crRef == NULL) {
err = ENOMEM;
}
if (err == 0) {
crRef->task = task;
crRef->crashedThreadIndex = kQCRNoThread;
crRef->requestedCPUType = cputype;
err = QSymCreateFromTask(task, suspend, crRef->requestedCPUType, &crRef->symRef);
}
if (err == 0) {
crRef->executable = QSymGetExecutableImage(crRef->symRef);
if (crRef->executable == NULL) {
err = EINVAL;
}
if (err == 0) {
crRef->actualCPUType = QMOImageGetCPUType(crRef->executable);
// If we didn't get the CPU type we requested, that's very weird.
assert( (crRef->requestedCPUType == CPU_TYPE_ANY) || (crRef->requestedCPUType == crRef->actualCPUType) );
}
}
// Fill out the threads array.
if (err == 0) {
thread_act_array_t threadsTmp;
mach_msg_type_number_t threadCountTmp;
threadsTmp = NULL;
kr = task_threads(crRef->task, &threadsTmp, &threadCountTmp);
err = QTMErrnoFromMachError(kr);
if (err == 0) {
crRef->threadCount = threadCountTmp;
crRef->threads = (QCRThread *) calloc(crRef->threadCount, sizeof(*crRef->threads));
if (crRef->threads == NULL) {
err = ENOMEM;
}
// Regardless of whether the calloc succeeded, we need to run the
// following loop. It either records the thread send rights we got
// back, or disposes of them. We need this silliness because we
// can't get a thread count (to calloc the array) without also
// getting rights at the same time, and we have to make sure that
// those rights get cleaned up if calloc fais.
for (threadIndex = 0; threadIndex < crRef->threadCount; threadIndex++) {
if (crRef->threads == NULL) {
krJunk = mach_port_deallocate(mach_task_self(), threadsTmp[threadIndex]);
assert(krJunk == KERN_SUCCESS);
} else {
crRef->threads[threadIndex].thread = threadsTmp[threadIndex];
}
}
}
if (threadsTmp != NULL) {
krJunk = vm_deallocate(mach_task_self(), (vm_address_t) threadsTmp, sizeof(*threadsTmp) * threadCountTmp);
assert(krJunk == KERN_SUCCESS);
}
}
// Calculate index of crashed thread. It's an error if we fail to
// find the thread in our thread list.
if ( (err == 0) && (crashedThread != MACH_PORT_NULL) ) {
err = EINVAL;
for (threadIndex = 0; threadIndex < crRef->threadCount; threadIndex++) {
if (crRef->threads[threadIndex].thread == crashedThread) {
err = QCRSetCrashedThreadIndex(crRef, threadIndex);
break;
}
}
}
// Clean up.
if (err != 0) {
QCRDestroy(crRef);
crRef = NULL;
}
*crRefPtr = crRef;
assert( (err == 0) == (*crRefPtr != NULL) );
assert( (*crRefPtr == NULL) || QCRIsValid(crRef) );
return err;
}
extern int QCRCreateFromSelf(QCrashReportRef *crRefPtr)
// See comment in header.
{
assert( crRefPtr != NULL);
assert(*crRefPtr == NULL);
return QCRCreateFromTask(mach_task_self(), false, mach_thread_self(), QMOGetLocalCPUType(), crRefPtr);
}
extern void QCRDestroy(QCrashReportRef crRef)
// See comment in header.
{
kern_return_t krJunk;
size_t threadIndex;
if (crRef != NULL) {
if (crRef->threads != NULL) {
for (threadIndex = 0; threadIndex < crRef->threadCount; threadIndex++) {
QCRThread * thisThread;
thisThread = &crRef->threads[threadIndex];
if (thisThread->thread != MACH_PORT_NULL) {
krJunk = mach_port_deallocate(mach_task_self(), thisThread->thread);
assert(krJunk == KERN_SUCCESS);
}
free(thisThread->state);
free(thisThread->frames);
}
}
QSymDestroy(crRef->symRef);
free(crRef);
}
}
#pragma mark - Accessors
extern QSymbolsRef QCRGetSymbols(QCrashReportRef crRef)
// See comment in header.
{
assert( QCRIsValid(crRef) );
return crRef->symRef;
}
extern size_t QCRGetThreadCount(QCrashReportRef crRef)
// See comment in header.
{
assert( QCRIsValid(crRef) );
return crRef->threadCount;
}
extern thread_t QCRGetThreadAtIndex(QCrashReportRef crRef, size_t threadIndex)
// See comment in header.
{
assert( QCRIsValid(crRef) );
return crRef->threads[threadIndex].thread;
}
extern void QCRGetImages(
QCrashReportRef crRef,
QMOImageRef ** imageArrayPtr,
size_t * imageCountPtr
)
// See comment in header.
{
assert( QCRIsValid(crRef) );
assert(imageArrayPtr != NULL);
assert(imageCountPtr != NULL);
QSymGetImages(crRef->symRef, imageArrayPtr, imageCountPtr);
}
static void ClearBacktraceAtIndex(
QCrashReportRef crRef,
size_t threadIndex
)
// For a given thread, clean its backtrace. This reset the thread to the
// point before a backtrace was ever taken.
{
assert( QCRIsValid(crRef) );
assert( threadIndex < crRef->threadCount );
free(crRef->threads[threadIndex].frames);
crRef->threads[threadIndex].frames = NULL;
crRef->threads[threadIndex].frameCount = 0;
assert( QCRIsValid(crRef) );
}
static void ClearThreadStateAndBacktraceAtIndex(
QCrashReportRef crRef,
size_t threadIndex
)
// For a given thread, clean its thread state. This reset the thread to the
// point before a thread state was ever fetched. This also clears the
// backtrace because the backtrace was based on the old thread state.
{
assert( QCRIsValid(crRef) );
assert( threadIndex < crRef->threadCount );
// Any backtrace based on the thread state is now going to be bogus, so
// clear it out.
ClearBacktraceAtIndex(crRef, threadIndex);
free(crRef->threads[threadIndex].state);
crRef->threads[threadIndex].stateFlavor = 0;
crRef->threads[threadIndex].state = NULL;
crRef->threads[threadIndex].stateSize = 0;
assert( QCRIsValid(crRef) );
}
extern int QCRGetThreadStateAtIndex(
QCrashReportRef crRef,
size_t threadIndex,
cpu_type_t * cpuTypePtr,
thread_state_flavor_t * threadStateFlavorPtr,
const void ** threadStatePtr,
size_t * threadStateSizePtr
)
// See comment in header.
{
int err;
assert( QCRIsValid(crRef) );
// Any combination of cpuTypePtr, threadStateFlavorPtr, threadStatePtr, and
// threadStateSizePtr may be NULL. This is useful because it allows other
// parts of the module to call QCRGetThreadStateAtIndex to populate the cache
// without having to provide any dummy parameters.
if (threadIndex >= crRef->threadCount) {
err = EINVAL;
} else {
// If we haven't already cached a thread's state, go get one now. This code
// is carefully written so as to not commit any results to the QCRThread
// structure until we're guaranteed success.
err = 0;
if (crRef->threads[threadIndex].state == NULL) {
assert(crRef->threads[threadIndex].stateSize == 0);
err = QBTCreateThreadState(
crRef->task,
crRef->threads[threadIndex].thread,
crRef->actualCPUType,
crRef->symRef,
&crRef->threads[threadIndex].stateFlavor,
&crRef->threads[threadIndex].state,
&crRef->threads[threadIndex].stateSize
);
// QBTCreateThreadState is required to either error (and leave these
// fields alone) or succeed (and set them up).
assert( (err == 0) == (crRef->threads[threadIndex].state != NULL) );
assert( (err == 0) == (crRef->threads[threadIndex].stateSize != 0) );
}
// Regardless of whether the above succeeded or failed, the QCRThread
// should still be valid. The QCRIsValid checks that.
assert( QCRIsValid(crRef) );
if (err == 0) {
if (cpuTypePtr != NULL) {
*cpuTypePtr = crRef->actualCPUType;
}
if (threadStateFlavorPtr != NULL) {
*threadStateFlavorPtr = crRef->threads[threadIndex].stateFlavor;
}
if (threadStatePtr != NULL) {
*threadStatePtr = crRef->threads[threadIndex].state;
}
if (threadStateSizePtr != NULL) {
*threadStateSizePtr = crRef->threads[threadIndex].stateSize;
}
}
}
assert( QCRIsValid(crRef) );
return err;
}
extern int QCRSetThreadStateAtIndex(
QCrashReportRef crRef,
size_t threadIndex,
thread_state_flavor_t threadStateFlavor,
const void * threadState,
size_t threadStateSize
)
// See comment in header.
{
int err;
void * state;
assert( QCRIsValid(crRef) );
assert(threadState != NULL);
assert(threadStateSize > 0);
assert((threadStateSize % sizeof(integer_t)) == 0);
state = NULL;
err = 0;
if (threadIndex >= crRef->threadCount) {
err = EINVAL;
}
// Allocate a buffer to hold our copy of the thread state.
if (err == 0) {
state = malloc(threadStateSize);
if (state == NULL) {
err = ENOMEM;
} else {
memcpy(state, threadState, threadStateSize);
}
}
// Replace the thread's current state, if any, with the copy.
if (err == 0) {
// Clear out the cached thread state for the thread, and any associated
// backtrace. That way the backtrace will be recalculated based on the
// new thread state.
ClearThreadStateAndBacktraceAtIndex(crRef, threadIndex);
assert(crRef->threads[threadIndex].state == NULL);
crRef->threads[threadIndex].stateOverridden = true;
crRef->threads[threadIndex].stateFlavor = threadStateFlavor;
crRef->threads[threadIndex].state = state;
crRef->threads[threadIndex].stateSize = threadStateSize;
}
assert( QCRIsValid(crRef) );
return err;
}
extern int QCRGetBacktraceAtIndex(
QCrashReportRef crRef,
size_t threadIndex,
const QBTFrame ** frameArrayPtr,
size_t * frameCountPtr
)
// See comment in header.
{
int err;
assert( QCRIsValid(crRef) );
assert(frameArrayPtr != NULL);
assert(frameCountPtr != NULL);
err = 0;
if (threadIndex >= crRef->threadCount) {
err = EINVAL;
}
// If we're been asked to get a backtrace of the current thread and the
// thread state hasn't been explicitly set, clear any cached copy of the
// thread state and the backtrace, and then get the thread state right now.
// This ensures that we always return the current backtrace if we're
// called by the current thread for the current thread.
if ( (err == 0)
&& (crRef->threads[threadIndex].thread == mach_thread_self())
&& ! crRef->threads[threadIndex].stateOverridden ) {
ClearThreadStateAndBacktraceAtIndex(crRef, threadIndex);
assert(crRef->threads[threadIndex].state == NULL);
err = QBTCreateThreadStateSelf(
&crRef->threads[threadIndex].stateFlavor,
&crRef->threads[threadIndex].state,
&crRef->threads[threadIndex].stateSize
);
}
// Get the thread state (if we haven't already).
if (err == 0) {
err = QCRGetThreadStateAtIndex(crRef, threadIndex, NULL, NULL, NULL, NULL);
}
// If we haven't already got a backtrace for this thread, go get one.
if ( (err == 0) && (crRef->threads[threadIndex].frames == NULL) ) {
bool done;
int retries;
QBTFrame * frames;
size_t frameToAllocate;
size_t frameCount;
// Start with 50 frames, which is pretty generous (QBTFrame structures are
// small) but certainly not always going to work.
retries = 0;
done = false;
frameToAllocate = 50;
do {
frames = (QBTFrame *) calloc(frameToAllocate, sizeof(QBTFrame));
if (frames == NULL) {
err = ENOMEM;
}
if (err == 0) {
err = QBacktraceMachThreadState(
crRef->task,
crRef->threads[threadIndex].stateFlavor,
crRef->threads[threadIndex].state,
crRef->threads[threadIndex].stateSize,
crRef->actualCPUType,
crRef->symRef,
0,
0,
frames,
frameToAllocate,
&frameCount
);
}
if (err == 0) {
if (frameCount <= frameToAllocate) {
assert(crRef->threads[threadIndex].frames == NULL);
crRef->threads[threadIndex].frames = frames;
crRef->threads[threadIndex].frameCount = frameCount;
frames = NULL; // to prevent it being freed
done = true;
} else {
frameToAllocate = frameCount;
retries += 1;
if (retries > 10) {
assert(false);
err = EINVAL;
}
}
}
// We don't need to call QBacktraceDisposeSymbols because we passed
// a symbols object into QBacktraceMachThreadState. Thus, the symbol
// strings are owned by the symbols object, and we don't need to clean
// them up here.
free(frames);
} while ( (err == 0) && ! done );
}
// Return the backtrace (either one we've cached from a previous call, or one
// we just got) to the caller
if (err == 0) {
*frameArrayPtr = crRef->threads[threadIndex].frames;
*frameCountPtr = crRef->threads[threadIndex].frameCount;
}
assert( (err != 0) || (*frameArrayPtr != NULL) );
assert( QCRIsValid(crRef) );
return err;
}
extern size_t QCRGetCrashedThreadIndex(QCrashReportRef crRef)
// See comment in header.
{
assert( QCRIsValid(crRef) );
return crRef->crashedThreadIndex;
}
extern int QCRSetCrashedThreadIndex(QCrashReportRef crRef, size_t threadIndex)
// See comment in header.
{
int err;
assert( QCRIsValid(crRef) );
err = 0;
if ( (threadIndex != kQCRNoThread) && (threadIndex >= crRef->threadCount) ) {
err = EINVAL;
}
if ( (err == 0) && (threadIndex != crRef->crashedThreadIndex) ) {
crRef->crashedThreadIndex = threadIndex;
}
assert( QCRIsValid(crRef) );
return err;
}
#pragma mark - Text Rendering
extern void QCRPrintBacktraces(QCrashReportRef crRef, FILE *f)
// See comment in header.
{
int err;
bool is64Bit;
size_t threadIndex;
const QBTFrame *frames;
size_t frameCount;
size_t frameIndex;
char * library;
char libraryName[36];
char * libraryNamePtr;
char offsetStr[32];
QTMAddr pc = 0;
size_t startframe = 0; // Added for BOINC
char atosPipeBuf[1024];
char nameBuf[256], pathToThisProcess[1024];
char outBuf[1024];
char *sourceSymbol, *symbolEnd;
FILE *atosPipe = NULL;
FILE *cppfiltPipe = NULL;
char saved_env[128], *env = NULL;
bool atosExists = false, cppfiltExists = false;
assert( QCRIsValid(crRef) );
assert( f != NULL );
is64Bit = QMOImageIs64Bit(crRef->executable);
GetNameOfAndPathToThisProcess(nameBuf, sizeof(nameBuf), pathToThisProcess, sizeof(pathToThisProcess));
atosExists = boinc_file_exists("/usr/bin/atos");
cppfiltExists = boinc_file_exists("/usr/bin/atos");
if (atosExists || cppfiltExists) {
// The bidirectional popen only works if the NSUnbufferedIO environment
// variable is set, so we save and restore its current value.
env = getenv("NSUnbufferedIO");
if (env) {
strlcpy(saved_env, env, sizeof(saved_env));
}
setenv("NSUnbufferedIO", "YES", 1);
}
if (atosExists) {
// The backtrace_symbols() and backtrace_symbols() APIs are limited to
// external symbols only, so we also use the atos command-line utility
// which gives us debugging symbols when available.
//
// For some reason, using the -p option with the value from getpid()
// fails here but the -o option with a path does work.
#ifdef __x86_64__
snprintf(atosPipeBuf, sizeof(atosPipeBuf), "/usr/bin/atos -o \"%s\" -arch x86_64", pathToThisProcess);
#elif defined (__i386__)
snprintf(atosPipeBuf, sizeof(atosPipeBuf), "/usr/bin/atos -o \"%s\" -arch i386", pathToThisProcess);
#else
snprintf(atosPipeBuf, sizeof(atosPipeBuf), "/usr/bin/atos -o \"%s\" -arch ppc", pathToThisProcess);
#endif
atosPipe = popen(atosPipeBuf, "r+");
if (atosPipe) {
setbuf(atosPipe, 0);
}
}
if (cppfiltExists) {
cppfiltPipe = popen("/usr/bin/c++filt -s gnu-v3 -n", "r+");
if (cppfiltPipe) {
setbuf(cppfiltPipe, 0);
}
}
for (threadIndex = 0; threadIndex < crRef->threadCount; threadIndex++) {
fprintf(f, "Thread %zd%s:\n", threadIndex, (threadIndex == crRef->crashedThreadIndex) ? " Crashed" : "");
err = QCRGetBacktraceAtIndex(crRef, threadIndex, &frames, &frameCount);
if (err == 0) {
// If this is the thread that crashed, skip frames after the one that generated the signal. (Added for BOINC)
startframe = 0;
if (threadIndex == crRef->crashedThreadIndex)
for (frameIndex = 0; frameIndex < frameCount; frameIndex++) {
if ((frames[frameIndex-1].flags & kQBTSignalHandlerMask))
startframe = frameIndex;
}
for (frameIndex = startframe; frameIndex < frameCount; frameIndex++) {
// The way that CrashReporter prints the library name is pretty
// wacky, and would require me to use functions that aren't
// available to 64-bit code on 10.4.x. So I've adopted a simple
// solution of just printing the path to the library, trimmed
// to fit the field. I trim at the front because the interesting
// stuff (most notably, the framework name) is at the end.
//
// Note that the field with is 35 and 32 is the field width
// minus the three dots that mark the truncation.
library = (char *)frames[frameIndex].library;
if (library == NULL) {
libraryName[0] = 0;
} else {
libraryNamePtr = strrchr(library, (int)'/');
if (libraryNamePtr == NULL) {
libraryNamePtr = library;
} else {
++libraryNamePtr;
}
if (strlen(libraryNamePtr) > 35) {
snprintf(libraryName, sizeof(libraryName), "...%s", libraryNamePtr + strlen(libraryNamePtr) - 32);
} else {
strlcpy(libraryName, libraryNamePtr, sizeof(libraryName));
}
}
pc = frames[frameIndex].pc;
if ( ! is64Bit ) {
// Without this, a value of ((QTMAddr) -1), which is generated
// by the backtrace code, will cause us to print an 16 digit
// hex number.
pc = pc & 0x00000000FFFFFFFFLL;
}
// OTOH, it's easy to emulate the symbol rendering. Note that
// I deliberately include the leading underscore. The fact
// that CrashReporter strips it out is a poor decision IMHO.
if (frames[frameIndex].symbol == NULL) {
outBuf[0] = 0;
offsetStr[0] = 0;
} else {
strlcpy(outBuf, frames[frameIndex].symbol, sizeof(outBuf));
if (cppfiltPipe) {
if (outBuf[0] == '_') {
fprintf(cppfiltPipe, "%s\n", outBuf+1);
} else {
fprintf(cppfiltPipe, "%s\n", outBuf);
}
BT_PersistentFGets(outBuf, sizeof(outBuf), cppfiltPipe);
symbolEnd = strchr(outBuf, (int)'\n');
if (symbolEnd) {
*symbolEnd = '\0';
}
}
snprintf(offsetStr, sizeof(offsetStr), " + %llu", (unsigned long long) frames[frameIndex].offset);
strlcat(outBuf, offsetStr, sizeof(outBuf));
if (atosPipe) {
fprintf(atosPipe, "%#llx\n", (QTMAddr)pc);
BT_PersistentFGets(atosPipeBuf, sizeof(atosPipeBuf), atosPipe);
sourceSymbol = strrchr(atosPipeBuf, (int)':');
if (sourceSymbol) {
if (*--sourceSymbol != ':') {
sourceSymbol = strrchr(atosPipeBuf, (int)'(');
if (sourceSymbol) {
strlcat(outBuf, sourceSymbol-1, sizeof(outBuf));
symbolEnd = strchr(outBuf, (int)'\n');
if (symbolEnd) {
*symbolEnd = '\0';
}
}
}
}
}
}
fprintf(
f,
"%-3d %-35s %#0*llx %s\n",
(int) (frameIndex - startframe), // startframe added for BOINC
libraryName,
is64Bit ? 18 : 10,
pc,
outBuf
);
}
}
fprintf(f, "\n");
}
if (atosPipe) {
pclose(atosPipe);
}
if (cppfiltPipe) {
pclose(cppfiltPipe);
}
if (atosExists || cppfiltExists) {
if (env) {
setenv("NSUnbufferedIO", saved_env, 1);
} else {
unsetenv("NSUnbufferedIO");
}
}
}
#if TARGET_CPU_PPC
static void PrintPowerPCThreadState(
QCrashReportRef crRef,
const char * threadID,
FILE * f
)
// Prints a PowerPC 32-bit thread state based on the thread state of the crashed
// thread.
//
// I'm really not happy with this. For a start, there's a whole bunch code
// that's shared between the various CPU architecture printing routines that
// could be factored out. However, doing that nicely would require me to
// strike a difficult balance between flexibility and complexity.
//
// Secondly, it would be nice if this routine was explicitly passed the
// thread state so that it could work on things other than the crashed
// thread. And that has implications for the API that wraps this up
// (QCRPrintThreadState).
//
// Hmmm, what to do. Nothing at the moment. Also spent way too much time
// on this code.
{
const ppc_thread_state_t * state;
const unsigned int * regBase;
int reg;
char regName[32];
assert( QCRIsValid(crRef) );
assert(crRef->crashedThreadIndex != kQCRNoThread);
assert( crRef->threads[crRef->crashedThreadIndex].state != NULL );
assert( crRef->threads[crRef->crashedThreadIndex].stateFlavor == PPC_THREAD_STATE );
if (crRef->threads[crRef->crashedThreadIndex].stateSize == (PPC_THREAD_STATE_COUNT * sizeof(integer_t))) {
state = (const ppc_thread_state_t *) crRef->threads[crRef->crashedThreadIndex].state;
/*
Thread 0 crashed with PPC Thread State:
srr0: 0x01be8f98 srr1: 0x0200f030 vrsave: 0x00000000
xer: 0x20000000 lr: 0x01be49bc ctr: 0x00000000 mq: 0x00000000
r0: 0x01be49b4 r1: 0xbffff630 r2: 0x0188e250 r3: 0xffffffff
r4: 0x00000052 r5: 0x00000004 r6: 0x00000000 r7: 0xbffff7cf
r8: 0xbffffcc7 r9: 0x0000000b r10: 0xa0000d84 r11: 0xa00041f4
r12: 0x0188e3a4 r13: 0x00000000 r14: 0x00000000 r15: 0x00000000
r16: 0x00000000 r17: 0x00000000 r18: 0x00000000 r19: 0x00000000
r20: 0x00ca0275 r21: 0xbffffa00 r22: 0xbffffcce r23: 0xbffffccc
r24: 0xbffffcc4 r25: 0xbffffb20 r26: 0xbffffcc4 r27: 0xbffffcc4
r28: 0x0188d650 r29: 0x01be92d8 r30: 0xbffff900 r31: 0x01be92e0
*/
fprintf(f, "%s crashed with PPC Thread State:\n", threadID);
#ifndef __LP64__
fprintf(f, " srr0: 0x%08x srr1: 0x%08x vrsave: 0x%08x\n", state->srr0, state->srr1, state->vrsave);
fprintf(f, " xer: 0x%08x lr: 0x%08x ctr: 0x%08x mq: 0x%08x\n", state->xer, state->lr, state->ctr, state->mq);
regBase = (const unsigned int *) &state->r0;
#else
fprintf(f, " srr0: 0x%08x srr1: 0x%08x vrsave: 0x%08x\n", state->srr0, state->srr1, state->vrsave);
fprintf(f, " xer: 0x%08x lr: 0x%08x ctr: 0x%08x mq: 0x%08x\n", state->xer, state->lr, state->ctr, state->mq);
regBase = (const unsigned int *) &state->r0;
#endif
for (reg = 0; reg < 32; reg++) {
if ((reg % 4) == 0) {
fprintf(f, " ");
}
snprintf(regName, sizeof(regName), "r%d", reg);
fprintf(f, "%4s: 0x%08x", regName, regBase[reg]);
if ((reg % 4) == 3) {
fprintf(f, "\n");
}
}
fprintf(f, "\n");
}
}
static void PrintPowerPC64ThreadState(
QCrashReportRef crRef,
const char * threadID,
FILE * f
)
// Prints a PowerPC 64-bit thread state based on the thread state of the
// crashed thread.
//
// See PrintPowerPCThreadState for comments about the overall approach.
{
const ppc_thread_state64_t * state;
const unsigned long long * regBase;
int reg;
char regName[32];
assert( QCRIsValid(crRef) );
assert(crRef->crashedThreadIndex != kQCRNoThread);
assert( crRef->threads[crRef->crashedThreadIndex].state != NULL );
assert( crRef->threads[crRef->crashedThreadIndex].stateFlavor == PPC_THREAD_STATE64 );
if (crRef->threads[crRef->crashedThreadIndex].stateSize == (PPC_THREAD_STATE64_COUNT * sizeof(integer_t))) {
state = (const ppc_thread_state64_t *) crRef->threads[crRef->crashedThreadIndex].state;
/*
Thread 0 crashed with PPC Thread State 64:
srr0: 0x0000000000000000 srr1: 0x000000004000d030 vrsave: 0x0000000000000000
cr: 0x44022282 xer: 0x0000000020000004 lr: 0x000000009000b15c ctr: 0x000000009000b200
r0: 0x00000000ffffffe1 r1: 0x00000000bfffeb10 r2: 0x00000000a073cdec r3: 0x0000000010004005
r4: 0x0000000003000006 r5: 0x0000000000000000 r6: 0x0000000000000450 r7: 0x0000000000001203
r8: 0x0000000000000000 r9: 0x0000000000000000 r10: 0x0000000000000003 r11: 0x00000000a0006a2c
r12: 0x000000009000b200 r13: 0x0000000000000000 r14: 0x0000000000000001 r15: 0x0000000000000001
r16: 0x0000000000000000 r17: 0x0000000000000000 r18: 0x000000000000430f r19: 0x0000000000000000
r20: 0x00000000101a6e8a r21: 0x00000000f8bd9d7f r22: 0x0000000000310808 r23: 0x00000000bfffebe0
r24: 0x0000000000000450 r25: 0x0000000000001203 r26: 0x0000000000000000 r27: 0x0000000000000000
r28: 0x0000000000000000 r29: 0x0000000003000006 r30: 0x0000000003000006 r31: 0x000000009075cdec
*/
fprintf(f, "%s crashed with PPC Thread State:\n", threadID);
#ifndef __LP64__
fprintf(f, " srr0: 0x%016llx srr1: 0x%016llx vrsave: 0x%016x\n", state->srr0, state->srr1, state->vrsave);
fprintf(f, " cr: 0x%08x xer: 0x%016llx lr: 0x%016llx ctr: 0x%016llx\n", state->cr, state->xer, state->lr, state->ctr);
regBase = (const unsigned long long *) &state->r0;
#else
fprintf(f, " srr0: 0x%016llx srr1: 0x%016llx vrsave: 0x%016x\n", state->srr0, state->srr1, state->vrsave);
fprintf(f, " cr: 0x%08x xer: 0x%016llx lr: 0x%016llx ctr: 0x%016llx\n", state->cr, state->xer, state->lr, state->ctr);
regBase = (const unsigned long long *) &state->r0;
#endif
for (reg = 0; reg < 32; reg++) {
if ((reg % 4) == 0) {
fprintf(f, " ");
}
snprintf(regName, sizeof(regName), "r%d", reg);
fprintf(f, "%4s: 0x%016llx", regName, regBase[reg]);
if ((reg % 4) == 3) {
fprintf(f, "\n");
}
}
fprintf(f, "\n");
}
}
#endif // TARGET_CPU_PPC
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
static void PrintX86ThreadState(
QCrashReportRef crRef,
const char * threadID,
FILE * f
)
// Prints a x86 32-bit thread state based on the thread state of the
// crashed thread.
//
// See PrintPowerPCThreadState for comments about the overall approach.
{
const x86_thread_state32_t * state;
const unsigned int * regBase;
int reg;
static const char * kRegNames[16] = {
"eax", "ebx", "ecx", "edx",
"edi", "esi", "ebp", "esp",
"ss", "efl", "eip", "cs",
"ds", "es", "fs", "gs"
};
assert( QCRIsValid(crRef) );
assert(crRef->crashedThreadIndex != kQCRNoThread);
assert( crRef->threads[crRef->crashedThreadIndex].state != NULL );
assert( crRef->threads[crRef->crashedThreadIndex].stateFlavor == x86_THREAD_STATE32 );
if (crRef->threads[crRef->crashedThreadIndex].stateSize == (x86_THREAD_STATE32_COUNT * sizeof(integer_t))) {
state = (const x86_thread_state32_t *) crRef->threads[crRef->crashedThreadIndex].state;
/*
Thread 0 crashed with X86 Thread State (32-bit):
eax: 0x00000000 ebx: 0x908156b2 ecx: 0xbfffee2c edx: 0x00000000
edi: 0x0031efa0 esi: 0x00000000 ebp: 0xbfffee98 esp: 0xbfffee70
ss: 0x0000001f efl: 0x00010282 eip: 0x908156b5 cs: 0x00000017
ds: 0x0000001f es: 0x0000001f fs: 0x00000000 gs: 0x00000037
*/
fprintf(f, "%s crashed with X86 Thread State (32-bit):\n", threadID);
#ifndef __LP64__
regBase = (const unsigned int *) &state->eax;
#else
regBase = (const unsigned int *) &state->eax;
#endif
for (reg = 0; reg < 16; reg++) {
if ((reg % 4) == 0) {
fprintf(f, " ");
}
fprintf(f, "%4s: 0x%08x", kRegNames[reg], regBase[reg]);
if ((reg % 4) == 3) {
fprintf(f, "\n");
}
}
fprintf(f, "\n");
}
}
static void PrintX86_64ThreadState(
QCrashReportRef crRef,
const char * threadID,
FILE * f
)
// Prints a x86 64-bit thread state based on the thread state of the
// crashed thread.
//
// See PrintPowerPCThreadState for comments about the overall approach.
{
const x86_thread_state64_t * state;
const unsigned long long * regBase;
int reg;
static const char * kRegNames[18] = {
"rax", "rbx", "rcx", "rdx",
"rdi", "rsi", "rbp", "rsp",
"r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
"rip", "rfl"
};
assert( QCRIsValid(crRef) );
assert(crRef->crashedThreadIndex != kQCRNoThread);
assert( crRef->threads[crRef->crashedThreadIndex].state != NULL );
assert( crRef->threads[crRef->crashedThreadIndex].stateFlavor == x86_THREAD_STATE64 );
if (crRef->threads[crRef->crashedThreadIndex].stateSize == (x86_THREAD_STATE64_COUNT * sizeof(integer_t))) {
state = (const x86_thread_state64_t *) crRef->threads[crRef->crashedThreadIndex].state;
/*
Unknown thread crashed with X86 Thread State (64-bit):
rax: 0x0000000000000000 rbx: 0x00007fff5fbffb98 rcx: 0x000000000000003a rdx: 0x0000000000000000
rdi: 0x0000000000000002 rsi: 0x00007fff5fbfedd0 rbp: 0x00007fff5fbffab0 rsp: 0x00007fff5fbff5f0
r8: 0x0000000000000e03 r9: 0x0000000000000000 r10: 0x0000000000000000 r11: 0x0000000000000246
r12: 0x00007fff5fbffb78 r13: 0x00007fff5fbffc18 r14: 0x0000000000000003 r15: 0x0000000000000000
rip: 0x000000010000f1fe rfl: 0x0000000000010202
*/
fprintf(f, "%s crashed with X86 Thread State (64-bit):\n", threadID);
#ifndef __LP64__
regBase = (const unsigned long long *) &state->rax;
#else
regBase = (const unsigned long long *) &state->rax;
#endif
for (reg = 0; reg < 18; reg++) {
fprintf(f, "%5s: 0x%08llx", kRegNames[reg], regBase[reg]);
if ((reg % 4) == 3) {
fprintf(f, "\n");
}
}
fprintf(f, "\n");
fprintf(f, "\n");
}
}
#endif
extern void QCRPrintThreadState(QCrashReportRef crRef, FILE *f)
// See comment in header.
{
int err;
char threadID[32];
assert( QCRIsValid(crRef) );
assert( f != NULL );
// Get the thread state, if we haven't already.
err = QCRGetThreadStateAtIndex(crRef, crRef->crashedThreadIndex, NULL, NULL, NULL, NULL);
if (err == 0) {
snprintf(threadID, sizeof(threadID), "Thread %zu", crRef->crashedThreadIndex);
// Dispatch to the printing routine.
//
// Each CPU type has its own thread state flavor namespace, although it's
// shared by the 32- and 64-bit variants.
switch (crRef->actualCPUType) {
#if TARGET_CPU_PPC
case CPU_TYPE_POWERPC:
#if 0 // BOINC does not support 64-bit PowerPC
case CPU_TYPE_POWERPC64:
#endif
switch (crRef->threads[crRef->crashedThreadIndex].stateFlavor) {
case PPC_THREAD_STATE:
PrintPowerPCThreadState(crRef, threadID, f);
break;
case PPC_THREAD_STATE64:
PrintPowerPC64ThreadState(crRef, threadID, f);
break;
default:
assert(false);
break;
}
break;
#endif // TARGET_CPU_PPC
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
case CPU_TYPE_X86:
case CPU_TYPE_X86_64:
switch (crRef->threads[crRef->crashedThreadIndex].stateFlavor) {
case x86_THREAD_STATE32:
PrintX86ThreadState(crRef, threadID, f);
break;
case x86_THREAD_STATE64:
PrintX86_64ThreadState(crRef, threadID, f);
break;
default:
assert(false);
break;
}
break;
#endif
default:
assert(false);
break;
}
}
}
extern void QCRPrintImages(QCrashReportRef crRef, FILE *f)
// See comment in header.
{
int err;
int width;
QMOImageRef * images;
size_t imageCount;
size_t imageIndex;
struct segment_command_64 seg;
const char * filePath;
QTMAddr imageStart;
QTMAddr imageEnd;
assert( QCRIsValid(crRef) );
assert( f != NULL );
fprintf(f, "Binary Images Description:\n");
width = QMOImageIs64Bit(crRef->executable) ? 18 : 10;
QCRGetImages(crRef, &images, &imageCount);
for (imageIndex = 0; imageIndex < imageCount; imageIndex++) {
QMOImageRef thisImage;
thisImage = images[imageIndex];
err = QMOImageGetSegmentByName(thisImage, "__TEXT", NULL, &seg);
assert(err == 0);
if (err == 0) {
filePath = QMOImageGetFilePath(thisImage);
if (filePath == NULL) {
filePath = "";
}
// Currently we just print the path to the library. CrashReporter
// has all sorts of wacky code to print the bundle ID and version
// numbers. This isn't easy to replicate in code that can only
// link with libSystem.
imageStart = seg.vmaddr + QMOImageGetSlide(thisImage);
imageEnd = imageStart + seg.vmsize - 1;
fprintf(f, "%#*llx - %#*llx %s\n", width, imageStart, width, imageEnd, filePath);
}
}
fprintf(f, "\n");
}
/////////////////////////////////////////////////////////////////
#pragma mark ***** Tool Helpers
#if 0 // Not used by BOINC
// Declare the standard C environ variable. I never understood why this
// global isn't declared in some public prototype.
extern char **environ;
static int ForkExecWithBootstrap(const char *toolArgs[], mach_port_t newBootstrap, pid_t *childPIDPtr)
// Runs a process with the specified bootstrap namespace. toolArgs is a
// NULL terminated list of argument for the tool. The first entry must be
// the path to the tool. newBootstrap is the name of a send right to the
// new bootstrap namespace. childPIDPtr must not be NULL. On entry,
// *childPIDPtr is ignored. On success, *childPIDPtr is the process ID
// of the new process. On error, *childPIDPtr is -1.
//
// IMPORTANT: This routine is not thread safe. On current systems the only
// reliable way to set the bootstrap port of a child process is for the
// process to inherit it. Thus, this routine works by setting the current
// process's bootstrap, forking the child, and then resetting the current
// process's bootstrap back to its previous value. If some other thread
// calls fork while we're doing this, it's child is going to run in the
// wrong namespace. Go team!
//
// You can solve this problem using posix_spawn, but it's not available on
// current systems.
{
int err;
kern_return_t kr;
kern_return_t junk;
mach_port_t oldBootstrap;
pid_t childPID;
assert(toolArgs != NULL);
assert(toolArgs[0] != NULL);
assert(newBootstrap != MACH_PORT_NULL);
assert( childPIDPtr != NULL);
oldBootstrap = MACH_PORT_NULL;
childPID = -1;
// Get the old bootstrap and set the new bootstrap.
kr = task_get_bootstrap_port(mach_task_self(), &oldBootstrap);
if (kr == KERN_SUCCESS) {
kr = task_set_bootstrap_port(mach_task_self(), newBootstrap);
}
// Do the standard fork/exec dance.
if (kr == KERN_SUCCESS) {
childPID = fork();
switch (childPID) {
case -1:
err = errno;
break;
case 0:
(void) execve(toolArgs[0], (char **) toolArgs, environ);
_exit(EXIT_FAILURE);
break;
default:
// parent execution continues below
err = 0;
break;
}
// In the parent, restore the bootstrap.
junk = task_set_bootstrap_port(mach_task_self(), oldBootstrap);
assert(junk == KERN_SUCCESS);
} else {
err = QTMErrnoFromMachError(kr);
}
// Clean up.
*childPIDPtr = childPID;
if (oldBootstrap != MACH_PORT_NULL) {
junk = mach_port_deallocate(mach_task_self(), oldBootstrap);
assert(junk == KERN_SUCCESS);
}
assert( (err == 0) == (*childPIDPtr != -1) );
return err;
}
#pragma mark - On task_for_pid and Task Control Ports
/*
On task_for_pid and Task Control Ports
--------------------------------------
When writing an external crash reporting tool, you really need to be able to
get at the memory, threads, and so on, of the target process. The only way
to do this on Mac OS X is to have a send right to the process's task
control port. And the best way to get that is to use a routine called
task_for_pid.
task_for_pid returns the name of a send right for the task control port for
the process with the specified process ID. This is fabulously useful for
debugging tools, like any form of external crash report. But, needless to
say, it represents somewhat of a security concern. Once you have access
to a task's control port, you can do anything you like to the task
(most notably, modify its memory). Therefore it's important that task_for_pid
be appropriately protected.
Initially (starting with Mac OS X 10.0), task_for_pid's security policy was
pretty simple. To summarise, the old policy was that task_for_pid would
succeed if:
o if the caller is running as root (EUID 0)
o otherwise if the caller is running as the same UID as the target (and the
target's EUID matches its RUID, that is, it's not a setuid binary)
Starting with the release of the Intel-based Macintosh computers, task_for_pid
security has been tightened up. The new policy is that task_for_pid suceeds:
o if the caller was running as root (EUID 0)
o if the pid is that of the caller
o if the caller is running as the same UID as the target and the target's
EUID matches its RUID, that is, it's not a setuid binary AND the caller
is in group "procmod" or "procview"
btw The long term goal of having both "procmod" and "procview" is that
task_for_pid would return a send right for the task control port only to those
processes in "procmod"; the callers in "procview" would only get a send right
to a task inspection port. This distinction is not currently implemented.
This new policy will be adopted by all systems (that is, PowerPC-based systems
as well as Intel-based systems) in a future version of Mac OS X.
This policy change is a pain for code, like this, that wants to manipulate
other processes. If you want to get the task control port for a task, your
options are much more limited:
1. run as ROOT (EUID 0) -- This is a bad idea for security reasons.
2. change the policy -- You can actually change the task_for_pid policy via
sysctl. This is also a bad idea. The policy was changed changed for a
reason, and it's not your place to subvert that change.
3. get into group "procmod" or "procview" -- This is the solution used by
Apple's tools, but it is tricky for third party developers in practise.
You have to make your executable set-group-id to one of these groups, and
that requires you to have some sort of installer to 'bless' the executable.
4. get the task to give you a send right to its task control port -- As with
any other other Mach right, you can send a send right for a task control
port between tasks as part of a Mach message.
Given the above, only option 4 is a reasonable solution. However, it does result
in a chicken and egg problem. How do you initially start communication with
the task? The solution to this is to use the Mach bootstrap service. This
service lets you register a port with a specific name, and other processes
within the same bootstrap context can look up that port using that name.
However, publishing your task control port via the bootstrap service is not
wise. Anyone could look up the port and start reading and writing your tasks
memory. Of course, this is what you want, except that you want to restrict
it to just your crash report tool.
You can solve that problem by creating a new bootstrap namespace (also known
as a subset) and registering your task control port in that namespace.
If you run your crash reporting tool in that namespace, it can access your
task control port. Moreover, only processes running in that namespace can
access it. That's the approach taken by the following code.
*/
extern int QCRExecuteCrashReportTool(const char *toolArgs[], pid_t *pidPtr, int *toolStatusPtr)
// See comment in header.
{
int err;
kern_return_t kr;
kern_return_t junk;
mach_port_t inheritedNamespace;
mach_port_t privateNamespace;
pid_t childPID;
pid_t waitResult;
int status;
assert(toolArgs != NULL);
assert(toolArgs[0] != NULL);
// pidPtr may be NULL
assert( (pidPtr == NULL) || (*pidPtr == -1) );
assert( (pidPtr == NULL) == (toolStatusPtr != NULL) );
inheritedNamespace = MACH_PORT_NULL;
privateNamespace = MACH_PORT_NULL;
// Get the old bootstarp.
kr = task_get_bootstrap_port(mach_task_self(), &inheritedNamespace);
// Create a private namespace based on it. It's this that we're going to
// give to the child.
if (kr == KERN_SUCCESS) {
kr = bootstrap_subset(inheritedNamespace, mach_task_self(), &privateNamespace);
}
// Register our task control port in the private namespace. Only someone
// with access to privateNamespace can look up our task control port. And
// we're only going to give that namespace to our child.
//
// Note that we don't have to worry about name uniqueness, because we just
// created the namespace and it doesn't have any entries. If someone in
// the parent namespace (inheritedNamespace) has registered this name,
// our registration masks it.
if (kr == KERN_SUCCESS) {
// Don't try this at home kiddies. It gives anyone that inherits this
// private namespace completely access to your process.
kr = bootstrap_register(privateNamespace, "QCrashReport", mach_task_self());
}
err = QTMErrnoFromMachError(kr);
// Run the tool with the private namespace.
if (err == 0) {
err = ForkExecWithBootstrap(toolArgs, privateNamespace, &childPID);
}
// Clean up. If we're running asynchronously, just pass the PID back to the
// caller. If we're running synchronously, wait for the child to quit.
if (err == 0) {
if (pidPtr != NULL) {
// Client is going to manage the child from here on.
*pidPtr = childPID;
} else {
// Client wants use to wait for the child to exit.
do {
waitResult = waitpid(childPID, &status, 0);
if (waitResult < 0) {
err = errno;
}
} while (err == EINTR);
if (err == 0) {
if ( WIFEXITED(status) ) {
*toolStatusPtr = WEXITSTATUS(status);
} else {
err = EINVAL;
}
}
}
}
// Clean up. Note that we can dispose of our reference to privateNamespace.
// The namespace will persist because the child has a send right for it.
// It will, however, be deactivated if we quit.
if (inheritedNamespace != MACH_PORT_NULL) {
junk = mach_port_deallocate(mach_task_self(), inheritedNamespace);
assert(junk == KERN_SUCCESS);
}
if (privateNamespace != MACH_PORT_NULL) {
junk = mach_port_deallocate(mach_task_self(), privateNamespace);
assert(junk == KERN_SUCCESS);
}
assert( (pidPtr == NULL) || ( (err == 0) == (*pidPtr != -1) ) );
return err;
}
extern int QCRGetParentTask(task_t *taskPtr)
// See comment in header.
{
int err;
kern_return_t kr;
kern_return_t junk;
mach_port_t bootstrap;
assert( taskPtr != NULL);
assert(*taskPtr == MACH_PORT_NULL);
bootstrap = MACH_PORT_NULL;
// Look up the parent's task control port by name in our bootstrap namespace.
// Easy peasy.
kr = task_get_bootstrap_port(mach_task_self(), &bootstrap);
if (kr == KERN_SUCCESS) {
kr = bootstrap_look_up(bootstrap, "QCrashReport", taskPtr);
}
err = QTMErrnoFromMachError(kr);
if (bootstrap != MACH_PORT_NULL) {
junk = mach_port_deallocate(mach_task_self(), bootstrap);
assert(junk == KERN_SUCCESS);
}
if (false) {
fprintf(stderr, "Process %ld waiting for debugger.\n", (long) getpid());
pause();
}
return err;
}
#endif // Not used by BOINC