mirror of https://github.com/BOINC/boinc.git
2413 lines
78 KiB
C
2413 lines
78 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
|
|
/*
|
|
* QMachOImage.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: QMachOImage.c
|
|
|
|
Contains: Mach-O image access.
|
|
|
|
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: QMachOImage.c,v $
|
|
Revision 1.1 2007/03/02 12:20:14
|
|
First checked in.
|
|
|
|
|
|
*/
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
// Our prototypes
|
|
|
|
#include "QMachOImage.h"
|
|
|
|
// Basic system interfaces
|
|
|
|
#include <TargetConditionals.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
// Mach-O interfaces
|
|
|
|
#include <mach-o/arch.h>
|
|
#include <mach-o/dyld.h>
|
|
#include <mach-o/fat.h>
|
|
#include <mach-o/loader.h>
|
|
#include <mach-o/nlist.h>
|
|
#include <mach-o/stab.h>
|
|
|
|
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
|
|
#include <mach/mach_vm.h>
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Compile-Time Assert Information
|
|
|
|
// At various places this code makes assumptions about the layout of various
|
|
// structures. Where I noticed these assumptions, I've used a compile-time
|
|
// assert to check them. Don't ask me how this works or I'll start to whimper.
|
|
|
|
#define compile_time_assert__(name, line) name ## line
|
|
#define compile_time_assert_(mustBeTrue, name, line) \
|
|
struct compile_time_assert__(name, line) { unsigned int msg: (mustBeTrue); }
|
|
#define compile_time_assert(mustBeTrue) \
|
|
compile_time_assert_(mustBeTrue, CompileTimeAssert, __LINE__)
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Core Code
|
|
|
|
// QMOSegment records information about a segment within an image. This is helpful for
|
|
// a bunch of reasons.
|
|
//
|
|
// o We store an array of these structures, which makes it easy to access the segments
|
|
// by simple array indexing.
|
|
//
|
|
// o The structure always store information about the segment as 64-bits, which means
|
|
// that, as we access the information, we don't need to worry whether it's 32- or 64-bit.
|
|
//
|
|
// o The structure always store information about the segment in native byte endian format.
|
|
//
|
|
// o It records whether we have the segment mapped, and the information required to
|
|
// unmap it
|
|
|
|
struct QMOSegment {
|
|
const struct load_command * cmd; // pointer to original load command
|
|
struct segment_command_64 seg; // copy of segment information, promoted to 64-bits if necessary
|
|
const char * segBase; // base address of mapped segment (or NULL if not mapped)
|
|
void * segMapRefCon; // place for image type to store information about the mapping
|
|
};
|
|
typedef struct QMOSegment QMOSegment;
|
|
|
|
// Likewise, we keep an array of information about all the sections in the image.
|
|
// This is primarily to support the various section access APIs.
|
|
|
|
struct QMOSection {
|
|
const QMOSegment * seg; // parent segment
|
|
struct section_64 sect; // copy of section information, promoted to 64-bits if necessary
|
|
};
|
|
typedef struct QMOSection QMOSection;
|
|
|
|
typedef struct QMOImage QMOImage;
|
|
// forward declaration
|
|
|
|
// Image Type Callbacks
|
|
// --------------------
|
|
// There are various types of QMOImage (local, from a remote task, from disk) that have
|
|
// lots in common. However, in some cases it's necessary to do things diffently for each
|
|
// type of image to do different things. In that case, the common code calls the image
|
|
// type specific code to do the work.
|
|
|
|
typedef int (*QMOMapRangeProc)(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void ** basePtr,
|
|
void ** mapRefConPtr
|
|
);
|
|
// Map a chunk of an image into the local address space. qmoImage is a reference
|
|
// to the image itself. offset and length define the chunk of the image. The
|
|
// callback can store a value in *mapRefConPtr to assist in its QMOUnmapRangeProc
|
|
// implementation.
|
|
//
|
|
// On entry, qmoImage must not be NULL
|
|
// On entry, offset is the offset, in bytes, of the chunk to map, relative to the
|
|
// machHeaderOffset supplied when the image was created
|
|
// On entry, length is the number of bytes to map; must be greater than 0
|
|
// On entry, basePtr must not be NULL
|
|
// On entry, *basePtr must be NULL
|
|
// On entry, mapRefConPtr must not be NULL
|
|
// Returns an errno-style error code
|
|
// On success, *basePtr must be the (non-NULL) address of the mapped chunk
|
|
|
|
typedef void (*QMOUnmapRangeProc)(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void * base,
|
|
void * mapRefCon
|
|
);
|
|
// Unmap a chunk of an image from the local address space. qmoImage is a reference
|
|
// to the image itself. offset and length define the chunk of the image; these
|
|
// will be exactly equal to one of the chunks previously mapped (that is, we
|
|
// won't map [100..200) and then unmap [100..150)). base is the address of the
|
|
// mapping, as returned by a previous QMOMapRangeProc call. mapRefCon is the
|
|
// per-mapping data, as returned by a previous QMOMapRangeProc call.
|
|
//
|
|
// On entry, qmoImage must not be NULL
|
|
// On entry, offset is the offset, in bytes, of the chunk to unmap, relative to the
|
|
// machHeaderOffset supplied when the image was created
|
|
// On entry, length is the number of bytes to unmap; must be greater than 0
|
|
// On entry, base must not be NULL
|
|
|
|
typedef int (*QMOCreateProc)(QMOImage *qmoImage, void *refCon);
|
|
// Called to tell the image type specific code to initialise itself.
|
|
// The purpose of this callback is to commit the refCon to the
|
|
// image. Regardless of whether this routine succeeds or not, it
|
|
// must leave the refCon in a state that can be correctly handled
|
|
// by the destroy callback (if any).
|
|
//
|
|
// This callback is optional. If it's not present, the core code
|
|
// commits the refCon by simple assignment.
|
|
//
|
|
// On entry, qmoImage must not be NULL
|
|
|
|
typedef void (*QMODestroyProc)(QMOImage *qmoImage);
|
|
// Destroy the type-specific data associated with the image. If you have
|
|
// a create callback, you are guaranteed that it will be called before
|
|
// this callback (although it might have failed).
|
|
//
|
|
// This callback is optional. If it's not present, the core code does
|
|
// nothing to destroy the refCon.
|
|
//
|
|
// On entry, qmoImage must not be NULL
|
|
|
|
// QMOImage represents a Mach-O image. It's the backing for the exported QMOImageRef
|
|
// type.
|
|
|
|
struct QMOImage {
|
|
const char * filePath; // can be NULL
|
|
|
|
// Mach-O header information
|
|
|
|
QTMAddr machHeaderOffset; // offset within 'container'
|
|
const struct mach_header * machHeader; // address of Mach-O header mapped into local process; includes all load commands
|
|
void * machHeaderRefCon; // refCon for unmapping
|
|
size_t machHeaderSize; // size of Mach-O header, including load commands
|
|
|
|
// Cached information
|
|
|
|
bool imageIDCached; // true iff imageID is valid
|
|
const char * imageID; // NULL is a valid value
|
|
|
|
// General information
|
|
|
|
QTMAddr slide; // amount to add to segment vmaddr to get actual addr in memory
|
|
bool prepared; // false if coming from file, true if coming from memory
|
|
bool is64Bit; // true if 64-bit Mach-O
|
|
bool byteSwapped; // true if byte swapped relative to current process
|
|
|
|
uint32_t segCount; // number of segments
|
|
QMOSegment * segments; // per-segment information
|
|
uint32_t sectCount; // number of sections
|
|
QMOSection * sections; // per-section information
|
|
|
|
QMOMapRangeProc mapRange; // type-specific mapping routine
|
|
QMOUnmapRangeProc unmapRange; // type-specific unmapping routine
|
|
QMODestroyProc destroy; // type-specific destroy routine (optional)
|
|
|
|
void * refCon; // storage for type-specific information
|
|
};
|
|
|
|
#if ! defined(NDEBUG)
|
|
|
|
static bool QMOImageIsValidLimited(QMOImageRef qmoImage)
|
|
// A limited form of QMOImageIsValid that's callback while qmoImage is still
|
|
// being constructed.
|
|
{
|
|
return (qmoImage != NULL)
|
|
&& (qmoImage->mapRange != NULL)
|
|
&& (qmoImage->unmapRange != NULL);
|
|
}
|
|
|
|
static bool QMOImageIsValid(QMOImageRef qmoImage)
|
|
// Returns true if qmoImage is valid.
|
|
{
|
|
return (qmoImage != NULL)
|
|
&& (qmoImage->machHeader != NULL)
|
|
&& (qmoImage->machHeaderSize >= sizeof(struct mach_header))
|
|
&& (qmoImage->segCount > 0)
|
|
&& (qmoImage->segments != NULL)
|
|
&& (qmoImage->segments[0].cmd != NULL)
|
|
&& (qmoImage->sectCount > 0)
|
|
&& (qmoImage->sections != NULL)
|
|
&& (qmoImage->sections[0].seg != NULL)
|
|
&& (qmoImage->mapRange != NULL)
|
|
&& (qmoImage->unmapRange != NULL);
|
|
}
|
|
|
|
#endif
|
|
|
|
typedef bool (*LoadCommandCompareProc)(QMOImageRef qmoImage, const struct load_command *cmd, void *compareRefCon);
|
|
// Callback for FindLoadCommand.
|
|
//
|
|
// IMPORTANT:
|
|
// The contents of the load command pointed to be cmd may be byte swapped.
|
|
//
|
|
// On entry, qmoImage will not be NULL
|
|
// On entry, cmd will not be NULL
|
|
// Return true if this load command is the one you're looking for
|
|
|
|
static const struct load_command * FindLoadCommand(
|
|
QMOImageRef qmoImage,
|
|
LoadCommandCompareProc compareProc,
|
|
void * compareRefCon
|
|
)
|
|
// Finds a load command within a Mach-O image. Iterates through the load commands,
|
|
// from first to last, calling compareProc on each one. If compareProc returns
|
|
// true, that load command becomes the function result. If compareProc never returns
|
|
// true, the function result is NULL.
|
|
{
|
|
uint32_t cmdIndex;
|
|
uint32_t cmdCount;
|
|
const struct load_command * firstCommand;
|
|
const struct load_command * thisCommand;
|
|
const char * commandLimit;
|
|
const struct load_command * result;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(qmoImage->machHeader != NULL);
|
|
assert(compareProc != NULL);
|
|
|
|
// Get the command count.
|
|
|
|
cmdCount = QMOImageToLocalUInt32(qmoImage, qmoImage->machHeader->ncmds);
|
|
|
|
// Get the start of the first command.
|
|
|
|
if (qmoImage->is64Bit) {
|
|
firstCommand = (const struct load_command *) (((const char *) qmoImage->machHeader) + sizeof(struct mach_header_64));
|
|
} else {
|
|
firstCommand = (const struct load_command *) (((const char *) qmoImage->machHeader) + sizeof(struct mach_header));
|
|
}
|
|
|
|
// Iterate through the commands until compareProc returns true.
|
|
|
|
result = NULL;
|
|
cmdIndex = 0;
|
|
commandLimit = ((const char *) qmoImage->machHeader) + qmoImage->machHeaderSize;
|
|
thisCommand = firstCommand;
|
|
while ( (result == NULL) && (cmdIndex < cmdCount) ) {
|
|
uint32_t thisCommandSize;
|
|
|
|
thisCommandSize = QMOImageToLocalUInt32(qmoImage, thisCommand->cmdsize);
|
|
|
|
assert( ((const char *) thisCommand) >= ((const char *) firstCommand) );
|
|
assert( ((const char *) thisCommand) < commandLimit );
|
|
assert( (((const char *) thisCommand) + thisCommandSize) <= commandLimit );
|
|
|
|
if ( compareProc(qmoImage, thisCommand, compareRefCon) ) {
|
|
result = thisCommand;
|
|
} else {
|
|
thisCommand = (const struct load_command *) (((const char *) thisCommand) + QMOImageToLocalUInt32(qmoImage, thisCommand->cmdsize));
|
|
cmdIndex += 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int MapMachHeader(QMOImageRef qmoImage)
|
|
// Map an image's Mach-O header (including its load commands) into memory,
|
|
// recording that mapping in the fields in qmoImage.
|
|
{
|
|
int err;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(qmoImage->machHeader == NULL); // that is, we haven't already mapped it
|
|
|
|
compile_time_assert( offsetof(struct mach_header, magic) == offsetof(struct mach_header_64, magic) );
|
|
compile_time_assert( offsetof(struct mach_header, sizeofcmds) == offsetof(struct mach_header_64, sizeofcmds) );
|
|
|
|
// Temporarily map the mach_header itself, so we can figure out what sort of
|
|
// header we're dealing with and get the size of the load commands.
|
|
|
|
{
|
|
size_t headerSize;
|
|
const struct mach_header * headerPtr;
|
|
void * headerRefCon;
|
|
|
|
headerPtr = NULL;
|
|
|
|
err = qmoImage->mapRange(
|
|
qmoImage,
|
|
qmoImage->machHeaderOffset,
|
|
sizeof(struct mach_header),
|
|
(const void **) &headerPtr,
|
|
&headerRefCon
|
|
);
|
|
if (err == 0) {
|
|
|
|
// Test the magic to check for Mach-O validity, size and byte order.
|
|
|
|
switch (headerPtr->magic) {
|
|
case MH_MAGIC_64:
|
|
case MH_CIGAM_64:
|
|
case MH_MAGIC:
|
|
case MH_CIGAM:
|
|
qmoImage->is64Bit = ( (headerPtr->magic == MH_MAGIC_64) || (headerPtr->magic == MH_CIGAM_64) );
|
|
qmoImage->byteSwapped = ( (headerPtr->magic == MH_CIGAM) || (headerPtr->magic == MH_CIGAM_64) );
|
|
if (qmoImage->is64Bit) {
|
|
headerSize = sizeof(struct mach_header_64);
|
|
} else {
|
|
headerSize = sizeof(struct mach_header);
|
|
}
|
|
|
|
// Now that we have set up qmoImage->byteSwapped, we can call QMOImageToLocalUInt32.
|
|
|
|
qmoImage->machHeaderSize = headerSize + QMOImageToLocalUInt32(qmoImage, headerPtr->sizeofcmds);
|
|
break;
|
|
default:
|
|
err = EINVAL;
|
|
break;
|
|
}
|
|
|
|
// Undo our temporary mapping.
|
|
|
|
qmoImage->unmapRange(qmoImage, qmoImage->machHeaderOffset, sizeof(struct mach_header), headerPtr, headerRefCon);
|
|
}
|
|
}
|
|
|
|
// Map the entire header permanently.
|
|
|
|
if (err == 0) {
|
|
err = qmoImage->mapRange(
|
|
qmoImage,
|
|
qmoImage->machHeaderOffset,
|
|
qmoImage->machHeaderSize,
|
|
(const void **) &qmoImage->machHeader,
|
|
&qmoImage->machHeaderRefCon
|
|
);
|
|
}
|
|
|
|
assert( (err == 0) == (qmoImage->machHeader != NULL) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool CountSegmentsAndSections(QMOImageRef qmoImage, const struct load_command *cmd, void *compareRefCon)
|
|
// A LoadCommandCompareProc callback used to count the number of segments
|
|
// and sections in the image. For each segment load command, increment the
|
|
// image's segment count by one and then extract the nsect field of the segment
|
|
// and increment the image's section count by that. Always return false so
|
|
// we iterate all load commands.
|
|
//
|
|
// See LoadCommandCompareProc for a description of the other parameters.
|
|
{
|
|
uint32_t cmdID;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(cmd != NULL);
|
|
assert(compareRefCon == NULL);
|
|
|
|
cmdID = QMOImageToLocalUInt32(qmoImage, cmd->cmd);
|
|
if ( (cmdID == LC_SEGMENT) || (cmdID == LC_SEGMENT_64) ) {
|
|
qmoImage->segCount += 1;
|
|
if (cmdID == LC_SEGMENT_64) {
|
|
qmoImage->sectCount += QMOImageToLocalUInt32(qmoImage, ((const struct segment_command_64 *) cmd)->nsects);
|
|
} else {
|
|
qmoImage->sectCount += QMOImageToLocalUInt32(qmoImage, ((const struct segment_command *) cmd)->nsects);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool InitSegment(QMOImageRef qmoImage, const struct load_command *cmd, void *compareRefCon)
|
|
// A LoadCommandCompareProc callback used to initialise the segments array.
|
|
// For each segment load command, set up the corresponding element of
|
|
// qmoImage->segments and increment the counter pointed to be compareRefCon.
|
|
// Always return false so we iterate all load commands.
|
|
//
|
|
// See LoadCommandCompareProc for a description of the other parameters.
|
|
{
|
|
uint32_t * segIndexPtr;
|
|
uint32_t cmdID;
|
|
struct segment_command_64 * outputSegCmd;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(cmd != NULL);
|
|
assert(compareRefCon != NULL);
|
|
assert(qmoImage->segments != NULL); // that is, we've already allocated the segment array
|
|
|
|
cmdID = QMOImageToLocalUInt32(qmoImage, cmd->cmd);
|
|
if ( (cmdID == LC_SEGMENT) || (cmdID == LC_SEGMENT_64) ) {
|
|
segIndexPtr = (uint32_t *) compareRefCon;
|
|
|
|
// Allocate a segment array element.
|
|
|
|
assert(*segIndexPtr < qmoImage->segCount); // segCount was set up by CountSegmentsAndSections, so we should never run out of elements
|
|
qmoImage->segments[*segIndexPtr].cmd = cmd;
|
|
outputSegCmd = &qmoImage->segments[*segIndexPtr].seg;
|
|
*segIndexPtr += 1;
|
|
|
|
// Convert the incoming segment command into the canonical
|
|
// segment_command_64 that we use internally. For all segments, this
|
|
// involves possible byte swapping. For 32-bit segments, we also have
|
|
// to promote 32-bit values to 64 bits.
|
|
|
|
if (cmdID == LC_SEGMENT_64) {
|
|
const struct segment_command_64 * segCmd64;
|
|
|
|
segCmd64 = (const struct segment_command_64 *) cmd;
|
|
|
|
outputSegCmd->cmd = QMOImageToLocalUInt32(qmoImage, segCmd64->cmd);
|
|
outputSegCmd->cmdsize = QMOImageToLocalUInt32(qmoImage, segCmd64->cmdsize);
|
|
memcpy(outputSegCmd->segname, segCmd64->segname, sizeof(outputSegCmd->segname));
|
|
outputSegCmd->vmaddr = QMOImageToLocalUInt64(qmoImage, segCmd64->vmaddr);
|
|
outputSegCmd->vmsize = QMOImageToLocalUInt64(qmoImage, segCmd64->vmsize);
|
|
outputSegCmd->fileoff = QMOImageToLocalUInt64(qmoImage, segCmd64->fileoff);
|
|
outputSegCmd->filesize = QMOImageToLocalUInt64(qmoImage, segCmd64->filesize);
|
|
outputSegCmd->maxprot = QMOImageToLocalUInt32(qmoImage, segCmd64->maxprot);
|
|
outputSegCmd->initprot = QMOImageToLocalUInt32(qmoImage, segCmd64->initprot);
|
|
outputSegCmd->nsects = QMOImageToLocalUInt32(qmoImage, segCmd64->nsects);
|
|
outputSegCmd->flags = QMOImageToLocalUInt32(qmoImage, segCmd64->flags);
|
|
} else {
|
|
const struct segment_command * segCmd;
|
|
|
|
segCmd = (const struct segment_command *) cmd;
|
|
|
|
outputSegCmd->cmd = QMOImageToLocalUInt32(qmoImage, segCmd->cmd);
|
|
outputSegCmd->cmdsize = QMOImageToLocalUInt32(qmoImage, segCmd->cmdsize);
|
|
memcpy(outputSegCmd->segname, segCmd->segname, sizeof(outputSegCmd->segname));
|
|
outputSegCmd->vmaddr = QMOImageToLocalUInt32(qmoImage, segCmd->vmaddr);
|
|
outputSegCmd->vmsize = QMOImageToLocalUInt32(qmoImage, segCmd->vmsize);
|
|
outputSegCmd->fileoff = QMOImageToLocalUInt32(qmoImage, segCmd->fileoff);
|
|
outputSegCmd->filesize = QMOImageToLocalUInt32(qmoImage, segCmd->filesize);
|
|
outputSegCmd->maxprot = QMOImageToLocalUInt32(qmoImage, segCmd->maxprot);
|
|
outputSegCmd->initprot = QMOImageToLocalUInt32(qmoImage, segCmd->initprot);
|
|
outputSegCmd->nsects = QMOImageToLocalUInt32(qmoImage, segCmd->nsects);
|
|
outputSegCmd->flags = QMOImageToLocalUInt32(qmoImage, segCmd->flags);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void InitSegmentSections(QMOImageRef qmoImage, uint32_t segIndex, uint32_t *sectIndexPtr)
|
|
// Called to initialise the sections array. segIndex indicates that segment
|
|
// whose sections we should parse. On entry, *sectIndexPtr is an index
|
|
// into the sections array of the place where we should start placing
|
|
// section information. On return, we've update *sectIndexPtr for every
|
|
// section that we filled in.
|
|
{
|
|
uint32_t sectCount;
|
|
uint32_t sectIndex;
|
|
const struct section_64 * sectArray64;
|
|
const struct section * sectArray;
|
|
bool is64;
|
|
|
|
// The section information is stored in an array with fixed size elements.
|
|
// However, both the base of the array and size of the elements varies for
|
|
// 32- and 64-bit segments.
|
|
|
|
is64 = (qmoImage->segments[segIndex].seg.cmd == LC_SEGMENT_64);
|
|
if (is64) {
|
|
sectArray64 = (const struct section_64 *) (((const char *) qmoImage->segments[segIndex].cmd) + sizeof(struct segment_command_64));
|
|
sectArray = NULL;
|
|
} else {
|
|
sectArray64 = NULL;
|
|
sectArray = (const struct section *) (((const char *) qmoImage->segments[segIndex].cmd) + sizeof(struct segment_command ));
|
|
}
|
|
|
|
// Iterate the sections in the segment.
|
|
|
|
sectCount = qmoImage->segments[segIndex].seg.nsects;
|
|
for (sectIndex = 0; sectIndex < sectCount; sectIndex++) {
|
|
struct section_64 * outputSect;
|
|
|
|
// Set up the segment pointer for this section.
|
|
|
|
qmoImage->sections[*sectIndexPtr + sectIndex].seg = &qmoImage->segments[segIndex];
|
|
|
|
// Convert the incoming section structure into the canonical
|
|
// section_64 structure that we use internally. For all sections, this
|
|
// involves possible byte swapping. For sections in 32-bit segments, we
|
|
// also have to promote 32-bit values to 64 bits.
|
|
|
|
assert( (*sectIndexPtr + sectIndex) < qmoImage->sectCount );
|
|
outputSect = &qmoImage->sections[*sectIndexPtr + sectIndex].sect;
|
|
if (is64) {
|
|
const struct section_64 * sect64;
|
|
|
|
sect64 = §Array64[sectIndex];
|
|
|
|
memcpy(outputSect->sectname, sect64->sectname, sizeof(outputSect->sectname));
|
|
memcpy(outputSect->segname, sect64->segname, sizeof(outputSect->segname));
|
|
outputSect->addr = QMOImageToLocalUInt64(qmoImage, sect64->addr);
|
|
outputSect->size = QMOImageToLocalUInt64(qmoImage, sect64->size);
|
|
outputSect->offset = QMOImageToLocalUInt32(qmoImage, sect64->offset);
|
|
outputSect->align = QMOImageToLocalUInt32(qmoImage, sect64->align);
|
|
outputSect->reloff = QMOImageToLocalUInt32(qmoImage, sect64->reloff);
|
|
outputSect->nreloc = QMOImageToLocalUInt32(qmoImage, sect64->nreloc);
|
|
outputSect->flags = QMOImageToLocalUInt32(qmoImage, sect64->flags);
|
|
outputSect->reserved1 = QMOImageToLocalUInt32(qmoImage, sect64->reserved1);
|
|
outputSect->reserved2 = QMOImageToLocalUInt32(qmoImage, sect64->reserved2);
|
|
outputSect->reserved3 = QMOImageToLocalUInt32(qmoImage, sect64->reserved3);
|
|
} else {
|
|
const struct section * sect;
|
|
|
|
sect = §Array[sectIndex];
|
|
|
|
memcpy(outputSect->sectname, sect->sectname, sizeof(outputSect->sectname));
|
|
memcpy(outputSect->segname, sect->segname, sizeof(outputSect->segname));
|
|
outputSect->addr = QMOImageToLocalUInt32(qmoImage, sect->addr);
|
|
outputSect->size = QMOImageToLocalUInt32(qmoImage, sect->size);
|
|
outputSect->offset = QMOImageToLocalUInt32(qmoImage, sect->offset);
|
|
outputSect->align = QMOImageToLocalUInt32(qmoImage, sect->align);
|
|
outputSect->reloff = QMOImageToLocalUInt32(qmoImage, sect->reloff);
|
|
outputSect->nreloc = QMOImageToLocalUInt32(qmoImage, sect->nreloc);
|
|
outputSect->flags = QMOImageToLocalUInt32(qmoImage, sect->flags);
|
|
outputSect->reserved1 = QMOImageToLocalUInt32(qmoImage, sect->reserved1);
|
|
outputSect->reserved2 = QMOImageToLocalUInt32(qmoImage, sect->reserved2);
|
|
outputSect->reserved3 = 0;
|
|
}
|
|
}
|
|
|
|
// Increment *sectIndexPtr for the number of sections that we added.
|
|
|
|
*sectIndexPtr += sectCount;
|
|
}
|
|
|
|
static int GetSegmentIndexByName(QMOImageRef qmoImage, const char *segName, uint32_t *segIndexPtr)
|
|
// Search the segments array for the first segment with the specified
|
|
// name. This is the guts of QMOImageGetSegmentByName.
|
|
//
|
|
// *** Should merge this into QMOImageGetSegmentByName.
|
|
{
|
|
int err;
|
|
bool found;
|
|
uint32_t segIndex;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(segName != NULL);
|
|
assert(segIndexPtr != NULL);
|
|
|
|
found = false;
|
|
segIndex = 0;
|
|
while ( ! found && segIndex < qmoImage->segCount ) {
|
|
found = ( strcmp(qmoImage->segments[segIndex].seg.segname, segName) == 0 );
|
|
if ( ! found ) {
|
|
segIndex += 1;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
*segIndexPtr = segIndex;
|
|
err = 0;
|
|
} else {
|
|
err = ESRCH;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int CalculateSlide(QMOImageRef qmoImage)
|
|
// Calculate the slide associated with an image. In some cases, we can get
|
|
// the slide (for example, for a local image, you can get the slide using
|
|
// _dyld_get_image_vmaddr_slide), but in other cases you always have to
|
|
// calculate it (for example, when using new-style remote image access).
|
|
// So, rather than messing around with conditional code, I always just
|
|
// calculate it myself.
|
|
{
|
|
int err;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
if ( ! qmoImage->prepared ) {
|
|
// For file-based images, we assume a slide of 0.
|
|
|
|
assert(qmoImage->slide == 0);
|
|
err = 0;
|
|
} else {
|
|
uint32_t textSegIndex;
|
|
struct segment_command_64 textSeg;
|
|
|
|
// For in-memory images, we have to do the maths. We find the __TEXT
|
|
// segment, find its virtual address (the address it wanted to load),
|
|
// and subtract that away from the start of the image (the address
|
|
// that the __TEXT segment ended up loading). This is slightly bogus
|
|
// because it's possible to construct a Mach-O image where the
|
|
// header isn't embedded in the __TEXT segment. But, in reality, this
|
|
// doesn't happen (and, if it did, other tools, like vmutils) would also
|
|
// fail).
|
|
|
|
err = GetSegmentIndexByName(qmoImage, "__TEXT", &textSegIndex);
|
|
if (err == 0) {
|
|
err = QMOImageGetSegmentByIndex(qmoImage, textSegIndex, &textSeg);
|
|
}
|
|
if (err == 0) {
|
|
qmoImage->slide = qmoImage->machHeaderOffset - textSeg.vmaddr;
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int QMOImageCreate(
|
|
QTMAddr machHeaderOffset,
|
|
const char * filePath,
|
|
bool prepared,
|
|
QMOMapRangeProc mapRange,
|
|
QMOUnmapRangeProc unmapRange,
|
|
QMOCreateProc create,
|
|
QMODestroyProc destroy,
|
|
void * refCon,
|
|
QMOImageRef * qmoImagePtr
|
|
)
|
|
// Common code use by all types of images to create an image object.
|
|
//
|
|
// mapRange, unmapRange, create and destroy are callbacks used to implement
|
|
// type-specific behaviour. See the description of their corresponding
|
|
// function pointer definition for details.
|
|
//
|
|
// refCon is data storage for the type-specific code. For example, for
|
|
// a file-based image, it is used to hold the file descriptor of the mapped
|
|
// file.
|
|
//
|
|
// filePath is the Mach-O backing store file for the image. It may be NULL.
|
|
//
|
|
// prepared indicates whether the image is in memory, having been prepared
|
|
// by dyld, or is coming from a Mach-O file. In the first case, the image
|
|
// operates using virtual addresses. In the second case, the image operates
|
|
// using file offsets.
|
|
//
|
|
// machHeaderOffset is the offset, within the container defined by the
|
|
// type-specific code, of the Mach-O header. Specifically:
|
|
//
|
|
// o for a file-based image (unprepared), this is the offset within the file
|
|
// o for a memory-based image (prepared), this is the virtual address of the
|
|
// mach_header
|
|
//
|
|
// When calling the mapRange and unmapRange callbacks, the core code assumes that:
|
|
//
|
|
// o the Mach-O header is available at machHeaderOffset
|
|
// o for an unprepared image, it adds machHeaderOffset to file-relative values to
|
|
// get the actual file offset of a segment
|
|
// o for prepared images, it ignores machHeaderOffset and accesses the segment
|
|
// via its virtual address
|
|
{
|
|
int err;
|
|
QMOImageRef qmoImage;
|
|
uint32_t segIndex;
|
|
uint32_t sectIndex;
|
|
|
|
// machHeaderOffset could be zero
|
|
// filePath may be NULL
|
|
assert(mapRange != NULL);
|
|
assert(unmapRange != NULL);
|
|
// create may be NULL
|
|
// destroy may be NULL
|
|
assert( qmoImagePtr != NULL);
|
|
assert(*qmoImagePtr == NULL);
|
|
|
|
// Allocate the memory for the image object, and fill out out the basic fields.
|
|
// This involves mapping in the Mach-O header to figure out the width (32- or
|
|
// 64-bit) and byte order of the image.
|
|
|
|
err = 0;
|
|
qmoImage = calloc(1, sizeof(*qmoImage));
|
|
if (qmoImage == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
if (err == 0) {
|
|
qmoImage->machHeaderOffset = machHeaderOffset;
|
|
qmoImage->prepared = prepared;
|
|
qmoImage->mapRange = mapRange;
|
|
qmoImage->unmapRange = unmapRange;
|
|
qmoImage->destroy = destroy;
|
|
|
|
// It is vital that we call the create callback (if any) before any
|
|
// other failure (other than the calloc). If there was a failure
|
|
// point before this, we could end up calling the destroy callback
|
|
// before calling the create callback. Thatd woul be bad for image
|
|
// types where a NULL refcon isn't appropriate as a nil value.
|
|
//
|
|
// An example of this is the file image type. The in this case, the
|
|
// nil value for the refcon is -1. The create callback is expected
|
|
// to set up the refCon correctly; if it fails, it must set it to
|
|
// an appropriate nil value.
|
|
|
|
if (create == NULL) {
|
|
qmoImage->refCon = refCon;
|
|
} else {
|
|
err = create(qmoImage, refCon);
|
|
}
|
|
}
|
|
if ( (err == 0) && (filePath != NULL) ) {
|
|
qmoImage->filePath = strdup(filePath);
|
|
if (qmoImage->filePath == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
err = MapMachHeader(qmoImage);
|
|
}
|
|
|
|
// Count the segments, allocate the array used to hold information about them,
|
|
// and then fill in that array. In the process, also fill out the sections
|
|
// array.
|
|
|
|
if (err == 0) {
|
|
(void) FindLoadCommand(qmoImage, CountSegmentsAndSections, NULL);
|
|
|
|
qmoImage->segments = calloc(qmoImage->segCount, sizeof(*qmoImage->segments));
|
|
qmoImage->sections = calloc(qmoImage->sectCount, sizeof(*qmoImage->sections));
|
|
if ( (qmoImage->segments == NULL) && (qmoImage->sections == NULL) ) {
|
|
err = ENOMEM;
|
|
}
|
|
|
|
}
|
|
if (err == 0) {
|
|
segIndex = 0;
|
|
|
|
(void) FindLoadCommand(qmoImage, InitSegment, &segIndex);
|
|
|
|
assert(segIndex == qmoImage->segCount);
|
|
}
|
|
if (err == 0) {
|
|
sectIndex = 0;
|
|
|
|
for (segIndex = 0; segIndex < qmoImage->segCount; segIndex++) {
|
|
InitSegmentSections(qmoImage, segIndex, §Index);
|
|
}
|
|
|
|
assert(sectIndex == qmoImage->sectCount);
|
|
}
|
|
|
|
// Once we have the segment information, we can calculate the slide.
|
|
|
|
if (err == 0) {
|
|
err = CalculateSlide(qmoImage);
|
|
}
|
|
|
|
// Clean up on error.
|
|
|
|
if (err != 0) {
|
|
QMOImageDestroy(qmoImage);
|
|
qmoImage = NULL;
|
|
}
|
|
*qmoImagePtr = qmoImage;
|
|
|
|
assert( (err == 0) == QMOImageIsValid(*qmoImagePtr) );
|
|
|
|
return err;
|
|
}
|
|
|
|
extern void QMOImageDestroy(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
uint32_t segIndex;
|
|
|
|
if (qmoImage != NULL) {
|
|
|
|
// Destroy the segments array.
|
|
|
|
if (qmoImage->segments != NULL) {
|
|
// Unmap any mapped segments.
|
|
|
|
for (segIndex = 0; segIndex < qmoImage->segCount; segIndex++) {
|
|
if (qmoImage->segments[segIndex].segBase != NULL) {
|
|
QTMAddr offset;
|
|
QTMAddr size;
|
|
|
|
if (qmoImage->prepared) {
|
|
offset = qmoImage->segments[segIndex].seg.vmaddr + qmoImage->slide;
|
|
size = qmoImage->segments[segIndex].seg.vmsize;
|
|
} else {
|
|
offset = qmoImage->segments[segIndex].seg.fileoff + qmoImage->machHeaderOffset;
|
|
size = qmoImage->segments[segIndex].seg.filesize;
|
|
}
|
|
qmoImage->unmapRange(qmoImage, offset, size, qmoImage->segments[segIndex].segBase, qmoImage->segments[segIndex].segMapRefCon);
|
|
|
|
qmoImage->segments[segIndex].segBase = 0;
|
|
qmoImage->segments[segIndex].segMapRefCon = NULL;
|
|
}
|
|
}
|
|
free(qmoImage->segments);
|
|
}
|
|
|
|
// Unmap the Mach-O header.
|
|
|
|
if (qmoImage->machHeader != NULL) {
|
|
qmoImage->unmapRange(qmoImage, qmoImage->machHeaderOffset, qmoImage->machHeaderSize, qmoImage->machHeader, qmoImage->machHeaderRefCon);
|
|
}
|
|
|
|
// Now that we're done unmapping, call the type-specific destroy callback
|
|
// so that it can clean up any information that it needed to have
|
|
// (typically this is hung off the refCon).
|
|
|
|
if (qmoImage->destroy != NULL) {
|
|
qmoImage->destroy(qmoImage);
|
|
}
|
|
|
|
// Free the memory for the object itself.
|
|
|
|
free( (void *) qmoImage->filePath );
|
|
free(qmoImage);
|
|
}
|
|
}
|
|
|
|
static int QMOImageMapSegmentByIndex(QMOImageRef qmoImage, uint32_t segIndex, const char **segBasePtr)
|
|
// Map a segment from a Mach-O image into the local process, returning the address
|
|
// of the mapped data.
|
|
{
|
|
int err;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(segIndex < qmoImage->segCount);
|
|
assert(segBasePtr != NULL);
|
|
|
|
// Have we mapped it yet? If not, we have to do some heavy lifting.
|
|
|
|
err = 0;
|
|
if (qmoImage->segments[segIndex].segBase == NULL) {
|
|
QTMAddr offset;
|
|
QTMAddr size;
|
|
|
|
// No, we need to map it now.
|
|
|
|
// Work out what to map. If the image has been prepared (that is,
|
|
// we're dealing with an image that's been mapped into memory
|
|
// by dyld), we operate on virtual addresses. OTOH, if the image
|
|
// is coming from a file, we operate in file-relative addresses.
|
|
|
|
if (qmoImage->prepared) {
|
|
offset = qmoImage->segments[segIndex].seg.vmaddr + qmoImage->slide;
|
|
size = qmoImage->segments[segIndex].seg.vmsize;
|
|
} else {
|
|
offset = qmoImage->segments[segIndex].seg.fileoff + qmoImage->machHeaderOffset;
|
|
size = qmoImage->segments[segIndex].seg.filesize;
|
|
}
|
|
err = qmoImage->mapRange(
|
|
qmoImage,
|
|
offset,
|
|
size,
|
|
(const void **) &qmoImage->segments[segIndex].segBase,
|
|
&qmoImage->segments[segIndex].segMapRefCon
|
|
);
|
|
}
|
|
|
|
// If all went well, return the address to the caller.
|
|
|
|
if (err == 0) {
|
|
*segBasePtr = qmoImage->segments[segIndex].segBase;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#pragma mark ***** Accessors
|
|
|
|
extern QTMAddr QMOImageGetSlide(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->slide;
|
|
}
|
|
|
|
extern bool QMOImageIs64Bit(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->is64Bit;
|
|
}
|
|
|
|
extern bool QMOImageIsByteSwapped(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->byteSwapped;
|
|
}
|
|
|
|
extern QTMAddr QMOImageGetMachHeaderOffset(QMOImageRef qmoImage)
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->machHeaderOffset;
|
|
}
|
|
|
|
extern const struct mach_header * QMOImageGetMachHeader(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->machHeader;
|
|
}
|
|
|
|
extern const char * QMOImageGetFilePath(QMOImageRef qmoImage)
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->filePath;
|
|
}
|
|
|
|
extern uint32_t QMOImageGetFileType(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
compile_time_assert( offsetof(struct mach_header, filetype) == offsetof(struct mach_header_64, filetype) );
|
|
|
|
return QMOImageToLocalUInt32(qmoImage, qmoImage->machHeader->filetype);
|
|
}
|
|
|
|
extern uint32_t QMOImageGetCPUType(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
compile_time_assert( offsetof(struct mach_header, cputype) == offsetof(struct mach_header_64, cputype) );
|
|
|
|
return QMOImageToLocalUInt32(qmoImage, qmoImage->machHeader->cputype);
|
|
}
|
|
|
|
extern uint32_t QMOImageGetCPUSubType(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
assert( offsetof(struct mach_header, cpusubtype) == offsetof(struct mach_header_64, cpusubtype) );
|
|
assert( offsetof(struct mach_header, cpusubtype) == offsetof(struct mach_header_64, cpusubtype) );
|
|
|
|
return QMOImageToLocalUInt32(qmoImage, qmoImage->machHeader->cpusubtype);
|
|
}
|
|
|
|
static bool CommandIDCompareProc(QMOImageRef qmoImage, const struct load_command *cmd, void *compareRefCon)
|
|
// A LoadCommandCompareProc callback used to implement FindLoadCommandByID.
|
|
// compareRefCon is a pointer to the Mach-O command ID that we're looking for.
|
|
//
|
|
// See LoadCommandCompareProc for a description of the other parameters.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(cmd != NULL);
|
|
|
|
return ( QMOImageToLocalUInt32(qmoImage, cmd->cmd) == *(uint32_t *) compareRefCon );
|
|
}
|
|
|
|
extern const struct load_command * QMOImageFindLoadCommandByID(QMOImageRef qmoImage, uint32_t cmdID)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return FindLoadCommand(qmoImage, CommandIDCompareProc, (void *) &cmdID);
|
|
}
|
|
|
|
extern uint32_t QMOImageGetSegmentCount(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->segCount;
|
|
}
|
|
|
|
extern int QMOImageGetSegmentByName(
|
|
QMOImageRef qmoImage,
|
|
const char * segName,
|
|
uint32_t * segIndexPtr,
|
|
struct segment_command_64 * segPtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
uint32_t segIndex;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(segName != NULL);
|
|
assert( (segIndexPtr != NULL) || (segPtr != NULL) );
|
|
|
|
err = GetSegmentIndexByName(qmoImage, segName, &segIndex);
|
|
if (err == 0) {
|
|
if (segIndexPtr != NULL) {
|
|
*segIndexPtr = segIndex;
|
|
}
|
|
if (segPtr != NULL) {
|
|
*segPtr = qmoImage->segments[segIndex].seg;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
extern int QMOImageGetSegmentByIndex(QMOImageRef qmoImage, uint32_t segIndex, struct segment_command_64 *segPtr)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(segPtr != NULL);
|
|
|
|
if (segIndex < qmoImage->segCount) {
|
|
*segPtr = qmoImage->segments[segIndex].seg;
|
|
err = 0;
|
|
} else {
|
|
err = EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
extern uint32_t QMOImageGetSectionCount(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
return qmoImage->sectCount;
|
|
}
|
|
|
|
extern int QMOImageGetSectionByName(
|
|
QMOImageRef qmoImage,
|
|
const char * segName,
|
|
const char * sectName,
|
|
uint32_t * sectIndexPtr,
|
|
struct section_64 * sectPtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
bool found;
|
|
uint32_t sectIndex;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert( (segName != NULL) || (sectName != NULL) );
|
|
assert( (sectIndexPtr != NULL) || (sectPtr != NULL) );
|
|
|
|
// Start by looking for the section.
|
|
|
|
sectIndex = 0;
|
|
found = false;
|
|
while ( ! found && (sectIndex < qmoImage->sectCount) ) {
|
|
found = ( (segName == NULL) || (strcmp(segName, qmoImage->sections[sectIndex].sect.segname) == 0) )
|
|
&& ( (sectName == NULL) || (strcmp(sectName, qmoImage->sections[sectIndex].sect.sectname) == 0) );
|
|
if ( ! found ) {
|
|
sectIndex += 1;
|
|
}
|
|
}
|
|
|
|
// If we find it, copy out the details to our client.
|
|
|
|
if (found) {
|
|
if (sectIndexPtr != NULL) {
|
|
*sectIndexPtr = sectIndex;
|
|
}
|
|
if (sectPtr != NULL) {
|
|
*sectPtr = qmoImage->sections[sectIndex].sect;
|
|
}
|
|
err = 0;
|
|
} else {
|
|
err = ESRCH;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
extern int QMOImageGetSectionByIndex(
|
|
QMOImageRef qmoImage,
|
|
uint32_t sectIndex,
|
|
struct section_64 * sectPtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
if (sectIndex < qmoImage->sectCount) {
|
|
*sectPtr = qmoImage->sections[sectIndex].sect;
|
|
err = 0;
|
|
} else {
|
|
err = EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#pragma mark ***** Utilities
|
|
|
|
extern uint8_t QMOImageToLocalUInt8(QMOImageRef qmoImage, uint8_t value)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValidLimited(qmoImage));
|
|
|
|
return value;
|
|
}
|
|
|
|
extern uint16_t QMOImageToLocalUInt16(QMOImageRef qmoImage, uint16_t value)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValidLimited(qmoImage));
|
|
|
|
if ( qmoImage->byteSwapped ) {
|
|
value = OSSwapInt16(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
extern uint32_t QMOImageToLocalUInt32(QMOImageRef qmoImage, uint32_t value)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValidLimited(qmoImage));
|
|
|
|
if ( qmoImage->byteSwapped ) {
|
|
value = OSSwapInt32(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
extern uint64_t QMOImageToLocalUInt64(QMOImageRef qmoImage, uint64_t value)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValidLimited(qmoImage));
|
|
|
|
if ( qmoImage->byteSwapped ) {
|
|
value = OSSwapInt64(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
#pragma mark ***** QMOFileImage
|
|
|
|
static int QMOFileImageMapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void ** basePtr,
|
|
void ** mapRefConPtr
|
|
)
|
|
// The map range callback for file-based images. This does its magic
|
|
// via mmap.
|
|
//
|
|
// See the comments for QMOMapRangeProc for a discussion of the parameters.
|
|
{
|
|
int err;
|
|
int fd;
|
|
void * base;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert( basePtr != NULL);
|
|
assert(*basePtr == NULL);
|
|
assert(mapRefConPtr != NULL);
|
|
|
|
fd = (int) (intptr_t) qmoImage->refCon;
|
|
|
|
err = 0;
|
|
base = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, offset);
|
|
if (base == MAP_FAILED) {
|
|
base = NULL;
|
|
err = errno;
|
|
}
|
|
*basePtr = base;
|
|
|
|
assert( (err == 0) == (*basePtr != NULL) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static void QMOFileImageUnmapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void * base,
|
|
void * mapRefCon
|
|
)
|
|
// The unmap range callback for file-based images.
|
|
//
|
|
// See the comments for QMOUnmapRangeProc for a discussion of the parameters.
|
|
{
|
|
#pragma unused(offset, mapRefCon)
|
|
int junk;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert(base != NULL);
|
|
|
|
junk = munmap( (void *) base, length);
|
|
assert(junk == 0);
|
|
}
|
|
|
|
static int QMOFileImageCreate(QMOImage *qmoImage, void *refCon)
|
|
// The create callback for file-based images. A file descriptor
|
|
// of the file to work on is in refCon. We dup this into the refCon
|
|
// so that, when the destroy callback is called, it can close safely
|
|
// close the file in the refCon.
|
|
//
|
|
// See the comments for QMOCreateProc for a discussion of the parameters.
|
|
{
|
|
int err;
|
|
int fd;
|
|
|
|
assert(qmoImage != NULL);
|
|
|
|
fd = (int) (intptr_t) refCon;
|
|
|
|
fd = dup(fd);
|
|
if (fd < 0) {
|
|
err = errno;
|
|
} else {
|
|
err = 0;
|
|
}
|
|
qmoImage->refCon = (void *) (intptr_t) fd;
|
|
|
|
return err;
|
|
}
|
|
|
|
static void QMOFileImageDestroy(QMOImage *qmoImage)
|
|
// The destroy callback for file-based images.
|
|
//
|
|
// See the comments for QMODestroyProc for a discussion of the parameters.
|
|
{
|
|
int fd;
|
|
int junk;
|
|
|
|
assert(qmoImage != NULL);
|
|
|
|
fd = (int) (intptr_t) qmoImage->refCon;
|
|
|
|
if (fd != -1) {
|
|
junk = close(fd);
|
|
assert(junk == 0);
|
|
}
|
|
}
|
|
|
|
static const NXArchInfo *NXGetLocalArchInfoFixed(void)
|
|
// A version of NXGetLocalArchInfo that works around the gotchas described
|
|
// below.
|
|
{
|
|
static const NXArchInfo * sCachedArch = NULL;
|
|
|
|
// NXGetLocalArchInfo (and related routines like NXGetArchInfoFromCpuType) may
|
|
// or may not return a pointer that you have to free <rdar://problem/5000965>.
|
|
// To limit the potential for leaks, I only ever call through to the underlying
|
|
// routine once. [This can still leak (if two threads initialise it at the
|
|
// same time, or if we need to apply the x86-64 workaround), but the leak
|
|
// is bounded.]
|
|
|
|
if (sCachedArch == NULL) {
|
|
sCachedArch = NXGetLocalArchInfo();
|
|
|
|
// For some reason, when running 64-bit on 64-bit architectures,
|
|
// NXGetLocalArchInfo returns the 32-bit architecture <rdar://problem/4996965>.
|
|
// If that happens, we change it to what we expect.
|
|
|
|
if (sCachedArch != NULL) {
|
|
#if TARGET_CPU_X86_64
|
|
if (sCachedArch->cputype == CPU_TYPE_X86) {
|
|
sCachedArch = NXGetArchInfoFromCpuType(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL);
|
|
}
|
|
#elif TARGET_CPU_PPC64
|
|
if (sCachedArch->cputype == CPU_TYPE_POWERPC) {
|
|
sCachedArch = NXGetArchInfoFromCpuType(CPU_TYPE_POWERPC64, CPU_SUBTYPE_POWERPC_ALL);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return sCachedArch;
|
|
}
|
|
|
|
extern cpu_type_t QMOGetLocalCPUType(void)
|
|
// See comment in header.
|
|
{
|
|
return NXGetLocalArchInfoFixed()->cputype;
|
|
}
|
|
|
|
static int FindBestFatArchitecture(
|
|
int fd,
|
|
const struct fat_header * fatHeader,
|
|
cpu_type_t cputype,
|
|
cpu_subtype_t cpusubtype,
|
|
off_t * machHeaderOffsetPtr
|
|
)
|
|
// For fat Mach-O file that starts with fatHeader, looking for the
|
|
// architecture that best matches cputype and cpusubtype. Return
|
|
// the file offset of that image in *machHeaderOffsetPtr.
|
|
//
|
|
// Keep in mind that a fat header is always big endian.
|
|
{
|
|
int err;
|
|
uint32_t archCount;
|
|
struct fat_arch * arches;
|
|
const struct fat_arch * bestArch = NULL;
|
|
|
|
assert( (cputype != CPU_TYPE_ANY) || (cpusubtype == 0) );
|
|
|
|
archCount = OSSwapBigToHostInt32(fatHeader->nfat_arch);
|
|
|
|
// Allocate a fat_arch array and fill it in by reading the file.
|
|
|
|
err = 0;
|
|
arches = malloc(archCount * sizeof(*arches));
|
|
if (arches == NULL) {
|
|
err = ENOMEM;
|
|
}
|
|
if (err == 0) {
|
|
ssize_t bytesRead;
|
|
|
|
bytesRead = read(fd, arches, archCount * sizeof(*arches));
|
|
if (bytesRead < 0) {
|
|
err = errno;
|
|
} else if (bytesRead != (archCount * sizeof(*arches))) {
|
|
err = EPIPE;
|
|
}
|
|
}
|
|
|
|
// Find the fat_arch that best matches the user's requirements.
|
|
|
|
if (err == 0) {
|
|
// This would do nothing on a big endian system, but it probably would take a
|
|
// whole bunch of code to do nothing. So we conditionalise it away.
|
|
|
|
#if TARGET_RT_LITTLE_ENDIAN
|
|
{
|
|
uint32_t archIndex;
|
|
|
|
for (archIndex = 0; archIndex < archCount; archIndex++) {
|
|
arches[archIndex].cputype = OSSwapBigToHostInt32(arches[archIndex].cputype);
|
|
arches[archIndex].cpusubtype = OSSwapBigToHostInt32(arches[archIndex].cpusubtype);
|
|
arches[archIndex].offset = OSSwapBigToHostInt32(arches[archIndex].offset);
|
|
arches[archIndex].size = OSSwapBigToHostInt32(arches[archIndex].size);
|
|
arches[archIndex].align = OSSwapBigToHostInt32(arches[archIndex].align);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// If the user requested any CPU type, first try to give them the current
|
|
// architecture, and if that fails just give them the first.
|
|
|
|
if (cputype == CPU_TYPE_ANY) {
|
|
const NXArchInfo * localArch;
|
|
|
|
assert(cpusubtype == 0);
|
|
|
|
localArch = NXGetLocalArchInfoFixed();
|
|
|
|
bestArch = NULL;
|
|
if (localArch != NULL) {
|
|
bestArch = NXFindBestFatArch(localArch->cputype, localArch->cpusubtype, arches, archCount);
|
|
if (bestArch == NULL) {
|
|
bestArch = NXFindBestFatArch(localArch->cputype, 0, arches, archCount);
|
|
}
|
|
}
|
|
if (bestArch == NULL) {
|
|
bestArch = NXFindBestFatArch(arches[0].cputype, cpusubtype, arches, archCount);
|
|
}
|
|
} else {
|
|
bestArch = NXFindBestFatArch(cputype, cpusubtype, arches, archCount);
|
|
}
|
|
if (bestArch == NULL) {
|
|
err = ESRCH;
|
|
}
|
|
}
|
|
|
|
// Return that architecture's offset.
|
|
|
|
if (err == 0) {
|
|
*machHeaderOffsetPtr = bestArch->offset;
|
|
}
|
|
|
|
free(arches);
|
|
|
|
return err;
|
|
}
|
|
|
|
extern int QMOImageCreateFromFile(const char *filePath, cpu_type_t cputype, cpu_subtype_t cpusubtype, QMOImageRef *qmoImagePtr)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
int junk;
|
|
int fd;
|
|
ssize_t bytesRead;
|
|
struct fat_header fatHeader;
|
|
off_t machHeaderOffset;
|
|
struct mach_header machHeader;
|
|
struct fat_arch arch;
|
|
const struct fat_arch * bestArch;
|
|
|
|
assert(filePath != NULL);
|
|
assert( (cputype != CPU_TYPE_ANY) || (cpusubtype == 0) );
|
|
assert( qmoImagePtr != NULL);
|
|
assert(*qmoImagePtr == NULL);
|
|
|
|
machHeaderOffset = 0; // quieten a warning
|
|
|
|
// Open the file.
|
|
|
|
err = 0;
|
|
fd = open(filePath, O_RDONLY);
|
|
if (fd < 0) {
|
|
err = errno;
|
|
}
|
|
|
|
// Read a potential fat header. Keep in mind that this is always big endian.
|
|
|
|
if (err == 0) {
|
|
bytesRead = read(fd, &fatHeader, sizeof(fatHeader));
|
|
if (bytesRead < 0) {
|
|
err = errno;
|
|
} else if (bytesRead != sizeof(fatHeader)) {
|
|
err = EPIPE;
|
|
}
|
|
}
|
|
|
|
// If it's there, deal with it. If not, we treat this as a thin file, that
|
|
// is, the Mach-O image starts at the front of the file.
|
|
|
|
if (err == 0) {
|
|
if ( OSSwapBigToHostInt32(fatHeader.magic) == FAT_MAGIC) {
|
|
err = FindBestFatArchitecture(fd, &fatHeader, cputype, cpusubtype, &machHeaderOffset);
|
|
} else {
|
|
machHeaderOffset = 0;
|
|
}
|
|
}
|
|
|
|
// Read the Mach-O header from the requested offset, and do a quick check to make sure
|
|
// that its magic is correct and that the architecture is compatible with the one the
|
|
// user requested. We do this by constructing a dummy fat_arch and passing it as
|
|
// a single element array to NXFindBestFatArch. If NXFindBestFatArch returns NULL,
|
|
// this single architecture isn't compatible with the architecture that the user
|
|
// requested.
|
|
|
|
if (err == 0) {
|
|
bytesRead = pread(fd, &machHeader, sizeof(machHeader), machHeaderOffset);
|
|
if (bytesRead < 0) {
|
|
err = errno;
|
|
} else if (bytesRead != sizeof(machHeader)) {
|
|
err = EPIPE;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
arch.cputype = machHeader.cputype;
|
|
arch.cpusubtype = machHeader.cpusubtype;
|
|
arch.offset = 0; // NXFindBestFatArch ignores these fields
|
|
arch.size = 0;
|
|
arch.align = 0;
|
|
|
|
if ( (machHeader.magic == MH_MAGIC) || (machHeader.magic == MH_MAGIC_64) ) {
|
|
// do nothing
|
|
} else if ( (machHeader.magic == MH_CIGAM) || (machHeader.magic == MH_CIGAM_64) ) {
|
|
arch.cputype = OSSwapInt32(arch.cputype);
|
|
arch.cpusubtype = OSSwapInt32(arch.cpusubtype);
|
|
} else {
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
if (err == 0) {
|
|
if (cputype == CPU_TYPE_ANY) {
|
|
cputype = arch.cputype;
|
|
}
|
|
bestArch = NXFindBestFatArch(cputype, cpusubtype, &arch, 1);
|
|
if (bestArch == NULL) {
|
|
err = ESRCH;
|
|
}
|
|
}
|
|
|
|
// Create an QMOImage for this file.
|
|
|
|
if (err == 0) {
|
|
err = QMOImageCreate(machHeaderOffset, filePath, false, QMOFileImageMapRange, QMOFileImageUnmapRange, QMOFileImageCreate, QMOFileImageDestroy, (void *) (intptr_t) fd, qmoImagePtr);
|
|
}
|
|
|
|
// Clean up. We can safe close fd because QMOFileImageCreate has dup'd it.
|
|
|
|
if ( (err != 0) && (fd != -1) ) {
|
|
junk = close(fd);
|
|
assert(junk == 0);
|
|
}
|
|
assert( (err == 0) == QMOImageIsValid(*qmoImagePtr) );
|
|
|
|
return err;
|
|
}
|
|
|
|
#pragma mark ***** QMOTaskImage
|
|
|
|
static int QMOTaskImageMapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void ** basePtr,
|
|
void ** mapRefConPtr
|
|
)
|
|
// The map range callback for remote images. This is much easier now that
|
|
// the QTaskMemory takes care of all the Mach rubbish.
|
|
//
|
|
// See the comments for QMOMapRangeProc for a discussion of the parameters.
|
|
{
|
|
int err;
|
|
task_t task;
|
|
const void * base;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert( basePtr != NULL);
|
|
assert(*basePtr == NULL);
|
|
assert(mapRefConPtr != NULL);
|
|
|
|
task = (task_t) (uintptr_t) qmoImage->refCon;
|
|
|
|
base = NULL;
|
|
|
|
// First try to remap the memory into our address space.
|
|
|
|
err = QTMRemap(task, offset, length, &base);
|
|
|
|
if (err == EFAULT) {
|
|
|
|
// If the mapping fails, just read the memory. This happens if the address
|
|
// is within the system shared region.
|
|
|
|
assert(base == NULL);
|
|
err = QTMReadAllocated(task, offset, length, &base);
|
|
}
|
|
if (err == 0) {
|
|
*basePtr = base;
|
|
*mapRefConPtr = NULL;
|
|
}
|
|
|
|
assert( (err == 0) == (*basePtr != NULL) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static void QMOTaskImageUnmapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void * base,
|
|
void * mapRefCon
|
|
)
|
|
// The unmap range callback for remote images.
|
|
//
|
|
// See the comments for QMOUnmapRangeProc for a discussion of the parameters.
|
|
{
|
|
#pragma unused(offset, mapRefCon)
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert(base != NULL);
|
|
|
|
QTMFree(base, length);
|
|
}
|
|
|
|
bool kQMachOImageTestSelfShortCircuit = true;
|
|
// By changing this variable to false you can disable the short circuit that
|
|
// we apply when targetting the local task.
|
|
//
|
|
// This is exported for the benefit of the unit test.
|
|
|
|
extern int QMOImageCreateFromTask(
|
|
task_t task,
|
|
QTMAddr machHeader,
|
|
const char * filePath,
|
|
QMOImageRef * qmoImagePtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
|
|
assert(task != MACH_PORT_NULL);
|
|
// machHeader could potentially be at zero
|
|
// filePath may be NULL
|
|
assert( qmoImagePtr != NULL);
|
|
assert(*qmoImagePtr == NULL);
|
|
|
|
if ( (task == mach_task_self()) && kQMachOImageTestSelfShortCircuit ) {
|
|
err = QMOImageCreateFromLocalImage( (const struct mach_header *) (uintptr_t) machHeader, filePath, qmoImagePtr);
|
|
} else {
|
|
err = QMOImageCreate(machHeader, filePath, true, QMOTaskImageMapRange, QMOTaskImageUnmapRange, NULL, NULL, (void *) (uintptr_t) task, qmoImagePtr);
|
|
}
|
|
|
|
assert( (err == 0) == QMOImageIsValid(*qmoImagePtr) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static int FindTaskDyldWithNonNativeRetry(task_t task, cpu_type_t cputype, QTMAddr *dyldAddrPtr);
|
|
// forward declaration
|
|
|
|
extern int QMOImageCreateFromTaskDyld(
|
|
task_t task,
|
|
cpu_type_t cputype,
|
|
QMOImageRef * qmoImagePtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
QMOImageRef qmoImage;
|
|
QTMAddr dyldAddr;
|
|
|
|
assert(task != MACH_PORT_NULL);
|
|
assert( qmoImagePtr != NULL);
|
|
assert(*qmoImagePtr == NULL);
|
|
|
|
qmoImage = NULL;
|
|
|
|
// Find dyld within the task and then create a remote image based on that.
|
|
// Seems easy huh? No way.
|
|
|
|
err = FindTaskDyldWithNonNativeRetry(task, cputype, &dyldAddr);
|
|
if (err == 0) {
|
|
err = QMOImageCreateFromTask(task, dyldAddr, NULL, &qmoImage);
|
|
}
|
|
|
|
// In the case of dyld, we assume that the dynamic linker ID is the file path.
|
|
// It's hard to get the dynamic linker ID before we create the image, so we create
|
|
// the image with a NULL filePath and then fill it in afterwards. If this
|
|
// fails, I leave the file path set to NULL; I don't consider this failure
|
|
// bad enough to warrant failing the entire routine.
|
|
|
|
if (err == 0) {
|
|
const struct dylinker_command * dyldCommand;
|
|
const char * filePath;
|
|
|
|
assert(qmoImage->filePath == NULL);
|
|
|
|
filePath = NULL;
|
|
|
|
dyldCommand = (const struct dylinker_command *) QMOImageFindLoadCommandByID(
|
|
qmoImage,
|
|
LC_ID_DYLINKER
|
|
);
|
|
if (dyldCommand != NULL) {
|
|
filePath = ((const char *) dyldCommand) + QMOImageToLocalUInt32(qmoImage, dyldCommand->name.offset);
|
|
}
|
|
if (filePath != NULL) {
|
|
qmoImage->filePath = strdup(filePath);
|
|
}
|
|
|
|
assert(qmoImage->filePath != NULL);
|
|
|
|
// Copy result out to client.
|
|
|
|
*qmoImagePtr = qmoImage;
|
|
}
|
|
|
|
assert( (err == 0) == QMOImageIsValid(*qmoImagePtr) );
|
|
|
|
return err;
|
|
}
|
|
|
|
#pragma mark ***** QMOLocalImage
|
|
|
|
static int QMOLocalImageMapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void ** basePtr,
|
|
void ** mapRefConPtr
|
|
)
|
|
// The map range callback for local images. This one is easy (-:
|
|
//
|
|
// See the comments for QMOMapRangeProc for a discussion of the parameters.
|
|
{
|
|
int err;
|
|
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert( basePtr != NULL);
|
|
assert(*basePtr == NULL);
|
|
assert(mapRefConPtr != NULL);
|
|
|
|
*basePtr = (const void *) (uintptr_t) offset;
|
|
*mapRefConPtr = NULL;
|
|
err = 0;
|
|
|
|
assert( (err == 0) == (*basePtr != NULL) );
|
|
|
|
return err;
|
|
}
|
|
|
|
static void QMOLocalImageUnmapRange(
|
|
QMOImage * qmoImage,
|
|
QTMAddr offset,
|
|
QTMAddr length,
|
|
const void * base,
|
|
void * mapRefCon
|
|
)
|
|
// The unmap range callback for local images.
|
|
//
|
|
// See the comments for QMOUnmapRangeProc for a discussion of the parameters.
|
|
{
|
|
#pragma unused(offset, mapRefCon)
|
|
assert(qmoImage != NULL);
|
|
assert(length > 0);
|
|
assert(base != NULL);
|
|
|
|
// do nothing
|
|
}
|
|
|
|
extern int QMOImageCreateFromLocalImage(
|
|
const struct mach_header * machHeader,
|
|
const char * filePath,
|
|
QMOImageRef * qmoImagePtr
|
|
)
|
|
// See comment in header.
|
|
{
|
|
// machHeader could potentially be at zero
|
|
// filePath may be NULL
|
|
assert( qmoImagePtr != NULL);
|
|
assert(*qmoImagePtr == NULL);
|
|
|
|
return QMOImageCreate( (QTMAddr) (uintptr_t) machHeader, filePath, true, QMOLocalImageMapRange, QMOLocalImageUnmapRange, NULL, NULL, NULL, qmoImagePtr);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
#pragma mark ***** Finding dyld
|
|
|
|
// This stuff is fairly well commented in the routines comments. Start with
|
|
// the comments for FindTaskDyldWithNonNativeRetry.
|
|
|
|
// In the debug version, we support an environment variable that logs progress
|
|
// for our dyld search. This is nice because is can be hard to debug otherwise
|
|
// (for example, when you're running PowerPC program using Rosetta).
|
|
|
|
#if defined(NDEBUG)
|
|
|
|
static inline bool LogDyldSearch(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#else
|
|
|
|
static bool LogDyldSearch(void)
|
|
{
|
|
static bool sInited = false;
|
|
static bool sLogDyldSearch = false;
|
|
|
|
if ( ! sInited ) {
|
|
sLogDyldSearch = (getenv("QMACHOIMAGE_LOG_DYLD_SEARCH") != NULL);
|
|
|
|
sInited = true;
|
|
}
|
|
|
|
return sLogDyldSearch;
|
|
}
|
|
|
|
#endif
|
|
|
|
static const char * GetLocalDyldPath(void)
|
|
// Get the path for the dyld associated with the current task.
|
|
// We do this by iterating through the list of images in the
|
|
// current task looking for the main executable's image (whose
|
|
// file type is MH_EXECUTE). In that we look for the
|
|
// LC_LOAD_DYLINKER load command, which contains the path to the
|
|
// dynamic linker requested by this image.
|
|
{
|
|
int err;
|
|
uint32_t imageCount;
|
|
uint32_t imageIndex;
|
|
const struct mach_header * thisImage;
|
|
static const char * sCachedResult = NULL;
|
|
|
|
if (sCachedResult == NULL) {
|
|
// Iterate the images in the current process looking for the main
|
|
// executable image (MH_EXECUTE).
|
|
|
|
imageCount = _dyld_image_count();
|
|
for (imageIndex = 0; imageIndex < imageCount; imageIndex++) {
|
|
|
|
compile_time_assert( offsetof(struct mach_header_64, filetype) == offsetof(struct mach_header, filetype) );
|
|
|
|
thisImage = _dyld_get_image_header(imageIndex);
|
|
if (thisImage->filetype == MH_EXECUTE) { // no need to byte swap because this is local only
|
|
QMOImageRef qmoImage;
|
|
const struct dylinker_command * loadDyldCmd;
|
|
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "GetLocalDyldPath: Found executable at %p.\n", thisImage);
|
|
}
|
|
|
|
// Within the main executable image, look for the LC_LOAD_DYLINKER
|
|
// load command. Extract the dyld's path from that.
|
|
|
|
qmoImage = NULL;
|
|
|
|
err = QMOImageCreateFromLocalImage(
|
|
thisImage,
|
|
NULL,
|
|
&qmoImage
|
|
);
|
|
if (err == 0) {
|
|
loadDyldCmd = (const struct dylinker_command *) QMOImageFindLoadCommandByID(
|
|
qmoImage,
|
|
LC_LOAD_DYLINKER
|
|
);
|
|
if (loadDyldCmd != NULL) {
|
|
sCachedResult = (((const char *) loadDyldCmd) + loadDyldCmd->name.offset); // no need to byte swap because this is local only
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "GetLocalDyldPath: Local dyld is '%s'.\n", sCachedResult);
|
|
}
|
|
}
|
|
}
|
|
QMOImageDestroy(qmoImage);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sCachedResult;
|
|
}
|
|
|
|
static QTMAddr GetLocalDyldAddr(void)
|
|
// Get the address that the current task /intended/ to load
|
|
// dyld. That is, get the default load address of the dyld
|
|
// requested by the current task. So, if the dyld was slid,
|
|
// this returns that address that it wanted to load at.
|
|
//
|
|
// If anything goes wrong, return 0. The caller can either
|
|
// ignore the error, or specifically check for 0, which is a
|
|
// very improbable load address for dyld.
|
|
{
|
|
int err;
|
|
const char * dyldPath;
|
|
QMOImageRef qmoImage;
|
|
struct segment_command_64 textSeg;
|
|
static QTMAddr sCachedResult = 0;
|
|
|
|
if (sCachedResult == 0) {
|
|
qmoImage = NULL;
|
|
|
|
// Get the path to the current task's dyld, using a hard-wired
|
|
// default if any goes wrong.
|
|
|
|
dyldPath = GetLocalDyldPath();
|
|
if (dyldPath == NULL) {
|
|
dyldPath = "/usr/lib/dyld";
|
|
}
|
|
|
|
// Create a file-based QMOImage from that, and then look up the __TEXT
|
|
// segment's vmaddr. Note that, if dyld is fat, QMOImageCreateFromFile
|
|
// will use the architecture that matches the local architecture (if
|
|
// possible).
|
|
|
|
err = QMOImageCreateFromFile(dyldPath, CPU_TYPE_ANY, 0, &qmoImage);
|
|
if (err == 0) {
|
|
err = QMOImageGetSegmentByName(qmoImage, "__TEXT", NULL, &textSeg);
|
|
}
|
|
if (err == 0) {
|
|
sCachedResult = textSeg.vmaddr;
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "GetLocalDyldAddr: Local dyld is at %p; based on fat file member with CPU type/subtype of %#x/%#x.\n", (void *) (uintptr_t) sCachedResult, (int) QMOImageGetCPUType(qmoImage), (int) QMOImageGetCPUSubType(qmoImage));
|
|
}
|
|
}
|
|
|
|
// Clean up.
|
|
|
|
QMOImageDestroy(qmoImage);
|
|
}
|
|
|
|
return sCachedResult;
|
|
}
|
|
|
|
static bool IsDyldAtAddress(task_t task, cpu_type_t cputype, QTMAddr addr, vm_prot_t prot)
|
|
// Returns true if the address within the specified task looks
|
|
// like it points to dyld. We first check that prot is what we'd
|
|
// expect for dydl. Then we read a mach_header from the
|
|
// address and check its magic, filetype, and cputype fields.
|
|
{
|
|
int err;
|
|
bool result;
|
|
struct mach_header machHeader;
|
|
|
|
assert(task != MACH_PORT_NULL);
|
|
|
|
result = false;
|
|
|
|
compile_time_assert( offsetof(struct mach_header_64, magic) == offsetof(struct mach_header, magic) );
|
|
compile_time_assert( offsetof(struct mach_header_64, filetype) == offsetof(struct mach_header, filetype) );
|
|
|
|
if ( (prot & (VM_PROT_READ | VM_PROT_EXECUTE)) == (VM_PROT_READ | VM_PROT_EXECUTE) ) {
|
|
err = QTMRead(task, addr, sizeof(machHeader), &machHeader);
|
|
if (err == 0) {
|
|
if ( (machHeader.magic == MH_MAGIC) || (machHeader.magic == MH_MAGIC_64) ) {
|
|
result = true;
|
|
} else if ( (machHeader.magic == MH_CIGAM) || (machHeader.magic == MH_CIGAM_64) ) {
|
|
machHeader.filetype = OSSwapInt32(machHeader.filetype);
|
|
machHeader.cputype = OSSwapInt32(machHeader.cputype);
|
|
result = true;
|
|
}
|
|
if (result) {
|
|
result = ( machHeader.filetype == MH_DYLINKER );
|
|
if (result) {
|
|
result = ((cputype == CPU_TYPE_ANY) || (cputype == machHeader.cputype));
|
|
if (result) {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: Found dyld at %#llx; CPU type is %#x.\n", addr, (int) machHeader.cputype);
|
|
}
|
|
} else {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: Mach header at %#llx is dyld but wrong CPU type (wanted %#x, got %#x).\n", addr, (int) cputype, (int) machHeader.cputype);
|
|
}
|
|
}
|
|
} else {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: Mach header at %#llx but not dyld.\n", addr);
|
|
}
|
|
}
|
|
} else {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: No Mach header at %#llx.\n", addr);
|
|
}
|
|
}
|
|
} else {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: Error %#x reading Mach header at %#llx.\n", err, addr);
|
|
}
|
|
}
|
|
} else {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "IsDyldAtAddress: Skipped region at %#llx because of protection (%d).\n", addr, (int) prot);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int FindTaskDyld(task_t task, cpu_type_t cputype, QTMAddr *dyldAddrPtr)
|
|
// Finds the address of dyld within a given task. Unfortunately, there is
|
|
// just no good way to do this because a) the task might have a different
|
|
// architecture, which might load dyld at a different address, and
|
|
// b) dyld can slide. So, we go hunting for dyld the hard way. We start
|
|
// by assuming that dyld loaded in the same place as it loaded in our
|
|
// task, which is a very strong heuristic (assuming that the target is the
|
|
// same architecture as us, which is also quite likely). If that doesn't pan
|
|
// out, we iterate through every memory region in the task look for one that
|
|
// starts with something that looks like dyld.
|
|
//
|
|
// Yetch!
|
|
//
|
|
// This is pretty much the same thing that's done by GDB and vmutils.
|
|
{
|
|
int err;
|
|
kern_return_t kr;
|
|
QTMAddr localDyldAddr;
|
|
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
|
|
mach_vm_address_t thisRegion;
|
|
#else
|
|
vm_address_t thisRegion;
|
|
#endif
|
|
assert(task != MACH_PORT_NULL);
|
|
assert(dyldAddrPtr != NULL);
|
|
|
|
// Find our dyld's address and see if the task has dyld at the same address.
|
|
|
|
localDyldAddr = GetLocalDyldAddr();
|
|
if ( IsDyldAtAddress(task, cputype, localDyldAddr, VM_PROT_READ | VM_PROT_EXECUTE) ) {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "FindTaskDyld: Got dyld at local address (%#llx).\n", localDyldAddr);
|
|
}
|
|
*dyldAddrPtr = localDyldAddr;
|
|
err = 0;
|
|
} else {
|
|
bool found;
|
|
mach_port_t junkObjName;
|
|
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "FindTaskDyld: Searching for dyld the hard way.\n");
|
|
}
|
|
|
|
// Well, that didn't work. Let's look the hard way.
|
|
|
|
found = false;
|
|
thisRegion = 0;
|
|
do {
|
|
// Because we don't actually look at the pointer size fields of the
|
|
// resulting structure, we should just be able to use VM_REGION_BASIC_INFO.
|
|
// However, it seems that VM_REGION_BASIC_INFO is not compatible with
|
|
// the 64-bit call variant (that is, mach_vm_region as opposed to
|
|
// vm_region). I really haven't investigated this properly because the
|
|
// workaround is pretty easy: use VM_REGION_BASIC_INFO_64 for
|
|
// everything. There may be compatibility consequences for this
|
|
// (for example, I suspect that VM_REGION_BASIC_INFO_COUNT_64 is not
|
|
// supported on 10.3). I'll deal with these when I come to them.
|
|
|
|
// For BOINC changes, See Mach Compatibility comments in QTaskMemory.c
|
|
|
|
mach_msg_type_number_t infoCount;
|
|
|
|
#if TARGET_CPU_X86 || TARGET_CPU_X86_64
|
|
|
|
mach_vm_size_t thisRegionSize;
|
|
vm_region_basic_info_data_64_t info;
|
|
|
|
infoCount = VM_REGION_BASIC_INFO_COUNT_64;
|
|
kr = mach_vm_region(
|
|
task,
|
|
&thisRegion,
|
|
&thisRegionSize,
|
|
VM_REGION_BASIC_INFO_64,
|
|
(vm_region_info_t) &info,
|
|
&infoCount,
|
|
&junkObjName
|
|
);
|
|
|
|
#else
|
|
|
|
vm_size_t thisRegionSize;
|
|
vm_region_basic_info_data_t info;
|
|
|
|
infoCount = VM_REGION_BASIC_INFO_COUNT;
|
|
kr = vm_region(
|
|
task,
|
|
&thisRegion,
|
|
&thisRegionSize,
|
|
VM_REGION_BASIC_INFO,
|
|
(vm_region_info_t) &info,
|
|
&infoCount,
|
|
&junkObjName
|
|
);
|
|
#endif
|
|
err = QTMErrnoFromMachError(kr);
|
|
|
|
if (err == 0) {
|
|
assert(infoCount == infoCount);
|
|
|
|
// We've found dyld if the memory region is read/no-write/execute
|
|
// and it starts with a mach_header that looks like dyld.
|
|
|
|
found = IsDyldAtAddress(task, cputype, thisRegion, info.protection);
|
|
if ( found ) {
|
|
*dyldAddrPtr = thisRegion;
|
|
} else {
|
|
thisRegion += thisRegionSize;
|
|
}
|
|
}
|
|
} while ( (err == 0) && ! found );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int FindTaskDyldWithNonNativeRetry(task_t task, cpu_type_t cputype, QTMAddr *dyldAddrPtr)
|
|
// A wrapper around FindTaskDyld that handles a nasty edge case. Namely,
|
|
// if the client asks for any dyld and the target task is being run using
|
|
// Rosetta, try to find the PowerPC dyld first and then, if that fails,
|
|
// look for any dyld. This gives an explicit priority to the PowerPC dyld
|
|
// for non-native tasks. Without this, you run into problems where a client
|
|
// doesn't know the CPU type of the target task (because they haven't connected
|
|
// to it yet), tries to connect up, connects up the wrong dyld, and it all
|
|
// goes south.
|
|
{
|
|
int err;
|
|
|
|
assert(task != MACH_PORT_NULL);
|
|
assert(dyldAddrPtr != NULL);
|
|
|
|
if ( (cputype == CPU_TYPE_ANY) && ! QTMTaskIsNative(task) ) {
|
|
err = FindTaskDyld(task, CPU_TYPE_POWERPC, dyldAddrPtr);
|
|
if (err != 0) {
|
|
if ( LogDyldSearch() ) {
|
|
fprintf(stderr, "FindTaskDyldWithNonNativeRetry: Failed to find PowerPC dyld; retrying for any dyld.\n");
|
|
}
|
|
err = FindTaskDyld(task, cputype, dyldAddrPtr);
|
|
}
|
|
} else {
|
|
err = FindTaskDyld(task, cputype, dyldAddrPtr);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#pragma mark ***** High-Level APIs
|
|
|
|
extern const char * QMOImageGetLibraryID(QMOImageRef qmoImage)
|
|
// See comment in header.
|
|
{
|
|
assert(QMOImageIsValid(qmoImage));
|
|
|
|
if ( ! qmoImage->imageIDCached ) {
|
|
const struct dylib_command * imageIDCommand;
|
|
|
|
imageIDCommand = (const struct dylib_command *) QMOImageFindLoadCommandByID(
|
|
qmoImage,
|
|
LC_ID_DYLIB
|
|
);
|
|
if (imageIDCommand != NULL) {
|
|
qmoImage->imageID = ((const char *) imageIDCommand) + QMOImageToLocalUInt32(qmoImage, imageIDCommand->dylib.name.offset);
|
|
}
|
|
|
|
qmoImage->imageIDCached = true;
|
|
}
|
|
|
|
return qmoImage->imageID;
|
|
}
|
|
|
|
extern int QMOImageIterateSymbols(QMOImageRef qmoImage, QMOSymbolIteratorProc callback, void *iteratorRefCon)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
const struct symtab_command * symTabCmd;
|
|
uint32_t linkEditSegIndex;
|
|
const char * linkEditSeg;
|
|
uint32_t symCount;
|
|
uint32_t symIndex;
|
|
QTMAddr linkEditFileOffset;
|
|
const char * stringBase;
|
|
const struct nlist * symBase;
|
|
const struct nlist_64 * symBase64;
|
|
uint32_t nameStringOffset;
|
|
const char * name;
|
|
bool stop;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(callback != NULL);
|
|
|
|
// Do some preparation. Get the LC_SYMTAB command and map the __LINKEDIT segment.
|
|
|
|
err = 0;
|
|
symTabCmd = (const struct symtab_command *) QMOImageFindLoadCommandByID(qmoImage, LC_SYMTAB);
|
|
if (symTabCmd == NULL) {
|
|
err = EINVAL;
|
|
}
|
|
if (err == 0) {
|
|
err = GetSegmentIndexByName(qmoImage, "__LINKEDIT", &linkEditSegIndex);
|
|
}
|
|
if (err == 0) {
|
|
err = QMOImageMapSegmentByIndex(qmoImage, linkEditSegIndex, &linkEditSeg);
|
|
}
|
|
|
|
// Rummage through the these two things to find the symbol list.
|
|
|
|
if (err == 0) {
|
|
symCount = QMOImageToLocalUInt32(qmoImage, symTabCmd->nsyms);
|
|
|
|
// The seg fields of the segments array have already been swapped, so we
|
|
// don't need to swap them here.
|
|
|
|
linkEditFileOffset = qmoImage->segments[linkEditSegIndex].seg.fileoff;
|
|
|
|
stringBase = linkEditSeg + QMOImageToLocalUInt32(qmoImage, symTabCmd->stroff) - linkEditFileOffset;
|
|
symBase = (const struct nlist *) (linkEditSeg + QMOImageToLocalUInt32(qmoImage, symTabCmd->symoff) - linkEditFileOffset);
|
|
|
|
stop = false;
|
|
|
|
if ( qmoImage->is64Bit ) {
|
|
symBase64 = (const struct nlist_64 *) symBase;
|
|
|
|
for (symIndex = 0; symIndex < symCount; symIndex++) {
|
|
nameStringOffset = QMOImageToLocalUInt32(qmoImage, symBase64[symIndex].n_un.n_strx);
|
|
if (nameStringOffset == 0) {
|
|
name = "";
|
|
} else {
|
|
name = stringBase + nameStringOffset;
|
|
}
|
|
|
|
err = callback(
|
|
qmoImage,
|
|
name,
|
|
QMOImageToLocalUInt8( qmoImage, symBase64[symIndex].n_type),
|
|
QMOImageToLocalUInt8( qmoImage, symBase64[symIndex].n_sect),
|
|
QMOImageToLocalUInt16(qmoImage, symBase64[symIndex].n_desc),
|
|
QMOImageToLocalUInt64(qmoImage, symBase64[symIndex].n_value),
|
|
iteratorRefCon,
|
|
&stop
|
|
);
|
|
|
|
if ( (err != 0) || stop ) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (symIndex = 0; symIndex < symCount; symIndex++) {
|
|
nameStringOffset = QMOImageToLocalUInt32(qmoImage, symBase[symIndex].n_un.n_strx);
|
|
if (nameStringOffset == 0) {
|
|
name = "";
|
|
} else {
|
|
name = stringBase + nameStringOffset;
|
|
}
|
|
|
|
err = callback(
|
|
qmoImage,
|
|
name,
|
|
QMOImageToLocalUInt8( qmoImage, symBase[symIndex].n_type),
|
|
QMOImageToLocalUInt8( qmoImage, symBase[symIndex].n_sect),
|
|
QMOImageToLocalUInt16(qmoImage, symBase[symIndex].n_desc),
|
|
QMOImageToLocalUInt32(qmoImage, symBase[symIndex].n_value),
|
|
iteratorRefCon,
|
|
&stop
|
|
);
|
|
|
|
if ( (err != 0) || stop ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// SymbolByNameIteratorContext is pointed to be the iteratorRefCon in SymbolByNameIterator.
|
|
|
|
struct SymbolByNameIteratorContext {
|
|
const char * symName;
|
|
QTMAddr symValue;
|
|
bool found;
|
|
};
|
|
typedef struct SymbolByNameIteratorContext SymbolByNameIteratorContext;
|
|
|
|
static int SymbolByNameIterator(
|
|
QMOImageRef qmoImage,
|
|
const char * name,
|
|
uint8_t type,
|
|
uint8_t sect,
|
|
uint16_t desc,
|
|
QTMAddr value,
|
|
void * iteratorRefCon,
|
|
bool * stopPtr
|
|
)
|
|
// The QMOImageIterateSymbols callback for QMOImageLookupSymbol.
|
|
{
|
|
#pragma unused(sect, desc)
|
|
int err;
|
|
SymbolByNameIteratorContext * context;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(name != NULL);
|
|
assert( stopPtr != NULL);
|
|
assert(*stopPtr == false);
|
|
|
|
err = 0;
|
|
|
|
// Check it's not a debugging symbol.
|
|
|
|
if ( ! (type & N_STAB) ) {
|
|
context = (SymbolByNameIteratorContext *) iteratorRefCon;
|
|
|
|
// See if the name matches.
|
|
|
|
if ( strcmp(name, context->symName) == 0 ) {
|
|
|
|
// Handle the symbol differently depending on its type.
|
|
|
|
switch (type & N_TYPE) {
|
|
case N_ABS:
|
|
context->symValue = value;
|
|
context->found = true;
|
|
*stopPtr = true;
|
|
break;
|
|
case N_SECT:
|
|
context->symValue = value + qmoImage->slide;
|
|
context->found = true;
|
|
*stopPtr = true;
|
|
break;
|
|
case N_INDR:
|
|
// *** should handle this, but it's hard
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
extern int QMOImageLookupSymbol(QMOImageRef qmoImage, const char *symName, QTMAddr *valuePtr)
|
|
// See comment in header.
|
|
{
|
|
int err;
|
|
SymbolByNameIteratorContext context;
|
|
|
|
assert(QMOImageIsValid(qmoImage));
|
|
assert(symName != NULL);
|
|
assert(valuePtr != NULL);
|
|
|
|
context.symName = symName;
|
|
context.symValue = 0;
|
|
context.found = false;
|
|
err = QMOImageIterateSymbols(qmoImage, SymbolByNameIterator, &context);
|
|
if (err == 0) {
|
|
if (context.found) {
|
|
*valuePtr = context.symValue;
|
|
} else {
|
|
err = ESRCH;
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|