diff options
Diffstat (limited to 'Mac/Modules/cg/CFMLateImport.c')
-rwxr-xr-x | Mac/Modules/cg/CFMLateImport.c | 1360 |
1 files changed, 1360 insertions, 0 deletions
diff --git a/Mac/Modules/cg/CFMLateImport.c b/Mac/Modules/cg/CFMLateImport.c new file mode 100755 index 0000000..bc2976e --- /dev/null +++ b/Mac/Modules/cg/CFMLateImport.c @@ -0,0 +1,1360 @@ +/* + File: CFMLateImport.c + + Contains: Implementation of CFM late import library. + + Written by: Quinn + + Copyright: Copyright © 1999 by Apple Computer, Inc., all rights reserved. + + You may incorporate this Apple sample source code into your program(s) without + restriction. This Apple sample source code has been provided "AS IS" and the + responsibility for its operation is yours. You are not permitted to redistribute + this Apple sample source code as "Apple sample source code" after having made + changes. If you're going to re-distribute the source, we require that you make + it clear in the source that the code was descended from Apple sample source + code, but that you've made changes. + + Change History (most recent first): + + <13> 24/9/01 Quinn Fixes to compile with C++ activated. + <12> 21/9/01 Quinn [2710489] Fix typo in the comments for FragmentLookup. + <11> 21/9/01 Quinn Changes for CWPro7 Mach-O build. + <10> 19/9/01 Quinn Corrected implementation of kPEFRelocSmBySection. Added + implementations of kPEFRelocSetPosition and kPEFRelocLgByImport + (from code contributed by Eric Grant, Ned Holbrook, and Steve + Kalkwarf), although I can't test them yet. + <9> 19/9/01 Quinn We now handle unpacked data sections, courtesy of some code from + Ned Holbrook. + <8> 19/9/01 Quinn Minor fixes for the previous checkin. Updated some comments and + killed some dead code. + <7> 19/9/01 Quinn Simplified API and implementation after a suggestion by Eric + Grant. You no longer have to CFM export a dummy function; you + can just pass in the address of your fragment's init routine. + <6> 15/2/01 Quinn Modify compile-time warnings to complain if you try to build + this module into a Mach-O binary. + <5> 5/2/01 Quinn Removed redundant assignment in CFMLateImportCore. + <4> 30/11/00 Quinn Added comment about future of data symbols in CF. + <3> 16/11/00 Quinn Allow symbol finding via a callback and use that to implement + CFBundle support. + <2> 18/10/99 Quinn Renamed CFMLateImport to CFMLateImportLibrary to allow for + possible future API expansion. + <1> 15/6/99 Quinn First checked in. +*/ + +// To Do List: +// +// o get rid of dependence on ANSI "string.h", but how? +// +// Done: +// +// Ã investigate alternative APIs, like an external lookup routine +// renamed CFMLateImport to CFMLateImportLibrary to allow for +// future expansion of the APIs for things like CFMLateImportSymbol +// Ã test with non-zero fragment offset in the file +// Ã test more with MPW fragments +// Ã test data imports + +///////////////////////////////////////////////////////////////// + +// MoreIsBetter Setup + +//#include "MoreSetup.h" +#define MoreAssert(x) (true) +#define MoreAssertQ(x) + +// Mac OS Interfaces + +#if ! MORE_FRAMEWORK_INCLUDES + #include <CodeFragments.h> + #include <PEFBinaryFormat.h> +#endif + +// Standard C Interfaces + +#include <string.h> + +// MIB Prototypes + +//#include "MoreInterfaceLib.h" +#define MoreBlockZero BlockZero + +// Our Prototypes + +#include "CFMLateImport.h" + +///////////////////////////////////////////////////////////////// + +#if TARGET_RT_MAC_MACHO + #error CFMLateImport is not suitable for use in a Mach-O project. +#elif !TARGET_RT_MAC_CFM || !TARGET_CPU_PPC + #error CFMLateImport has not been qualified for 68K or CFM-68K use. +#endif + +///////////////////////////////////////////////////////////////// +#pragma mark ----- Utility Routines ----- + +static OSStatus FSReadAtOffset(SInt16 refNum, SInt32 offset, SInt32 count, void *buffer) + // A convenient wrapper around PBRead which has two advantages + // over FSRead. First, it takes count as a value parameter. + // Second, it reads from an arbitrary offset into the file, + // which avoids a bunch of SetFPos calls. + // + // I guess this should go into "MoreFiles.h", but I'm not sure + // how we're going to integrate such a concept into MIB yet. +{ + ParamBlockRec pb; + + pb.ioParam.ioRefNum = refNum; + pb.ioParam.ioBuffer = (Ptr) buffer; + pb.ioParam.ioReqCount = count; + pb.ioParam.ioPosMode = fsFromStart; + pb.ioParam.ioPosOffset = offset; + + return PBReadSync(&pb); +} + +///////////////////////////////////////////////////////////////// +#pragma mark ----- Late Import Engine ----- + +// This structure represents the core data structure of the late import +// engine. It basically holds information about the fragment we're going +// to fix up. It starts off with the first three fields, which are +// provided by the client. Then, as we procede through the operation, +// we fill out more fields. + +struct FragToFixInfo { + CFragSystem7DiskFlatLocator locator; // How to find the fragment's container. + CFragConnectionID connID; // CFM connection to the fragment. + CFragInitFunction initRoutine; // The CFM init routine for the fragment. + PEFContainerHeader containerHeader; // The CFM header, read in from the container. + PEFSectionHeader *sectionHeaders; // The CFM section headers. A pointer block containing an array of containerHeader.sectionCount elements. + PEFLoaderInfoHeader *loaderSection; // The entire CFM loader section in a pointer block. + SInt16 fileRef; // A read-only path to the CFM container. We keep this here because one that one routine needs to read from the container. + void *section0Base; // The base address of section 0, which we go through hoops to calculate. + void *section1Base; // The base address of section 1, which we go through hoops to calculate. + Boolean disposeSectionPointers; // See below. +}; +typedef struct FragToFixInfo FragToFixInfo; + +// The disposeSectionPointers Boolean is designed for future cool VM +// support. If VM is on, the entire code fragment is file mapped into +// high memory, including the data we're forced to allocate the +// sectionHeaders and loaderSection memory blocks to maintain. If +// we could find the address of the entire file mapped container, +// we could access the information directly from there and thus +// we wouldn't need to allocate (or dispose of) the memory blocks +// for sectionHeaders and loaderSection. +// +// I haven't implemented this yet because a) I'm not sure how to do +// it with documented APIs, and b) I couldn't be bothered, but +// disposeSectionPointers remains as vestigial support for the concept. + +static OSStatus ReadContainerBasics(FragToFixInfo *fragToFix) + // Reads some basic information from the container of the + // fragment to fix and stores it in various fields of + // fragToFix. This includes: + // + // o containerHeader -- The contain header itself. + // o sectionHeaders -- The array of section headers (in a newly allocated pointer block). + // o loaderSection -- The entire loader section (in a newly allocated pointer block). + // + // Also sets disposeSectionPointers to indicate whether + // the last two pointers should be disposed of. + // + // Finally, it leaves the container file open for later + // folks who want to read data from it. +{ + OSStatus err; + UInt16 sectionIndex; + Boolean found; + + MoreAssertQ(fragToFix != nil); + MoreAssertQ(fragToFix->locator.fileSpec != nil); + MoreAssertQ(fragToFix->connID != nil); + MoreAssertQ(fragToFix->loaderSection == nil); + MoreAssertQ(fragToFix->sectionHeaders == nil); + MoreAssertQ(fragToFix->fileRef == 0); + + fragToFix->disposeSectionPointers = true; + + // Open up the file, read the container head, then read in + // all the section headers, then go looking through the + // section headers for the loader section (PEF defines + // that there can be only one). + + err = FSpOpenDF(fragToFix->locator.fileSpec, fsRdPerm, &fragToFix->fileRef); + if (err == noErr) { + err = FSReadAtOffset(fragToFix->fileRef, + fragToFix->locator.offset, + sizeof(fragToFix->containerHeader), + &fragToFix->containerHeader); + if (err == noErr) { + if ( fragToFix->containerHeader.tag1 != kPEFTag1 + || fragToFix->containerHeader.tag2 != kPEFTag2 + || fragToFix->containerHeader.architecture != kCompiledCFragArch + || fragToFix->containerHeader.formatVersion != kPEFVersion) { + err = cfragFragmentFormatErr; + } + } + if (err == noErr) { + fragToFix->sectionHeaders = (PEFSectionHeader *) NewPtr(fragToFix->containerHeader.sectionCount * sizeof(PEFSectionHeader)); + err = MemError(); + } + if (err == noErr) { + err = FSReadAtOffset(fragToFix->fileRef, + fragToFix->locator.offset + sizeof(fragToFix->containerHeader), + fragToFix->containerHeader.sectionCount * sizeof(PEFSectionHeader), + fragToFix->sectionHeaders); + } + if (err == noErr) { + sectionIndex = 0; + found = false; + while ( sectionIndex < fragToFix->containerHeader.sectionCount && ! found ) { + found = (fragToFix->sectionHeaders[sectionIndex].sectionKind == kPEFLoaderSection); + if ( ! found ) { + sectionIndex += 1; + } + } + } + if (err == noErr && ! found) { + err = cfragNoSectionErr; + } + + // Now read allocate a pointer block and read the loader section into it. + + if (err == noErr) { + fragToFix->loaderSection = (PEFLoaderInfoHeader *) NewPtr(fragToFix->sectionHeaders[sectionIndex].containerLength); + err = MemError(); + } + if (err == noErr) { + err = FSReadAtOffset(fragToFix->fileRef, + fragToFix->locator.offset + fragToFix->sectionHeaders[sectionIndex].containerOffset, + fragToFix->sectionHeaders[sectionIndex].containerLength, + fragToFix->loaderSection); + } + } + + // No clean up. The client must init fragToFix to zeros and then + // clean up regardless of whether we return an error. + + return err; +} + +static UInt32 DecodeVCountValue(const UInt8 *start, UInt32 *outCount) + // Given a pointer to the start of a variable length PEF value, + // work out the value (in *outCount). Returns the number of bytes + // consumed by the value. +{ + UInt8 * bytePtr; + UInt8 byte; + UInt32 count; + + bytePtr = (UInt8 *)start; + + // Code taken from "PEFBinaryFormat.h". + count = 0; + do { + byte = *bytePtr++; + count = (count << kPEFPkDataVCountShift) | (byte & kPEFPkDataVCountMask); + } while ((byte & kPEFPkDataVCountEndMask) != 0); + + *outCount = count; + return bytePtr - start; +} + +static UInt32 DecodeInstrCountValue(const UInt8 *inOpStart, UInt32 *outCount) + // Given a pointer to the start of an opcode (inOpStart), work out the + // count argument for that opcode (*outCount). Returns the number of + // bytes consumed by the opcode and count combination. +{ + MoreAssertQ(inOpStart != nil); + MoreAssertQ(outCount != nil); + + if (PEFPkDataCount5(*inOpStart) != 0) + { + // Simple case, count encoded in opcode. + *outCount = PEFPkDataCount5(*inOpStart); + return 1; + } + else + { + // Variable-length case. + return 1 + DecodeVCountValue(inOpStart + 1, outCount); + } +} + +static OSStatus UnpackPEFDataSection(const UInt8 * const packedData, UInt32 packedSize, + UInt8 * const unpackedData, UInt32 unpackedSize) +{ + OSErr err; + UInt32 offset; + UInt8 opCode; + UInt8 * unpackCursor; + + MoreAssertQ(packedData != nil); + MoreAssertQ(unpackedData != nil); + MoreAssertQ(unpackedSize >= packedSize); + + // The following asserts assume that the client allocated the memory with NewPtr, + // which may not always be true. However, the asserts' value in preventing accidental + // memory block overruns outweighs the possible maintenance effort. + + MoreAssertQ( packedSize == GetPtrSize( (Ptr) packedData ) ); + MoreAssertQ( unpackedSize == GetPtrSize( (Ptr) unpackedData) ); + + err = noErr; + offset = 0; + unpackCursor = unpackedData; + while (offset < packedSize) { + MoreAssertQ(unpackCursor < &unpackedData[unpackedSize]); + + opCode = packedData[offset]; + + switch (PEFPkDataOpcode(opCode)) { + case kPEFPkDataZero: + { + UInt32 count; + + offset += DecodeInstrCountValue(&packedData[offset], &count); + + MoreBlockZero(unpackCursor, count); + unpackCursor += count; + } + break; + + case kPEFPkDataBlock: + { + UInt32 blockSize; + + offset += DecodeInstrCountValue(&packedData[offset], &blockSize); + + BlockMoveData(&packedData[offset], unpackCursor, blockSize); + unpackCursor += blockSize; + offset += blockSize; + } + break; + + case kPEFPkDataRepeat: + { + UInt32 blockSize; + UInt32 repeatCount; + UInt32 loopCounter; + + offset += DecodeInstrCountValue(&packedData[offset], &blockSize); + offset += DecodeVCountValue(&packedData[offset], &repeatCount); + repeatCount += 1; // stored value is (repeatCount - 1) + + for (loopCounter = 0; loopCounter < repeatCount; loopCounter++) { + BlockMoveData(&packedData[offset], unpackCursor, blockSize); + unpackCursor += blockSize; + } + offset += blockSize; + } + break; + + case kPEFPkDataRepeatBlock: + { + UInt32 commonSize; + UInt32 customSize; + UInt32 repeatCount; + const UInt8 *commonData; + const UInt8 *customData; + UInt32 loopCounter; + + offset += DecodeInstrCountValue(&packedData[offset], &commonSize); + offset += DecodeVCountValue(&packedData[offset], &customSize); + offset += DecodeVCountValue(&packedData[offset], &repeatCount); + + commonData = &packedData[offset]; + customData = &packedData[offset + commonSize]; + + for (loopCounter = 0; loopCounter < repeatCount; loopCounter++) { + BlockMoveData(commonData, unpackCursor, commonSize); + unpackCursor += commonSize; + BlockMoveData(customData, unpackCursor, customSize); + unpackCursor += customSize; + customData += customSize; + } + BlockMoveData(commonData, unpackCursor, commonSize); + unpackCursor += commonSize; + offset += (repeatCount * (commonSize + customSize)) + commonSize; + } + break; + + case kPEFPkDataRepeatZero: + { + UInt32 commonSize; + UInt32 customSize; + UInt32 repeatCount; + const UInt8 *customData; + UInt32 loopCounter; + + offset += DecodeInstrCountValue(&packedData[offset], &commonSize); + offset += DecodeVCountValue(&packedData[offset], &customSize); + offset += DecodeVCountValue(&packedData[offset], &repeatCount); + + customData = &packedData[offset]; + + for (loopCounter = 0; loopCounter < repeatCount; loopCounter++) { + MoreBlockZero(unpackCursor, commonSize); + unpackCursor += commonSize; + BlockMoveData(customData, unpackCursor, customSize); + unpackCursor += customSize; + customData += customSize; + } + MoreBlockZero(unpackCursor, commonSize); + unpackCursor += commonSize; + offset += repeatCount * customSize; + } + break; + + default: + #if MORE_DEBUG + DebugStr("\pUnpackPEFDataSection: Unexpected data opcode"); + #endif + err = cfragFragmentCorruptErr; + goto leaveNow; + break; + } + } + +leaveNow: + return err; +} + +/* SetupSectionBaseAddresses Rationale + ----------------------------------- + + OK, here's where things get weird. In order to run the relocation + engine, I need to be able to find the base address of an instantiated + section of the fragment we're fixing up given only its section number. + This isn't hard for CFM to do because it's the one that instantiated the + sections in the first place. It's surprisingly difficult to do if + you're not CFM. [And you don't have access to the private CFM APis for + doing it.] + + [Alan Lillich is going to kill me when he reads this! I should point out + that TVector's don't have to contain two words, they can be longer, + and that the second word isn't necessarily a TOC pointer, it's + just that the calling conventions require that it be put in the + TOC register when the code is called. + + Furthermore, the code section isn't always section 0, and the data + section isn't always section 1, and there can be zero to many sections + of each type. + + But these niceties are besides the point: I'm doing something tricky + because I don't have a nice API for getting section base addresses. + If I had a nice API for doing that, none of this code would exist. + ] + + The technique is very sneaky (thanks to Eric Grant). The fragment to + fix necessarily has a CFM init routine (because it needs that routine + in order to capture the fragment location and connection ID). Thus the + fragment to fix must have a TVector in its data section. TVectors are + interesting because they're made up of two words. The first is a pointer + to the code that implements the routine; the second is a pointer to the TOC + for the fragment that's exporting the TVector. How TVectors are + created is interesting too. On disk, a TVector consists of two words, + the first being the offset from the start of the code section to the + routine, the second being the offset from the start of the data section + to the TOC base. When CFM prepares a TVector, it applies the following + transform: + + tvector.codePtr = tvector.codeOffset + base of code section + tvector.tocPtr = tvector.tocOffset + base of data section + + Now, you can reverse these questions to make them: + + base of code section = tvector.codePtr - tvector.codeOffset + base of data section = tvector.dataPtr - tvector.dataOffset + + So if you can find the relocated contents of the TVector and + find the original offsets that made up the TVector, you can then + calculate the base address of both the code and data sections. + + Finding the relocated contents of the TVector is easy; I simply + require the client to pass in a pointer to its init routine. + A routine pointer is a TVector pointer, so you can just cast it + and extract the pair of words. + + Finding the original offsets is a trickier. My technique is to + look up the init routine in the fragment's loader info header. This + yields the section number and offset where the init routine's unrelocated + TVector exists. Once I have that, I can just read the unrelocated TVector + out of the file and extract the offsets. +*/ + +struct TVector { + void *codePtr; + void *tocPtr; +}; +typedef struct TVector TVector; + +static OSStatus SetupSectionBaseAddresses(FragToFixInfo *fragToFix) + // This routine initialises the section0Base and section1Base + // base fields of fragToFix to the base addresses of the + // instantiated fragment represented by the other fields + // of fragToFix. The process works in three states: + // + // 1. Find the contents of the relocated TVector of the + // fragment's initialisation routine, provided to us by + // the caller. + // + // 2. Find the contents of the non-relocated TVector by + // looking it up in the PEF loader info header and then + // using that to read the TVector contents from disk. + // This yields the offsets from the section bases for + // the init routine. + // + // 3. Subtract 2 from 3. +{ + OSStatus err; + TVector * relocatedExport; + SInt32 initSection; + UInt32 initOffset; + PEFSectionHeader * initSectionHeader; + Ptr packedDataSection; + Ptr unpackedDataSection; + TVector originalOffsets; + + packedDataSection = nil; + unpackedDataSection = nil; + + // Step 1. + + // First find the init routine's TVector, which gives us the relocated + // offsets of the init routine into the data and code sections. + + relocatedExport = (TVector *) fragToFix->initRoutine; + + // Step 2. + + // Now find the init routine's TVector's offsets in the data section on + // disk. This gives us the raw offsets from the data and code section + // of the beginning of the init routine. + + err = noErr; + initSection = fragToFix->loaderSection->initSection; + initOffset = fragToFix->loaderSection->initOffset; + if (initSection == -1) { + err = cfragFragmentUsageErr; + } + if (err == noErr) { + MoreAssertQ( initSection >= 0 ); // Negative indexes are pseudo-sections which are just not allowed! + MoreAssertQ( initSection < fragToFix->containerHeader.sectionCount ); + + initSectionHeader = &fragToFix->sectionHeaders[initSection]; + + // If the data section is packed, unpack it to a temporary buffer and then get the + // original offsets from that buffer. If the data section is unpacked, just read + // the original offsets directly off the disk. + + if ( initSectionHeader->sectionKind == kPEFPackedDataSection ) { + + // Allocate space for packed and unpacked copies of the section. + + packedDataSection = NewPtr(initSectionHeader->containerLength); + err = MemError(); + + if (err == noErr) { + unpackedDataSection = NewPtr(initSectionHeader->unpackedLength); + err = MemError(); + } + + // Read the contents of the packed section. + + if (err == noErr) { + err = FSReadAtOffset( fragToFix->fileRef, + fragToFix->locator.offset + + initSectionHeader->containerOffset, + initSectionHeader->containerLength, + packedDataSection); + } + + // Unpack the data into the unpacked section. + + if (err == noErr) { + err = UnpackPEFDataSection( (UInt8 *) packedDataSection, initSectionHeader->containerLength, + (UInt8 *) unpackedDataSection, initSectionHeader->unpackedLength); + } + + // Extract the init routine's TVector from the unpacked section. + + if (err == noErr) { + BlockMoveData(unpackedDataSection + initOffset, &originalOffsets, sizeof(TVector)); + } + + } else { + MoreAssertQ(fragToFix->sectionHeaders[initSection].sectionKind == kPEFUnpackedDataSection); + err = FSReadAtOffset(fragToFix->fileRef, + fragToFix->locator.offset + + fragToFix->sectionHeaders[initSection].containerOffset + + initOffset, + sizeof(TVector), + &originalOffsets); + } + } + + // Step 3. + + // Do the maths to subtract the unrelocated offsets from the current address + // to get the base address. + + if (err == noErr) { + fragToFix->section0Base = ((char *) relocatedExport->codePtr) - (UInt32) originalOffsets.codePtr; + fragToFix->section1Base = ((char *) relocatedExport->tocPtr) - (UInt32) originalOffsets.tocPtr; + } + + // Clean up. + + if (packedDataSection != nil) { + DisposePtr(packedDataSection); + MoreAssertQ( MemError() == noErr ); + } + if (unpackedDataSection != nil) { + DisposePtr(unpackedDataSection); + MoreAssertQ( MemError() == noErr ); + } + return err; +} + +static void *GetSectionBaseAddress(const FragToFixInfo *fragToFix, UInt16 sectionIndex) + // This routine returns the base of the instantiated section + // whose index is sectionIndex. This routine is the evil twin + // of SetupSectionBaseAddresses. It simply returns the values + // for section 0 and 1 that we derived in SetupSectionBaseAddresses. + // In a real implementation, this routine would call CFM API + // to get this information, and SetupSectionBaseAddresses would + // not exist, but CFM does not export the necessary APIs to + // third parties. +{ + void *result; + + MoreAssertQ(fragToFix != nil); + MoreAssertQ(fragToFix->containerHeader.tag1 == kPEFTag1); + + switch (sectionIndex) { + case 0: + result = fragToFix->section0Base; + break; + case 1: + result = fragToFix->section1Base; + break; + default: + result = nil; + break; + } + return result; +} + + +static OSStatus FindImportLibrary(PEFLoaderInfoHeader *loaderSection, const char *libraryName, PEFImportedLibrary **importLibrary) + // This routine finds the import library description (PEFImportedLibrary) + // for the import library libraryName in the PEF loader section. + // It sets *importLibrary to the address of the description. +{ + OSStatus err; + UInt32 librariesRemaining; + PEFImportedLibrary *thisImportLibrary; + Boolean found; + + MoreAssertQ(loaderSection != nil); + MoreAssertQ(libraryName != nil); + MoreAssertQ(importLibrary != nil); + + // Loop through each import library looking for a matching name. + + // Initialise thisImportLibrary to point to the byte after the + // end of the loader section's header. + + thisImportLibrary = (PEFImportedLibrary *) (loaderSection + 1); + librariesRemaining = loaderSection->importedLibraryCount; + found = false; + while ( librariesRemaining > 0 && ! found ) { + // PEF defines that import library names will have + // a null terminator, so we can just use strcmp. + found = (strcmp( libraryName, + ((char *)loaderSection) + + loaderSection->loaderStringsOffset + + thisImportLibrary->nameOffset) == 0); + // *** Remove ANSI strcmp eventually. + if ( ! found ) { + thisImportLibrary += 1; + librariesRemaining -= 1; + } + } + + if (found) { + *importLibrary = thisImportLibrary; + err = noErr; + } else { + *importLibrary = nil; + err = cfragNoLibraryErr; + } + return err; +} + +static OSStatus LookupSymbol(CFMLateImportLookupProc lookup, void *refCon, + PEFLoaderInfoHeader *loaderSection, + UInt32 symbolIndex, + UInt32 *symbolValue) + // This routine is used to look up a symbol during relocation. + // "lookup" is a client callback and refCon is its argument. + // Typically refCon is the CFM connection to the library that is + // substituting for the weak linked library. loaderSection + // is a pointer to the loader section of the fragment to fix up. + // symbolIndex is the index of the imported symbol in the loader section. + // The routine sets the word pointed to by symbolValue to the + // value of the symbol. + // + // The routine works by using symbolIndex to index into the imported + // symbol table to find the offset of the symbol's name in the string + // table. It then looks up the symbol by calling the client's "lookup" + // function and passes the resulting symbol address back in symbolValue. +{ + OSStatus err; + UInt32 *importSymbolTable; + UInt32 symbolStringOffset; + Boolean symbolIsWeak; + CFragSymbolClass symbolClass; + char *symbolStringAddress; + Str255 symbolString; + + MoreAssertQ(lookup != nil); + MoreAssertQ(loaderSection != nil); + MoreAssertQ(symbolIndex < loaderSection->totalImportedSymbolCount); + MoreAssertQ(symbolValue != nil); + + // Find the base of the imported symbol table. + + importSymbolTable = (UInt32 *)(((char *)(loaderSection + 1)) + (loaderSection->importedLibraryCount * sizeof(PEFImportedLibrary))); + + // Grab the appropriate entry out of the table and + // extract the information from that entry. + + symbolStringOffset = importSymbolTable[symbolIndex]; + symbolClass = PEFImportedSymbolClass(symbolStringOffset); + symbolIsWeak = ((symbolClass & kPEFWeakImportSymMask) != 0); + symbolClass = symbolClass & ~kPEFWeakImportSymMask; + symbolStringOffset = PEFImportedSymbolNameOffset(symbolStringOffset); + + // Find the string for the symbol in the strings table and + // extract it from the table into a Pascal string on the stack. + + symbolStringAddress = ((char *)loaderSection) + loaderSection->loaderStringsOffset + symbolStringOffset; + symbolString[0] = strlen(symbolStringAddress); // *** remove ANSI strlen + BlockMoveData(symbolStringAddress, &symbolString[1], symbolString[0]); + + // Look up the symbol in substitute library. If it fails, return + // a 0 value and check whether the error is fatal (a strong linked + // symbol) or benign (a weak linked symbol). + + err = lookup(symbolString, symbolClass, (void **) symbolValue, refCon); + if (err != noErr) { + *symbolValue = 0; + if (symbolIsWeak) { + err = noErr; + } + } + return err; +} + +// The EngineState structure encapsulates all of the persistent state +// of the CFM relocation engine virtual machine. I originally defined +// this structure so I could pass the state around between routines +// that implement various virtual opcodes, however I later worked +// out that the relocation was sufficiently simple that I could put it +// in in one routine. Still, I left the state in this structure in +// case I ever need to reverse that decision. It's also a convenient +// instructional design. + +struct EngineState { + UInt32 currentReloc; // Index of current relocation opcodes + UInt32 terminatingReloc; // Index of relocation opcodes which terminates relocation + UInt32 *sectionBase; // Start of the section + UInt32 *relocAddress; // Address within the section where the relocations are to be performed + UInt32 importIndex; // Symbol index, which is used to access an imported symbol's address + void *sectionC; // Memory address of an instantiated section within the PEF container; this variable is used by relocation opcodes that relocate section addresses + void *sectionD; // Memory address of an instantiated section within the PEF container; this variable is used by relocation opcodes that relocate section addresses +}; +typedef struct EngineState EngineState; + +// Note: +// If I ever have to support the repeat opcodes, I'll probably +// have to add a repeat counter to EngineState. + +static OSStatus InitEngineState(const FragToFixInfo *fragToFix, + UInt16 relocHeaderIndex, + EngineState *state) + // This routine initialises the engine state suitably for + // running the relocation opcodes for the section whose + // index is relocHeaderIndex. relocHeaderIndex is not a + // a section number. See the comment where it's used below + // for details. The routine basically fills out all the fields + // in the EngineState structure as described by + // "Mac OS Runtime Architectures". +{ + OSStatus err; + PEFLoaderRelocationHeader *relocHeader; + + MoreAssertQ(fragToFix != nil); + MoreAssertQ(state != nil); + + // This bit is tricky. relocHeaderIndex is an index into the relocation + // header table, starting at relocSectionCount (which is in the loader + // section header) for the first relocated section and decrementing + // down to 1 for the last relocated section. I find the relocation + // header by using relocHeaderIndex as a index backwards from the + // start of the relocation opcodes (ie relocInstrOffset). If you + // look at the diagram of the layout of the container in + // "PEFBinaryFormat.h", you'll see that the relocation opcodes + // immediately follow the relocation headers. + // + // I did this because the alternative (starting at the loader + // header and stepping past the import library table and the + // import symbol table) was a pain. + + relocHeader = (PEFLoaderRelocationHeader *) (((char *) fragToFix->loaderSection) + fragToFix->loaderSection->relocInstrOffset - relocHeaderIndex * sizeof(PEFLoaderRelocationHeader)); + + MoreAssertQ(relocHeader->reservedA == 0); // PEF spec says it must be; we check to try to catch bugs in calculation of relocHeader + + state->currentReloc = relocHeader->firstRelocOffset; + state->terminatingReloc = relocHeader->firstRelocOffset + relocHeader->relocCount; + state->sectionBase = (UInt32 *) GetSectionBaseAddress(fragToFix, relocHeader->sectionIndex); + state->relocAddress = state->sectionBase; + state->importIndex = 0; + + // From "Mac OS Runtime Architectures": + // + // The sectionC and sectionD variables actually contain the + // memory address of an instantiated section minus the + // default address for that section. The default address for a + // section is contained in the defaultAddress field of the + // section header. However, in almost all cases the default + // address should be 0, so the simplified definition suffices. + // + // In the debug version, we drop into MacsBug if this weird case + // ever executes because it's more likely we made a mistake than + // we encountered a section with a default address. + + state->sectionC = GetSectionBaseAddress(fragToFix, 0); + if (state->sectionC != nil) { + #if MORE_DEBUG + if (fragToFix->sectionHeaders[0].defaultAddress != 0) { + DebugStr("\pInitEngineState: Executing weird case."); + } + #endif + (char *) state->sectionC -= fragToFix->sectionHeaders[0].defaultAddress; + } + state->sectionD = GetSectionBaseAddress(fragToFix, 1); + if (state->sectionD != nil) { + #if MORE_DEBUG + if (fragToFix->sectionHeaders[1].defaultAddress != 0) { + DebugStr("\pInitEngineState: Executing weird case."); + } + #endif + (char *) state->sectionD -= fragToFix->sectionHeaders[1].defaultAddress; + } + + err = noErr; + if (state->relocAddress == nil) { + err = cfragFragmentUsageErr; + } + return err; +} + +// kPEFRelocBasicOpcodes is a table that maps the top 7 bits of the opcode +// to a fundamental action. It's contents are defined for me in "PEFBinaryFormat.h", +// which is really convenient. + +static UInt8 kPEFRelocBasicOpcodes[kPEFRelocBasicOpcodeRange] = { PEFMaskedBasicOpcodes }; + +static OSStatus RunRelocationEngine(const FragToFixInfo *fragToFix, + PEFImportedLibrary *importLibrary, + CFMLateImportLookupProc lookup, void *refCon) + // This is where the rubber really hits the. Given a fully + // populated fragToFix structure, the import library description + // of the weak imported library we're resolving, and a connection + // to the library we're going to substitute it, re-execute the + // relocation instructions (CFM has already executed them once) + // but only *do* instructions (ie store the change to the data section) + // that CFM skipped because the weak symbols were missing. +{ + OSStatus err; + EngineState state; + UInt16 sectionsLeftToRelocate; + UInt32 totalRelocs; + UInt16 *relocInstrTable; + UInt16 opCode; + + MoreAssertQ(fragToFix != nil); + MoreAssertQ(fragToFix->containerHeader.tag1 == kPEFTag1); + MoreAssertQ(fragToFix->sectionHeaders != nil); + MoreAssertQ(fragToFix->loaderSection != nil); + MoreAssertQ(fragToFix->section0Base != nil); // Technically, having a nil for these two is not a problem, ... + MoreAssertQ(fragToFix->section1Base != nil); // but in practise it a wildly deviant case and we should know about it. + MoreAssertQ(importLibrary != nil); + MoreAssertQ(lookup != nil); + + // Before entering the loop, work out some information in advance. + + // totalRelocs is only used for debugging, to make sure our + // relocation PC (state.currentReloc) doesn't run wild. + + totalRelocs = (fragToFix->loaderSection->loaderStringsOffset - fragToFix->loaderSection->relocInstrOffset) / sizeof(UInt16); + + // relocInstrTable is the base address of the table of relocation + // instructions in the fragment to fix. + + relocInstrTable = (UInt16 *)((char *) fragToFix->loaderSection + fragToFix->loaderSection->relocInstrOffset); + + // sectionsLeftToRelocate is the loop counter for the outer loop. + + MoreAssertQ(fragToFix->loaderSection->relocSectionCount <= 0x0FFFF); + sectionsLeftToRelocate = fragToFix->loaderSection->relocSectionCount; + + // Now let's run the relocation engine. We run it once per + // section in the table. Each time around, we init the engine + // and then loop again, this time executing individual opcodes. + // The opcode loop terminates when the relocation PC + // (state.currentReloc) hits the final opcode (state.terminatingReloc). + + // Note: + // One design decision I made was to totally re-init the engine state + // for each section. The CFM spec is unclear as to whether you're supposed + // to totally re-init the engine state, or just re-init the section-specific + // state (ie currentReloc, terminatingReloc, and relocAddress). I hope this + // is correct, but it's hard to test without having a fragment with multiple + // relocated sections, which is difficult to create. + + // How do I decide which opcodes should be effective (ie make changes to + // the section being relocated) and which opcodes should just be executed + // for their side effects (ie updated state.relocAddress or state.importIndex)? + // The answer is both simple and subtle. Opcodes whose actions are dependent + // on a symbol that was in the weak linked library are effective, those that + // an independent of those symbols are not. The only opcodes that use + // symbolic values are kPEFRelocImportRun and kPEFRelocSmByImport, and + // these are only if the symbol is in the weak linked library. + // All other cases are executed for their side effects only. + // + // How do I determine if a symbol is in the weak linked library? + // Well I know the symbol's index and I know the lower bound and count + // of the symbols in the weak linked library, so I just do a simple + // bounds test, ie + // + // firstImportedSymbol <= importIndex < firstImportedSymbol + importedSymbolCount + + // From this code, it's relatively easy to see which relocation opcodes + // aren't implemented. If you ever encounter one, you'll find yourself + // in MacsBug with a message telling you which opcode was found. The + // two big groups of opcodes I skipped were the large format opcodes + // and the repeating opcodes. I skipped them because: + // + // a) I haven't got a way to generate them in a PEF container that I can + // test against. Without that, there's no way I could be assured that + // the code worked. + // + // b) I'm lazy. + + err = noErr; + while ( sectionsLeftToRelocate > 0 ) { + err = InitEngineState(fragToFix, sectionsLeftToRelocate, &state); + if (err != noErr) { + goto leaveNow; + } + + while ( state.currentReloc != state.terminatingReloc ) { + + MoreAssertQ( state.currentReloc < totalRelocs ); + + opCode = relocInstrTable[state.currentReloc]; + switch ( PEFRelocBasicOpcode(opCode) ) { + case kPEFRelocBySectDWithSkip: + { + UInt16 skipCount; + UInt16 relocCount; + + skipCount = ((opCode >> 6) & 0x00FF); + relocCount = (opCode & 0x003F); + state.relocAddress += skipCount; + state.relocAddress += relocCount; + } + break; + case kPEFRelocBySectC: + case kPEFRelocBySectD: + { + UInt16 runLength; + + runLength = (opCode & 0x01FF) + 1; + state.relocAddress += runLength; + } + break; + case kPEFRelocTVector12: + { + UInt16 runLength; + + runLength = (opCode & 0x01FF) + 1; + state.relocAddress += (runLength * 3); + } + break; + case kPEFRelocTVector8: + case kPEFRelocVTable8: + { + UInt16 runLength; + + runLength = (opCode & 0x01FF) + 1; + state.relocAddress += (runLength * 2); + } + break; + case kPEFRelocImportRun: + { + UInt32 symbolValue; + UInt16 runLength; + + runLength = (opCode & 0x01FF) + 1; + while (runLength > 0) { + if ( state.importIndex >= importLibrary->firstImportedSymbol && state.importIndex < (importLibrary->firstImportedSymbol + importLibrary->importedSymbolCount) ) { + err = LookupSymbol(lookup, refCon, fragToFix->loaderSection, state.importIndex, &symbolValue); + if (err != noErr) { + goto leaveNow; + } + *(state.relocAddress) += symbolValue; + } + state.importIndex += 1; + state.relocAddress += 1; + runLength -= 1; + } + } + break; + case kPEFRelocSmByImport: + { + UInt32 symbolValue; + UInt32 index; + + index = (opCode & 0x01FF); + if ( index >= importLibrary->firstImportedSymbol && index < (importLibrary->firstImportedSymbol + importLibrary->importedSymbolCount) ) { + err = LookupSymbol(lookup, refCon, fragToFix->loaderSection, index, &symbolValue); + if (err != noErr) { + goto leaveNow; + } + *(state.relocAddress) += symbolValue; + } + state.importIndex = index + 1; + state.relocAddress += 1; + } + break; + case kPEFRelocSmSetSectC: + { + UInt32 index; + + index = (opCode & 0x01FF); + state.sectionC = GetSectionBaseAddress(fragToFix, index); + MoreAssertQ(state.sectionC != nil); + } + break; + case kPEFRelocSmSetSectD: + { + UInt32 index; + + index = (opCode & 0x01FF); + state.sectionD = GetSectionBaseAddress(fragToFix, index); + MoreAssertQ(state.sectionD != nil); + } + break; + case kPEFRelocSmBySection: + state.relocAddress += 1; + break; + case kPEFRelocIncrPosition: + { + UInt16 offset; + + offset = (opCode & 0x0FFF) + 1; + ((char *) state.relocAddress) += offset; + } + break; + case kPEFRelocSmRepeat: + #if MORE_DEBUG + DebugStr("\pRunRelocationEngine: kPEFRelocSmRepeat not yet implemented"); + #endif + err = unimpErr; + goto leaveNow; + break; + case kPEFRelocSetPosition: + { + UInt32 offset; + + // Lot's of folks have tried various interpretations of the description of + // this opCode in "Mac OS Runtime Architectures" (which states "This instruction + // sets relocAddress to the address of the section offset offset." *smile*). + // I eventually dug into the CFM source code to find my interpretation, which + // I believe is correct. The key point is tht the offset is relative to + // the start of the section for which these relocations are being performed. + + // Skip to next reloc word, which is the second chunk of the offset. + + state.currentReloc += 1; + + // Extract offset based on the most significant 10 bits in opCode and + // the next significant 16 bits in the next reloc word. + + offset = PEFRelocSetPosFullOffset(opCode, relocInstrTable[state.currentReloc]); + + state.relocAddress = (UInt32 *) ( ((char *) state.sectionBase) + offset); + } + break; + case kPEFRelocLgByImport: + { + UInt32 symbolValue; + UInt32 index; + + // Get the 26 bit symbol index from the current and next reloc words. + + state.currentReloc += 1; + index = PEFRelocLgByImportFullIndex(opCode, relocInstrTable[state.currentReloc]); + + if ( index >= importLibrary->firstImportedSymbol && index < (importLibrary->firstImportedSymbol + importLibrary->importedSymbolCount) ) { + err = LookupSymbol(lookup, refCon, fragToFix->loaderSection, index, &symbolValue); + if (err != noErr) { + goto leaveNow; + } + *(state.relocAddress) += symbolValue; + } + state.importIndex = index + 1; + state.relocAddress += 1; + } + break; + case kPEFRelocLgRepeat: + #if MORE_DEBUG + DebugStr("\pRunRelocationEngine: kPEFRelocLgRepeat not yet implemented"); + #endif + err = unimpErr; + goto leaveNow; + break; + case kPEFRelocLgSetOrBySection: + #if MORE_DEBUG + DebugStr("\pRunRelocationEngine: kPEFRelocLgSetOrBySection not yet implemented"); + #endif + err = unimpErr; + goto leaveNow; + break; + case kPEFRelocUndefinedOpcode: + err = cfragFragmentCorruptErr; + goto leaveNow; + break; + default: + MoreAssertQ(false); + err = cfragFragmentCorruptErr; + goto leaveNow; + break; + } + state.currentReloc += 1; + } + + sectionsLeftToRelocate -= 1; + } + +leaveNow: + return err; +} + +extern pascal OSStatus CFMLateImportCore(const CFragSystem7DiskFlatLocator *fragToFixLocator, + CFragConnectionID fragToFixConnID, + CFragInitFunction fragToFixInitRoutine, + ConstStr255Param weakLinkedLibraryName, + CFMLateImportLookupProc lookup, + void *refCon) + // See comments in interface part. +{ + OSStatus err; + OSStatus junk; + FragToFixInfo fragToFix; + PEFImportedLibrary *importLibrary; + char weakLinkedLibraryNameCString[256]; + + MoreAssertQ(fragToFixLocator != nil); + MoreAssertQ(fragToFixConnID != nil); + MoreAssertQ(fragToFixInitRoutine != nil); + MoreAssertQ(weakLinkedLibraryName != nil); + MoreAssertQ(lookup != nil); + + // Fill out the bits of fragToFix which are passed in + // by the client. + + MoreBlockZero(&fragToFix, sizeof(fragToFix)); + fragToFix.locator = *fragToFixLocator; + fragToFix.connID = fragToFixConnID; + fragToFix.initRoutine = fragToFixInitRoutine; + + // Make a C string from weakLinkedLibraryName. + + BlockMoveData(weakLinkedLibraryName + 1, weakLinkedLibraryNameCString, weakLinkedLibraryName[0]); + weakLinkedLibraryNameCString[weakLinkedLibraryName[0]] = 0; + + // Get the basic information from the fragment. + // Fills out the containerHeader, sectionHeaders, loaderSection and fileRef fields + // of fragToFix. + + err = ReadContainerBasics(&fragToFix); + + // Set up the base address fields in fragToFix (ie section0Base and section1Base) + // by looking up our init routine (fragToFix.initRoutine) and subtracting + // away the section offsets (which we get from the disk copy of the section) + // to derive the bases of the sections themselves. + + if (err == noErr) { + err = SetupSectionBaseAddresses(&fragToFix); + } + + // Look inside the loader section for the import library description + // of weakLinkedLibraryName. We need this to know the range of symbol + // indexes we're going to fix up. + + if (err == noErr) { + err = FindImportLibrary(fragToFix.loaderSection, weakLinkedLibraryNameCString, &importLibrary); + } + + // Do a quick check to ensure that the library was actually imported weak. + // If it wasn't, it doesn't make much sense to resolve its weak imports + // later on. Resolving them again is likely to be bad. + + if (err == noErr) { + if ((importLibrary->options & kPEFWeakImportLibMask) == 0) { + err = cfragFragmentUsageErr; + } + } + + // Now run the main relocation engine. + + if (err == noErr) { + err = RunRelocationEngine(&fragToFix, importLibrary, lookup, refCon); + } + + // Clean up. + + if (fragToFix.disposeSectionPointers) { + if (fragToFix.fileRef != 0) { + junk = FSClose(fragToFix.fileRef); + MoreAssertQ(junk == noErr); + } + if (fragToFix.loaderSection != nil) { + DisposePtr( (Ptr) fragToFix.loaderSection); + MoreAssertQ(MemError() == noErr); + } + if (fragToFix.sectionHeaders != nil) { + DisposePtr( (Ptr) fragToFix.sectionHeaders); + MoreAssertQ(MemError() == noErr); + } + } + return err; +} + +static pascal OSStatus FragmentLookup(ConstStr255Param symName, CFragSymbolClass symClass, + void **symAddr, void *refCon) + // This is the CFMLateImportLookupProc callback used when + // late importing from a CFM shared library. +{ + OSStatus err; + CFragConnectionID connIDToImport; + CFragSymbolClass foundSymClass; + + MoreAssertQ(symName != nil); + MoreAssertQ(symAddr != nil); + MoreAssertQ(refCon != nil); + + connIDToImport = (CFragConnectionID) refCon; + + // Shame there's no way to validate that connIDToImport is valid. + + err = FindSymbol(connIDToImport, symName, (Ptr *) symAddr, &foundSymClass); + if (err == noErr) { + // If the symbol isn't of the right class, we act like we didn't + // find it, but also assert in the debug build because weird things + // are afoot. + if (foundSymClass != symClass) { + MoreAssertQ(false); + *symAddr = nil; + err = cfragNoSymbolErr; + } + } + return err; +} + +extern pascal OSStatus CFMLateImportLibrary(const CFragSystem7DiskFlatLocator *fragToFixLocator, + CFragConnectionID fragToFixConnID, + CFragInitFunction fragToFixInitRoutine, + ConstStr255Param weakLinkedLibraryName, + CFragConnectionID connIDToImport) + // See comments in interface part. +{ + MoreAssertQ(connIDToImport != nil); + return CFMLateImportCore(fragToFixLocator, fragToFixConnID, fragToFixInitRoutine, + weakLinkedLibraryName, FragmentLookup, connIDToImport); +} + +static pascal OSStatus BundleLookup(ConstStr255Param symName, CFragSymbolClass symClass, + void **symAddr, void *refCon) + // This is the CFMLateImportLookupProc callback used when + // late importing from a CFBundle. +{ + OSStatus err; + CFBundleRef bundleToImport; + CFStringRef symNameStr; + + MoreAssertQ(symName != nil); + MoreAssertQ(symAddr != nil); + MoreAssertQ(refCon != nil); + + symNameStr = nil; + + bundleToImport = (CFBundleRef) refCon; + + // Shame there's no way to validate that bundleToImport is really a bundle. + + // We can only find function pointers because CFBundleGetFunctionPointerForName + // only works for function pointers. So if the client is asking for something + // other than a function pointer (ie TVector symbol) then we don't even true. + // Also assert in the debug build because this shows a certain lack of + // understanding on the part of the client. + // + // CF is being revise to support accessing data symbols using a new API + // (currently this is available to Apple internal developers as + // CFBundleGetDataPointerForName). When the new API is available in a + // public header file I should revise this code to lift this restriction. + + err = noErr; + if (symClass != kTVectorCFragSymbol) { + MoreAssertQ(false); + err = cfragNoSymbolErr; + } + if (err == noErr) { + symNameStr = CFStringCreateWithPascalString(kCFAllocatorSystemDefault, + symName, kCFStringEncodingMacRoman); + if (symNameStr == nil) { + err = coreFoundationUnknownErr; + } + } + if (err == noErr) { + *symAddr = CFBundleGetFunctionPointerForName(bundleToImport, symNameStr); + if (*symAddr == nil) { + err = cfragNoSymbolErr; + } + } + if (symNameStr != nil) { + CFRelease(symNameStr); + } + return err; +} + +extern pascal OSStatus CFMLateImportBundle(const CFragSystem7DiskFlatLocator *fragToFixLocator, + CFragConnectionID fragToFixConnID, + CFragInitFunction fragToFixInitRoutine, + ConstStr255Param weakLinkedLibraryName, + CFBundleRef bundleToImport) + // See comments in interface part. +{ + MoreAssertQ(bundleToImport != nil); + return CFMLateImportCore(fragToFixLocator, fragToFixConnID, fragToFixInitRoutine, + weakLinkedLibraryName, BundleLookup, bundleToImport); +} |