Loading...
common/ProcessAtlas.cpp /dev/null dyld-1340
--- /dev/null
+++ dyld/dyld-1340/common/ProcessAtlas.cpp
@@ -0,0 +1,2139 @@
+/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
+ *
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <TargetConditionals.h>
+
+#if !TARGET_OS_EXCLAVEKIT
+
+#include <span>
+#include <atomic>
+#include <cstring>
+#include <Block.h>
+#include <ctype.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <libproc.h>
+
+#include <sys/attr.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/fsgetpath.h>
+
+#include <mach/mach_time.h> // mach_absolute_time()
+#include <mach/mach_vm.h>
+#include <mach-o/dyld_priv.h> // FIXME: We can remove this once we fully integrate into dyld4
+#include "dyld_cache_format.h"
+//FIXME: We should remove this header
+#include "dyld_process_info_internal.h" // For dyld_all_image_infos_{32,64}
+
+#include "Defines.h"
+#include "Header.h"
+#include "DyldSharedCache.h"
+#include "PVLEInt64.h"
+#include "MachOLoaded.h"
+#include "ProcessAtlas.h"
+#include "Utilities.h"
+#include "SafeVMPrimitives.h"
+
+#include "CRC32c.h"
+#include "UUID.h"
+#include "Bitmap.h"
+
+using lsl::CRC32c;
+using lsl::Allocator;
+using lsl::emitPVLEUInt64;
+using lsl::readPVLEUInt64;
+using lsl::Bitmap;
+
+using dyld3::FatFile;
+
+using mach_o::Header;
+// TODO: forEach shared cache needs to filter out subcaches and skip them
+
+// The allocations made by a snapshot need to last for the life of a spanshot. In libdyld that is under the caller control
+// and thus we need to use a persistent or concurrent allocator. Inside of dyld that will be scoped to the the current
+// image loading operation, and so we can use an ephtmeral allocatr
+
+#if BUILDING_DYLD
+#define _transactionalAllocator _ephemeralAllocator
+#else
+#define _transactionalAllocator MemoryManager::memoryManager().defaultAllocator()
+#endif
+
+#define BLEND_KERN_RETURN_LOCATION(kr, loc) (kr) = ((kr & 0x00ffffff) | loc<<24);
+
+namespace {
+static const size_t kCachePeekSize = 0x4000;
+
+static const dyld_cache_header* cacheFilePeek(int fd, uint8_t* firstPage) {
+    // sanity check header
+    if ( pread(fd, firstPage, kCachePeekSize, 0) != kCachePeekSize ) {
+        return nullptr;
+    }
+    const dyld_cache_header* cache = (dyld_cache_header*)firstPage;
+    if ( strncmp(cache->magic, "dyld_v1", strlen("dyld_v1")) != 0 ) {
+        return nullptr;
+    }
+    return cache;
+}
+
+static void getCacheInfo(const dyld_cache_header *cache, uint64_t &headerSize, bool& splitCache) {
+    // If we have sub caches, then the cache header itself tells us how much space we need to cover all caches
+    if ( cache->mappingOffset >= offsetof(dyld_cache_header, subCacheArrayCount) ) {
+        // New style cache
+        headerSize = cache->subCacheArrayOffset + (sizeof(dyld_subcache_entry)*cache->subCacheArrayCount);
+        splitCache = true;
+    } else {
+        // Old style cache
+        headerSize = cache->imagesOffsetOld + (sizeof(dyld_cache_image_info)*cache->imagesCountOld);
+        splitCache = false;
+    }
+}
+
+static void getBaseCachePath(const char* mainPath, char basePathBuffer[]) {
+    const char* devExt = dyld4::Utils::strrstr(mainPath, DYLD_SHARED_CACHE_DEVELOPMENT_EXT);
+    if ( !devExt ) {
+        strcpy(basePathBuffer, mainPath);
+        return;
+    }
+    size_t len = devExt - mainPath;
+    strncpy(basePathBuffer, mainPath, len);
+    basePathBuffer[len] = '\0';
+}
+}
+
+namespace dyld4 {
+namespace Atlas {
+
+#pragma mark -
+#pragma mark Mappers
+
+static
+void printMapping(dyld_cache_mapping_and_slide_info* mapping, uint8_t index, uint64_t slide) {
+#if 0
+    const char* mappingName = "*unknown*";
+    if ( mapping->maxProt & VM_PROT_EXECUTE ) {
+        mappingName = "__TEXT";
+    } else if ( mapping->maxProt & VM_PROT_WRITE ) {
+        if ( mapping->flags & DYLD_CACHE_MAPPING_AUTH_DATA ) {
+            if ( mapping->flags & DYLD_CACHE_MAPPING_DIRTY_DATA )
+                mappingName = "__AUTH_DIRTY";
+            else if ( mapping->flags & DYLD_CACHE_MAPPING_CONST_DATA )
+                mappingName = "__AUTH_CONST";
+            else
+                mappingName = "__AUTH";
+        } else {
+            if ( mapping->flags & DYLD_CACHE_MAPPING_DIRTY_DATA )
+                mappingName = "__DATA_DIRTY";
+            else if ( mapping->flags & DYLD_CACHE_MAPPING_CONST_DATA )
+                mappingName = "__DATA_CONST";
+            else
+                mappingName = "__DATA";
+        }
+    }
+    else if ( mapping->maxProt & VM_PROT_READ ) {
+        mappingName = "__LINKEDIT";
+    }
+
+    fprintf(stderr, "%16s %4lluMB,  file offset: #%u/0x%08llX -> 0x%08llX,  address: 0x%08llX -> 0x%08llX\n",
+            mappingName, mapping->size / (1024*1024), index, mapping->fileOffset,
+            mapping->fileOffset + mapping->size, mapping->address + slide,
+            mapping->address + mapping->size + slide);
+#endif
+}
+
+SharedPtr<Mapper> Mapper::mapperForSharedCache(Allocator& _ephemeralAllocator, FileRecord& file, SafePointer baseAddress) {
+    bool        useLocalCache   = false;
+    size_t      length          = 0;
+    uint64_t    slide     = 0;
+    UUID        uuid;
+
+    int fd = open(file.getPath(), O_RDONLY);
+    if ( fd == -1 ) {
+        return nullptr;
+    }
+    //TODO: Replace this with a set
+    OrderedSet<int> fds(_ephemeralAllocator);
+    fds.insert(fd);
+    uint8_t firstPage[kCachePeekSize];
+    const dyld_cache_header* onDiskCacheHeader = cacheFilePeek(fd, &firstPage[0]);
+    if (!onDiskCacheHeader) {
+        for (auto deadFd : fds) {
+            close(deadFd);
+        }
+        return nullptr;
+    }
+    uuid = UUID(&onDiskCacheHeader->uuid[0]);
+    if (!onDiskCacheHeader) {
+        for (auto deadFd : fds) {
+            close(deadFd);
+        }
+        return nullptr;
+    }
+    const void* localBaseAddress = _dyld_get_shared_cache_range(&length);
+    if (localBaseAddress) {
+        auto localCacheHeader = ((dyld_cache_header*)localBaseAddress);
+        auto localUUID = UUID(&localCacheHeader->uuid[0]);
+        if (localUUID == uuid) {
+            useLocalCache = true;
+        }
+    }
+    if (!baseAddress) {
+        // No base address passed in, treat as unslid
+        baseAddress = onDiskCacheHeader->sharedRegionStart;
+    }
+    slide = (uint64_t)baseAddress-(uint64_t)onDiskCacheHeader->sharedRegionStart;
+    uint64_t headerSize = 0;
+    bool splitCache = false;
+    getCacheInfo(onDiskCacheHeader, headerSize, splitCache);
+    if (splitCache && (onDiskCacheHeader->imagesCount == 0)) {
+        //This is a subcache, bail
+        for (auto deadFd : fds) {
+            close(deadFd);
+        }
+        return nullptr;
+    }
+    void* mapping = mmap(nullptr, (size_t)headerSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
+    if (mapping == MAP_FAILED) {
+        for (auto deadFd : fds) {
+            close(deadFd);
+        }
+        return nullptr;
+    }
+    auto onDiskHeaderBytes = (uint8_t*)mapping;
+    auto onDiskCacheMappings = (dyld_cache_mapping_and_slide_info*)&onDiskHeaderBytes[onDiskCacheHeader->mappingWithSlideOffset];
+    Vector<Mapper::Mapping> mappings(_transactionalAllocator);
+    for (auto i = 0; i < onDiskCacheHeader->mappingWithSlideCount; ++i) {
+        if (useLocalCache && ((onDiskCacheMappings[i].maxProt & VM_PROT_WRITE) != VM_PROT_WRITE)) {
+            // This region is immutable, use in memory version
+            printMapping(&onDiskCacheMappings[i], 255, slide);
+            mappings.emplace_back((Mapper::Mapping){
+                .address    = onDiskCacheMappings[i].address + slide,
+                .size       = onDiskCacheMappings[i].size,
+                // No file, just use the address
+                .offset     = onDiskCacheMappings[i].address - onDiskCacheHeader->sharedRegionStart + (uint64_t)localBaseAddress,
+                .fd         = -1
+            });
+        } else {
+            printMapping(&onDiskCacheMappings[i], fd, slide);
+            mappings.emplace_back((Mapper::Mapping){
+                .address    = onDiskCacheMappings[i].address + slide,
+                .size       = onDiskCacheMappings[i].size,
+                .offset     = onDiskCacheMappings[i].fileOffset,
+                .fd         = fd
+            });
+        }
+    }
+    if (splitCache) {
+        auto subCaches = (dyld_subcache_entry*)&onDiskHeaderBytes[onDiskCacheHeader->subCacheArrayOffset];
+        for (auto i = 0; i < onDiskCacheHeader->subCacheArrayCount; ++i) {
+            char subCachePath[PATH_MAX];
+            if (onDiskCacheHeader->mappingOffset <= offsetof(dyld_cache_header, cacheSubType)) {
+                snprintf(&subCachePath[0], sizeof(subCachePath), "%s.%u", file.getPath(), i+1);
+            } else {
+                char basePath[PATH_MAX];
+                getBaseCachePath(file.getPath(), basePath);
+                snprintf(&subCachePath[0], sizeof(subCachePath), "%s%s", basePath, subCaches[i].fileSuffix);
+            }
+            fd = open(subCachePath, O_RDONLY);
+            fds.insert(fd);
+            if ( fd == -1 ) {
+                break;
+            }
+            // TODO: We should check we have enough space, but for now just allocate a page
+            uint8_t firstSubPage[kCachePeekSize];
+            const dyld_cache_header* subCache = cacheFilePeek(fd, &firstSubPage[0]);
+            if (!subCache) {
+                for (auto deadFd : fds) {
+                    close(deadFd);
+                }
+                continue;
+            }
+            auto subCacheheaderBytes = (uint8_t*)subCache;
+            auto subCacheMappings = (dyld_cache_mapping_and_slide_info*)&subCacheheaderBytes[subCache->mappingWithSlideOffset];
+
+            auto onDiskSubcacheUUID = UUID(subCache->uuid);
+            uint8_t uuidBuf[16];
+            if (onDiskCacheHeader->mappingOffset <= offsetof(dyld_cache_header, cacheSubType))  {
+                auto subCacheArray = (dyld_subcache_entry_v1*)&onDiskHeaderBytes[onDiskCacheHeader->subCacheArrayOffset];
+                memcpy(uuidBuf, subCacheArray[i].uuid, 16);
+            } else {
+                auto subCacheArray = (dyld_subcache_entry*)&onDiskHeaderBytes[onDiskCacheHeader->subCacheArrayOffset];
+                memcpy(uuidBuf, subCacheArray[i].uuid, 16);
+            }
+            auto subcacheUUID = UUID(uuidBuf);
+            if (subcacheUUID != onDiskSubcacheUUID) {
+                for (auto deadFd : fds) {
+                    close(deadFd);
+                }
+                return nullptr;
+            }
+
+            for (auto j = 0; j < subCache->mappingWithSlideCount; ++j) {
+                if (useLocalCache && ((subCacheMappings[j].maxProt & VM_PROT_WRITE) != VM_PROT_WRITE)) {
+                    // This region is immutable, use in memory version
+                    printMapping(&subCacheMappings[j], 255, slide);;
+                    mappings.emplace_back((Mapper::Mapping){
+                        .address    = subCacheMappings[j].address + slide,
+                        .size       = subCacheMappings[j].size,
+                        // No file, just use the address
+                        .offset     = subCacheMappings[j].address - onDiskCacheHeader->sharedRegionStart + (uint64_t)localBaseAddress,
+                        .fd         = -1
+                    });
+                } else {
+                    printMapping(&subCacheMappings[j], fd, slide);
+                    mappings.emplace_back((Mapper::Mapping){
+                        .address    = subCacheMappings[j].address + slide,
+                        .size       = subCacheMappings[j].size,
+                        .offset     = subCacheMappings[j].fileOffset,
+                        .fd         = fd
+                    });
+                }
+            }
+        }
+    }
+    for (auto activeMapping : mappings) {
+        fds.erase(activeMapping.fd);
+    }
+    for (auto deadFd : fds) {
+        close(deadFd);
+    }
+    munmap(mapping,(size_t)headerSize);
+    return _transactionalAllocator.makeShared<Mapper>(_transactionalAllocator, mappings);
+}
+
+
+std::pair<SharedPtr<Mapper>,uint64_t> Mapper::mapperForSharedCacheLocals(Allocator& _ephemeralAllocator, FileRecord& file) {
+    int fd = file.open(O_RDONLY);
+    if (fd == -1) {
+        return { SharedPtr<Mapper>(), 0};
+    }
+    size_t fileSize = file.size();
+    if (fd == 0) {
+        return { SharedPtr<Mapper>(), 0};
+    }
+    // sanity check header
+    uint8_t firstPage[kCachePeekSize];
+    const dyld_cache_header* cache = cacheFilePeek(fd, &firstPage[0]);
+    if (!cache) {
+        file.close();
+        return { SharedPtr<Mapper>(), 0};
+    }
+    uint64_t baseAddress = 0;
+
+    // We want the cache header, which is at the start of the, and the locals, which are at the end.
+    // Just map the whole file as a single range, as we need file offsets in the mappings anyway
+    // With split caches, this is more reasonable as the locals are in their own file, so we want more or
+    // less the whole file anyway, and there's no wasted space for __TEXT, __DATA, etc.
+//    fprintf(stderr, "Mapping\n");
+//    fprintf(stderr, "fd\tAddress\tFile Offset\tSize\n");
+//    fprintf(stderr, "%u\t0x%llx\t0x%x\t%llu\n", fd, baseAddress, 0, (uint64_t)statbuf.st_size);
+    Vector<Mapper::Mapping> mappings(_transactionalAllocator);
+    mappings.emplace_back((Mapper::Mapping){
+        .address = baseAddress,
+        .size = fileSize,
+        .offset = 0,
+        .fd = fd
+    });
+    return { _transactionalAllocator.makeShared<Mapper>(_transactionalAllocator, mappings), baseAddress };
+}
+
+SharedPtr<Mapper> Mapper::mapperForMachO(Allocator& _ephemeralAllocator, FileRecord& file, const UUID& uuid, const SafePointer baseAddress) {
+    const char* filePath = file.getPath();
+    // open filePath
+    int fd = dyld3::open(filePath, O_RDONLY, 0);
+    if ( fd == -1 ) {
+        ::close(fd);
+        return nullptr;
+    }
+    // get file size of filePath
+    struct stat sb;
+    if ( fstat(fd, &sb) == -1 ) {
+        ::close(fd);
+        return nullptr;
+    }
+
+    // mmap whole file temporarily
+    void* tempMapping = ::mmap(nullptr, (size_t)sb.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE | MAP_RESILIENT_CODESIGN, fd, 0);
+    if ( tempMapping == MAP_FAILED ) {
+        ::close(fd);
+        return nullptr;
+    }
+
+    // if fat file, pick matching slice
+    __block const Header*    mf         = nullptr;
+    const FatFile*              ff         = FatFile::isFatFile(tempMapping);
+    __block uint64_t            fileOffset = 0;
+    if (ff) {
+        uint64_t                fileLength = sb.st_size;
+        Diagnostics             diag;
+        ff->forEachSlice(diag, fileLength, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void *sliceStart, uint64_t sliceSize, bool &stop) {
+            auto slice = (Header*)sliceStart;
+            uuid_t sliceUUIDRaw;
+            slice->getUuid(sliceUUIDRaw);
+            auto sliceUUID = UUID(sliceUUIDRaw);
+            if (uuid == sliceUUID) {
+                mf = slice;
+                fileOffset = (uint64_t)sliceStart - (uint64_t)ff;
+                stop = true;
+                return;
+            }
+        });
+        diag.clearError();
+    }
+    if (!mf) {
+        auto slice =  Header::isMachO({(uint8_t*)tempMapping, (size_t)sb.st_size});
+        if (slice) {
+            uuid_t sliceUUID;
+            slice->getUuid(sliceUUID);
+            if (uuid == UUID(sliceUUID)) {
+                mf = slice;
+            }
+        }
+    }
+    if (!mf) {
+        ::munmap(tempMapping, (size_t)sb.st_size);
+        ::close(fd);
+        return nullptr;
+    }
+    __block Vector<Mapper::Mapping> mappings(_transactionalAllocator);
+    __block uint64_t slide = 0;
+    mf->forEachSegment(^(const Header::SegmentInfo &info, bool &stop) {
+        if ( info.segmentName == "__TEXT" ) {
+            slide = (uint64_t)baseAddress - info.vmaddr;
+        }
+//        fprintf(stderr, "Mapping\n");
+//        fprintf(stderr, "fd\tAddress\tFile Offset\tSize\n");
+//        fprintf(stderr, "%u\t0x%llx\t0x%llx\t%llu\n", fd, info.vmAddr + slide, info.fileOffset + fileOffset, info.vmSize);
+        mappings.emplace_back((Mapper::Mapping) {
+            .address = info.vmaddr + slide,
+            .size = info.vmsize,
+            .offset = info.fileOffset + fileOffset,
+            .fd = fd
+        });
+    });
+    ::munmap(tempMapping, (size_t)sb.st_size);
+    return _transactionalAllocator.makeShared<Mapper>(_transactionalAllocator, mappings);
+}
+
+Mapper::Mapper(Allocator& allocator) : _mappings(allocator), _flatMapping(nullptr), _allocator(allocator) {}
+Mapper::Mapper(Allocator& allocator, const Vector<Mapping>& mapper)
+    : _mappings(mapper, allocator), _flatMapping(nullptr), _allocator(allocator) {}
+
+Mapper::~Mapper() {
+    assert(_flatMapping == nullptr);
+    //TODO: Replace this with a set
+    Vector<int> fds(_allocator);
+    for (auto& mapping : _mappings) {
+        if (mapping.fd == -1) { continue; }
+        if (std::find(fds.begin(), fds.end(), mapping.fd) == fds.end()) {
+            fds.push_back(mapping.fd);
+        }
+    }
+    for (auto& fd : fds) {
+        close(fd);
+    }
+}
+
+std::pair<SafePointer,bool> Mapper::map(const SafePointer addr, uint64_t size) const {
+    if (_flatMapping) {
+        uint64_t offset = (uint64_t)addr-(uint64_t)baseAddress();
+        return {(uint64_t)((uintptr_t)_flatMapping+offset),false};
+    }
+    if (_mappings.size() == 0) {
+        // No mappings means we are an identity mapper
+        return { addr, false };
+    }
+    for (const auto& mapping : _mappings) {
+        if (((uint64_t)addr >= mapping.address) && ((uint64_t)addr < (mapping.address + mapping.size))) {
+            if (mapping.fd == -1) {
+                return {(((uint64_t)addr-mapping.address)+mapping.offset), false};
+            }
+            assert(((uint64_t)addr + size) <= mapping.address + mapping.size);
+            uint64_t offset = (uint64_t)addr - mapping.address + mapping.offset;
+            // Handle unaligned mmap
+            void* newMapping = nullptr;
+            size_t extraBytes = 0;
+            uint64_t roundedOffset = offset & (-1*PAGE_SIZE);
+            extraBytes = (size_t)offset - (size_t)roundedOffset;
+            newMapping = mmap(nullptr, (size_t)size+extraBytes, PROT_READ, MAP_FILE | MAP_PRIVATE, mapping.fd, roundedOffset);
+            if (newMapping == MAP_FAILED) {
+//                fprintf(stderr, "mmap failed: %s (%d)\n", strerror(errno), errno);
+                return { SafePointer(), false};
+            }
+            return {(uint64_t)((uintptr_t)newMapping+extraBytes),true};
+        }
+    }
+    return {SafePointer(), false};
+}
+
+void Mapper::unmap(const SafePointer addr, uint64_t size) const {
+    void* roundedAddr = (void*)((intptr_t)(uint64_t)addr & (-1*PAGE_SIZE));
+    size_t extraBytes = (uintptr_t)(uint64_t)addr - (uintptr_t)roundedAddr;
+    munmap(roundedAddr, (size_t)size+extraBytes);
+}
+
+const SafePointer Mapper::baseAddress() const {
+    return _mappings[0].address;
+}
+
+const uint64_t Mapper::size() const {
+    return (_mappings.back().address - _mappings[0].address) + _mappings.back().size;
+}
+
+bool Mapper::pin() {
+    assert(_flatMapping == nullptr);
+    //TODO: Move onto dyld allocators once we merge the large allocations support
+    if (vm_allocate(mach_task_self(), (vm_address_t*)&_flatMapping, (vm_size_t)size(), VM_FLAGS_ANYWHERE) != KERN_SUCCESS) {
+        return false;
+    }
+    for (const auto& mapping : _mappings) {
+        uint64_t destAddr = (mapping.address - _mappings[0].address) + (uint64_t)_flatMapping;
+        if (mapping.fd == -1) {
+            if (vm_copy(mach_task_self(), (vm_address_t)mapping.address, (vm_size_t)mapping.size, (vm_address_t)destAddr) != KERN_SUCCESS) {
+                unpin();
+                return false;
+            }
+        } else {
+            if (mmap((void*)destAddr, (vm_size_t)mapping.size, PROT_READ, MAP_FILE | MAP_PRIVATE | MAP_FIXED, mapping.fd, mapping.offset) == MAP_FAILED) {
+                unpin();
+                return false;
+            }
+        }
+    }
+    return true;
+}
+void Mapper::unpin() {
+    assert(_flatMapping != nullptr);
+    vm_deallocate(mach_task_self(), (vm_address_t)_flatMapping, (vm_size_t)size());
+    _flatMapping = nullptr;
+}
+
+void Mapper::dump() const {
+    fprintf(stderr, "fd\tAddress\tSize\n");
+    
+    for (const auto& mapping : _mappings) {
+        fprintf(stderr, "%d\t0x%llx\t%llu\n", mapping.fd, mapping.address, mapping.size);
+    }
+}
+
+#pragma mark -
+#pragma mark Image
+
+#if BUILDING_DYLD
+Image::Image(RuntimeState* state, Allocator& ephemeralAllocator, SharedPtr<Mapper>& mapper, const Loader* ldr)
+    :   _ephemeralAllocator(ephemeralAllocator), _mapper(mapper), _rebasedAddress((uint64_t)ldr->loadAddress(*state)) {
+        auto fileID = ldr->fileID(*state);
+        if (fileID.inode() &&  fileID.device()) {
+            _file = state->fileManager.fileRecordForFileID(ldr->fileID(*state));
+            if ( _file.volume().empty() ) {
+                _file = state->fileManager.fileRecordForPath(ephemeralAllocator, ldr->path(*state));
+            }
+        } else {
+            _file = state->fileManager.fileRecordForPath(ephemeralAllocator, ldr->path(*state));
+        }
+    }
+#endif
+Image::Image(Allocator& ephemeralAllocator, FileRecord&& file, SharedPtr<Mapper>& mapper, const SafePointer mh)
+    : _ephemeralAllocator(ephemeralAllocator), _file(std::move(file)), _mapper(mapper), _rebasedAddress(mh) {}
+Image::Image(Allocator& ephemeralAllocator, FileRecord&& file, SharedPtr<Mapper>& mapper, const SafePointer mh, const UUID& uuid)
+    :  _ephemeralAllocator(ephemeralAllocator), _file(std::move(file)), _mapper(mapper), _uuid(uuid), _rebasedAddress(mh) {}
+
+Image::Image(Allocator& ephemeralAllocator, SharedPtr<Mapper>& mapper, SafePointer baseAddress, uint64_t cacheSlide, SharedCache* sharedCache)
+    : _ephemeralAllocator(ephemeralAllocator), _mapper(mapper), _sharedCacheSlide(cacheSlide), _rebasedAddress(((uint64_t)baseAddress+cacheSlide)), _sharedCache(sharedCache) {}
+
+Image::Image(Image&& other) : _ephemeralAllocator(other._ephemeralAllocator) {
+    swap(other);
+}
+
+Image& Image::operator=(Image&& other) {
+    swap(other);
+    return *this;
+}
+
+std::strong_ordering Image::operator<=>(const Image& other) const {
+    return (rebasedAddress() <=> other.rebasedAddress());
+}
+
+void Image::swap(Image& other) {
+    using std::swap;
+
+    if (this == &other) { return; }
+    swap(_uuid,                 other._uuid);
+    swap(_ml,                   other._ml);
+    swap(_sharedCacheSlide,     other._sharedCacheSlide);
+    swap(_rebasedAddress,       other._rebasedAddress);
+    swap(_mapper,               other._mapper);
+    swap(_sharedCache,          other._sharedCache);
+    swap(_installname,          other._installname);
+    swap(_file,                 other._file);
+    swap(_uuidLoaded,           other._uuidLoaded);
+    swap(_installnameLoaded,    other._installnameLoaded);
+    swap(_mapperFailed,         other._mapperFailed);
+}
+
+const MachOLoaded* Image::ml() const {
+    if (_mapperFailed) {
+        return nullptr;
+    }
+    if (!_ml) {
+        SafePointer slidML = rebasedAddress();
+        // Note, using 4k here as we might be an arm64e process inspecting an x86_64 image, which uses 4k pages
+        if (!_mapper && !_mapperFailed) {
+            _mapper = Mapper::mapperForMachO(_transactionalAllocator, _file, _uuid, _rebasedAddress);
+        }
+        if (!_mapper) {
+            _mapperFailed = true;
+            return nullptr;
+        }
+        _ml = _mapper->map<MachOLoaded>(slidML, 4096);
+        if (!_ml) {
+           _mapperFailed = true;
+           return nullptr;
+        }
+        size_t size = _ml->sizeofcmds;
+        if ( _ml->magic == MH_MAGIC_64 ) {
+            size += sizeof(mach_header_64);
+        } else {
+            size += sizeof(mach_header);
+        }
+        if (size > 4096) {
+            _ml = _mapper->map<MachOLoaded>(slidML, size);
+            if (!_ml) {
+               _mapperFailed = true;
+               return nullptr;
+            }
+        }
+    }
+    // This is a bit of a mess. With compact info this will be unified, but for now we use a lot of hacky abstactions here to deal with
+    // in process / vs out of process / vs shared cache.
+    return &*_ml;
+}
+
+const UUID& Image::uuid() const {
+    if (!_uuidLoaded) {
+        uuid_t fileUUID;
+        const Header* mh = (Header*)ml();
+        if (mh && mh->hasMachOMagic()) {
+            if (mh->getUuid(fileUUID))
+                _uuid = UUID(fileUUID);
+        }
+        _uuidLoaded = true;
+    }
+    return _uuid;
+}
+
+SafePointer Image::rebasedAddress() const {
+    return (uint64_t)_rebasedAddress;
+}
+
+
+const char* Image::installname() const {
+    if (!_installnameLoaded) {
+        if (ml()) {
+            _installname = ((const Header*)ml())->installName();
+        }
+        _installnameLoaded = true;
+    }
+    return _installname;
+}
+const char* Image::filename() const {
+    if (_sharedCache) { return nullptr; }
+    return _file.getPath();
+}
+
+const FileRecord& Image::file() const {
+    return _file;
+}
+
+const SharedCache* Image::sharedCache() const {
+    return _sharedCache;
+}
+
+uint64_t Image::sharedCacheVMOffset() const {
+    return (uint64_t)_rebasedAddress - (uint64_t)sharedCache()->rebasedAddress();
+}
+
+uint32_t Image::pointerSize() {
+    if (!ml()) { return 0; }
+    return ml()->pointerSize();
+}
+
+bool Image::forEachSegment(void (^block)(const char* segmentName, uint64_t vmAddr, uint64_t vmSize, int perm)) {
+    if (!ml()) { return false; }
+    __block uint64_t slide = (uint64_t)_rebasedAddress - ((const Header*)ml())->preferredLoadAddress();
+    ((const Header*)ml())->forEachSegment(^(const Header::SegmentInfo &info, bool &stop) {
+        uint64_t vmAddr = 0x0;
+        if ( _sharedCacheSlide.has_value() ) {
+            vmAddr = info.vmaddr + _sharedCacheSlide.value();
+        } else {
+            if ( ml()->isMainExecutable() ) {
+                if ( info.segmentName.starts_with("__PAGEZERO") )
+                    return;
+            }
+            vmAddr = info.vmaddr + slide;
+        }
+        block(info.segmentName.data(), vmAddr, info.vmsize, info.initProt);
+    });
+    return true;
+}
+
+bool Image::forEachSection(void (^block)(const char* segmentName, const char* sectionName, uint64_t vmAddr, uint64_t vmSize)) {
+    if (!ml()) { return false; }
+    __block uint64_t slide = (uint64_t)_rebasedAddress - ((const Header*)ml())->preferredLoadAddress();
+    ((const Header*)ml())->forEachSection(^(const Header::SectionInfo &info, bool &stop) {
+        uint64_t sectAddr = 0x0;
+        if ( _sharedCacheSlide.has_value() ) {
+            sectAddr = info.address + _sharedCacheSlide.value();
+        } else {
+            sectAddr = info.address + slide;
+        }
+        block(info.segmentName.data(), info.sectionName.data(), sectAddr, info.size);
+    });
+    return true;
+}
+
+bool Image::contentForSegment(const char* segmentName, void (^contentReader)(const void* content, uint64_t vmAddr, uint64_t vmSize)) {
+    if (!ml()) { return false; }
+    __block bool result = false;
+    __block uint64_t slide = (uint64_t)_rebasedAddress - ((const Header*)ml())->preferredLoadAddress();
+    ((const Header*)ml())->forEachSegment(^(const Header::SegmentInfo &info, bool &stop) {
+        if ( segmentName != info.segmentName ) { return; }
+        uint64_t vmAddr = 0;
+        if ( _sharedCacheSlide.has_value() ) {
+            vmAddr = info.vmaddr + _sharedCacheSlide.value();
+        } else {
+            if ( ml()->isMainExecutable() ) {
+                if ( info.segmentName.starts_with("__PAGEZERO") )
+                    return;
+            }
+            vmAddr = info.vmaddr + slide;
+        }
+
+        if (info.vmsize) {
+            auto content = _mapper->map<uint8_t>(vmAddr, info.vmsize);
+            contentReader((void*)&*content, vmAddr, info.vmsize);
+        } else {
+            contentReader(nullptr, vmAddr, 0);
+        }
+        result = true;
+        stop = true;
+    });
+    return result;
+}
+
+bool Image::contentForSection(const char* segmentName, const char* sectionName,
+                              void (^contentReader)(const void* content, uint64_t vmAddr, uint64_t vmSize)) {
+    __block bool result = false;
+    __block uint64_t slide = (uint64_t)_rebasedAddress - ((const Header*)ml())->preferredLoadAddress();
+    ((const Header*)ml())->forEachSection(^(const Header::SectionInfo &info, bool &stop) {
+        if ( segmentName != info.segmentName ) { return; }
+        if ( sectionName != info.sectionName ) { return; }
+        uint64_t sectAddr = 0;
+        if ( _sharedCacheSlide.has_value() ) {
+            sectAddr = info.address + _sharedCacheSlide.value();
+        } else {
+            sectAddr = info.address + slide;
+        }
+        if (info.size) {
+            auto content = _mapper->map<uint8_t>(sectAddr, info.size);
+            contentReader((void*)&*content, sectAddr, info.size);
+        } else {
+            contentReader(nullptr, sectAddr, 0);
+        }
+        result = true;
+        stop = true;
+    });
+    return result;
+}
+
+#pragma mark -
+#pragma mark Shared Cache Locals
+
+SharedCacheLocals::SharedCacheLocals(SharedPtr<Mapper>& M, bool use64BitDylibOffsets)
+    : _mapper(M), _use64BitDylibOffsets(use64BitDylibOffsets) {
+    auto header = _mapper->map<dyld_cache_header>(0ULL, sizeof(dyld_cache_header));
+
+    // Map in the whole locals buffer.
+    // TODO: Once we have the symbols in their own file, simplify this to just map the whole file
+    // and not do the header and locals separately
+    _locals = _mapper->map<uint8_t>(header->localSymbolsOffset, header->localSymbolsSize);
+}
+
+const dyld_cache_local_symbols_info* SharedCacheLocals::localInfo() const {
+    return (const dyld_cache_local_symbols_info*)(&*_locals);
+}
+
+bool SharedCacheLocals::use64BitDylibOffsets() const {
+    return _use64BitDylibOffsets;
+}
+
+#pragma mark -
+#pragma mark Shared Cache
+
+// Copied from DyldSharedCache::mappedSize()
+static uint64_t cacheMappedSize(Mapper::Pointer<dyld_cache_header>& header, SharedPtr<Mapper>& mapper,
+                                uint64_t rebasedAddress, bool splitCache)
+{
+    // If we have sub caches, then the cache header itself tells us how much space we need to cover all caches
+    if (header->mappingOffset >= offsetof(dyld_cache_header, subCacheArrayCount) ) {
+        return header->sharedRegionSize;
+    } else {
+        auto headerBytes = (uint8_t*)&*header;
+        auto mappings = (dyld_cache_mapping_and_slide_info*)&headerBytes[header->mappingWithSlideOffset];
+        uint64_t endAddress = 0;
+        for (auto i = 0; i < header->mappingWithSlideCount; ++i) {
+            if (endAddress < mappings[i].address + mappings[i].size) {
+                endAddress = mappings[i].address + mappings[i].size;
+            }
+        }
+        if (splitCache) {
+            for (auto i = 0; i < header->subCacheArrayCount; ++i) {
+                uint64_t subCacheOffset = 0;
+                if (header->mappingOffset <= offsetof(dyld_cache_header, cacheSubType) ) {
+                    auto subCaches = (dyld_subcache_entry_v1*)&headerBytes[header->subCacheArrayOffset];
+                    subCacheOffset = subCaches[i].cacheVMOffset;
+                } else {
+                    auto subCaches = (dyld_subcache_entry*)&headerBytes[header->subCacheArrayOffset];
+                    subCacheOffset = subCaches[i].cacheVMOffset;
+                }
+                auto subCacheHeader = mapper->map<dyld_cache_header>(subCacheOffset + rebasedAddress, PAGE_SIZE);
+                uint64_t subCacheHeaderSize = 0;
+                bool splitCacheUnused;
+                getCacheInfo(&*subCacheHeader, subCacheHeaderSize, splitCacheUnused);
+                if (subCacheHeaderSize > PAGE_SIZE) {
+                    subCacheHeader = mapper->map<dyld_cache_header>(subCacheOffset + rebasedAddress, subCacheHeaderSize);
+                }
+                auto subCacheHeaderBytes = (uint8_t*)&*subCacheHeader;
+                auto subCacheMappings = (dyld_cache_mapping_and_slide_info*)&subCacheHeaderBytes[subCacheHeader->mappingWithSlideOffset];
+                for (auto j = 0; j < subCacheHeader->mappingWithSlideCount; ++j) {
+                    if (endAddress < subCacheMappings[j].address + subCacheMappings[j].size) {
+                        endAddress = subCacheMappings[j].address + subCacheMappings[j].size;
+                    }
+                }
+            }
+        }
+        return endAddress - header->sharedRegionStart;
+    }
+}
+
+SharedCache::SharedCache(Allocator& ephemeralAllocator, FileRecord&& file, SharedPtr<Mapper>& mapper, SafePointer rebasedAddress, bool P)
+    : _ephemeralAllocator(ephemeralAllocator), _file(std::move(file)), _mapper(mapper),  _rebasedAddress(rebasedAddress), _private(P)
+{
+    assert(_mapper);
+    _header = _mapper->map<dyld_cache_header>(_rebasedAddress, PAGE_SIZE);
+    uint64_t headerSize = 0;
+    bool splitCache = false;
+    getCacheInfo(&*_header, headerSize, splitCache);
+    if (headerSize > PAGE_SIZE) {
+        _header = _mapper->map<dyld_cache_header>(_rebasedAddress, headerSize);
+    }
+    _uuid = UUID(&_header->uuid[0]);
+    _slide = (uint64_t)_rebasedAddress -  _header->sharedRegionStart;
+    _size = cacheMappedSize(_header, _mapper, (uint64_t)rebasedAddress, splitCache);
+}
+
+
+void SharedCache::forEachInstalledCacheWithSystemPath(Allocator& _ephemeralAllocator, FileManager& fileManager, const char* systemPath, void (^block)(SharedCache* cache)) {
+    // TODO: We can make this more resilient by encoding all the paths in a special section /usr/lib/dyld, and then parsing them out
+    // Search all paths we might find shared caches at for any OS in the last 2+ years
+    static const char* cacheDirPaths[] = {
+        "/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/",
+        "/System/Volumes/Preboot/Cryptexes/OS/System/DriverKit/System/Library/dyld/",
+        "/private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld/",
+        "/private/preboot/Cryptexes/OS/System/DriverKit/System/Library/dyld/",
+        "/System/Cryptexes/OS/System/Library/Caches/com.apple.dyld/",
+        "/System/Cryptexes/OS/System/Library/dyld/",
+        "/System/Cryptexes/ExclaveOS/System/ExclaveKit/System/Library/dyld/",
+        "/System/Volumes/Preboot/Cryptexes/Incoming/OS/System/Library/dyld/",
+        "/System/Volumes/Preboot/Cryptexes/Incoming/OS/System/DriverKit/System/Library/dyld/",
+        "/private/preboot/Cryptexes/Incoming/OS/System/Library/Caches/com.apple.dyld/",
+        "/private/preboot/Cryptexes/Incoming/OS/System/DriverKit/System/Library/dyld/",
+        "/System/Cryptexes/Incoming/OS/System/Library/Caches/com.apple.dyld/",
+        "/System/Cryptexes/Incoming/OS/System/Library/dyld/",
+        "/System/Library/Caches/com.apple.dyld/",
+        "/System/DriverKit/System/Library/dyld/",
+        "/System/ExclaveKit/System/Library/dyld/",
+        "/System/Library/dyld/"
+    };
+
+    OrderedSet<const char*, lsl::ConstCharStarCompare> realPaths(_ephemeralAllocator);
+    for ( int i = 0; i < sizeof(cacheDirPaths)/sizeof(char*); i++ ) {
+        char systemCacheDirPath[PATH_MAX];
+        systemCacheDirPath[0] = 0;
+        if ( systemPath != nullptr ) {
+            if ( Utils::concatenatePaths(systemCacheDirPath, systemPath, PATH_MAX) >= PATH_MAX )
+                continue;
+        }
+        if ( Utils::concatenatePaths(systemCacheDirPath, cacheDirPaths[i], PATH_MAX) >= PATH_MAX )
+            continue;
+
+        char systemCacheDirRealPath[PATH_MAX];
+        systemCacheDirRealPath[0] = 0;
+        if ( realpath(systemCacheDirPath, systemCacheDirRealPath) == nullptr )
+            continue;
+        if ( Utils::concatenatePaths(systemCacheDirRealPath, "/", PATH_MAX) >= PATH_MAX )
+            continue;
+
+        const char* systemDirDup = _ephemeralAllocator.strdup(systemCacheDirRealPath);
+        auto insertRes = realPaths.insert(systemDirDup);
+        if ( !insertRes.second ) {
+            _ephemeralAllocator.free((void*)systemDirDup);
+            continue;
+        }
+
+        DIR* dirp = ::opendir(systemCacheDirRealPath);
+        if ( dirp != NULL) {
+            dirent entry;
+            dirent* entp = NULL;
+            char cachePath[PATH_MAX];
+            cachePath[0] = 0;
+            while ( ::readdir_r(dirp, &entry, &entp) == 0 ) {
+                if ( entp == NULL )
+                    break;
+                if ( entp->d_type != DT_REG )
+                    continue;
+                const char* leafName = entp->d_name;
+                if ( DyldSharedCache::isSubCachePath(leafName) )
+                    continue;
+                if ( strlcpy(cachePath, systemCacheDirRealPath, PATH_MAX) >= PATH_MAX )
+                    continue;
+                if ( Utils::concatenatePaths(cachePath, entp->d_name, PATH_MAX) >= PATH_MAX )
+                    continue;
+
+                // FIXME: The memory managemnt here is awful, fix with allocators
+                auto cacheFile = fileManager.fileRecordForPath(_ephemeralAllocator, cachePath);
+                auto cache = Atlas::SharedCache::createForFileRecord(_ephemeralAllocator, std::move(cacheFile));
+                if (cache) {
+                    cache.withUnsafe([&](auto cachePtr){
+                        block(cachePtr);
+                    });
+                }
+            }
+            closedir(dirp);
+        }
+    }
+    for (auto path : realPaths) {
+        _ephemeralAllocator.free((void*)path);
+    }
+}
+
+UniquePtr<SharedCache> SharedCache::createForFileRecord(Allocator& _ephemeralAllocator, FileRecord&& file) {
+    auto uuid = UUID();
+    auto fileMapper = Mapper::mapperForSharedCache(_ephemeralAllocator, file, 0ULL);
+    if (!fileMapper) { return nullptr; }
+    return _transactionalAllocator.makeUnique<SharedCache>(_ephemeralAllocator, std::move(file), fileMapper, (uint64_t)fileMapper->baseAddress(), true);
+}
+
+
+const UUID& SharedCache::uuid() const {
+    return _uuid;
+}
+
+SafePointer SharedCache::rebasedAddress() const {
+    return _rebasedAddress;
+}
+
+uint64_t SharedCache::size() const {
+    return _size;
+}
+
+const FileRecord& SharedCache::file() const {
+    return _file;
+}
+
+
+void SharedCache::forEachFilePath(void (^block)(const char* file_path)) const {
+    block(_file.getPath());
+
+    uint64_t headerSize = 0;
+    bool splitCache = false;
+    getCacheInfo(&*_header, headerSize, splitCache);
+
+    if (splitCache) {
+        auto headerBytes = (std::byte*)&*_header;
+        char subCachePath[PATH_MAX];
+        if (_header->mappingOffset <= offsetof(dyld_cache_header, cacheSubType)) {
+            for (auto i = 0; i < _header->subCacheArrayCount; ++i) {
+                snprintf(&subCachePath[0], sizeof(subCachePath), "%s.%u", _file.getPath(), i+1);
+                block(subCachePath);
+            }
+        } else {
+            auto subCacheArray = (dyld_subcache_entry*)&headerBytes[_header->subCacheArrayOffset];
+            for (auto i = 0; i < _header->subCacheArrayCount; ++i) {
+                snprintf(&subCachePath[0], sizeof(subCachePath), "%s%s", _file.getPath(), subCacheArray[i].fileSuffix);
+                block(subCachePath);
+            }
+        }
+        if ( (_header->mappingOffset >= offsetof(dyld_cache_header, symbolFileUUID)) && !uuid_is_null(_header->symbolFileUUID) ) {
+            strlcpy(&subCachePath[0], _file.getPath(), PATH_MAX);
+            // On new caches, the locals come from a new subCache file
+            if (strstr(subCachePath, ".development") != nullptr) {
+                subCachePath[strlen(subCachePath)-(strlen(".development"))] = 0;
+            }
+            strlcat(subCachePath, ".symbols", PATH_MAX);
+            block(subCachePath);
+        }
+    }
+}
+
+bool SharedCache::isPrivateMapped() const {
+    return _private;
+}
+
+size_t SharedCache::imageCount() const {
+    return (size_t)_header->imagesTextCount;
+}
+
+void SharedCache::forEachImage(void (^block)(Image* image)) {
+    auto headerBytes = (uint8_t*)&*_header;
+    auto images = std::span((const dyld_cache_image_text_info*)&headerBytes[_header->imagesTextOffset], (size_t)_header->imagesTextCount);
+    for (auto i : images) {
+        auto image = Image(_ephemeralAllocator, _mapper, i.loadAddress, _slide, this);
+        block(&image);
+    }
+}
+
+void SharedCache::withImageForIndex(uint32_t idx, void (^block)(Image* image)) {
+    auto headerBytes = (uint8_t*)&*_header;
+    auto images = std::span((const dyld_cache_image_text_info*)&headerBytes[_header->imagesTextOffset], (size_t)_header->imagesTextCount);
+    auto image = Image(_ephemeralAllocator, _mapper, images[idx].loadAddress, _slide, this);
+    block(&image);
+}
+
+// Maps the local symbols for this shared cache.
+// Locals are in an unmapped part of the file, so we have to map then in separately
+UniquePtr<SharedCacheLocals> SharedCache::localSymbols() const {
+    // The locals might be in their own locals file, or in the main cache file.
+    // Where it is depends on the cache header
+    char localSymbolsCachePath[PATH_MAX];
+    strlcpy(&localSymbolsCachePath[0], _file.getPath(), PATH_MAX);
+    bool useSymbolsFile = (_header->mappingOffset >= offsetof(dyld_cache_header, symbolFileUUID));
+    if ( useSymbolsFile ) {
+        if ( uuid_is_null(_header->symbolFileUUID) )
+            return nullptr;
+
+        // On new caches, the locals come from a new subCache file
+        if (strstr(localSymbolsCachePath, DYLD_SHARED_CACHE_DEVELOPMENT_EXT ) != nullptr) {
+            localSymbolsCachePath[strlen(localSymbolsCachePath)-(strlen(DYLD_SHARED_CACHE_DEVELOPMENT_EXT ))] = 0;
+        }
+        strlcat(localSymbolsCachePath, ".symbols", PATH_MAX);
+    } else {
+        if ( (_header->localSymbolsSize == 0) || (_header->localSymbolsOffset == 0) )
+            return nullptr;
+    }
+    // TODO: Create Path extension helpers for FileRecord
+    auto localSymbolsCacheFile = _file.fileManager().fileRecordForPath(_ephemeralAllocator, localSymbolsCachePath);
+    auto [fileMapper, baseAddress] = Mapper::mapperForSharedCacheLocals(_ephemeralAllocator, localSymbolsCacheFile);
+    if (!fileMapper) { return nullptr; }
+    // Use placement new since operator new is not available
+    return _transactionalAllocator.makeUnique<SharedCacheLocals>(fileMapper, useSymbolsFile);
+}
+
+bool SharedCache::pin() {
+    return _mapper->pin();
+}
+
+void SharedCache::unpin() {
+    return _mapper->unpin();
+}
+
+#ifdef TARGET_OS_OSX
+bool SharedCache::mapSubCacheAndInvokeBlock(const dyld_cache_header* subCacheHeader,
+                                            void (^block)(const void* cacheBuffer, size_t size)) {
+    bool result = true;
+    auto subCacheHeaderBytes = (uint8_t*)subCacheHeader;
+    uint64_t fileSize = 0;
+    for(auto i = 0; i < subCacheHeader->mappingCount; ++i) {
+        auto mapping = (dyld_cache_mapping_info*)&subCacheHeaderBytes[subCacheHeader->mappingOffset+(i*sizeof(dyld_cache_mapping_info))];
+        uint64_t regionEndSize = mapping->fileOffset + mapping->size;
+        if (fileSize < regionEndSize) {
+            fileSize = regionEndSize;
+        }
+    }
+    vm_address_t mappedSubCache = 0;
+    if (vm_allocate(mach_task_self(), &mappedSubCache, (size_t)fileSize, VM_FLAGS_ANYWHERE) != KERN_SUCCESS) {
+        result = false;
+        return result;
+    }
+    for(auto i = 0; i < _header->mappingCount; ++i) {
+        //    _slide = _baseAddress -  _header->sharedRegionStart;
+        auto mapping = (dyld_cache_mapping_info*)&subCacheHeaderBytes[subCacheHeader->mappingOffset+(i*sizeof(dyld_cache_mapping_info))];
+        auto mappingBytes = _mapper->map<uint8_t>(mapping->address - _slide, mapping->size);
+        kern_return_t r = vm_copy(mach_task_self(), (vm_address_t)&*mappingBytes, (vm_size_t)mapping->size, (vm_address_t)(mappedSubCache+mapping->fileOffset));
+        if ( r != KERN_SUCCESS ) {
+            result = false;
+            break;
+        }
+    }
+    if ( result )
+        block((void*)mappedSubCache, (size_t)fileSize);
+
+    assert(vm_deallocate(mach_task_self(), (vm_address_t)mappedSubCache, (vm_size_t)fileSize) == KERN_SUCCESS);
+    return result;
+}
+
+bool SharedCache::forEachSubcache4Rosetta(void (^block)(const void* cacheBuffer, size_t size)) {
+    if (strcmp(_header->magic, "dyld_v1  x86_64") != 0) {
+        return false;
+    }
+    uint64_t headerSize;
+    bool splitCache = false;
+    getCacheInfo(&*_header, headerSize, splitCache);
+    mapSubCacheAndInvokeBlock(&*_header, block);
+    auto headerBytes = (uint8_t*)&*_header;
+    if (splitCache) {
+        for (auto i = 0; i < _header->subCacheArrayCount; ++i) {
+            uint64_t subCacheOffset = 0;
+            if (_header->mappingOffset <= offsetof(dyld_cache_header, cacheSubType) ) {
+                auto subCaches = (dyld_subcache_entry_v1*)&headerBytes[_header->subCacheArrayOffset];
+                subCacheOffset = subCaches[i].cacheVMOffset;
+            } else {
+                auto subCaches = (dyld_subcache_entry*)&headerBytes[_header->subCacheArrayOffset];
+                subCacheOffset = subCaches[i].cacheVMOffset;
+            }
+            auto subCacheHeader = _mapper->map<dyld_cache_header>((uint64_t)rebasedAddress() + subCacheOffset, PAGE_SIZE);
+            uint64_t subCacheHeaderSize = subCacheHeader->mappingOffset+subCacheHeader->mappingCount*sizeof(dyld_cache_mapping_info);
+            getCacheInfo(&*_header, headerSize, splitCache);
+            if (subCacheHeaderSize > PAGE_SIZE) {
+                subCacheHeader = _mapper->map<dyld_cache_header>((uint64_t)rebasedAddress() + subCacheOffset, subCacheHeaderSize);
+            }
+//            printf("Subcache Offset: %lx\n", (uintptr_t)&headerBytes[subCacheOffset]);
+//            printf("subCacheHeader: %lx\n", (uintptr_t)&*subCacheHeader);
+//            printf("Subcache magic: %s\n", subCacheHeader->magic);
+            mapSubCacheAndInvokeBlock(&*subCacheHeader, block);
+        }
+    }
+    return true;
+}
+#endif
+
+#if BUILDING_LIBDYLD
+#pragma mark -
+#pragma mark Process
+
+Process::Process(Allocator& ephemeralAllocator, FileManager& fileManager, task_read_t task, kern_return_t *kr)
+    :   _ephemeralAllocator(ephemeralAllocator), _fileManager(fileManager), _task(task), _queue(dispatch_queue_create("com.apple.dyld.introspection", NULL)),
+        _registeredNotifiers(_transactionalAllocator), _registeredUpdaters(_transactionalAllocator)  {
+    _snapshot = getSnapshot(kr);
+}
+
+Process::~Process() {
+    dispatch_async_and_wait(_queue, ^{
+        if (_state == Connected) {
+            teardownNotifications();
+        }
+    });
+    dispatch_release(_queue);
+}
+
+kern_return_t Process::task_dyld_process_info_notify_register(task_t target_task, mach_port_t notify) {
+#if TARGET_OS_SIMULATOR
+    static dispatch_once_t onceToken;
+    static kern_return_t (*tdpinr)(task_t, mach_port_t) = nullptr;
+    dispatch_once(&onceToken, ^{
+        tdpinr = (kern_return_t (*)(task_t, mach_port_t))dlsym(RTLD_DEFAULT, "task_dyld_process_info_notify_register");
+    });
+    if (tdpinr) {
+        return tdpinr(target_task, notify);
+    }
+    return KERN_FAILURE;
+#else
+    return ::task_dyld_process_info_notify_register(target_task, notify);
+#endif
+}
+
+kern_return_t Process::task_dyld_process_info_notify_deregister(task_t target_task, mach_port_t notify) {
+#if TARGET_OS_SIMULATOR
+    static dispatch_once_t onceToken;
+    static kern_return_t (*tdpind)(task_t, mach_port_t) = nullptr;
+    dispatch_once(&onceToken, ^{
+        tdpind = (kern_return_t (*)(task_t, mach_port_t))dlsym(RTLD_DEFAULT, "task_dyld_process_info_notify_deregister");
+    });
+    if (tdpind) {
+        return tdpind(target_task, notify);
+    }
+    return KERN_FAILURE;
+#else
+    // Our libsystem does not have task_dyld_process_info_notify_deregister, emulate
+    return ::task_dyld_process_info_notify_deregister(target_task, notify);
+#endif
+}
+
+// Some day the kernel will setup a compact info for us, so there will always be one, but for now synthesize one for processes
+// that launch suspended and have not run long enough to have one
+UniquePtr<ProcessSnapshot> Process::synthesizeSnapshot(kern_return_t *kr) {
+    kern_return_t krSink = KERN_SUCCESS;
+    if (kr == nullptr) {
+        kr = &krSink;
+    }
+
+    auto result = _transactionalAllocator.makeUnique<ProcessSnapshot>(_ephemeralAllocator, _fileManager, false);
+    pid_t   pid;
+    *kr = pid_for_task(_task, &pid);
+    if ( *kr != KERN_SUCCESS ) {
+        BLEND_KERN_RETURN_LOCATION(*kr, 0xea);
+        *kr |= 0xeb000000UL;
+        return nullptr;
+    }
+
+    mach_task_basic_info ti;
+    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
+    *kr = task_info(_task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count);
+    if ( *kr != KERN_SUCCESS ) {
+        BLEND_KERN_RETURN_LOCATION(*kr, 0xe9);
+        return nullptr;
+    }
+
+    bool foundDyld              = false;
+    bool foundMainExecutable    = false;
+    mach_vm_size_t      size;
+    for (mach_vm_address_t address = 0; ; address += size) {
+        vm_region_basic_info_data_64_t  info;
+        mach_port_t                     objectName;
+        unsigned int                    infoCount = VM_REGION_BASIC_INFO_COUNT_64;
+        if ( mach_vm_region(_task, &address, &size, VM_REGION_BASIC_INFO,
+                            (vm_region_info_t)&info, &infoCount, &objectName) != KERN_SUCCESS ) {
+            break;
+        }
+        if ( info.protection != (VM_PROT_READ|VM_PROT_EXECUTE) ) {
+            continue;
+        }
+        char executablePath[PATH_MAX+1];
+        auto bytes = UniquePtr<std::byte>((std::byte*)_ephemeralAllocator.malloc((size_t)size));
+        bytes.withUnsafe([&](std::byte* unsafeBytes) {
+            mach_vm_size_t readSize = 0;
+            *kr = mach_vm_read_overwrite(_task, address, size, (mach_vm_address_t)&unsafeBytes[0], &readSize);
+            if ( *kr != KERN_SUCCESS ) {
+                BLEND_KERN_RETURN_LOCATION(*kr, 0xe8);
+                return;
+            }
+            auto mh = Header::isMachO({(const uint8_t*)unsafeBytes, (size_t)readSize});
+            if (!mh) {
+                return;
+            }
+            if ( mh->isMainExecutable() ) {
+                int len = proc_regionfilename(pid, address, executablePath, PATH_MAX);
+                if ( len != 0 ) {
+                    executablePath[len] = '\0';
+                }
+                SharedPtr<Mapper> mapper = nullptr;
+                auto file = _fileManager.fileRecordForPath(_transactionalAllocator, executablePath);
+                uuid_t rawUUID;
+                mh->getUuid(rawUUID);
+                auto uuid = UUID(rawUUID);
+                result->addImage(Image(_transactionalAllocator, std::move(file), mapper, (uint64_t)address, uuid));
+                foundMainExecutable = true;
+            } else if ( mh->isDylinker() ) {
+                int len = proc_regionfilename(pid, address, executablePath, PATH_MAX);
+                if ( len != 0 ) {
+                    executablePath[len] = '\0';
+                }
+                SharedPtr<Mapper> mapper = nullptr;
+                auto file = _fileManager.fileRecordForPath(_transactionalAllocator, executablePath);
+                uuid_t rawUUID;
+                mh->getUuid(rawUUID);
+                auto uuid = UUID(rawUUID);
+                result->addImage(Image(_transactionalAllocator, std::move(file), mapper, (uint64_t)address, uuid));
+                foundDyld = true;
+            }
+        });
+        if (foundDyld && foundMainExecutable) {
+            return result;
+        }
+    }
+
+    if ( *kr != KERN_SUCCESS ) {
+        return nullptr;
+    }
+
+    if ( foundMainExecutable ) {
+        // return result even if no dyld was found. It could be present in the shared cache
+        return result;
+    }
+
+    // Something failed, we don't know what
+    *kr = KERN_FAILURE;
+    return nullptr;
+}
+
+UniquePtr<ProcessSnapshot> Process::getSnapshot(kern_return_t *kr) {
+    kern_return_t krSink = KERN_SUCCESS;
+    if (kr == nullptr) {
+        kr = &krSink;
+    }
+    mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
+    task_dyld_info_data_t task_dyld_info;
+    *kr = task_info(_task, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count);
+    if ( *kr != KERN_SUCCESS ) {
+        BLEND_KERN_RETURN_LOCATION(*kr, 0xef);
+        return nullptr;
+    }
+    //The kernel will return MACH_VM_MIN_ADDRESS for an executable that has not had dyld loaded
+    if (task_dyld_info.all_image_info_addr == MACH_VM_MIN_ADDRESS) {
+        BLEND_KERN_RETURN_LOCATION(*kr, 0xee);
+        return nullptr;
+    }
+    uint64_t failedAddress = 0;
+    while (1) {
+        // Using mach_vm_read_overwrite because this is part of dyld. If the file is removed or the codesignature is invalid
+        // then the system is broken beyond recovery anyway
+        auto taskInfoBuffer = SafeRemoteBuffer(_task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, kr);
+        if (*kr != KERN_SUCCESS) {
+            BLEND_KERN_RETURN_LOCATION(*kr, 0xed);
+            // If we cannot read the all image info this is game over
+            return nullptr;
+        }
+        uint64_t compactInfoAddress;
+        uint64_t compactInfoSize;
+        if (task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) {
+            const dyld_all_image_infos_32* info = (const dyld_all_image_infos_32*)&taskInfoBuffer.data()[0];
+            compactInfoAddress              = info->compact_dyld_image_info_addr;
+            compactInfoSize                 = info->compact_dyld_image_info_size;
+        } else {
+            const dyld_all_image_infos_64* info = (const dyld_all_image_infos_64*)&taskInfoBuffer.data()[0];
+            compactInfoAddress              = info->compact_dyld_image_info_addr;
+            compactInfoSize                 = info->compact_dyld_image_info_size;
+        }
+        if (compactInfoSize == 0) {
+            return synthesizeSnapshot(kr);
+        }
+        
+        auto compactInfoBuffer = SafeRemoteBuffer(_task, compactInfoAddress,compactInfoSize, kr);
+        if (*kr != KERN_SUCCESS) {
+            BLEND_KERN_RETURN_LOCATION(*kr, 0xec);
+            if (compactInfoAddress == failedAddress) {
+                // We tried the same address twice and it failed both times, this is not a simple mutation issue, give up and reutrn an error
+                return nullptr;
+            }
+            failedAddress = compactInfoAddress;
+            // The read failed, chances are the process mutated the compact info, retry
+            continue;
+        }
+        UniquePtr<ProcessSnapshot> result = _transactionalAllocator.makeUnique<ProcessSnapshot>(_ephemeralAllocator, _fileManager, false, compactInfoBuffer.data());
+        if (!result->valid()) {
+            // Something blew up we don't know what
+            *kr = KERN_FAILURE;
+            BLEND_KERN_RETURN_LOCATION(*kr, 0xeb);
+            return nullptr;
+        }
+        return result;
+    }
+}
+
+void Process::setupNotifications(kern_return_t *kr) {
+    assert(dispatch_get_current_queue() == _queue);
+    assert(kr != NULL);
+    assert(_state == Disconnected);
+    // Allocate a port to listen on in this monitoring task
+    mach_port_options_t options = { .flags = MPO_IMPORTANCE_RECEIVER | MPO_CONTEXT_AS_GUARD | MPO_STRICT, .mpl = { MACH_PORT_QLIMIT_DEFAULT }};
+    *kr = mach_port_construct(mach_task_self(), &options, (mach_port_context_t)this, &_port);
+    if (*kr != KERN_SUCCESS) {
+        return;
+    }
+    // Setup notifications in case the send goes away
+    mach_port_t previous = MACH_PORT_NULL;
+    *kr = mach_port_request_notification(mach_task_self(), _port, MACH_NOTIFY_NO_SENDERS, 1, _port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
+    if ((*kr != KERN_SUCCESS) || previous != MACH_PORT_NULL) {
+        (void)mach_port_destruct(mach_task_self(), _port, 0, (mach_port_context_t)this);
+        return;
+    }
+    *kr = task_dyld_process_info_notify_register(_task, _port);
+    if (*kr != KERN_SUCCESS) {
+        (void)mach_port_destruct(mach_task_self(), _port, 0, (mach_port_context_t)this);
+        return;
+    }
+    _machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _port, 0, _queue);
+    if (_machSource == nullptr) {
+        (void)mach_port_destruct(mach_task_self(), _port, 0, (mach_port_context_t)this);
+        return;
+    }
+    dispatch_source_set_event_handler(_machSource, ^{ handleNotifications(); });
+    // Copy these into locals so the block captures them as const instead of implicitly referring to the members via this
+    task_read_t blockTask = _task;
+    mach_port_t blockPort = _port;
+    dispatch_source_t blockSource = _machSource;
+    dispatch_source_set_cancel_handler(_machSource, ^{
+        (void)task_dyld_process_info_notify_deregister(blockTask, blockPort);
+        (void)mach_port_destruct(mach_task_self(), blockPort, 0, (mach_port_context_t)this);
+        dispatch_release(blockSource);
+    });
+    dispatch_activate(_machSource);
+    _state = Connected;
+}
+
+void Process::teardownNotifications() {
+    assert(dispatch_get_current_queue() == _queue);
+    assert(_state == Connected);
+    if (_machSource) {
+        dispatch_source_cancel(_machSource);
+        _port = 0;
+        _machSource = NULL;
+        _state = Disconnected;
+        // We leave the handle records so that we can correctly process release, but we release
+        // the resources
+        for (auto& [handle,updaterRecord] : _registeredUpdaters) {
+            assert(handle != 0);
+            if (updaterRecord.queue) {
+                dispatch_release(updaterRecord.queue);
+                updaterRecord.queue = nullptr;
+            }
+            if (updaterRecord.block) {
+                Block_release(updaterRecord.block);
+                updaterRecord.block = nullptr;
+            }
+        }
+        for (auto& [handle,notifierRecord] : _registeredNotifiers) {
+            assert(handle != 0);
+            if (notifierRecord.queue) {
+                dispatch_release(notifierRecord.queue);
+                notifierRecord.queue = nullptr;
+            }
+            if (notifierRecord.block) {
+                Block_release(notifierRecord.block);
+                notifierRecord.block = nullptr;
+            }
+        }
+    }
+}
+
+void Process::handleNotifications() {
+    if (_state != Connected) { return; }
+    // This event handler block has an implicit reference to "this"
+    // if incrementing the count goes to one, that means the object may have already been destroyed
+    uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE] = {};
+    mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer;
+
+    kern_return_t r = mach_msg(h, MACH_RCV_MSG | MACH_RCV_VOUCHER| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0), 0, sizeof(messageBuffer)-sizeof(mach_msg_audit_trailer_t), _port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+    if ( r == KERN_SUCCESS && !(h->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
+        //fprintf(stderr, "received message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
+        if ( (h->msgh_id & 0xFFFFF000) == DYLD_PROCESS_EVENT_ID_BASE ) {
+            if (h->msgh_size != sizeof(mach_msg_header_t)) {
+                teardownNotifications();
+            } else if ((h->msgh_id & ~0xFFFFF000) == DYLD_REMOTE_EVENT_ATLAS_CHANGED) {
+                kern_return_t kr;
+                auto newSnapshot = getSnapshot(&kr);
+                if (kr == KERN_SUCCESS) {
+                    newSnapshot.withUnsafe([&](ProcessSnapshot* newSanpshotPtr) {
+                        _snapshot->forEachImageNotIn(*newSanpshotPtr, ^(Image *image) {
+                            for(auto& updaterRecord : _registeredUpdaters) {
+                                dispatch_async_and_wait(updaterRecord.second.queue, ^{
+                                    updaterRecord.second.block(image, false);
+                                });
+                            }
+                        });
+                    });
+                    _snapshot.withUnsafe([&](ProcessSnapshot* oldSnapshot) {
+                        newSnapshot->forEachImageNotIn(*oldSnapshot, ^(Image *image) {
+                            for(auto& updaterRecord : _registeredUpdaters) {
+                                dispatch_async_and_wait(updaterRecord.second.queue, ^{
+                                    updaterRecord.second.block(image, true);
+                                });
+                            }
+                        });
+                    });
+                }
+                _snapshot = std::move(newSnapshot);
+                //FIXME: Should we do something on failure here?
+            } else {
+                for (auto& [handle,notifier] : _registeredNotifiers) {
+                    if ((h->msgh_id & ~0xFFFFF000) == notifier.notifierID) {
+                        dispatch_async_and_wait(notifier.queue, notifier.block);
+                    }
+                }
+            }
+            mach_msg_header_t replyHeader;
+            replyHeader.msgh_bits        = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_REMOTE(h->msgh_bits), 0, 0, 0);
+            replyHeader.msgh_id          = 0;
+            replyHeader.msgh_local_port  = MACH_PORT_NULL;
+            replyHeader.msgh_remote_port  = h->msgh_remote_port;
+            replyHeader.msgh_reserved    = 0;
+            replyHeader.msgh_size        = sizeof(replyHeader);
+            r = mach_msg(&replyHeader, MACH_SEND_MSG, replyHeader.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
+            if (r == KERN_SUCCESS) {
+                h->msgh_remote_port = MACH_PORT_NULL;
+            } else {
+                teardownNotifications();
+            }
+        } else if ( h->msgh_id == MACH_NOTIFY_NO_SENDERS ) {
+            // Validate this notification came from the kernel
+            const mach_msg_audit_trailer_t *audit_tlr = (mach_msg_audit_trailer_t *)((uint8_t *)h + round_msg(h->msgh_size));
+            if (audit_tlr->msgh_trailer_type == MACH_MSG_TRAILER_FORMAT_0
+                && audit_tlr->msgh_trailer_size >= sizeof(mach_msg_audit_trailer_t)
+                // We cannot link to libbsm, so we are hardcoding the audit token offset (5)
+                // And the value the represents the kernel (0)
+                && audit_tlr->msgh_audit.val[5] == 0) {
+                teardownNotifications();
+            }
+        } else if ( h->msgh_id != DYLD_PROCESS_INFO_NOTIFY_LOAD_ID
+                   && h->msgh_id != DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID
+                   && h->msgh_id != DYLD_PROCESS_INFO_NOTIFY_MAIN_ID) {
+            fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
+        }
+    } else {
+        fprintf(stderr, "dyld: received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
+    }
+    mach_msg_destroy(h);
+}
+
+uint32_t Process::registerAtlasChangedEventHandler(kern_return_t *kr, dispatch_queue_t queue, void (^block)(Image* image, bool load)) {
+    __block uint32_t result = 0;
+    dispatch_async_and_wait(_queue, ^{
+        if (_state == Disconnected) {
+            setupNotifications(kr);
+            if (*kr != KERN_SUCCESS) {
+                return;
+            }
+        }
+
+        // Connection is setup, which means remote process will now block whenever updates occur
+        if (*kr != KERN_SUCCESS) {
+            teardownNotifications();
+            return;
+        }
+        // Call for every image already in snapshot
+        _snapshot->forEachImage(^(Image *image) {
+            block(image, true);
+        });
+        assert(_state == Connected);
+        dispatch_retain(queue);
+        result = _handleIdx++;
+        _registeredUpdaters.insert({result,(ProcessUpdateRecord){queue, Block_copy(block)}});
+    });
+    return result;
+}
+
+uint32_t Process::registerEventHandler(kern_return_t *kr, uint32_t event, dispatch_queue_t queue, void (^block)()) {
+    __block uint32_t result = 0;
+    dispatch_async_and_wait(_queue, ^{
+        if (_state == Disconnected) {
+            setupNotifications(kr);
+            if (*kr != KERN_SUCCESS) {
+                return;
+            }
+        }
+        assert(_state == Connected);
+        dispatch_retain(queue);
+        result = _handleIdx++;
+        _registeredNotifiers.insert({result,(ProcessNotifierRecord){queue, Block_copy(block), event}});
+    });
+    return result;
+}
+
+void Process::unregisterEventHandler(uint32_t handle) {
+    dispatch_async_and_wait(_queue, ^{
+        if (auto i = _registeredUpdaters.find(handle); i != _registeredUpdaters.end()) {
+            assert(i->second.block != NULL);
+            if (i->second.queue) {
+                dispatch_release(i->second.queue);
+            }
+            if (i->second.block) {
+                Block_release(i->second.block);
+            }
+            _registeredUpdaters.erase(i);
+        } else if (auto j = _registeredNotifiers.find(handle); j != _registeredNotifiers.end()) {
+            if (j->second.queue) {
+                dispatch_release(j->second.queue);
+            }
+            if (j->second.block) {
+                Block_release(j->second.block);
+            }
+            _registeredNotifiers.erase(j);
+        }
+    });
+}
+
+#endif /* BUILDING_LIBDYLD */
+
+#pragma mark -
+#pragma mark Process Snapshot
+
+ProcessSnapshot::ProcessSnapshot(Allocator& ephemeralAllocator, FileManager& fileManager, bool useIdentityMapper)
+    :   _ephemeralAllocator(ephemeralAllocator), _fileManager(fileManager), _images(_transactionalAllocator),
+        _identityMapper(_transactionalAllocator.makeShared<Mapper>(_transactionalAllocator)), _useIdentityMapper(useIdentityMapper) {}
+
+#if BUILDING_LIBDYLD && !TARGET_OS_DRIVERKIT && !TARGET_OS_EXCLAVEKIT
+// This function is a private interface between libdyld and Dyld.framework and implemented in Swift
+// there is no header
+extern "C" const bool unwrapCompactInfo(void* _Nonnull buffer, uint64_t* _Nonnull size);
+#endif /* BUILDING_LIBDYLD && !TARGET_OS_DRIVERKIT && !TARGET_OS_EXCLAVEKIT */
+
+ProcessSnapshot::ProcessSnapshot(Allocator& ephemeralAllocator, FileManager& fileManager, bool useIdentityMapper, const std::span<std::byte> data)
+    :   _ephemeralAllocator(ephemeralAllocator), _fileManager(fileManager), _images(_transactionalAllocator),
+        _identityMapper(_transactionalAllocator.makeShared<Mapper>(_transactionalAllocator)), _useIdentityMapper(useIdentityMapper) {
+        Serializer serializer(*this);
+        bool deserializedSucceeed = serializer.deserialize(data);
+#if BUILDING_LIBDYLD && !TARGET_OS_DRIVERKIT && !TARGET_OS_EXCLAVEKIT
+        static dispatch_once_t onceToken;
+        static __typeof__(unwrapCompactInfo) *unwrapCompactInfoPtr = nullptr;
+        if (!deserializedSucceeed) {
+            // If we failed we try to load the unwrap function
+            dispatch_once(&onceToken, ^{
+                // We attempt a dlopen() here since the unwrapCompactInfo is not in the build yet, and this is a temporary compatibility hack
+                void* dyldFrameworkHandle = dlopen("/System/Library/PrivateFrameworks/Dyld.framework/Dyld", RTLD_NOW);
+                unwrapCompactInfoPtr = (__typeof__(unwrapCompactInfo)*)dlsym(dyldFrameworkHandle, "unwrapCompactInfo");
+            });
+        }
+        // Only try the fallback if we managed to load the unwrap function
+        if (unwrapCompactInfoPtr && !deserializedSucceeed) {
+            std::byte* unwrappedData = (std::byte*)_transactionalAllocator.malloc(data.size());
+            std::copy(data.begin(), data.end(), unwrappedData);
+            uint64_t unwrappedSize = data.size();
+            if (unwrapCompactInfoPtr((void*)unwrappedData, &unwrappedSize)) {
+                std::span<std::byte> unwrappedSpan = std::span<std::byte>(unwrappedData, (size_t)unwrappedSize);
+                deserializedSucceeed = serializer.deserialize(unwrappedSpan);
+            }
+            free((void*)unwrappedData);
+        }
+#endif /* BUILDING_LIBDYLD && !TARGET_OS_DRIVERKIT && !TARGET_OS_EXCLAVEKIT */
+        if (!deserializedSucceeed) {
+            // Deerialization failed, reset the snapshot and mark invalid
+            _images.clear();
+            if (_bitmap) {
+                _bitmap->clear();
+            }
+            _sharedCache        = nullptr;
+            _platform           = 0;
+            _initialImageCount  = 0;
+            _dyldState          = 0;
+            _valid              = false;
+        }
+}
+
+SharedPtr<Mapper>& ProcessSnapshot::identityMapper() {
+    return _identityMapper;
+}
+
+bool ProcessSnapshot::valid() const {
+    return _valid;
+}
+
+void ProcessSnapshot::forEachImage(void (^block)(Image* image)) {
+    bool processedCacheImages = false;
+    auto processSharedCacheImages = [&] {
+        if (processedCacheImages) { return; }
+        processedCacheImages = true;
+        for (auto i = 0; i < _sharedCache->imageCount(); ++i) {
+            if (!_bitmap->checkBit(i)) { continue; }
+            _sharedCache->withImageForIndex(i, ^(Image *image) {
+                block(image);
+            });
+        }
+    };
+    for (auto& image : _images) {
+        if (_sharedCache && (image->rebasedAddress() >= _sharedCache->rebasedAddress())) {
+            processSharedCacheImages();
+        }
+        block(&*image);
+    }
+    if (_sharedCache) {
+        processSharedCacheImages();
+    }
+}
+
+void ProcessSnapshot::forEachImageNotIn(const ProcessSnapshot& other, void (^block)(Image* image)) {
+    bool processedCacheImages = false;
+    auto processSharedCacheImages = [&] {
+        if (processedCacheImages) { return; }
+        if (!_sharedCache) { return; }
+        for (auto i = 0; i < _sharedCache->imageCount(); ++i) {
+            if (!_bitmap->checkBit(i)) { continue; }
+            if (other._sharedCache && other._bitmap->checkBit(i)) { continue; }
+            _sharedCache->withImageForIndex(i, ^(Image *image) {
+                block(image);
+            });
+        }
+    };
+
+    uint64_t address    = ~0ULL;
+    auto i              = other._images.begin();
+    if (i != other._images.end()) {
+        address = (uint64_t)(*i)->rebasedAddress();
+    }
+
+    for (auto& image : _images) {
+        if (_sharedCache && (image->rebasedAddress() >= _sharedCache->rebasedAddress())) {
+            processSharedCacheImages();
+        }
+        for ( ; image->rebasedAddress() > address; ++i) {
+            if (i == other._images.end()) {
+                address = ~0ULL;
+                break;
+            }
+            address = (uint64_t)(*i)->rebasedAddress();
+        }
+        if (image->rebasedAddress() != address) {
+            block(&*image);
+        }
+    }
+    if (_sharedCache) {
+        processSharedCacheImages();
+    }
+}
+
+
+UniquePtr<SharedCache>& ProcessSnapshot::sharedCache() {
+    return _sharedCache;
+}
+
+#if BUILDING_DYLD
+void ProcessSnapshot::addImages(RuntimeState* state, Vector<ConstAuthLoader>& loaders) {
+    for (auto& ldr : loaders) {
+        if (_sharedCache && ldr->dylibInDyldCache) {
+            _bitmap->setBit(ldr->ref.index);
+        } else {
+            _images.insert(_transactionalAllocator.makeUnique<Image>(state, _ephemeralAllocator, identityMapper(), ldr));
+        }
+    }
+}
+
+void ProcessSnapshot::removeImages(RuntimeState* state, const std::span<const Loader*>& loaders) {
+    for (auto ldr: loaders) {
+        removeImageAtAddress((uint64_t)ldr->loadAddress(*state));
+    }
+}
+#endif
+
+
+void ProcessSnapshot::addImage(Image&& image) {
+    _images.insert(_transactionalAllocator.makeUnique<Image>(std::move(image)));
+}
+
+#if BUILDING_DYLD || BUILDING_UNIT_TESTS
+void ProcessSnapshot::addSharedCache(SharedCache&& sharedCache) {
+    _sharedCache = _transactionalAllocator.makeUnique<SharedCache>(std::move(sharedCache));
+    _bitmap = _transactionalAllocator.makeUnique<Bitmap>(_transactionalAllocator, _sharedCache->imageCount());
+}
+
+void ProcessSnapshot::addSharedCacheImage(const struct mach_header* mh) {
+    assert(mh->flags & MH_DYLIB_IN_CACHE);
+    auto header = (dyld_cache_header*)(uint64_t)_sharedCache->rebasedAddress();
+    auto headerBytes = (uint8_t*)header;
+    auto slide = (uint64_t)header - header->sharedRegionStart;
+    auto images = std::span((const dyld_cache_image_text_info*)&headerBytes[header->imagesTextOffset], (size_t)header->imagesTextCount);
+    auto i = std::find_if(images.begin(), images.end(), [&](const dyld_cache_image_text_info& other) {
+        return (other.loadAddress == ((uint64_t)mh-slide));
+    });
+    assert(i != images.end());
+    _bitmap->setBit(i-images.begin());
+}
+
+
+
+void ProcessSnapshot::removeImageAtAddress(uint64_t address) {
+    //FIXME: Perf improvements by bisection. More generally remove UniquePtrs from sets needs a better solution
+    for (auto i = _images.begin(); i != _images.end(); ++i) {
+        if ((*i)->rebasedAddress() == address) {
+            _images.erase(i);
+            return;
+        }
+    }
+}
+
+Vector<std::byte> ProcessSnapshot::serialize() {
+    Serializer serializer(*this);
+    return serializer.serialize();
+}
+
+void ProcessSnapshot::setInitialImageCount(uint64_t imageCount) {
+    _initialImageCount = imageCount;
+}
+
+void ProcessSnapshot::setDyldState(uint64_t state) {
+    _dyldState = state;
+}
+
+void ProcessSnapshot::setPlatform(uint64_t platform) {
+    _platform = platform;
+}
+
+#endif /* BUILDING_DYLD || BUILDING_UNIT_TESTS */
+
+
+void ProcessSnapshot::dump() {
+    forEachImage(^(Image *image) {
+        char uuidStr[64];
+        image->uuid().dumpStr(uuidStr);
+        const char* name = image->installname();
+        if (!name) {
+            name = image->filename();
+        }
+        if (!name) {
+            name = "<unknown>";
+        }
+        fprintf(stderr, "0x%llx %s %s\n",  (uint64_t)image->rebasedAddress(), uuidStr, name);
+    });
+}
+
+#pragma mark -
+#pragma mark Process Snapshot Serializer
+
+//
+//           ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+//           │Compact Info                                                                                                                            │
+//           │   ┌────────────────────────────┐                                                                                                       │
+//           │   │uint32_t magic              │           ┌────────────────────────────┐                                                              │
+//           │   ├────────────────────────────┤     ┌────▶│PVLEUInt64 count            │                                                              │
+//           │   │uint32_t version            │     │     ├────────────────────────────┘                                                              │
+//           │   ├────────────────────────────┤     │      UUID elt[0]                 │◀───────────────┐                                             │
+//           │   │uint64_t systemInfoAddress  │     │     ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │                                             │
+//           │   ├────────────────────────────┤     │      ...                         │                │                                             │
+//           │   │uint32_t systemInfoSize     │     │     ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │                                             │
+//           │   ├────────────────────────────┤     │      UUID elt[count - 1]         │                │                                             │
+//           │   │uint32_t genCount           │     │     └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │                                             │
+//           │   ├────────────────────────────┤     │                                                   │                                             │
+//           │   │uint64_t timeStamp          │     │                                                   │                                             │
+//           │   ├────────────────────────────┤     │                                                   │                                             │
+//           │   │uint32_t crc32              │     │             ┌────────────────────────────┐        │                                             │
+//           │   ├────────────────────────────┤     │   ┌────────▶│PVLEUInt64 count            │        │                                             │
+//           │   │PVLEUInt64 processFlags     │     │   │         ├────────────────────────────┘        │                                             │
+//           │   ├────────────────────────────┤     │   │          char data[count - 1]        │◀───────┼─────┐                                       │
+//           │   │PVLEUInt64 platform         │     │   │         └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─         │     │                                       │
+//           │   ├────────────────────────────┤     │   │                                               │     │                                       │
+//           │   │PVLEUInt64 initialImageCount│     │   │                                               │     │                                       │
+//           │   ├────────────────────────────┤     │   │               ┌────────────────────────────┐  │     │                                       │
+//           │   │PVLEUInt64 dyldState        │     │   │   ─ ─ ─ ─ ─ ─▶│PVLEUInt64 flags            │  │     │                                       │
+//           │   ├────────────────────────────┤     │   │  │            ├────────────────────────────┤  │     │                                       │
+//           │   │VolumeUUIDs                 │─────┘   │               │PVLEUInt64 baseAddress      │  │     │                                       │
+//           │   ├────────────────────────────┤         │  │            ├────────────────────────────┘  │     │                                       │
+//           │   │StringTableBuffer           │─────────┘                UUID                        │  │     │                                       │
+//           │   ├────────────────────────────┤            │            ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─   │     │                                       │
+//           │   │sharedCache                 │── ─ ─ ─ ─ ─              PVLEUInt64 volumeUUID       │──┘     │                                       │
+//           │   ├────────────────────────────┤                         ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─         │                                       │
+//           │   │images                      │───────┐                  PVLEUInt64 objectID         │        │                                       │
+//           │   ├────────────────────────────┤       │                 ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─         │                                       │
+//           │   │Zero padding (% 16)         │       │                  PVLEUInt64 path             │────────┘                                       │
+//           │   └────────────────────────────┘       │                 ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                ┌────────────────────────────┐   │
+//           │                                        │                  Bitmap bitmap               │──────────────▶│PVLEUInt64 count            │   │
+//           │                                        │                 └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                ├────────────────────────────┘   │
+//           │                                        │                                                               byte data[count - 1]        │   │
+//           │                                        │                                                              └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─    │
+//           │                                        │                                                                                               │
+//           │                                        │        ┌────────────────────────────┐           ┌────────────────────────────┐                │
+//           │                                        └───────▶│PVLEUInt64 count            │     ┌────▶│PVLEUInt64 flags            │                │
+//           │                                                 ├────────────────────────────┘     │     ├────────────────────────────┤                │
+//           │                                                  MappedFileInfo elt[0]       │─────┘     │PVLEUInt64 baseAddress      │                │
+//           │                                                 ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─            ├────────────────────────────┘                │
+//           │                                                  ...                         │            UUID                        │                │
+//           │                                                 ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─            ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │
+//           │                                                  MappedFileInfo elt[count -  │            PVLEUInt64 volumeUUID       │                │
+//           │                                                 └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─            ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │
+//           │                                                                                           PVLEUInt64 objectID         │                │
+//           │                                                                                          ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │
+//           │                                                                                           PVLEUInt64 path             │                │
+//           │                                                                                          └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │
+//           │                                                                                                                                        │
+//           └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+ProcessSnapshot::Serializer::Serializer(ProcessSnapshot& processSnapshot)
+    :   _processSnapshot(processSnapshot), _ephemeralAllocator(_processSnapshot._ephemeralAllocator),
+        _fileManager(_processSnapshot._fileManager), _images(_processSnapshot._images),
+        _sharedCache(_processSnapshot._sharedCache), _bitmap(_processSnapshot._bitmap), _volumeUUIDs(_ephemeralAllocator),
+        _strings(_ephemeralAllocator), _stringTableBuffer(_ephemeralAllocator),
+        _stringTableOffsets(_ephemeralAllocator), _platform(_processSnapshot._platform),
+        _initialImageCount(_processSnapshot._initialImageCount), _dyldState(_processSnapshot._dyldState)
+{}
+
+void ProcessSnapshot::Serializer::emitStringRef(const char* string, Vector<std::byte>& data) {
+    auto i = std::lower_bound(_strings.begin(), _strings.end(), string, lsl::ConstCharStarCompare());
+    if (i == _strings.end()) {
+        i = std::lower_bound(_strings.begin(), _strings.end(), "???", lsl::ConstCharStarCompare());
+    }
+    contract(i != _strings.end());
+    contract(strcmp(*i, string) == 0);
+    uint32_t index = (uint32_t)((uintptr_t)(*i) - (uintptr_t)&_stringTableBuffer[0]);
+    contract(strcmp(*i, &_stringTableBuffer[index]) == 0);
+    emitPVLEUInt64(index, data);
+}
+
+void ProcessSnapshot::Serializer::emitMappedFileInfo(uint64_t rebasedAddress, const UUID& uuid, const FileRecord& file, Vector<std::byte>& data) {
+    uint64_t flags = 0;
+    if (uuid) {
+        flags |= kMappedFileFlagsHasUUID;
+    }
+    if (file.persistent()) {
+        flags |= kMappedFileFlagsHasFileID;
+    } else if (file.getPath()) {
+        flags |= kMappedFileFlagsHasFilePath;
+    }
+    emitPVLEUInt64(flags,           data);
+    emitPVLEUInt64(rebasedAddress,  data);
+    if (flags & kMappedFileFlagsHasUUID) {
+        std::copy(uuid.begin(), uuid.end(), std::back_inserter(data));
+    }
+    if (flags & kMappedFileFlagsHasFileID) {
+        auto i = std::lower_bound(_volumeUUIDs.begin(), _volumeUUIDs.end(), file.volume());
+        assert(i != _volumeUUIDs.end());
+        assert(*i == file.volume());
+        uint16_t index = i - _volumeUUIDs.begin();
+        emitPVLEUInt64(index, data);
+        emitPVLEUInt64(file.objectID(), data);
+    }
+    if (flags & kMappedFileFlagsHasFilePath) {
+        emitStringRef(file.getPath(), data);
+    }
+}
+
+bool ProcessSnapshot::Serializer::readMappedFileInfo(std::span<std::byte>& data, uint64_t& rebasedAddress, UUID& uuid, FileRecord& file) {
+    uint64_t flags = 0;
+    if (!readPVLEUInt64(data, flags)
+        || !readPVLEUInt64(data, rebasedAddress)) {
+        return false;
+    }
+    if (flags & kMappedFileFlagsHasUUID) {
+        if (data.size() < 16) {
+            return false;
+        }
+        uuid = UUID(&data[0]);
+        data = data.last(data.size()-16);
+    }
+    if (flags & kMappedFileFlagsHasFileID) {
+        uint64_t volumeIndex = 0;
+        uint64_t objectID = 0;
+        if (!readPVLEUInt64(data, volumeIndex)
+            || !readPVLEUInt64(data, objectID)
+            || volumeIndex >= _volumeUUIDs.size()) {
+            return false;
+        }
+        file = _fileManager.fileRecordForVolumeUUIDAndObjID(_volumeUUIDs[(size_t)volumeIndex], objectID);
+    }
+    if (flags & kMappedFileFlagsHasFilePath) {
+        uint64_t pathOffset = 0;
+        if (!readPVLEUInt64(data, pathOffset) || pathOffset >= _stringTableBuffer.size()) {
+            return false;
+        }
+        file = _fileManager.fileRecordForPath(_ephemeralAllocator, &_stringTableBuffer[(size_t)pathOffset]);
+    }
+    return true;
+}
+
+Vector<std::byte> ProcessSnapshot::Serializer::serialize() {
+    _timestamp = mach_absolute_time();
+    _genCount++;
+    auto result = Vector<std::byte>(_ephemeralAllocator);
+    // We need unique all the strings and UUIDs and place them in sorted tables
+    // FIXME: We should use vectors and sort them since it faster in pathological cases, but we need a non-allocating sort
+    OrderedSet<const char*, lsl::ConstCharStarCompare>   stringSet(_ephemeralAllocator);
+    OrderedSet<UUID>                                     volumeUUIDSet(_ephemeralAllocator);
+    if (PAGE_SIZE == 16384) {
+        _processFlags |= kProcessFlagsHas16kPages;
+    }
+    if (_sharedCache) {
+        _processFlags |= kProcessFlagsHasSharedCache;
+        auto& file = _sharedCache->file();
+        if (file.persistent()) {
+            volumeUUIDSet.insert(file.volume());
+        } else if (auto filePath = file.getPath()) {
+            stringSet.insert(filePath);
+        } else {
+            stringSet.insert("???");
+        }
+        //FIXME record private cache info
+    }
+    for (const auto& image : _images) {
+        auto& file = image->file();
+        if (file.persistent()) {
+            volumeUUIDSet.insert(file.volume());
+        } else if (auto filePath = file.getPath()) {
+            stringSet.insert(filePath);
+        } else {
+            stringSet.insert("???");
+        }
+    }
+    // Insert them into vectors so we can get offsets cheaply
+    _volumeUUIDs = Vector<UUID>(volumeUUIDSet.begin(), volumeUUIDSet.end(), _ephemeralAllocator);
+
+    for (const auto& string : stringSet) {
+        _stringTableOffsets.push_back((uint32_t)_stringTableBuffer.size());
+        for(auto i = 0; string[i] != 0; ++i) {
+            _stringTableBuffer.push_back(string[i]);
+        }
+        _stringTableBuffer.push_back(0);
+    }
+    for (const auto& offset : _stringTableOffsets) {
+        _strings.push_back(&_stringTableBuffer[offset]);
+    }
+
+    // First, serializer the various pieces of metadata using fixed with ints
+    emit<uint32_t>(_magic,              result);
+    emit<uint32_t>(_version,            result);
+    emit<uint64_t>(_systemInfoAddress,  result);
+    emit<uint32_t>(_systemInfoSize,     result);
+    emit<uint32_t>(_genCount,           result);
+    emit<uint64_t>(_timestamp,          result);
+    emit<uint32_t>(_crc32c,             result);
+
+    // Switch over to variable width now that we are past pieces of the header the kernel may want to parse
+    emitPVLEUInt64(_processFlags,       result);
+    emitPVLEUInt64(_platform,           result);
+    emitPVLEUInt64(_initialImageCount,  result);
+    emitPVLEUInt64(_dyldState,           result);
+
+    emitPVLEUInt64(_volumeUUIDs.size(), result);
+    for (const auto& uuid : _volumeUUIDs) {
+        std::copy(uuid.begin(), uuid.end(), std::back_inserter(result));
+    }
+    emitPVLEUInt64(_stringTableBuffer.size(), result);
+    std::copy((std::byte*)_stringTableBuffer.begin(), (std::byte*)_stringTableBuffer.end(), std::back_inserter(result));
+    if (_processFlags & kProcessFlagsHasSharedCache) {
+        uint64_t address = (uint64_t)_sharedCache->rebasedAddress()/((_processFlags & kProcessFlagsHas16kPages) ? 16384 : 4096);
+        emitMappedFileInfo(address, _sharedCache->uuid(), _sharedCache->file(), result);
+        emitPVLEUInt64(_bitmap->size(), result);
+        if (_bitmap->size() > 0)
+            emit(_bitmap->bytes(), result);
+    }
+
+    emitPVLEUInt64(_images.size(), result);
+    uint64_t lastAddress = 0;
+    for (const auto& image : _images) {
+        uint64_t address = ((uint64_t)image->rebasedAddress()-lastAddress)/((_processFlags & kProcessFlagsHas16kPages) ? 16384 : 4096);
+        lastAddress = (uint64_t)image->rebasedAddress();
+        emitMappedFileInfo(address, image->uuid(), image->file(), result);
+    }
+    while(result.size()%16 != 0) {
+        emit<uint8_t>(0, result);
+    }
+    CRC32c checksumer;
+    checksumer(result);
+    *((uint32_t*)&result[32]) = checksumer;
+    return result;
+}
+
+bool ProcessSnapshot::Serializer::deserialize(const std::span<std::byte> data) {
+    auto i = data;
+    if (i.size() < 36) {
+        // Ensure data is at least large enough to read the header
+        return false;
+    }
+    // Confirm magic
+    _magic              = read<uint32_t>(i);
+    _version            = read<uint32_t>(i);
+    _systemInfoAddress  = read<uint64_t>(i);
+    _systemInfoSize     = read<uint32_t>(i);
+    _genCount           = read<uint32_t>(i);
+    _timestamp          = read<uint64_t>(i);
+    _crc32c             = read<uint32_t>(i);
+    if (_magic != kMagic) {
+        return false;
+    }
+    if (_version != 0) {
+        return false;
+    }
+    CRC32c checksumer;
+    checksumer(std::span(&data[0], 32));
+    checksumer((uint32_t)0); // Zero out the actual checksum
+    checksumer(std::span(&data[36], data.size() - 36));
+    if (_crc32c != checksumer) {
+        return false;
+    }
+    uint64_t volumeUUIDCount = 0;
+    if (!readPVLEUInt64(i, _processFlags)
+        || !readPVLEUInt64(i, _platform)
+        || !readPVLEUInt64(i, _initialImageCount)
+        || !readPVLEUInt64(i, _dyldState)
+        || !readPVLEUInt64(i, volumeUUIDCount)) {
+        return false;
+    }
+    if (i.size() < volumeUUIDCount*16) {
+        return false;
+    }
+    for (auto j = 0; j < volumeUUIDCount; ++j) {
+        UUID volumeUUID(&i[j*16]);
+        _volumeUUIDs.push_back(volumeUUID);
+    }
+    i = i.last((size_t)(i.size()-(16*volumeUUIDCount)));
+    uint64_t stringTableSize = 0;
+    if (!readPVLEUInt64(i, stringTableSize)
+        || i.size() < stringTableSize) {
+        return false;
+    }
+    _stringTableBuffer.reserve((size_t)stringTableSize);
+    std::copy((uint8_t*)&i[0], (uint8_t*)&i[(size_t)stringTableSize], std::back_inserter(_stringTableBuffer));
+    i = i.last((size_t)(i.size()-stringTableSize));
+
+    if (_processFlags & kProcessFlagsHasSharedCache) {
+        uint64_t rebasedAddress;
+        UUID uuid;
+        FileRecord file;
+        if ( !readMappedFileInfo(i, rebasedAddress, uuid, file) )
+            return false;
+        rebasedAddress = rebasedAddress * ((_processFlags & kProcessFlagsHas16kPages) ? kPageSize16K : kPageSize4K);
+        SharedPtr<Mapper> mapper = nullptr;
+        if (_processSnapshot._useIdentityMapper) {
+            mapper = _processSnapshot.identityMapper();
+        } else {
+#if BUILDING_DYLD || BUILDING_UNIT_TESTS
+            mapper = _transactionalAllocator.makeShared<Mapper>(_transactionalAllocator);
+#else
+            mapper = Mapper::mapperForSharedCache(_transactionalAllocator, file, rebasedAddress);
+#endif
+        }
+        if (!mapper) {
+            return false;
+        }
+        _sharedCache = _transactionalAllocator.makeUnique<SharedCache>(_transactionalAllocator, std::move(file), mapper,
+                                                                       rebasedAddress, _processFlags & kProcessFlagsHasPrivateCache);
+        uint64_t encodedSize = 0;
+        if (!readPVLEUInt64(i, encodedSize)) {
+            return false;
+        }
+        _bitmap = _transactionalAllocator.makeUnique<Bitmap>(_transactionalAllocator, (size_t)encodedSize, i);
+        if (_bitmap->size() == 0) {
+            return false;
+        }
+    }
+    uint64_t imageCount = 0;
+    if (!readPVLEUInt64(i, imageCount)) {
+        return false;
+    }
+    uint64_t lastAddress = 0;
+    for (auto j = 0; j < imageCount; ++j) {
+        uint64_t rebasedAddress;
+        UUID uuid;
+        FileRecord file;
+        if ( !readMappedFileInfo(i, rebasedAddress, uuid, file) )
+            return false;
+        rebasedAddress = (rebasedAddress * ((_processFlags & kProcessFlagsHas16kPages) ? 16384 : 4096)) + lastAddress;
+        lastAddress = rebasedAddress;
+        SharedPtr<Mapper> mapper = nullptr;
+        if (_processSnapshot._useIdentityMapper) {
+            mapper = _processSnapshot.identityMapper();
+        }
+#if BUILDING_DYLD || BUILDING_UNIT_TESTS
+        else {
+            mapper = _transactionalAllocator.makeShared<Mapper>(_transactionalAllocator);
+        }
+#endif
+        auto image = Image(_transactionalAllocator, std::move(file), mapper, (uint64_t)rebasedAddress, uuid);
+        _images.insert(_transactionalAllocator.makeUnique<Image>(std::move(image)));
+    }
+    return true;
+}
+
+};
+};
+#endif // !TARGET_OS_EXCLAVEKIT
+