]> git.saurik.com Git - apple/dyld.git/blobdiff - testing/run-static/run-static.cpp
dyld-832.7.1.tar.gz
[apple/dyld.git] / 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 (file)
index 0000000..a06ea63
--- /dev/null
@@ -0,0 +1,230 @@
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <mach-o/loader.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include <TargetConditionals.h>
+
+#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;
+}