boinc/lib/mac/QMachOImage.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 = &sectArray64[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 = &sectArray[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, &sectIndex);
}
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;
}