boinc/lib/mac/QTaskMemory.c

568 lines
18 KiB
C

// Berkeley Open Infrastructure for Network Computing
// http://boinc.berkeley.edu
// Copyright (C) 2005 University of California
//
// This is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// To view the GNU Lesser General Public License visit
// http://www.gnu.org/copyleft/lesser.html
// or write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/*
* QTaskMemory.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: QTaskMemory.c
Contains: Task memory access abstraction.
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: QTaskMemory.c,v $
Revision 1.2 2007/03/02 12:25:50
Fixed a problem where a routine that should be static was mistakenly extern.
Revision 1.1 2007/03/02 12:20:37
First checked in.
*/
/////////////////////////////////////////////////////////////////
// Our prototypes
#include "QTaskMemory.h"
// System includes
#include <TargetConditionals.h>
#include <AvailabilityMacros.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/errno.h>
#include <sys/sysctl.h>
#include <sys/utsname.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
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
#include <mach/mach_vm.h>
#endif
#if defined(__cplusplus)
}
#endif
/////////////////////////////////////////////////////////////////
#pragma mark ***** Mach Compatibility
// Prior to 10.3, Mac OS X did not support 64-bit address spaces. Thus, key Mach
// data structures, like vm_address_t, were 32-bits in size, and can't be changed
// for binary compatibility. So, all of these data types, and the functions that
// use them, are defined to scale with the address space: they are 32-bit for
// 32-bit clients and 64-bit for 64-bit clients.
//
// To make it possible to create 32-bit programs that operate on 64-bit address spaces,
// 10.4 introduces new Mach data structures and routines, all prefixed by mach_, that
// are fixed at 64-bits. For example, vm_address_t is supplanted by mach_vm_address_t,
// and vm_read is supplanted by mach_vm_read.
//
// Unfortunately, the routines are only available on 10.4 and later. So, we can't just
// call them directly if we want to run on 10.3. The original QCrashReport code from
// Apple DTS used weak linking to solve this. But weak linking may cause crashes if
// the code does not specifically test for the presence of weak-linked APIs on older
// versions of the OS. For this reason, BOINC uses MacOSX10.3.9.sdk for PowerPC builds
// to avoid weak linking.
//
// Finally, the old routines are not available to 64-bit clients on Intel.
//
// BOINC use this code only to analyze the process which called it, and this code
// supports 64-bit backtraces only for Intel processors, so we use the new routines
// for Intel builds and the old routines for PowerPC builds.
//
/////////////////////////////////////////////////////////////////
extern int QTMErrnoFromMachError(kern_return_t kr)
// See comment in header.
{
int err;
switch (kr) {
case KERN_SUCCESS:
err = 0;
break;
case KERN_INVALID_ADDRESS:
err = EFAULT;
break;
case KERN_PROTECTION_FAILURE:
err = EACCES;
break;
case KERN_NO_SPACE:
err = ENOMEM;
break;
case KERN_INVALID_ARGUMENT:
err = EINVAL;
break;
case KERN_FAILURE:
err = EINVAL;
break;
case KERN_NAME_EXISTS:
err = EEXIST;
break;
case KERN_INVALID_NAME:
err = EBADF;
break;
default:
if ( (((int) kr) >= 0) && (((int) kr) <= ELAST) ) {
fprintf(stderr, "QTMErrnoFromMachError: Mapping unrecognised Mach error in the errno range (%#x) to EINVAL (%d).", (unsigned int) kr, EINVAL);
err = EINVAL;
} else {
err = (int) kr;
}
break;
}
return err;
}
extern int QTMRead(task_t task, QTMAddr addrWithinTask, size_t size, void *addrLocal)
// See comment in header.
{
int err;
kern_return_t kr;
assert(task != MACH_PORT_NULL);
assert(size > 0);
assert(addrLocal != NULL);
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
mach_vm_size_t bytesRead;
kr = mach_vm_read_overwrite(task, addrWithinTask, (mach_vm_size_t) size, (mach_vm_address_t) (uintptr_t) addrLocal, &bytesRead);
#else
vm_size_t bytesRead;
kr = vm_read_overwrite(task, (vm_address_t) addrWithinTask, (vm_size_t) size, (vm_address_t) addrLocal, &bytesRead);
#endif
err = QTMErrnoFromMachError(kr);
// AFAIK mach_vm_read_overwrite will not return partial data; that is, you
// get everything (no error) or you get nothing (error). The following
// checks that assertion.
assert( (err != 0) || (bytesRead == size) );
return err;
}
extern int QTMReadAllocated(task_t task, QTMAddr addrWithinTask, size_t size, const void **bufPtr)
// See comment in header.
{
int err;
kern_return_t kr;
vm_offset_t addrLocal;
mach_msg_type_number_t bytesRead;
assert(task != MACH_PORT_NULL);
assert(size > 0);
assert( bufPtr != NULL);
assert(*bufPtr == NULL);
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
kr = mach_vm_read(task, addrWithinTask, size, &addrLocal, &bytesRead);
#else
kr = vm_read(task, addrWithinTask, size, &addrLocal, &bytesRead);
#endif
err = QTMErrnoFromMachError(kr);
// AFAIK mach_vm_read will not return partial data; that is, you get
// everything (no error) or you get nothing (error). The following
// checks that assertion.
assert( (err != 0) || (bytesRead == size) );
if (err == 0) {
*bufPtr = (const void *) (uintptr_t) addrLocal;
// Pointer truncation should never occur (because addrLocal is a
// vm_offset_t, which with the current address space) but, if it does,
// the following should catch it.
assert( ((uintptr_t) *bufPtr) == addrLocal );
}
assert( (err == 0) == (*bufPtr != NULL) );
return err;
}
extern int QTMRemap(task_t task, QTMAddr addrWithinTask, size_t size, const void **bufPtr)
// See comment in header.
{
int err;
kern_return_t kr;
vm_prot_t curProt;
vm_prot_t maxProt;
assert(task != MACH_PORT_NULL);
assert(size > 0);
assert( bufPtr != NULL);
assert(*bufPtr == NULL);
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
mach_vm_address_t addrLocal;
addrLocal = 0;
kr = mach_vm_remap(
mach_task_self(), // target_task
&addrLocal, // target_address
size, // size
0, // mask
true, // anywhere
task, // src_task
addrWithinTask, // src_address
true, // copy
&curProt, // cur_protection
&maxProt, // max_protection
VM_INHERIT_NONE
);
#else
vm_address_t addrLocal;
addrLocal = 0;
kr = vm_remap(
mach_task_self(), // target_task
&addrLocal, // target_address
size, // size
0, // mask
true, // anywhere
task, // src_task
addrWithinTask, // src_address
true, // copy
&curProt, // cur_protection
&maxProt, // max_protection
VM_INHERIT_NONE
);
#endif
err = QTMErrnoFromMachError(kr);
if (err == 0) {
*bufPtr = (const void *) (uintptr_t) addrLocal;
// Pointer truncation should never occur (because we're remapping into
// the current task) but, if it does, the following should catch it.
assert( (uintptr_t) *bufPtr == addrLocal );
}
return err;
}
extern void QTMFree(const void *buf, size_t size)
// See comment in header.
{
kern_return_t junk;
if (buf == NULL) {
assert(size > 0);
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
junk = mach_vm_deallocate(mach_task_self(), (uintptr_t) buf, (mach_vm_size_t) size);
#else
junk = vm_deallocate(mach_task_self(), (vm_address_t) buf, (vm_size_t) size);
#endif
assert(junk == KERN_SUCCESS);
}
}
/////////////////////////////////////////////////////////////////
#pragma mark ***** Extra Stuff
extern int QTMGetDarwinOSRelease(int *majorPtr, int *minorPtr, int *bugPtr)
// See comment in header.
{
int err;
struct utsname names;
int scanResult;
static int sMajor;
static int sMinor;
static int sBug;
// If we haven't already got the OS release, get it now.
err = 0;
if (sMajor == 0) {
err = uname(&names);
if (err < 0) {
err = errno;
}
if (err == 0) {
// Parse the three dot separated components of the release string.
// If we don't get exactly three, we've confused and we error.
scanResult = sscanf(names.release, "%d.%d.%d", &sMajor, &sMinor, &sBug);
if (scanResult != 3) {
err = EINVAL;
}
}
}
// Return it to our caller.
if (err == 0) {
if (majorPtr != NULL) {
*majorPtr = sMajor;
}
if (minorPtr != NULL) {
*minorPtr = sMinor;
}
if (bugPtr != NULL) {
*bugPtr = sBug;
}
}
return err;
}
#if 0 // Not used by BOINC
extern bool QTMTaskIs64Bit(task_t task)
// See comments in header.
//
// This implementation uses sysctl to get the process information structure
// and then checks the P_LP64 flag. This is less than ideal because the
// sysctl interface for getting process information is not exactly well-liked
// by kernel engineering. OTOH, the alternatives are, on average, less nice,
// so I've gone with this.
{
kern_return_t kr;
bool result;
int major;
pid_t pid;
int err;
int mib[4];
struct kinfo_proc info;
size_t size;
assert(task != MACH_PORT_NULL);
// We default to assuming that the process is 32-bit.
result = false;
// The bit denoted by P_LP64 the flag was used for a different purpose
// (P_INMEM) prior to 10.4. So we only look at the bit on 10.4 and later.
// Earlier systems didn't support 64-bit processes, so the default value
// of false is correct.
err = QTMGetDarwinOSRelease(&major, NULL, NULL);
assert(err == 0);
if ( (err == 0) && (major >= kQTMDarwinOSMajorForMacOSX104) ) {
kr = pid_for_task(task, &pid);
assert(kr == KERN_SUCCESS);
err = QTMErrnoFromMachError(kr);
if (err == 0) {
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = pid;
// Call sysctl.
size = sizeof(info);
err = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
// We're 64-bit if the P_LP64 flag is set.
if (err == 0) {
result = (info.kp_proc.p_flag & P_LP64) != 0;
}
}
}
// We really shouldn't get any error from the above. If we do, assert in
// the debug build and just return the default value (32-bit) in the
// production build.
return result;
}
#endif
static int sysctlbyname_with_pid(
const char * name,
pid_t pid,
void * oldp,
size_t * oldlenp,
void * newp,
size_t newlen
)
// Stolen directly from the "Universal Binaries Programming Guidelines"
// document.
{
if (pid == 0) {
if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) {
// fprintf(stderr, "sysctlbyname_with_pid(0): sysctlbyname failed: %s\n", strerror(errno));
return -1;
}
} else {
int mib[CTL_MAXNAME];
size_t len = CTL_MAXNAME;
if (sysctlnametomib(name, mib, &len) == -1) {
// fprintf(stderr, "sysctlbyname_with_pid: sysctlnametomib failed: %s\n", strerror(errno));
return -1;
}
mib[len] = pid;
len++;
if (sysctl(mib, len, oldp, oldlenp, newp, newlen) == -1) {
// fprintf(stderr, "sysctlbyname_with_pid: sysctl failed: %s\n", strerror(errno));
return -1;
}
}
return 0;
}
static int is_pid_native(pid_t pid)
// Stolen directly from the "Universal Binaries Programming Guidelines"
// document.
{
int ret = 0;
size_t sz = sizeof(ret);
if (sysctlbyname_with_pid("sysctl.proc_native", pid,
&ret, &sz, NULL, 0) == -1) {
if (errno == ENOENT) {
return 1;
}
// fprintf(stderr, "is_pid_native: sysctlbyname_with_pid failed: %s\n", strerror(errno));
return -1;
}
return ret;
}
extern bool QTMTaskIsNative(task_t task)
// See comments in header.
{
kern_return_t kr;
pid_t pid;
assert(task != MACH_PORT_NULL);
pid = -1;
kr = pid_for_task(task, &pid);
assert(kr == KERN_SUCCESS);
// is_pid_native returns 1 (native), 0 (non-native), or -1 (error). I want
// to treat everything except 0 as native. That is, an error is consider
// to be native. I do this because native to make native the default choice.
return (is_pid_native(pid) != 0);
}