// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2022 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 additional 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>

#undef assert
#undef __assert
#define	assert(e)	((void)0)

// 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.

//#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 0 //! 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
    // penalty for things like getting the backtrace if you actually need them.
{
    int                     err;
    kern_return_t           kr;
    kern_return_t           krJunk __attribute__((unused));
    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 fails.

            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 __attribute__((unused));
    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_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_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 succeeds:

    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 practice.
       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
