X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/d3f1e533acc7f70659b8bde9b6c040974f05e03b..bc3b7c8cda49ed8598284a489c0bb9694c67c6a4:/testing/run-static/run-static.cpp diff --git a/testing/run-static/run-static.cpp b/testing/run-static/run-static.cpp new file mode 100644 index 0000000..a06ea63 --- /dev/null +++ b/testing/run-static/run-static.cpp @@ -0,0 +1,230 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "ClosureFileSystemPhysical.h" +#include "MachOAnalyzer.h" +#include "MachOFile.h" + +#include "../testing/test-cases/kernel-test-runner.h" + +const bool isLoggingEnabled = false; + +int entryFunc(const TestRunnerFunctions* funcs); +typedef __typeof(&entryFunc) EntryFuncTy; + +TestRunnerFunctions testFuncs = { + .version = 1, + .mhs = { nullptr, nullptr, nullptr, nullptr }, + .basePointers = { nullptr, nullptr, nullptr, nullptr }, + .printf = &::printf, + .exit = &::exit, + .testPass = &_PASS, + .testFail = &_FAIL, + .testLog = &_LOG, + .testTimeout = &_TIMEOUT, +}; + +struct LoadedMachO { + const dyld3::MachOAnalyzer* ma = nullptr; + // base pointer is the same as 'ma' when the binary has __TEXT first, + // but will point at where we mapped __DATA if building a reverse auxKC. + const void* basePointer = nullptr; +}; + +LoadedMachO loadPath(const char* binaryPath) { + __block Diagnostics diag; + dyld3::closure::FileSystemPhysical fileSystem; + dyld3::closure::LoadedFileInfo info; + char realerPath[MAXPATHLEN]; + __block bool printedError = false; + if (!fileSystem.loadFile(binaryPath, info, realerPath, ^(const char* format, ...) { + fprintf(stderr, "run-static: "); + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + printedError = true; + })) { + if (!printedError ) + fprintf(stderr, "run-static: %s: file not found\n", binaryPath); + exit(1); + } + + const char* currentArchName = dyld3::MachOFile::currentArchName(); + const dyld3::GradedArchs& currentArchs = dyld3::GradedArchs::forName(currentArchName); + __block const dyld3::MachOFile* mf = nullptr; + __block uint64_t sliceOffset = 0; + if ( dyld3::FatFile::isFatFile(info.fileContent) ) { + const dyld3::FatFile* ff = (dyld3::FatFile*)info.fileContent; + ff->forEachSlice(diag, info.fileContentLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, + const void* sliceStart, uint64_t sliceSize, bool& stop) { + const dyld3::MachOFile* sliceMF = (dyld3::MachOFile*)sliceStart; + if ( currentArchs.grade(sliceMF->cputype, sliceMF->cpusubtype, false) != 0 ) { + mf = sliceMF; + sliceOffset = (uint64_t)mf - (uint64_t)ff; + stop = true; + return; + } + }); + + if ( diag.hasError() ) { + fprintf(stderr, "Error: %s\n", diag.errorMessage()); + return { nullptr, nullptr }; + } + + if ( mf == nullptr ) { + fprintf(stderr, "Could not use binary '%s' because it does not contain a slice compatible with host '%s'\n", + binaryPath, currentArchName); + return { nullptr, nullptr }; + } + } else { + mf = (dyld3::MachOFile*)info.fileContent; + if ( !mf->isMachO(diag, info.sliceLen) ) { + fprintf(stderr, "Could not use binary '%s' because '%s'\n", binaryPath, diag.errorMessage()); + return { nullptr, nullptr }; + } + + if ( currentArchs.grade(mf->cputype, mf->cpusubtype, false) == 0 ) { + fprintf(stderr, "Could not use binary '%s' because 'incompatible arch'\n", binaryPath); + return { nullptr, nullptr }; + } + } + + if ( !mf->isFileSet() ) { + fprintf(stderr, "Could not use binary '%s' because 'it is not a static executable'\n", binaryPath); + return { nullptr, nullptr }; + } + + uint64_t mappedSize = ((dyld3::MachOAnalyzer*)mf)->mappedSize(); + vm_address_t mappedAddr; + if ( ::vm_allocate(mach_task_self(), &mappedAddr, (size_t)mappedSize, VM_FLAGS_ANYWHERE) != 0 ) { + fprintf(stderr, "Could not use binary '%s' because 'vm allocation failure'\n", binaryPath); + return { nullptr, nullptr }; + } + + int fd = open(binaryPath, O_RDONLY); + if ( fd == 0 ) { + fprintf(stderr, "Could not open binary '%s' because '%s'\n", binaryPath, strerror(errno)); + return { nullptr, nullptr }; + } + + __block uint64_t baseAddress = ~0ULL; + __block uint64_t textSegVMAddr = ~0ULL; + mf->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) { + baseAddress = std::min(baseAddress, info.vmAddr); + if ( strcmp(info.segName, "__TEXT") == 0 ) { + textSegVMAddr = info.vmAddr; + } + }); + + uint64_t loadAddress = (uint64_t)mappedAddr; + if ( isLoggingEnabled ) { + fprintf(stderr, "Mapping binary built at 0x%llx to 0x%llx\n", baseAddress, loadAddress); + } + mf->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) { + uint64_t requestedLoadAddress = info.vmAddr - baseAddress + loadAddress; + if ( isLoggingEnabled ) + fprintf(stderr, "Mapping %p: %s with perms %d\n", (void*)requestedLoadAddress, info.segName, info.protections); + if ( info.vmSize == 0 ) + return; + size_t readBytes = pread(fd, (void*)requestedLoadAddress, (uintptr_t)info.fileSize, sliceOffset + info.fileOffset); + if ( readBytes != info.fileSize ) { + fprintf(stderr, "Didn't read enough bytes\n"); + exit(1); + } + // __DATA_CONST is read-only when we actually run live, but this test runner fixes up __DATA_CONST after this vm_protect + // For now just don't make __DATA_CONST read only + uint32_t protections = info.protections; + if ( !strcmp(info.segName, "__DATA_CONST") ) + protections = VM_PROT_READ | VM_PROT_WRITE; + const bool setCurrentPermissions = false; + kern_return_t r = vm_protect(mach_task_self(), (vm_address_t)requestedLoadAddress, (uintptr_t)info.vmSize, setCurrentPermissions, protections); + if ( r != KERN_SUCCESS ) { + diag.error("vm_protect didn't work because %d", r); + stop = true; + return; + } + }); + + if ( diag.hasError() ) { + fprintf(stderr, "Error: %s\n", diag.errorMessage()); + return { nullptr, nullptr }; + } + + if ( textSegVMAddr != baseAddress ) { + // __DATA is first. ma should still point to __TEXT + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)(mappedAddr + textSegVMAddr - baseAddress); + if ( !ma->validMachOForArchAndPlatform(diag, (size_t)mappedSize, binaryPath, currentArchs, dyld3::Platform::unknown, false) ) { + fprintf(stderr, "Error: %s\n", diag.errorMessage()); + exit(1); + } + return { ma, (const void*)mappedAddr }; + } + + // __TEXT is first, so ma and base address are the same + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)mappedAddr; + if ( !ma->validMachOForArchAndPlatform(diag, (size_t)mappedSize, binaryPath, currentArchs, dyld3::Platform::unknown, false) ) { + fprintf(stderr, "Error: %s\n", diag.errorMessage()); + exit(1); + } + return { ma, (const void*)mappedAddr }; +} + +int main(int argc, const char * argv[]) { + bool unsupported = false; +#if TARGET_OS_WATCH + // HACK: Watch archs are not supported right now, so just return + unsupported = true; +#endif + if ( unsupported ) { + funcs = &testFuncs; + PASS("Success"); + } + + if ( (argc < 2) || (argc > 5) ) { + fprintf(stderr, "Usage: run-static *path to static binary* [- - *path to auc kc*]\n"); + return 1; + } + + for (unsigned i = 1; i != argc; ++i) { + if ( !strcmp(argv[i], "-") ) + continue; + LoadedMachO macho = loadPath(argv[i]); + if ( macho.ma == nullptr ) + return 1; + testFuncs.mhs[i - 1] = macho.ma; + testFuncs.basePointers[i - 1] = macho.basePointer; + } + + uint64_t entryOffset = 0; + bool usesCRT = false; + const dyld3::MachOAnalyzer* ma = (const dyld3::MachOAnalyzer*)testFuncs.mhs[0]; + if ( !ma->getEntry(entryOffset, usesCRT) ) { + fprintf(stderr, "Could not use binary '%s' because 'no entry defined'\n", argv[1]); + return 1; + } + + EntryFuncTy entryFunc = (EntryFuncTy)((uint8_t*)testFuncs.mhs[0] + entryOffset); +#if __has_feature(ptrauth_calls) + entryFunc = (EntryFuncTy)__builtin_ptrauth_sign_unauthenticated((void*)entryFunc, 0, 0); +#endif + fprintf(stderr, "Entering static binary at %p\n", entryFunc); + //kill(getpid(), SIGSTOP); + int returnCode = entryFunc(&testFuncs); + if ( returnCode != 0 ) { + fprintf(stderr, "Binary '%s' returned non-zero value %d\n", argv[1], returnCode); + return returnCode; + } + return 0; +}