diff options
Diffstat (limited to 'view/sharedcache/core/SharedCache.cpp')
| -rw-r--r-- | view/sharedcache/core/SharedCache.cpp | 4154 |
1 files changed, 398 insertions, 3756 deletions
diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 27367a45..24bc4619 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -1,3975 +1,617 @@ -// -// Created by kat on 5/19/23. -// - -/* --- - * This is the primary image loader logic for Shared Caches - * - * It is standalone code that operates on a DSCView. - * - * This has to recreate _all_ of the Mach-O View logic, but slightly differently, as everything is spicy and weird and - * different enough that it's not worth trying to make a shared base class. - * - * The SharedCache api object is a 'Controller' that serializes its own state in view metadata. - * - * It is multithreading capable (multiple SharedCache objects can exist and do things on different threads, it will manage) - * - * View state is saved to BinaryView any time it changes, however due to json deser speed we must also cache it on heap. - * This cache is 'load bearing' and controllers on other threads may serialize it back to view after making changes, so it - * must be kept up to date. - * - * - * - * */ - #include "SharedCache.h" -#include "binaryninjaapi.h" -#include "DSCView.h" -#include "ObjC.h" -#include <algorithm> -#include <fcntl.h> +#include <regex> #include <filesystem> -#include <limits> -#include <memory> -#include <mutex> -#include <optional> -#include <unordered_map> -#include <utility> -#include <vector> +#include "MachO.h" +#include "SlideInfo.h" using namespace BinaryNinja; -using namespace SharedCacheCore; - -namespace SharedCacheCore { - -namespace { - -#ifdef _MSC_VER - -int count_trailing_zeros(uint64_t value) { - unsigned long index; // 32-bit long on Windows - if (_BitScanForward64(&index, value)) { - return index; - } else { - return 64; // If the value is 0, return 64. - } -} -#else -int count_trailing_zeros(uint64_t value) { - return value == 0 ? 64 : __builtin_ctzll(value); -} -#endif - -struct MemoryRegionStatus -{ - bool loaded = false; - bool headerInitialized = false; -}; - -} // unnamed namespace - -// State that does not change after `PerformInitialLoad`. -struct SharedCache::CacheInfo : - public MetadataSerializable<SharedCache::CacheInfo, std::optional<SharedCache::CacheInfo>> -{ - std::vector<BackingCache> backingCaches; - std::unordered_map<uint64_t, SharedCacheMachOHeader> headers; - std::vector<CacheImage> images; - std::unordered_map<std::string, uint64_t> imageStarts; - AddressRangeMap<MemoryRegion> memoryRegions; - - std::optional<std::pair<uint64_t, uint64_t>> objcOptimizationDataRange; - - std::string baseFilePath; - SharedCacheFormat cacheFormat = RegularCacheFormat; - - MemoryRegion* AddMemoryRegion(MemoryRegion region); - void AddPotentiallyOverlappingMemoryRegion(MemoryRegion region); -#ifndef NDEBUG - void Verify() const; -#endif - - uint64_t BaseAddress() const; - - void Store(SerializationContext&) const; - static std::optional<SharedCache::CacheInfo> Load(DeserializationContext&); -}; - -struct State : public MetadataSerializable<State> -{ - // Map from start address of a region to the region's status. - std::unordered_map<uint64_t, MemoryRegionStatus> memoryRegionStatus; - std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>> - exportInfos; - std::unordered_map<uint64_t, std::shared_ptr<std::vector<Ref<Symbol>>>> symbolInfos; - - // Store only. Loading is done via `ModifiedState`. - void Store(SerializationContext&, std::optional<DSCViewState> viewState) const; -}; - -struct SharedCache::ModifiedState : public State, public MetadataSerializable<SharedCache::ModifiedState> -{ - std::optional<DSCViewState> viewState; - - using Base = MetadataSerializable<SharedCache::ModifiedState>; - using Base::AsMetadata; - using Base::LoadFromString; - - void Store(SerializationContext&) const; - static SharedCache::ModifiedState Load(DeserializationContext&); - static SharedCache::ModifiedState LoadAll(BinaryNinja::BinaryView*, const CacheInfo&); - - void Merge(SharedCache::ModifiedState&& other); -}; - -struct SharedCache::ViewSpecificState -{ - std::mutex typeLibraryMutex; - std::unordered_map<std::string, Ref<TypeLibrary>> typeLibraries; - - std::mutex viewOperationsThatInfluenceMetadataMutex; - std::atomic<BNDSCViewLoadProgress> progress; +// The next id to use when calling Cache::AddEntry +static CacheEntryId nextId = 1; - std::mutex cacheInfoMutex; - std::shared_ptr<const SharedCache::CacheInfo> cacheInfo; - - std::mutex stateMutex; - struct State state; - - std::atomic<DSCViewState> viewState; - uint64_t savedModifications = 0; -}; - -namespace { - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForId(uint64_t viewIdentifier, bool insertIfNeeded = true) +Ref<Symbol> CacheSymbol::ToBNSymbol() const { - static std::mutex viewSpecificStateMutex; - static std::unordered_map<uint64_t, std::weak_ptr<SharedCache::ViewSpecificState>> viewSpecificState; - - std::lock_guard lock(viewSpecificStateMutex); - - if (auto it = viewSpecificState.find(viewIdentifier); it != viewSpecificState.end()) - { - if (auto statePtr = it->second.lock()) - return statePtr; - } - - if (!insertIfNeeded) - return nullptr; - - auto statePtr = std::make_shared<SharedCache::ViewSpecificState>(); - viewSpecificState[viewIdentifier] = statePtr; - - // Prune entries for any views that are no longer in use. - for (auto it = viewSpecificState.begin(); it != viewSpecificState.end(); ) - { - if (it->second.expired()) - it = viewSpecificState.erase(it); - else - ++it; - } - - return statePtr; -} - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForView(Ref<BinaryNinja::BinaryView> view) -{ - return ViewSpecificStateForId(view->GetFile()->GetSessionId()); -} - -std::string base_name(std::string const& path) -{ - return path.substr(path.find_last_of("/\\") + 1); + QualifiedName qname; + std::string shortName = name; + if (DemangleLLVM(name, qname, true)) + shortName = qname.GetString(); + return new Symbol(type, shortName, shortName, name, address, nullptr); } -BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) +std::vector<std::string> CacheImage::GetDependencies() const { - - uint32_t flags = 0; - if (initProt & MACHO_VM_PROT_READ) - flags |= SegmentReadable; - if (initProt & MACHO_VM_PROT_WRITE) - flags |= SegmentWritable; - if (initProt & MACHO_VM_PROT_EXECUTE) - flags |= SegmentExecutable; - if (((initProt & MACHO_VM_PROT_WRITE) == 0) && - ((maxProt & MACHO_VM_PROT_WRITE) == 0)) - flags |= SegmentDenyWrite; - if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && - ((maxProt & MACHO_VM_PROT_EXECUTE) == 0)) - flags |= SegmentDenyExecute; - return (BNSegmentFlag)flags; -} - -BNSectionSemantics SectionSemanticsForRegion(const MemoryRegion& region) -{ - if ((region.flags & SegmentExecutable) && (region.flags & SegmentDenyWrite)) - return ReadOnlyCodeSectionSemantics; - - if (region.flags & SegmentExecutable) - return DefaultSectionSemantics; - - if (region.flags & SegmentDenyWrite) - return ReadOnlyDataSectionSemantics; - - return ReadWriteDataSectionSemantics; -} - - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" -static int64_t readSLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint8_t cur; - int64_t value = 0; - size_t shift = 0; - while (current != end) - { - cur = *current++; - value |= (cur & 0x7f) << shift; - shift += 7; - if ((cur & 0x80) == 0) - break; - } - value = (value << (64 - shift)) >> (64 - shift); - return value; -} -#pragma clang diagnostic pop - - -static uint64_t readLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t result = 0; - int bit = 0; - do - { - if (current >= end) - return -1; - - uint64_t slice = *current & 0x7f; - - if (bit > 63) - return -1; - else - { - result |= (slice << bit); - bit += 7; - } - } while (*current++ & 0x80); - return result; -} - - -uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t value = readLEB128(current, end); - if ((int64_t)value == -1) - throw ReadException(); - return value; -} - -} // unnamed namespace - -uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) -{ - std::shared_ptr<MMappedFileAccessor> baseFile; - try { - baseFile = MapFileWithoutApplyingSlide(dscView->GetFile()->GetOriginalFilename()); - } - catch (...){ - LogError("Shared Cache preload: Failed to open file %s", dscView->GetFile()->GetOriginalFilename().c_str()); - return 0; - } - - dyld_cache_header header {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&header, 0, std::min(header_size, sizeof(dyld_cache_header))); - - SharedCacheFormat cacheFormat; - - if (header.imagesCountOld != 0) - cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = header.mappingOffset; - if (headerEnd > subCacheOff) - { - if (header.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(dscView, baseFile->Path() + ".01"))) - cacheFormat = LargeCacheFormat; - else - cacheFormat = SplitCacheFormat; - } - else - cacheFormat = iOS16CacheFormat; - } - - switch (cacheFormat) - { - case RegularCacheFormat: - { - return 1; - } - case LargeCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 1; - } - case SplitCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - case iOS16CacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - } -} - -MemoryRegion* SharedCache::CacheInfo::AddMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return nullptr; - - auto [it, inserted] = memoryRegions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); - assert(inserted); -#ifndef NDEBUG - Verify(); -#endif - return &it->second; -} - -void SharedCache::CacheInfo::AddPotentiallyOverlappingMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return; - - // First region at or past the start of the region. - auto begin = memoryRegions.lower_bound(region.AsAddressRange().start); - if (begin == memoryRegions.end()) - { - AddMemoryRegion(std::move(region)); - return; - } - - // First region past the end of the region. - auto end = memoryRegions.lower_bound(region.AsAddressRange().end); - - for (auto it = begin; it != end; ++it) - { - uint64_t newRegionSize = it->second.start - region.start; - if (newRegionSize) - { - MemoryRegion newRegion(region); - newRegion.size = newRegionSize; - AddMemoryRegion(std::move(newRegion)); - } - - region.start = it->second.start + it->second.size; - region.size -= (newRegionSize + it->second.size); - } - - AddMemoryRegion(std::move(region)); -} - -#ifndef NDEBUG -void SharedCache::CacheInfo::Verify() const -{ - if (memoryRegions.size() < 2) - { - return; - } - - auto it = memoryRegions.begin(); - auto lastIt = it++; - for (auto lastIt = it++; it != memoryRegions.end(); lastIt = it++) - { - const auto& [lastAddress, lastRegion] = *lastIt; - const auto& [address, region] = *it; - assert(lastRegion.start == lastAddress.start && lastRegion.start + lastRegion.size == lastAddress.end); - assert(region.start == address.start && region.start + region.size == address.end); - assert(lastAddress.start < address.start); - assert(lastAddress.end <= address.start); - } -} -#endif - -static MemoryRegionStatus* StatusForMemoryRegion( - std::unordered_map<uint64_t, MemoryRegionStatus>& memoryRegionStatus, const MemoryRegion& region) -{ - auto it = memoryRegionStatus.find(region.start); - if (it == memoryRegionStatus.end()) - return nullptr; - - return &it->second; -} - -bool SharedCache::MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->loaded; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->loaded; - - return false; -} - -void SharedCache::SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) -{ - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.loaded = true; -} - -bool SharedCache::MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->headerInitialized; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->headerInitialized; - - return false; + if (header) + return header->dylibs; + return {}; } -void SharedCache::SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) +CacheEntry::CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector<dyld_cache_mapping_info> mappings, std::unordered_map<std::string, dyld_cache_image_info> images) { - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.headerInitialized = true; + m_filePath = std::move(filePath); + m_fileName = std::move(fileName); + m_type = type; + m_header = header; + m_mappings = std::move(mappings); + m_images = std::move(images); } -void SharedCache::PerformInitialLoad(std::lock_guard<std::mutex>& lock) +std::optional<CacheEntry> CacheEntry::FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type) { - std::unique_lock viewOperationsLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - auto path = m_dscView->GetFile()->GetOriginalFilename(); - auto baseFile = MapFileWithoutApplyingSlide(path); - - m_logger->LogInfo("Loading caches..."); - m_viewSpecificState->progress = LoadProgressLoadingCaches; - - CacheInfo initialState; - initialState.baseFilePath = path; - initialState.cacheFormat = RegularCacheFormat; + auto file = FileAccessorCache::Global().Open(filePath).lock(); - // TODO: If this fails we should prompt the user to select there file, this probably means that - // TODO: They are opening a database with no original file path. - DataBuffer sig = baseFile->ReadBuffer(0, 4); + // TODO: Pull this out into another function so we can do IsValidDSCFile or something. + // We first want to make sure that the base file is dyld. + // All entries must start with "dyld". + DataBuffer sig = file->ReadBuffer(0, 4); if (sig.GetLength() != 4) - throw std::runtime_error("Not a valid dyld_shared_cache file"); + return std::nullopt; const char* magic = (char*)sig.GetData(); if (strncmp(magic, "dyld", 4) != 0) - throw std::runtime_error("Not a valid dyld_shared_cache file"); - - dyld_cache_header primaryCacheHeader {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&primaryCacheHeader, 0, std::min(header_size, sizeof(dyld_cache_header))); - - if (primaryCacheHeader.imagesCountOld != 0) - initialState.cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = primaryCacheHeader.mappingOffset; - if (headerEnd > subCacheOff) - { - if (primaryCacheHeader.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(m_dscView, baseFile->Path() + ".01"))) - initialState.cacheFormat = LargeCacheFormat; - else - initialState.cacheFormat = SplitCacheFormat; - } - else - initialState.cacheFormat = iOS16CacheFormat; - } - - if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) - { - uint64_t objcOptsOffset = primaryCacheHeader.objcOptsOffset; - uint64_t objcOptsSize = primaryCacheHeader.objcOptsSize; - initialState.objcOptimizationDataRange = {objcOptsOffset, objcOptsSize}; - } - - std::vector<MemoryRegion> nonImageMemoryRegions; - switch (initialState.cacheFormat) - { - case RegularCacheFormat: - { - dyld_cache_mapping_info mapping {}; - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCountOld; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffsetOld + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - m_logger->LogInfo("Found %d images in the shared cache", primaryCacheHeader.imagesCountOld); - - if (primaryCacheHeader.branchPoolsCount) - { - std::vector<uint64_t> addresses; - addresses.reserve(primaryCacheHeader.branchPoolsCount); - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - addresses.push_back(baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()))); - } - baseFile.reset(); // No longer needed, we're about to remap this file into VM space so we can load these. - uint64_t i = 0; - for (auto address : addresses) - { - i++; - auto vm = GetVMMap(); - auto machoHeader = SharedCache::LoadHeaderForAddress(vm, address, "dyld_shared_cache_branch_islands_" + std::to_string(i)); - if (machoHeader) - { - for (const auto& segment : machoHeader->segments) - { - MemoryRegion stubIslandRegion; - stubIslandRegion.start = segment.vmaddr; - stubIslandRegion.size = segment.filesize; - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - std::string segNameStr = std::string(segName); - stubIslandRegion.prettyName = "dyld_shared_cache_branch_islands_" + std::to_string(i) + "::" + segNameStr; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - } - } - - m_logger->LogInfo("Found %d branch pools in the shared cache", primaryCacheHeader.branchPoolsCount); - - break; - } - case LargeCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - break; - } - case SplitCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - baseFile.reset(); - - for (size_t i = 1; i <= subCacheCount; i++) - { - auto subCachePath = path + "." + std::to_string(i); - auto subCacheFilename = mainFileName + "." + std::to_string(i); - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to locate .symbols subcache. Non-exported symbol information may be missing."); - } - break; - } - case iOS16CacheFormat: - { - dyld_cache_mapping_info mapping {}; - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - - if (subCachePath.find(".dylddata") != std::string::npos) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion dyldDataRegion; - dyldDataRegion.start = address; - dyldDataRegion.size = size; - dyldDataRegion.prettyName = subCacheFilename + "::_data" + std::to_string(j); - dyldDataRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable); - dyldDataRegion.type = MemoryRegion::Type::DyldData; - nonImageMemoryRegions.push_back(std::move(dyldDataRegion)); - } - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try - { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (subCacheFile->ReadUInt32(16) > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected, using default size"); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSymbols; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to load the symbols cache"); - } - break; - } - } - baseFile.reset(); - - m_logger->LogInfo("Loading images..."); - m_viewSpecificState->progress = LoadProgressLoadingImages; + return std::nullopt; - // We have set up enough metadata to map VM now. + // Read the header, this _should_ be compatible with all known DSC formats. + // Mason: the above is not true! https://github.com/Vector35/binaryninja-api/issues/6073 + dyld_cache_header header = {}; + file->Read(&header, 0, sizeof(header)); - auto vm = GetVMMap(initialState); - if (!vm) + // Read the mappings using the headers `mappingCount` and `mappingOffset`. + dyld_cache_mapping_info currentMapping = {}; + std::vector<dyld_cache_mapping_info> mappings; + for (size_t i = 0; i < header.mappingCount; i++) { - m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal."); - return; + file->Read(¤tMapping, header.mappingOffset + (i * sizeof(currentMapping)), sizeof(currentMapping)); + mappings.push_back(currentMapping); } - for (const auto& start : initialState.imageStarts) - { - try - { - auto imageHeader = SharedCache::LoadHeaderForAddress(vm, start.second, start.first); - if (!imageHeader) - { - m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str()); - continue; - } - if (imageHeader->linkeditPresent && vm->AddressIsMapped(imageHeader->linkeditSegment.vmaddr)) - { - auto mapping = vm->MappingAtAddress(imageHeader->linkeditSegment.vmaddr); - imageHeader->exportTriePath = mapping.first.fileAccessor->filePath(); - } - initialState.headers[start.second] = imageHeader.value(); - CacheImage image; - image.installName = start.first; - image.headerLocation = start.second; - for (const auto& segment : imageHeader->segments) - { - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - - // Many images include a __LINKEDIT segment that share a single region in the shared cache. - // Reuse the same `MemoryRegion` to represent all of these linkedit regions. - if (std::string(segName) == "__LINKEDIT") - { - if (auto it = initialState.memoryRegions.find(segment.vmaddr); - it != initialState.memoryRegions.end()) - { - image.regionStarts.push_back(it->second.start); - continue; - } - } - MemoryRegion sectionRegion; - sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName); - sectionRegion.start = segment.vmaddr; - sectionRegion.size = segment.vmsize; - sectionRegion.imageStart = start.second; - uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); - - // if we're positive we have an entry point for some reason, force the segment - // executable. this helps with kernel images. - for (auto &entryPoint : imageHeader->m_entryPoints) - if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) - flags |= SegmentExecutable; - - sectionRegion.flags = (BNSegmentFlag)flags; - sectionRegion.type = MemoryRegion::Type::Image; - if (auto region = initialState.AddMemoryRegion(std::move(sectionRegion))) - image.regionStarts.push_back(region->start); - } - initialState.images.push_back(image); - } - catch (std::exception& ex) - { - m_logger->LogError("Failed to load Mach-O header for %s: %s", start.first.c_str(), ex.what()); - } - } - - m_logger->LogInfo("Loaded %d Mach-O headers", initialState.headers.size()); - - for (auto& memoryRegion : nonImageMemoryRegions) + // Handle special entry types. + if (fileName.find(".dylddata") != std::string::npos) { - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(memoryRegion)); + // We found a single dyld data cache entry file. Mark it as such! + type = CacheEntryType::DyldData; } - m_logger->LogInfo("Loaded %zu stub island or dyld memory regions", nonImageMemoryRegions.size()); - - for (const auto& cache : initialState.backingCaches) + else if (fileName.find(".symbols") != std::string::npos) { - size_t i = 0; - for (const auto& mapping : cache.mappings) - { - MemoryRegion region; - region.start = mapping.address; - region.size = mapping.size; - region.prettyName = base_name(cache.path) + "::" + std::to_string(i++); - region.flags = SegmentFlagsFromMachOProtections(mapping.initProt, mapping.maxProt); - region.type = MemoryRegion::Type::NonImage; - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(region)); - } + // We found a single symbols cache entry file. Mark it as such! + type = CacheEntryType::Symbols; } - - m_cacheInfo = std::make_shared<CacheInfo>(std::move(initialState)); - m_modifiedState->viewState = DSCViewStateLoaded; - SaveCacheInfoToDSCView(lock); - - m_logger->LogInfo("Finished loading..."); - m_viewSpecificState->progress = LoadProgressFinished; -} - -std::shared_ptr<VM> SharedCache::GetVMMap() -{ - return GetVMMap(*m_cacheInfo); -} - -std::shared_ptr<VM> SharedCache::GetVMMap(const CacheInfo& cacheInfo) -{ - std::shared_ptr<VM> vm = std::make_shared<VM>(0x1000); - - uint64_t baseAddress = cacheInfo.BaseAddress(); - Ref<Logger> logger = m_logger; - for (const auto& cache : cacheInfo.backingCaches) + else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0 + && header.imagesTextOffset == 0) { - for (const auto& mapping : cache.mappings) - { - vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.address, mapping.fileOffset, mapping.size, cache.path, - [vm, baseAddress, logger](std::shared_ptr<MMappedFileAccessor> mmap){ - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }); - } + // Stub entry file, should only have a single mapping and no images. + // NOTE: If we end up identifying something incorrectly as a stub we need to restrict this further. + // We found a single stub cache entry file. Mark it as such! + type = CacheEntryType::Stub; } - return vm; -} - - -bool SharedCache::DeserializeFromRawView(std::lock_guard<std::mutex>& lock) -{ - std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex); - - m_cacheInfo = std::make_shared<CacheInfo>(); - m_modifiedState = std::make_unique<ModifiedState>(); - m_modifiedState->viewState = DSCViewStateUnloaded; - - // First try and load from the view itself. - if (m_viewSpecificState->cacheInfo) + // Gather all images for the entry. + std::unordered_map<std::string, dyld_cache_image_info> images; + dyld_cache_image_info currentImg {}; + for (size_t i = 0; i < header.imagesCount; i++) { - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; - return true; + file->Read( + ¤tImg, header.imagesOffset + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - // Second try and load from the view metadata. - if (SharedCacheMetadata::ViewHasMetadata(m_dscView)) + // Handle old dyld format that uses old images field. + for (size_t i = 0; i < header.imagesCountOld; i++) { - auto metadata = SharedCacheMetadata::LoadFromView(m_dscView); - if (!metadata) - return false; - - m_viewSpecificState->viewState = metadata->state->viewState.value_or(DSCViewStateUnloaded); - m_viewSpecificState->state = static_cast<State>(std::move(*metadata->state)); - m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo); - - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; + file->Read( + ¤tImg, header.imagesOffsetOld + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - return true; -} - - -// static -void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t base, Ref<Logger> logger) -{ - if (file->SlideInfoWasApplied()) - return; - - dyld_cache_header baseHeader; - file->Read(&baseHeader, 0, sizeof(dyld_cache_header)); - - std::vector<std::pair<uint64_t, MappingInfo>> mappings; - - if (baseHeader.slideInfoOffsetUnused) + // NOTE: I am not sure how the header type has changed over time but if apple is replacing fields with other ones + // NOTE: And branchPoolsCount is not zero for earlier shared caches (non split cache ones) than we need to check + // this! Also make pseudo-image for the branch pools, so we can map them in to the binary view. + for (size_t i = 0; i < header.branchPoolsCount; i++) { - // Legacy - - auto slideInfoOff = baseHeader.slideInfoOffsetUnused; - auto slideInfoVersion = file->ReadUInt32(slideInfoOff); - if (slideInfoVersion != 2 && slideInfoVersion != 3) - { - logger->LogError("Unsupported slide info version %d", slideInfoVersion); - throw std::runtime_error("Unsupported slide info version"); - } - - MappingInfo map; - - file->Read(&map.mappingInfo, baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info), sizeof(dyld_cache_mapping_info)); - map.slideInfoVersion = slideInfoVersion; - if (map.slideInfoVersion == 2) - file->Read(&map.slideInfoV2, slideInfoOff, sizeof(dyld_cache_slide_info_v2)); - else if (map.slideInfoVersion == 3) - file->Read(&map.slideInfoV3, slideInfoOff, sizeof(dyld_cache_slide_info_v3)); - - mappings.emplace_back(slideInfoOff, map); + dyld_cache_image_info branchIslandImg = {}; + // TODO: uint64_t means this only works on 64bit... tbh tho this is fine this is a new addition so 32bit doesnt + // apply here. + // TODO: If we want to make this work for other addr sizes we need the binary view in this function. + branchIslandImg.address = header.branchPoolsOffset + (i * sizeof(uint64_t)); + // Mason: why such a long name for the image??? + auto imageName = fmt::format("dyld_shared_cache_branch_islands_{}", i); + images.insert_or_assign(imageName, branchIslandImg); } - else - { - dyld_cache_header targetHeader; - file->Read(&targetHeader, 0, sizeof(dyld_cache_header)); - - if (targetHeader.mappingWithSlideCount == 0) - { - logger->LogDebug("No mappings with slide info found"); - } - - for (auto i = 0; i < targetHeader.mappingWithSlideCount; i++) - { - dyld_cache_mapping_and_slide_info mappingAndSlideInfo; - file->Read(&mappingAndSlideInfo, targetHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info)), sizeof(dyld_cache_mapping_and_slide_info)); - if (mappingAndSlideInfo.slideInfoFileOffset) - { - MappingInfo map; - if (mappingAndSlideInfo.size == 0) - continue; - map.slideInfoVersion = file->ReadUInt32(mappingAndSlideInfo.slideInfoFileOffset); - logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); - map.mappingInfo.address = mappingAndSlideInfo.address; - map.mappingInfo.size = mappingAndSlideInfo.size; - map.mappingInfo.fileOffset = mappingAndSlideInfo.fileOffset; - if (map.slideInfoVersion == 2) - { - file->Read( - &map.slideInfoV2, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v2)); - } - else if (map.slideInfoVersion == 3) - { - file->Read( - &map.slideInfoV3, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v3)); - map.slideInfoV3.auth_value_add = base; - } - else if (map.slideInfoVersion == 5) - { - file->Read( - &map.slideInfoV5, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info5)); - map.slideInfoV5.value_add = base; - } - else - { - logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); - continue; - } - - uint64_t slideInfoOffset = mappingAndSlideInfo.slideInfoFileOffset; - mappings.emplace_back(slideInfoOffset, map); - logger->LogDebug("Filename: %s", file->Path().c_str()); - logger->LogDebug("Slide Info Offset: 0x%llx", slideInfoOffset); - logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); - logger->LogDebug("Slide Info v", map.slideInfoVersion); - } - } - } - - if (mappings.empty()) - { - logger->LogDebug("No slide info found"); - file->SetSlideInfoWasApplied(true); - return; - } - - for (const auto& [off, mapping] : mappings) - { - logger->LogDebug("Slide Info Version: %d", mapping.slideInfoVersion); - uint64_t extrasOffset = off; - uint64_t pageStartsOffset = off; - uint64_t pageStartCount; - uint64_t pageSize; - - if (mapping.slideInfoVersion == 2) - { - pageStartsOffset += mapping.slideInfoV2.page_starts_offset; - pageStartCount = mapping.slideInfoV2.page_starts_count; - pageSize = mapping.slideInfoV2.page_size; - extrasOffset += mapping.slideInfoV2.page_extras_offset; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t start = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) - continue; - - auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) - { - uintptr_t slideAmount = 0; - - auto deltaMask = slideInfo.delta_mask; - auto valueMask = ~deltaMask; - auto valueAdd = slideInfo.value_add; - - auto deltaShift = count_trailing_zeros(deltaMask) - 2; - - uint32_t pageOffset = startOffset; - uint32_t delta = 1; - while ( delta != 0 ) - { - uint64_t loc = pageContent + pageOffset; - try - { - uintptr_t rawValue = file->ReadULong(loc); - delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); - uintptr_t value = (rawValue & valueMask); - if (value != 0) - { - value += valueAdd; - value += slideAmount; - } - pageOffset += delta; - file->WritePointer(loc, value); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide pointer at 0x%llx\n", loc); - break; - } - } - }; - - if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) - { - int j=(start & 0x3FFF); - bool done = false; - do - { - uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t)); - try - { - auto extra = file->ReadUShort(extraCursor); - uint16_t aStart = extra; - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = (aStart & 0x3FFF)*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - done = (extra & DYLD_CACHE_SLIDE_PAGE_ATTR_END); - ++j; - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide extra at 0x%llx\n", cursor); - break; - } - } while (!done); - } - else - { - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = start*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 3) { - // Slide Info Version 3 Logic - pageStartsOffset += sizeof(dyld_cache_slide_info_v3); - pageStartCount = mapping.slideInfoV3.page_starts_count; - pageSize = mapping.slideInfoV3.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer3); - try - { - dyld_cache_slide_pointer3 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.plain.offsetToNextPointer; - if (slideInfo.auth.authenticated) - { - uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; - value += mapping.slideInfoV3.auth_value_add; - file->WritePointer(loc, value); - } - else - { - uint64_t value51 = slideInfo.plain.pointerValue; - uint64_t top8Bits = value51 & 0x0007F80000000000; - uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; - uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 5) - { - pageStartsOffset += sizeof(dyld_cache_slide_info5); - pageStartCount = mapping.slideInfoV5.page_starts_count; - pageSize = mapping.slideInfoV5.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer5); - try - { - dyld_cache_slide_pointer5 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.regular.next; - if (slideInfo.auth.auth) - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset; - file->WritePointer(loc, value); - } - else - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide info at 0x%llx\n", cursor); - } - } - } - } - // logger->LogDebug("Applied slide info for %s (0x%llx rewrites)", file->Path().c_str(), rewrites.size()); - file->SetSlideInfoWasApplied(true); + return CacheEntry(filePath, fileName, type, header, mappings, images); } - -SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : - m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView)) +WeakFileAccessor CacheEntry::GetAccessor() const { - std::lock_guard lock(m_mutex); - m_logger = LogRegistry::GetLogger("SharedCache", dscView->GetFile()->GetSessionId()); - if (dscView->GetTypeName() != VIEW_NAME) - { - // Unreachable? - m_logger->LogError("Attempted to create SharedCache object from non-Shared Cache view"); - return; - } - - ++sharedCacheReferences; - INIT_SHAREDCACHE_API_OBJECT() - if (!DeserializeFromRawView(lock)) - { - // TODO: We need a way to prompt the user and ask if they want to continue (like BNDB version upgrades) - // TODO: To do that we really need to consolidate _where_ SharedCache is called. - // TODO: Specifically the **first** call to this must originate in DSCView, which is NOT the case currently. - m_logger->LogWarn("Metadata was invalid, recreating initial load of shared cache information..."); - } - - if (m_modifiedState->viewState.value_or(m_viewSpecificState->viewState) != DSCViewStateUnloaded) - { - m_viewSpecificState->progress = LoadProgressFinished; - return; - } - - try { - PerformInitialLoad(lock); - } - catch (std::exception& e) - { - m_logger->LogError("Failed to perform initial load of Shared Cache: %s", e.what()); - } - - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - bool autoLoadLibsystem = true; - if (settings && settings->Contains("loader.dsc.autoLoadLibSystem")) - { - autoLoadLibsystem = settings->Get<bool>("loader.dsc.autoLoadLibSystem", m_dscView); - } - if (autoLoadLibsystem) - { - for (const auto& [_, header] : m_cacheInfo->headers) - { - if (header.installName.find("libsystem_c.dylib") != std::string::npos) - { - m_logger->LogInfo("Loading core libsystem_c.dylib library"); - LoadImageWithInstallName(lock, header.installName, false); - break; - } - } - } -} - -SharedCache::~SharedCache() { - --sharedCacheReferences; + return FileAccessorCache::Global().Open(m_filePath); } -SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) +std::optional<uint64_t> CacheEntry::GetHeaderAddress() const { - if (dscView->GetTypeName() != VIEW_NAME) - return nullptr; - try { - return new SharedCache(dscView); - } - catch (...) - { - return nullptr; - } + // The mapping at file offset 0 will contain the header (duh). + return GetMappedAddress(0); } -std::optional<uint64_t> SharedCache::GetImageStart(const std::string_view installName) +std::optional<uint64_t> CacheEntry::GetMappedAddress(uint64_t fileOffset) const { - const auto& imageStarts = m_cacheInfo->imageStarts; - auto it = std::find_if(imageStarts.begin(), imageStarts.end(), [&] (auto image) { - return image.first == installName; - }); - - if (it != imageStarts.end()) - return it->second; - + for (const auto& mapping : m_mappings) + if (mapping.fileOffset <= fileOffset && mapping.fileOffset + mapping.size > fileOffset) + return mapping.address + (fileOffset - mapping.fileOffset); return std::nullopt; } -const SharedCacheMachOHeader* SharedCache::HeaderForAddress(uint64_t address) -{ - // It is very common for `HeaderForAddress` to be called with an address corresponding to a header. - if (auto it = m_cacheInfo->headers.find(address); it != m_cacheInfo->headers.end()) - return &it->second; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it == m_cacheInfo->memoryRegions.end()) - return nullptr; - - if (auto headerAddress = it->second.imageStart) - return HeaderForAddress(headerAddress); - - // Found a region, but its `imageStart` was 0. This should mean it doesn't belong to an image. - assert(it->second.type != MemoryRegion::Type::Image); - return nullptr; -} - -std::string SharedCache::NameForAddress(uint64_t address) +SharedCache::SharedCache(uint64_t addressSize) { - if (auto it = m_cacheInfo->memoryRegions.find(address); it != m_cacheInfo->memoryRegions.end()) - return it->second.prettyName; - - return ""; + m_addressSize = addressSize; + m_vm = std::make_shared<VirtualMemory>(); } -std::string SharedCache::ImageNameForAddress(uint64_t address) -{ - if (auto header = HeaderForAddress(address)) - return header->identifierPrefix; - - return ""; -} -bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC) +void SharedCache::AddImage(CacheImage image) { - if (auto header = HeaderForAddress(address)) { - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, header->installName, skipObjC); - } - - return false; + m_images.insert({image.headerAddress, std::move(image)}); } -bool SharedCache::LoadSectionAtAddress(uint64_t address) +void SharedCache::AddRegion(CacheRegion region) { - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - auto vm = GetVMMap(); - if (!vm) { - m_logger->LogError("Failed to map VM pages for Shared Cache."); - return false; - } - - SharedCacheMachOHeader targetHeader; - const CacheImage* targetImage = nullptr; - const MemoryRegion* targetSegment = nullptr; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it != m_cacheInfo->memoryRegions.end()) + // Handle overlapping regions here. + const auto regionRange = region.AsAddressRange(); + // First region at or past the start of the region. + const auto begin = m_regions.lower_bound(regionRange.start); + if (begin == m_regions.end()) { - const MemoryRegion* region = &it->second; - for (auto& image : m_cacheInfo->images) - { - if (std::find(image.regionStarts.begin(), image.regionStarts.end(), region->start) == image.regionStarts.end()) - continue; - - targetHeader = m_cacheInfo->headers.at(image.headerLocation); - targetImage = ℑ - targetSegment = region; - break; - } + AddNonOverlappingRegion(std::move(region)); + return; } - if (!targetSegment) + // First region past the end of the region. + const auto end = m_regions.lower_bound(regionRange.end); + + for (auto it = begin; it != end; ++it) { - auto regionIt = m_cacheInfo->memoryRegions.find(address); - if (regionIt == m_cacheInfo->memoryRegions.end()) + const uint64_t newRegionSize = it->second.start - region.start; + if (newRegionSize) { - m_logger->LogError("Failed to find a segment containing address 0x%llx", address); - return false; + CacheRegion newRegion(region); + newRegion.size = newRegionSize; + AddNonOverlappingRegion(std::move(newRegion)); } - auto& region = regionIt->second; - if (MemoryRegionIsLoaded(lock, region)) - return true; - - m_logger->LogInfo( - "Loading region of type %d named %s @ 0x%llx", region.type, region.prettyName.c_str(), region.start); - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - - auto reader = VMReader(vm); - auto buff = reader.ReadBuffer(region.start, region.size); - - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - m_dscView->AddUserSection(region.prettyName, region.start, region.size, SectionSemanticsForRegion(region)); - - SetMemoryRegionIsLoaded(lock, region); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - return true; + region.start = it->second.start + it->second.size; + region.size -= (newRegionSize + it->second.size); } - auto id = m_dscView->BeginUndoActions(); - auto reader = VMReader(vm); - - m_logger->LogDebug("Partial loading image %s", targetHeader.installName.c_str()); - - auto targetFile = vm->MappingAtAddress(targetSegment->start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(targetSegment->start, targetSegment->size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(targetSegment->prettyName, targetSegment->start, buff, targetSegment->flags); - - SetMemoryRegionIsLoaded(lock, *targetSegment); - - if (!MemoryRegionIsHeaderInitialized(lock, *targetSegment)) - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), targetHeader, {targetSegment}); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; + // Add remaining region. + if (region.size > 0) + AddNonOverlappingRegion(std::move(region)); } -static void GetObjCSettings(Ref<BinaryView> view, bool* processObjCMetadata, bool* processCFStrings) +bool SharedCache::AddNonOverlappingRegion(CacheRegion region) { - auto settings = view->GetLoadSettings(VIEW_NAME); - *processCFStrings = true; - *processObjCMetadata = true; - if (settings && settings->Contains("loader.dsc.processCFStrings")) - *processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", view); - if (settings && settings->Contains("loader.dsc.processObjC")) - *processObjCMetadata = settings->Get<bool>("loader.dsc.processObjC", view); + auto [_, inserted] = m_regions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); + return inserted; } -static void ProcessObjCSectionsForImageWithName(std::string baseName, std::shared_ptr<VM> vm, std::shared_ptr<DSCObjC::DSCObjCProcessor> objc, bool processCFStrings, bool processObjCMetadata, Ref<Logger> logger) +void SharedCache::AddSymbol(CacheSymbol symbol) { - try - { - if (processObjCMetadata) - objc->ProcessObjCData(baseName); - if (processCFStrings) - objc->ProcessCFStrings(baseName); - } - catch (const std::exception& ex) - { - logger->LogWarn("Error processing ObjC data for image %s: %s", baseName.c_str(), ex.what()); - } - catch (...) - { - logger->LogWarn("Error processing ObjC data for image %s", baseName.c_str()); - } + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) +void SharedCache::AddSymbols(std::vector<CacheSymbol> symbols) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; - - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); - - ProcessObjCSectionsForImageWithName(base_name(installName), vm, objc, processCFStrings, processObjCMetadata, m_logger); -} - -void SharedCache::ProcessAllObjCSections() -{ - std::lock_guard lock(m_mutex); - ProcessAllObjCSections(lock); + for (auto& symbol : symbols) + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessAllObjCSections(std::lock_guard<std::mutex>& lock) +CacheEntryId SharedCache::AddEntry(CacheEntry entry) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; + // TODO: Maybe check to see if we already added the file? + // TODO: I doubt we will ever accidentally call this for the same entry... + // This is monotonically increasing so you can tell how many times we have called this function :) + CacheEntryId id = nextId++; - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); + // Get the file accessor to associate with the virtual memory region. + auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath()); - std::set<uint64_t> processedImageHeaders; - for (auto region : GetMappedRegions()) + // Populate virtual memory using the entry mappings, by doing so we can now + // read the memory of the mapped regions of the cache entry file. + const auto& mappings = entry.GetMappings(); + for (const auto& mapping : mappings) { - // Don't repeat the same images multiple times - auto header = HeaderForAddress(region->start); - if (!header) - continue; - if (processedImageHeaders.find(header->textBase) != processedImageHeaders.end()) - continue; - processedImageHeaders.insert(header->textBase); + m_vm->MapRegion(fileAccessor, {mapping.address, mapping.address + mapping.size}, mapping.fileOffset); - ProcessObjCSectionsForImageWithName(header->identifierPrefix, vm, objc, processCFStrings, processObjCMetadata, m_logger); + // Recalculate the base address. + if (mapping.address < m_baseAddress || m_baseAddress == 0) + m_baseAddress = mapping.address; } -} -bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) -{ - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, installName, skipObjC); + // We are done and can make the entry visible to the entire cache. + m_entries.insert({id, std::move(entry)}); + return id; } -bool SharedCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName, bool skipObjC) +bool SharedCache::ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info) { - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - - m_logger->LogInfo("Loading image %s", installName.c_str()); - - auto vm = GetVMMap(); - const CacheImage* targetImage = nullptr; - - for (auto& cacheImage : m_cacheInfo->images) - { - if (cacheImage.installName == installName) - { - targetImage = &cacheImage; - break; - } - } - - if (!targetImage) - { - m_logger->LogError("Failed to find target image %s", installName.c_str()); - return false; - } - - auto it = m_cacheInfo->headers.find(targetImage->headerLocation); - if (it == m_cacheInfo->headers.end()) - { - m_logger->LogError("Failed to find target image header %s", installName.c_str()); - return false; - } - - const auto& header = it->second; - - auto id = m_dscView->BeginUndoActions(); - m_modifiedState->viewState = DSCViewStateLoadedWithImages; - - auto reader = VMReader(vm); - reader.Seek(targetImage->headerLocation); - - std::vector<const MemoryRegion*> regionsToLoad; - regionsToLoad.reserve(targetImage->regionStarts.size()); - - for (auto regionStart : targetImage->regionStarts) - { - const auto& region = m_cacheInfo->memoryRegions.find(regionStart)->second; - bool allowLoadingLinkedit = false; - if (settings && settings->Contains("loader.dsc.allowLoadingLinkeditSegments")) - allowLoadingLinkedit = settings->Get<bool>("loader.dsc.allowLoadingLinkeditSegments", m_dscView); - if ((region.prettyName.find("__LINKEDIT") != std::string::npos) && !allowLoadingLinkedit) - continue; - - if (MemoryRegionIsLoaded(lock, region)) - { - m_logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str()); - continue; - } - - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(region.start, region.size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - - SetMemoryRegionIsLoaded(lock, region); - regionsToLoad.push_back(®ion); - } - - // Regions for this image are already loaded, skip un-needed analysis! - if (regionsToLoad.empty()) + auto imageHeader = SharedCacheMachOHeader::ParseHeaderForAddress(m_vm, info.address, path); + if (!imageHeader.has_value()) return false; - auto typeLib = TypeLibraryForImage(header.installName); - - auto h = SharedCache::LoadHeaderForAddress(vm, targetImage->headerLocation, installName); - if (!h) - { - SaveModifiedStateToDSCView(lock); - return false; - } - - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), *h, regionsToLoad); - SaveModifiedStateToDSCView(lock); + // Add the image to the cache. + CacheImage image; + image.headerAddress = info.address; + image.path = path; - if (!skipObjC) + // Add all image regions. + for (const auto& segment : imageHeader->segments) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - ProcessObjCSectionsForImageWithName(h->identifierPrefix, vm, std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false), processCFStrings, processObjCMetadata, m_logger); - } - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; -} - -std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::shared_ptr<VM> vm, uint64_t address, std::string installName) -{ - SharedCacheMachOHeader header; - - header.textBase = address; - header.installName = installName; - header.identifierPrefix = base_name(installName); + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; - std::string errorMsg; - // address is a Raw file offset - VMReader reader(vm); - reader.Seek(address); - - header.ident.magic = reader.Read32(); - - BNEndianness endianness; - if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) - endianness = LittleEndian; - else if (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) - endianness = BigEndian; - else - { - return {}; - } - - reader.SetEndianness(endianness); - header.ident.cputype = reader.Read32(); - header.ident.cpusubtype = reader.Read32(); - header.ident.filetype = reader.Read32(); - header.ident.ncmds = reader.Read32(); - header.ident.sizeofcmds = reader.Read32(); - header.ident.flags = reader.Read32(); - if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 - { - header.ident.reserved = reader.Read32(); - } - header.loadCommandOffset = reader.GetOffset(); - - bool first = true; - // Parse segment commands - try - { - for (size_t i = 0; i < header.ident.ncmds; i++) + // Many images include a __LINKEDIT segment that share a single region in the shared cache. + // Reuse the same `MemoryRegion` to represent all of these link edit regions. + // Check to see if we have a shared region, if so skip it. + if (std::string(segName) == "__LINKEDIT") { - // BNLogInfo("of 0x%llx", reader.GetOffset()); - load_command load; - segment_command_64 segment64; - section_64 sect; - memset(§, 0, sizeof(sect)); - size_t curOffset = reader.GetOffset(); - load.cmd = reader.Read32(); - load.cmdsize = reader.Read32(); - size_t nextOffset = curOffset + load.cmdsize; - if (load.cmdsize < sizeof(load_command)) - return {}; - - switch (load.cmd) - { - case LC_MAIN: + // TODO: Loosen this to any shared region? + if (const auto linkEditRegion = GetRegionAt(segment.vmaddr)) { - uint64_t entryPoint = reader.Read64(); - header.entryPoints.push_back({entryPoint, true}); - (void)reader.Read64(); // Stack start - break; - } - case LC_SEGMENT: // map the 32bit version to 64 bits - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read32(); - segment64.vmsize = reader.Read32(); - segment64.fileoff = reader.Read32(); - segment64.filesize = reader.Read32(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read32(); - sect.size = reader.Read32(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_SEGMENT_64: - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read64(); - segment64.vmsize = reader.Read64(); - segment64.fileoff = reader.Read64(); - segment64.filesize = reader.Read64(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) - { - header.linkeditSegment = segment64; - header.linkeditPresent = true; - } - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read64(); - sect.size = reader.Read64(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - sect.reserved3 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_ROUTINES: // map the 32bit version to 64bits - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read32(); - header.routines64.init_module = reader.Read32(); - header.routines64.reserved1 = reader.Read32(); - header.routines64.reserved2 = reader.Read32(); - header.routines64.reserved3 = reader.Read32(); - header.routines64.reserved4 = reader.Read32(); - header.routines64.reserved5 = reader.Read32(); - header.routines64.reserved6 = reader.Read32(); - header.routinesPresent = true; - break; - case LC_ROUTINES_64: - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read64(); - header.routines64.init_module = reader.Read64(); - header.routines64.reserved1 = reader.Read64(); - header.routines64.reserved2 = reader.Read64(); - header.routines64.reserved3 = reader.Read64(); - header.routines64.reserved4 = reader.Read64(); - header.routines64.reserved5 = reader.Read64(); - header.routines64.reserved6 = reader.Read64(); - header.routinesPresent = true; - break; - case LC_FUNCTION_STARTS: - header.functionStarts.funcoff = reader.Read32(); - header.functionStarts.funcsize = reader.Read32(); - header.functionStartsPresent = true; - break; - case LC_SYMTAB: - header.symtab.symoff = reader.Read32(); - header.symtab.nsyms = reader.Read32(); - header.symtab.stroff = reader.Read32(); - header.symtab.strsize = reader.Read32(); - break; - case LC_DYSYMTAB: - header.dysymtab.ilocalsym = reader.Read32(); - header.dysymtab.nlocalsym = reader.Read32(); - header.dysymtab.iextdefsym = reader.Read32(); - header.dysymtab.nextdefsym = reader.Read32(); - header.dysymtab.iundefsym = reader.Read32(); - header.dysymtab.nundefsym = reader.Read32(); - header.dysymtab.tocoff = reader.Read32(); - header.dysymtab.ntoc = reader.Read32(); - header.dysymtab.modtaboff = reader.Read32(); - header.dysymtab.nmodtab = reader.Read32(); - header.dysymtab.extrefsymoff = reader.Read32(); - header.dysymtab.nextrefsyms = reader.Read32(); - header.dysymtab.indirectsymoff = reader.Read32(); - header.dysymtab.nindirectsyms = reader.Read32(); - header.dysymtab.extreloff = reader.Read32(); - header.dysymtab.nextrel = reader.Read32(); - header.dysymtab.locreloff = reader.Read32(); - header.dysymtab.nlocrel = reader.Read32(); - header.dysymPresent = true; - break; - case LC_DYLD_CHAINED_FIXUPS: - header.chainedFixups.dataoff = reader.Read32(); - header.chainedFixups.datasize = reader.Read32(); - header.chainedFixupsPresent = true; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - header.dyldInfo.rebase_off = reader.Read32(); - header.dyldInfo.rebase_size = reader.Read32(); - header.dyldInfo.bind_off = reader.Read32(); - header.dyldInfo.bind_size = reader.Read32(); - header.dyldInfo.weak_bind_off = reader.Read32(); - header.dyldInfo.weak_bind_size = reader.Read32(); - header.dyldInfo.lazy_bind_off = reader.Read32(); - header.dyldInfo.lazy_bind_size = reader.Read32(); - header.dyldInfo.export_off = reader.Read32(); - header.dyldInfo.export_size = reader.Read32(); - header.exportTrie.dataoff = header.dyldInfo.export_off; - header.exportTrie.datasize = header.dyldInfo.export_size; - header.exportTriePresent = true; - header.dyldInfoPresent = true; - break; - case LC_DYLD_EXPORTS_TRIE: - header.exportTrie.dataoff = reader.Read32(); - header.exportTrie.datasize = reader.Read32(); - header.exportTriePresent = true; - break; - case LC_THREAD: - case LC_UNIXTHREAD: - /*while (reader.GetOffset() < nextOffset) - { - - thread_command thread; - thread.flavor = reader.Read32(); - thread.count = reader.Read32(); - switch (m_archId) - { - case MachOx64: - m_logger->LogDebug("x86_64 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex64, sizeof(thread.statex64)); - header.entryPoints.push_back({thread.statex64.rip, false}); - break; - case MachOx86: - m_logger->LogDebug("x86 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE32) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex86, sizeof(thread.statex86)); - header.entryPoints.push_back({thread.statex86.eip, false}); - break; - case MachOArm: - m_logger->LogDebug("Arm Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); - header.entryPoints.push_back({thread.statearmv7.r15, false}); - break; - case MachOAarch64: - case MachOAarch6432: - m_logger->LogDebug("Aarch64 Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); - header.entryPoints.push_back({thread.stateaarch64.pc, false}); - break; - case MachOPPC: - m_logger->LogDebug("PPC Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //Read individual entries for endian reasons - header.entryPoints.push_back({reader.Read32(), false}); - (void)reader.Read32(); - (void)reader.Read32(); - //Read the rest of the structure - (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); - break; - case MachOPPC64: - m_logger->LogDebug("PPC64 Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - header.entryPoints.push_back({reader.Read64(), false}); - (void)reader.Read64(); - (void)reader.Read64(); // Stack start - (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); - break; - default: - m_logger->LogError("Unknown archid: %x", m_archId); - } - - }*/ - break; - case LC_LOAD_DYLIB: - { - uint32_t offset = reader.Read32(); - if (offset < nextOffset) - { - reader.Seek(curOffset + offset); - std::string libname = reader.ReadCString(reader.GetOffset()); - header.dylibs.push_back(libname); - } - } - break; - case LC_BUILD_VERSION: - { - // m_logger->LogDebug("LC_BUILD_VERSION:"); - header.buildVersion.platform = reader.Read32(); - header.buildVersion.minos = reader.Read32(); - header.buildVersion.sdk = reader.Read32(); - header.buildVersion.ntools = reader.Read32(); - // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); - // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); - // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); - for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) - { - uint32_t tool = reader.Read32(); - uint32_t version = reader.Read32(); - header.buildToolVersions.push_back({tool, version}); - // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), - // BuildToolVersionToString(version).c_str()); - } - break; - } - case LC_FILESET_ENTRY: - { - throw ReadException(); - } - default: - // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), - // load.cmdsize); - break; - } - if (reader.GetOffset() != nextOffset) - { - // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, - // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); - } - reader.Seek(nextOffset); - } - - for (auto& section : header.sections) - { - char sectionName[17]; - memcpy(sectionName, section.sectname, sizeof(section.sectname)); - sectionName[16] = 0; - if (header.identifierPrefix.empty()) - header.sectionNames.push_back(sectionName); - else - header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); - } - } - catch (ReadException&) - { - return {}; - } - - return header; -} - - -void SharedCache::ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header, uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex) -{ - auto addressSize = m_dscView->GetAddressSize(); - auto strings = file->ReadBuffer(stringsOffset, stringsSize); - - std::vector<Ref<Symbol>> symbolList; - for (uint64_t i = 0; i < nlistCount; i++) - { - uint64_t entryIndex = (nlistStartIndex + i); - - nlist_64 nlist = {}; - if (addressSize == 4) - { - // 32-bit DSC - struct nlist nlist32 = {}; - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist32)), sizeof(nlist32)); - nlist.n_strx = nlist32.n_strx; - nlist.n_type = nlist32.n_type; - nlist.n_sect = nlist32.n_sect; - nlist.n_desc = nlist32.n_desc; - nlist.n_value = nlist32.n_value; - } - else - { - // 64-bit DSC - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist)), sizeof(nlist)); - } - - auto symbolAddress = nlist.n_value; - if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0) - continue; - - if (nlist.n_strx >= stringsSize) - { - m_logger->LogError("Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %llu for file %s", entryIndex, nlist.n_strx, stringsSize, file->Path().c_str()); - continue; - } - - std::string symbolName((char*)strings.GetDataAt(nlist.n_strx)); - if (symbolName == "<redacted>") - continue; - - std::optional<BNSymbolType> symbolType; - if ((nlist.n_type & N_TYPE) == N_SECT && nlist.n_sect > 0 && (size_t)(nlist.n_sect - 1) < header.sections.size()) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_TYPE) == N_ABS) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_EXT)) - { - symbolType = ExternalSymbol; - } - - if (!symbolType.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " has unknown symbol type", symbolName.c_str(), symbolAddress); - continue; - } - - std::optional<uint32_t> flags; - for (auto s : header.sections) - { - if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size) - { - flags = s.flags; - } - } - - if (symbolType != ExternalSymbol) - { - if (!flags.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " is not in any section", symbolName.c_str(), symbolAddress); + image.regionStarts.push_back(linkEditRegion->start); continue; } - - if ((flags.value() & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags.value() & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - symbolType = FunctionSymbol; - else - symbolType = DataSymbol; } - if ((nlist.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) - symbolAddress++; - QualifiedName demangledName = {}; - std::string shortName = symbolName; - if (DemangleLLVM(symbolName, demangledName, true)) - shortName = demangledName.GetString(); - Ref<Symbol> sym = new Symbol(symbolType.value(), shortName, shortName, symbolName, symbolAddress, nullptr); - symbolList.emplace_back(sym); - } + CacheRegion sectionRegion; + sectionRegion.type = CacheRegionType::Image; + sectionRegion.name = imageHeader->identifierPrefix + "::" + std::string(segName); + sectionRegion.start = segment.vmaddr; + sectionRegion.size = segment.vmsize; + // Associate this region with this image, this makes it easier to identify what image owns this region. + sectionRegion.imageStart = image.headerAddress; - auto symListPtr = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbolList)); - m_modifiedState->symbolInfos.emplace(header.textBase, symListPtr); -} + uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); + // if we're positive we have an entry point for some reason, force the segment + // executable. this helps with kernel images. + for (const auto& entryPoint : imageHeader->m_entryPoints) + if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) + flags |= SegmentExecutable; + sectionRegion.flags = static_cast<BNSegmentFlag>(flags); -void SharedCache::ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol) -{ - Ref<Function> func = nullptr; - auto symbolAddress = symbol->GetAddress(); - - if (symbol->GetType() == FunctionSymbol) - { - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); + // Add the image section to the cache and also to the image region starts + AddRegion(sectionRegion); + image.regionStarts.push_back(sectionRegion.start); } - if (typeLib) - { - auto type = m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}); - // TODO: This is still auto - if (type) - view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); - else - view->DefineAutoUserSymbol(symbol); - } - else - { - view->DefineAutoUserSymbol(symbol); - } + // Add the exported symbols to the available symbols. + std::vector<CacheSymbol> exportSymbols = imageHeader->ReadExportSymbolTrie(*m_vm); + AddSymbols(std::move(exportSymbols)); - if (!func) - func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress); - if (func) - { - if (symbol->GetFullName() == "_objc_msgSend") - { - func->SetHasVariableArguments(false); - } - else if (symbol->GetFullName().find("_objc_retain_x") != std::string::npos || symbol->GetFullName().find("_objc_release_x") != std::string::npos) - { - auto x = symbol->GetFullName().rfind("x"); - auto num = symbol->GetFullName().substr(x + 1); - - std::vector<BinaryNinja::FunctionParameter> callTypeParams; - auto cc = m_dscView->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + // This is behind a shared pointer as the header itself is very large. + // TODO: Make this a unique pointer? I think the image should own the header at this point? + image.header = std::make_shared<SharedCacheMachOHeader>(*imageHeader); - callTypeParams.push_back({"obj", m_dscView->GetTypeByName({ "id" }), true, BinaryNinja::Variable()}); - - auto funcType = BinaryNinja::Type::FunctionType(m_dscView->GetTypeByName({ "id" }), cc, callTypeParams); - func->SetUserType(funcType); - } - } + AddImage(std::move(image)); + return true; } - -void SharedCache::InitializeHeader( - std::lock_guard<std::mutex>& lock, - Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<const MemoryRegion*> regionsToLoad) +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryImages(const CacheEntry& entry) { - Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME); - bool applyFunctionStarts = true; - if (settings && settings->Contains("loader.dsc.processFunctionStarts")) - applyFunctionStarts = settings->Get<bool>("loader.dsc.processFunctionStarts", view); - - for (size_t i = 0; i < header.sections.size(); i++) - { - bool skip = true; - for (const auto& region : regionsToLoad) - { - if (header.sections[i].addr >= region->start && header.sections[i].addr < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - skip = false; - break; - } - } - if (!header.sections[i].size || skip) - continue; - - std::string type; - BNSectionSemantics semantics = DefaultSectionSemantics; - switch (header.sections[i].flags & 0xff) - { - case S_REGULAR: - if (header.sections[i].flags & S_ATTR_PURE_INSTRUCTIONS) - { - type = "PURE_CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else if (header.sections[i].flags & S_ATTR_SOME_INSTRUCTIONS) - { - type = "CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else - { - type = "REGULAR"; - } - break; - case S_ZEROFILL: - type = "ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_CSTRING_LITERALS: - type = "CSTRING_LITERALS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_4BYTE_LITERALS: - type = "4BYTE_LITERALS"; - break; - case S_8BYTE_LITERALS: - type = "8BYTE_LITERALS"; - break; - case S_LITERAL_POINTERS: - type = "LITERAL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_NON_LAZY_SYMBOL_POINTERS: - type = "NON_LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_LAZY_SYMBOL_POINTERS: - type = "LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_SYMBOL_STUBS: - type = "SYMBOL_STUBS"; - semantics = ReadOnlyCodeSectionSemantics; - break; - case S_MOD_INIT_FUNC_POINTERS: - type = "MOD_INIT_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_MOD_TERM_FUNC_POINTERS: - type = "MOD_TERM_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_COALESCED: - type = "COALESCED"; - break; - case S_GB_ZEROFILL: - type = "GB_ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_INTERPOSING: - type = "INTERPOSING"; - break; - case S_16BYTE_LITERALS: - type = "16BYTE_LITERALS"; - break; - case S_DTRACE_DOF: - type = "DTRACE_DOF"; - break; - case S_LAZY_DYLIB_SYMBOL_POINTERS: - type = "LAZY_DYLIB_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_THREAD_LOCAL_REGULAR: - type = "THREAD_LOCAL_REGULAR"; - break; - case S_THREAD_LOCAL_ZEROFILL: - type = "THREAD_LOCAL_ZEROFILL"; - break; - case S_THREAD_LOCAL_VARIABLES: - type = "THREAD_LOCAL_VARIABLES"; - break; - case S_THREAD_LOCAL_VARIABLE_POINTERS: - type = "THREAD_LOCAL_VARIABLE_POINTERS"; - break; - case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: - type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; - break; - default: - type = "UNKNOWN"; - break; - } - if (i >= header.sectionNames.size()) - break; - if (strncmp(header.sections[i].sectname, "__text", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyCodeSectionSemantics; - if (strncmp(header.sections[i].sectname, "__const", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - if (strncmp(header.sections[i].sectname, "__data", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadWriteDataSectionSemantics; - if (strncmp(header.sections[i].segname, "__DATA_CONST", sizeof(header.sections[i].segname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - - view->AddUserSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, - type, header.sections[i].align); - } - - auto typeLib = view->GetTypeLibrary(header.installName); + for (const auto& [imagePath, imageInfo] : entry.GetImages()) + ProcessEntryImage(imagePath, imageInfo); +} - BinaryReader virtualReader(view); +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryRegions(const CacheEntry& entry) +{ + auto entryHeader = entry.GetHeader(); - bool applyHeaderTypes = false; - for (const auto& region : regionsToLoad) + // Collect pool addresses as non image memory regions. + for (size_t i = 0; i < entryHeader.branchPoolsCount; i++) { - if (header.textBase >= region->start && header.textBase < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - applyHeaderTypes = true; - + auto branchPoolAddr = entryHeader.branchPoolsOffset + (i * m_addressSize); + auto header = SharedCacheMachOHeader::ParseHeaderForAddress( + m_vm, branchPoolAddr, "dyld_shared_cache_branch_islands_" + std::to_string(i)); + // Stop processing branch pools if a header fails to parse. + if (!header.has_value()) break; - } - } - if (applyHeaderTypes) - { - view->DefineDataVariable(header.textBase, Type::NamedType(view, QualifiedName("mach_header_64"))); - view->DefineAutoSymbol( - new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); - try + // Gather all non image regions from the branch islands. + for (const auto& segment : header->segments) { - virtualReader.Seek(header.textBase + sizeof(mach_header_64)); - size_t sectionNum = 0; - for (size_t i = 0; i < header.ident.ncmds; i++) - { - load_command load; - uint64_t curOffset = virtualReader.GetOffset(); - load.cmd = virtualReader.Read32(); - load.cmdsize = virtualReader.Read32(); - uint64_t nextOffset = curOffset + load.cmdsize; - switch (load.cmd) - { - case LC_SEGMENT: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command"))); - virtualReader.SeekRelative(5 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative((8 * 8) + 4); - } - break; - } - case LC_SEGMENT_64: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command_64"))); - virtualReader.SeekRelative(7 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section_64"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section_64::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative(10 * 8); - } - break; - } - case LC_SYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("symtab"))); - break; - case LC_DYSYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dysymtab"))); - break; - case LC_UUID: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("uuid"))); - break; - case LC_ID_DYLIB: - case LC_LOAD_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dylib_command"))); - if (load.cmdsize - 24 <= 150) - view->DefineDataVariable( - curOffset + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); - break; - case LC_CODE_SIGNATURE: - case LC_SEGMENT_SPLIT_INFO: - case LC_FUNCTION_STARTS: - case LC_DATA_IN_CODE: - case LC_DYLIB_CODE_SIGN_DRS: - case LC_DYLD_EXPORTS_TRIE: - case LC_DYLD_CHAINED_FIXUPS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("linkedit_data"))); - break; - case LC_ENCRYPTION_INFO: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("encryption_info"))); - break; - case LC_VERSION_MIN_MACOSX: - case LC_VERSION_MIN_IPHONEOS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("version_min"))); - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dyld_info"))); - break; - default: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("load_command"))); - break; - } + CacheRegion stubIslandRegion; + stubIslandRegion.start = segment.vmaddr; + stubIslandRegion.size = segment.filesize; + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + std::string segNameStr = std::string(segName); + stubIslandRegion.name = fmt::format("dyld_shared_cache_branch_islands_{}::{}", i, segNameStr); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - view->DefineAutoSymbol(new Symbol(DataSymbol, - "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, - LocalBinding)); - virtualReader.Seek(nextOffset); - } - } - catch (ReadException&) - { - LogError("Error when applying Mach-O header types at %" PRIx64, header.textBase); - } - } - - if (applyFunctionStarts && header.functionStartsPresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - auto funcStarts = - vm->MappingAtAddress(header.linkeditSegment.vmaddr) - .first.fileAccessor->lock() - ->ReadBuffer(header.functionStarts.funcoff, header.functionStarts.funcsize); - uint64_t curfunc = header.textBase; - uint64_t curOffset; - - auto current = static_cast<const uint8_t*>(funcStarts.GetData()); - auto end = current + funcStarts.GetLength(); - while (current != end) - { - curOffset = readLEB128(current, end); - bool addFunction = false; - for (const auto& region : regionsToLoad) - { - if (curfunc >= region->start && curfunc < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - addFunction = true; - } - } - // LogError("0x%llx, 0x%llx", header.textBase, curOffset); - if (curOffset == 0 || !addFunction) - continue; - curfunc += curOffset; - uint64_t target = curfunc; - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - view->AddFunctionForAnalysis(targetPlatform, target); + // Add the stub islands to the cache. + AddRegion(std::move(stubIslandRegion)); } } - if (header.symtab.symoff != 0 && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - // Mach-O View symtab processing with - // a ton of stuff cut out so it can work - auto reader = vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - ProcessSymbols( - reader, - header, - header.symtab.stroff, - header.symtab.strsize, - header.symtab.symoff, - header.symtab.nsyms - ); - } - - view->BeginBulkModifySymbols(); - for (const auto& symbol : *m_modifiedState->symbolInfos[header.textBase]) - ApplySymbol(view, typeLib, symbol); + // Get the mapping. + const auto& entryMappings = entry.GetMappings(); - if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + // Add the mapping regions for the given entry type. + // By default, we will just add all the mappings as read-write. + switch (entry.GetType()) { - auto symbols = GetExportListForHeader(lock, header, [&]() { - return vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - }); - - for (const auto& [symbolAddress, symbol] : *symbols) - ApplySymbol(view, typeLib, symbol); - } - view->EndBulkModifySymbols(); - - for (auto region : regionsToLoad) - { - SetMemoryRegionHeaderInitialized(lock, *region); - } -} - - -void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, - const uint8_t* begin, const uint8_t* end, const uint8_t* current, uint64_t textBase, const std::string& currentText) -{ - if (current >= end) - throw ReadException(); - - uint64_t terminalSize = readValidULEB128(current, end); - const uint8_t* child = current + terminalSize; - if (terminalSize != 0) + case CacheEntryType::DyldData: { - uint64_t flags = readValidULEB128(current, end); - if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - uint64_t imageOffset = readValidULEB128(current, end); - if (!currentText.empty() && textBase + imageOffset) - { - uint32_t flags; - BNSymbolType type; - for (auto s : header.sections) - { - if (s.addr < textBase + imageOffset) - { - if (s.addr + s.size > textBase + imageOffset) - { - flags = s.flags; - break; - } - } - } - if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - type = FunctionSymbol; - else - type = DataSymbol; + CacheRegion mappingRegion; + mappingRegion.start = mapping.address; + mappingRegion.size = mapping.size; + mappingRegion.name = fmt::format("{}::_data_{}", entry.GetFileName(), lastMappingIndex++); + mappingRegion.flags = SegmentReadable; + mappingRegion.type = CacheRegionType::DyldData; -#if EXPORT_TRIE_DEBUG - // BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset); -#endif - auto symbol = new Symbol(type, currentText, textBase + imageOffset, nullptr); - symbolList.emplace_back(symbol); - } + // Add the dyld data mapping as a region to the cache. + AddRegion(std::move(mappingRegion)); } + break; } - current = child; - uint8_t childCount = *current++; - std::string childText = currentText; - for (uint8_t i = 0; i < childCount; ++i) - { - if (current >= end) - throw ReadException(); - auto it = std::find(current, end, 0); - childText.append(current, it); - current = it + 1; - if (current >= end) - throw ReadException(); - auto next = readValidULEB128(current, end); - if (next == 0) - throw ReadException(); - ReadExportNode(symbolList, header, begin, end, begin + next, textBase, childText); - childText.resize(currentText.size()); - } -} - - -std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header) -{ - if (!header.exportTrie.datasize) - return {}; - - try - { - std::vector<Ref<Symbol>> symbols; - auto [begin, end] = linkeditFile->ReadSpan(header.exportTrie.dataoff, header.exportTrie.datasize); - ReadExportNode(symbols, header, begin, end, begin, header.textBase, ""); - return symbols; - } - catch (std::exception& e) - { - BNLogError("Failed to load Export Trie"); - return {}; - } -} - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const { - if (auto it = m_modifiedState->exportInfos.find(baseAddress); it != m_modifiedState->exportInfos.end()) - return it->second; - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->stateMutex); - if (auto it = m_viewSpecificState->state.exportInfos.find(baseAddress); it != m_viewSpecificState->state.exportInfos.end()) - return it->second; - - return nullptr; -} - - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExportListForHeader( - std::lock_guard<std::mutex>& lock, const SharedCacheMachOHeader& header, - std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList) -{ - if (auto exportList = GetExistingExportListForBaseAddress(lock, header.textBase)) - { - if (didModifyExportList) - *didModifyExportList = false; - - return exportList; - } - - std::shared_ptr<MMappedFileAccessor> linkeditFile = provideLinkeditFile(); - if (!linkeditFile) + case CacheEntryType::Stub: { - if (didModifyExportList) - *didModifyExportList = false; - - return nullptr; - } + // Stub entry file, should only have a single mapping and no images. + auto stubMapping = entryMappings[0]; + CacheRegion stubIslandRegion; + stubIslandRegion.start = stubMapping.address; + stubIslandRegion.size = stubMapping.size; + stubIslandRegion.name = fmt::format("{}::_stubs", entry.GetFileName()); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - // FIXME: This is the only place ParseExportTrie is used, it can be optimized for the output we need here. - std::vector<Ref<Symbol>> exportList = SharedCache::ParseExportTrie(linkeditFile, header); - auto exportMapping = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(exportList.size()); - for (auto& sym : exportList) - { - exportMapping->insert_or_assign(sym->GetAddress(), std::move(sym)); + // Add the stub island to the cache. + AddRegion(std::move(stubIslandRegion)); } - - m_modifiedState->exportInfos.emplace(header.textBase, exportMapping); - if (didModifyExportList) - *didModifyExportList = true; - - return exportMapping; -} - - -std::vector<std::string> SharedCache::GetAvailableImages() -{ - std::vector<std::string> installNames; - installNames.reserve(m_cacheInfo->headers.size()); - for (const auto& header : m_cacheInfo->headers) + default: { - installNames.push_back(header.second.installName); - } - return installNames; -} - - -std::unordered_map<std::string, std::vector<Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait() -{ - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - bool doSave = false; - std::unordered_map<std::string, std::vector<Ref<Symbol>>> symbolsByImageName(m_cacheInfo->images.size()); - - for (const auto& img : m_cacheInfo->images) - { - auto header = HeaderForAddress(img.headerLocation); - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } - catch (...) - { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", - header->exportTriePath.c_str(), - header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }, &doSave); - - if (!exportList) - continue; - - auto& symbols = symbolsByImageName[img.installName]; - symbols.reserve(exportList->size()); - for (const auto& [_, symbol] : *exportList) + // Fill in all the gaps in the mapping with non image regions. + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - symbols.push_back(symbol); + // Add the remaining gap. + CacheRegion nonImageRegion; + nonImageRegion.start = mapping.address; + nonImageRegion.size = mapping.size; + nonImageRegion.name = fmt::format("{}::{}", entry.GetFileName(), lastMappingIndex++); + nonImageRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentWritable); + nonImageRegion.type = CacheRegionType::NonImage; + AddRegion(std::move(nonImageRegion)); } + break; } - - // Only save to DSC view if a header was actually loaded - if (doSave) - SaveModifiedStateToDSCView(lock); - - return symbolsByImageName; -} - - -std::string SharedCache::SerializedImageHeaderForAddress(uint64_t address) -{ - auto header = HeaderForAddress(address); - if (header) - { - return header->AsString(); - } - return ""; -} - - -std::string SharedCache::SerializedImageHeaderForName(std::string name) -{ - if (auto it = m_cacheInfo->imageStarts.find(name); it != m_cacheInfo->imageStarts.end()) - { - if (auto header = HeaderForAddress(it->second)) - return header->AsString(); - } - return ""; -} - -Ref<TypeLibrary> SharedCache::TypeLibraryForImage(const std::string& installName) -{ - std::lock_guard lock(m_viewSpecificState->typeLibraryMutex); - if (auto it = m_viewSpecificState->typeLibraries.find(installName); it != m_viewSpecificState->typeLibraries.end()) - return it->second; - - auto typeLib = m_dscView->GetTypeLibrary(installName); - if (!typeLib) - { - auto typeLibs = m_dscView->GetDefaultPlatform()->GetTypeLibrariesByName(installName); - if (!typeLibs.empty()) - { - typeLib = typeLibs[0]; - m_dscView->AddTypeLibrary(typeLib); - } } - - m_viewSpecificState->typeLibraries[installName] = typeLib; - return typeLib; } -void SharedCache::FindSymbolAtAddrAndApplyToAddr( - uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) +void SharedCache::ProcessEntrySlideInfo(const CacheEntry& entry) { - std::lock_guard lock(m_mutex); - - std::string prefix = ""; - if (symbolLocation != targetLocation) - prefix = "j_"; - - if (auto targetSymbol = m_dscView->GetSymbolByAddress(targetLocation)) - { - // A symbol already exists at the target location. If the source and target address are the same, - // there's nothing more to do. If they're different but the symbol has the `j_` prefix that is added - // to stubs, there's also nothing more to do. - if (symbolLocation == targetLocation || targetSymbol->GetFullName().find("j_") != std::string::npos) - return; - } - - if (symbolLocation != targetLocation) - { - if (auto symbol = m_dscView->GetSymbolByAddress(symbolLocation)) - { - // A symbol already exists at the source location. Add a stub symbol at `targetLocation` based on the existing symbol. - auto id = m_dscView->BeginUndoActions(); - if (m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - m_dscView->DefineUserSymbol(new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - else - m_dscView->DefineUserSymbol(new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - m_dscView->ForgetUndoActions(id); - return; - } - } - - // No existing symbol was found at `symbolLocation` or `targetLocation`. Search the export list - // for the image containing `symbolLocation` to find a symbol corresponding to that address. - - auto header = HeaderForAddress(symbolLocation); - if (!header) - return; - - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } catch (...) { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", header->exportTriePath.c_str(), header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }); - - if (!exportList) - return; - - auto it = exportList->find(symbolLocation); - if (it == exportList->end()) - return; - - const auto& symbol = it->second; - auto id = m_dscView->BeginUndoActions(); - auto typeLib = TypeLibraryForImage(header->installName); - auto type = typeLib ? m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}) : nullptr; - - if (auto func = m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - { - m_dscView->DefineUserSymbol( - new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - if (type) - func->SetUserType(type); - if (triggerReanalysis) - func->Reanalyze(); - } - else - { - m_dscView->DefineUserSymbol( - new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - if (type) - m_dscView->DefineUserDataVariable(targetLocation, type); - } - - m_dscView->ForgetUndoActions(id); + auto slideInfoProcessor = SlideInfoProcessor(GetBaseAddress()); + slideInfoProcessor.ProcessEntry(*m_vm, entry); } - -bool SharedCache::SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryContaining(const uint64_t address) const { - if (!m_dscView) - return false; - - // The initial load should only populate `m_cacheInfo` and should not modify any state. - assert(m_modifiedState->exportInfos.size() == 0); - assert(m_modifiedState->symbolInfos.size() == 0); - assert(m_modifiedState->memoryRegionStatus.size() == 0); - - auto data = m_cacheInfo->AsMetadata(); - m_dscView->StoreMetadata(SharedCacheMetadata::Tag, data); - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->cacheInfoMutex); - if (m_cacheInfo) + for (const auto& mapping : entry.GetMappings()) { - if (!m_viewSpecificState->cacheInfo) - m_viewSpecificState->cacheInfo = m_cacheInfo; - - // At this point we expect cache info and view state cache to be the same. - assert(m_viewSpecificState->cacheInfo == m_cacheInfo); + if (address >= mapping.address && address < mapping.address + mapping.size) + return entry; } } - m_metadataValid = true; - return true; + return std::nullopt; } -bool SharedCache::SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryWithImage(const CacheImage& image) const { - if (!m_dscView) - return false; - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->stateMutex); - - uint64_t modificationNumber = m_viewSpecificState->savedModifications++; - if (modificationNumber == 0) - { - // The cached state in the view-specific state has not yet been saved. - // For the initial load of a shared cache this will be empty, but if - // the shared cache has been loaded from a database then this will - // contain the full state that was saved. - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_viewSpecificState->state.AsMetadata(m_viewSpecificState->viewState); - - m_dscView->StoreMetadata(metadataKey, data); - modificationNumber = m_viewSpecificState->savedModifications++; - } - - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_modifiedState->AsMetadata(); - - m_dscView->StoreMetadata(metadataKey, data); - - Ref<Metadata> count = new Metadata(m_viewSpecificState->savedModifications); - m_dscView->StoreMetadata(SharedCacheMetadata::ModifiedStateCountTag, count); - - m_viewSpecificState->state.exportInfos.merge(m_modifiedState->exportInfos); - m_viewSpecificState->state.symbolInfos.merge(m_modifiedState->symbolInfos); - // `merge` will move a node to the target map if the corresponding key does not yet exist. - // If we've redundantly loaded symbols, we may be left with symbols in the source maps. - m_modifiedState->exportInfos.clear(); - m_modifiedState->symbolInfos.clear(); - - for (auto& [region, status] : m_modifiedState->memoryRegionStatus) + for (const auto& [_, currentImage] : entry.GetImages()) { - m_viewSpecificState->state.memoryRegionStatus[region] = status; + if (currentImage.address == image.headerAddress) + return entry; } - m_modifiedState->memoryRegionStatus.clear(); - - // Clean up any metadata entries past the current modification number. - // These can happen after being loaded from a database as all modifications are - // merged into a single state object and the modification count is reset to zero. - for (size_t i = modificationNumber + 1; i < std::numeric_limits<size_t>::max(); ++i) - { - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - bool done = true; - if (m_dscView->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->RemoveMetadata(metadataKey); - } - if (m_dscView->GetParentView()->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->GetParentView()->RemoveMetadata(metadataKey); - } - if (done) - break; - } - } - - if (m_modifiedState->viewState) - { - m_viewSpecificState->viewState = m_modifiedState->viewState.value(); - m_modifiedState->viewState = std::nullopt; } - m_metadataValid = true; - - return true; -} - - -std::vector<const MemoryRegion*> SharedCache::GetMappedRegions() const -{ - std::scoped_lock lock(m_mutex, m_viewSpecificState->stateMutex); - - std::vector<const MemoryRegion*> regions; - regions.reserve(m_viewSpecificState->state.memoryRegionStatus.size() + m_modifiedState->memoryRegionStatus.size()); - for (auto& [regionStart, status] : m_viewSpecificState->state.memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - for (auto& [regionStart, status] : m_modifiedState->memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - std::sort(regions.begin(), regions.end()); - regions.erase(std::unique(regions.begin(), regions.end()), regions.end()); - return regions; -} - -bool SharedCache::IsMemoryMapped(uint64_t address) -{ - return m_dscView->IsValidOffset(address); -} - -void Serialize(SerializationContext& context, const dyld_cache_mapping_info& value) -{ - context.writer.StartArray(); - Serialize(context, value.address); - Serialize(context, value.size); - Serialize(context, value.fileOffset); - Serialize(context, value.maxProt); - Serialize(context, value.initProt); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<dyld_cache_mapping_info>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - dyld_cache_mapping_info mapping; - auto s2 = s.GetArray(); - mapping.address = s2[0].GetUint64(); - mapping.size = s2[1].GetUint64(); - mapping.fileOffset = s2[2].GetUint64(); - mapping.maxProt = s2[3].GetUint(); - mapping.initProt = s2[4].GetUint(); - b.push_back(mapping); - } -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::optional<std::pair<uint64_t, uint64_t>>& value) -{ - if (!context.doc.HasMember(name.data())) - { - value = std::nullopt; - return; - } - - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const AddressRange& value) -{ - Serialize(context, std::make_pair(value.start, value.end)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRange& value) -{ - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const MemoryRegionStatus& status) -{ - context.writer.StartArray(); - Serialize(context, status.loaded); - Serialize(context, status.headerInitialized); - context.writer.EndArray(); -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, MemoryRegionStatus>& statuses) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair : array) - { - auto statusArray = pair[1].GetArray(); - MemoryRegionStatus status; - status.loaded = statusArray[0].GetBool(); - status.headerInitialized = statusArray[1].GetBool(); - statuses[pair[0].GetUint64()] = std::move(status); - } -} - -void Serialize(SerializationContext& context, const Ref<Symbol>& value) -{ - context.writer.StartArray(); - Serialize(context, value->GetRawNameRef()); - Serialize(context, value->GetAddress()); - Serialize(context, value->GetType()); - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>& value) -{ - context.writer.StartArray(); - for (const auto& [_, symbol] : *value) - { - Serialize(context, symbol); - } - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::vector<Ref<Symbol>>>& value) -{ - Serialize(context, *value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::optional<DSCViewState>& viewState) -{ - auto& value = context.doc[name.data()]; - if (value.IsNull()) - viewState = std::nullopt; - else - viewState = (DSCViewState)value.GetUint(); -} - -void SharedCache::CacheInfo::Store(SerializationContext& context) const -{ - Serialize(context, "metadataVersion", METADATA_VERSION); - - MSS(backingCaches); - MSS(headers); - MSS(images); - MSS(imageStarts); - MSS(memoryRegions); - MSS(objcOptimizationDataRange); - MSS(baseFilePath); - MSS_CAST(cacheFormat, uint8_t); + return std::nullopt; } -// static -std::optional<SharedCache::CacheInfo> SharedCache::CacheInfo::Load(DeserializationContext& context) +std::optional<CacheRegion> SharedCache::GetRegionAt(const uint64_t address) const { - if (!context.doc.HasMember("metadataVersion")) - { - LogError("Shared Cache metadata version missing"); + const auto it = m_regions.find(address); + if (it == m_regions.end()) return std::nullopt; - } - - if (context.doc["metadataVersion"].GetUint() != METADATA_VERSION) - return std::nullopt; - - CacheInfo cacheInfo; - cacheInfo.MSL(backingCaches); - cacheInfo.MSL(headers); - cacheInfo.MSL(images); - cacheInfo.MSL(imageStarts); - cacheInfo.MSL(memoryRegions); - cacheInfo.MSL(objcOptimizationDataRange); - cacheInfo.MSL(baseFilePath); - cacheInfo.MSL_CAST(cacheFormat, uint8_t, SharedCacheFormat); - - // Older metadata may be missing the `imageStart` field on `MemoryRegion`. - bool regionsMissingImageStart = - std::any_of(cacheInfo.memoryRegions.begin(), cacheInfo.memoryRegions.end(), [](const auto& pair) { - const auto& region = pair.second; - return region.type == MemoryRegion::Type::Image && region.imageStart == 0; - }); - - if (regionsMissingImageStart) - { - for (const auto& [start, header] : cacheInfo.headers) - { - for (const auto& segment : header.segments) - { - const auto regionIt = cacheInfo.memoryRegions.find(segment.vmaddr); - assert(regionIt != cacheInfo.memoryRegions.end()); - auto& region = regionIt->second; - assert(!region.imageStart || region.imageStart == start); - region.imageStart = start; - } - } - } - - return cacheInfo; -} -void State::Store(SerializationContext& context, std::optional<DSCViewState> viewState) const -{ - MSS(memoryRegionStatus); - MSS(viewState); + return it->second; } -void SharedCache::ModifiedState::Store(SerializationContext& context) const +std::optional<CacheRegion> SharedCache::GetRegionContaining(const uint64_t address) const { - State::Store(context, viewState); -} - -SharedCache::ModifiedState SharedCache::ModifiedState::Load(DeserializationContext& context) -{ - SharedCache::ModifiedState state; - state.MSL(memoryRegionStatus); - state.MSL(viewState); - return state; -} - -SharedCache::ModifiedState SharedCache::ModifiedState::LoadAll(BinaryNinja::BinaryView *dscView, const CacheInfo& cacheInfo) -{ - uint64_t stateCount = dscView->GetUIntMetadata(SharedCacheMetadata::ModifiedStateCountTag); - SharedCache::ModifiedState state; - for (uint64_t i = 0; i < stateCount; ++i) - { - std::string key = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - std::string serialized = dscView->GetStringMetadata(key); - auto thisState = SharedCache::ModifiedState::LoadFromString(serialized); - state.Merge(std::move(thisState)); - } - return state; -} - -void SharedCache::ModifiedState::Merge(SharedCache::ModifiedState&& newer) -{ - memoryRegionStatus.merge(newer.memoryRegionStatus); - exportInfos.merge(newer.exportInfos); - symbolInfos.merge(newer.symbolInfos); - - if (newer.viewState) - viewState = newer.viewState; -} - -void BackingCache::Store(SerializationContext& context) const -{ - MSS(path); - MSS_CAST(cacheType, uint32_t); - MSS(mappings); -} - -BackingCache BackingCache::Load(DeserializationContext& context) -{ - BackingCache cache; - cache.MSL(path); - cache.MSL_CAST(cacheType, uint32_t, BNBackingCacheType); - cache.MSL(mappings); - return cache; -} - -void CacheImage::Store(SerializationContext& context) const -{ - MSS(installName); - MSS(headerLocation); - MSS(regionStarts); -} - -// static -CacheImage CacheImage::Load(DeserializationContext& context) -{ - CacheImage cacheImage; - cacheImage.MSL(installName); - cacheImage.MSL(headerLocation); - cacheImage.MSL(regionStarts); - return cacheImage; -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<BackingCache>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(BackingCache::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<CacheImage>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(CacheImage::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, SharedCacheMachOHeader>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair_value : array) - { - auto pair = pair_value.GetArray(); - b[pair[0].GetUint64()] = SharedCacheMachOHeader::LoadFromValue(pair[1]); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRangeMap<MemoryRegion>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& key_value : array) - { - auto key_value_pair = key_value.GetArray(); - auto key_pair = key_value_pair[0].GetArray(); - AddressRange key = {key_pair[0].GetUint64(), key_pair[1].GetUint64()}; - b[key] = MemoryRegion::LoadFromValue(key_value_pair[1]); - } -} - -const std::vector<BackingCache>& SharedCache::BackingCaches() const -{ - return m_cacheInfo->backingCaches; -} - -DSCViewState SharedCache::ViewState() const { - { - std::lock_guard lock(m_mutex); - if (auto& viewState = m_modifiedState->viewState) - return *viewState; - } - - return m_viewSpecificState->viewState; -} - -const std::unordered_map<std::string, uint64_t>& SharedCache::AllImageStarts() const -{ - return m_cacheInfo->imageStarts; -} - -const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImageHeaders() const -{ - return m_cacheInfo->headers; -} - -uint64_t SharedCache::CacheInfo::BaseAddress() const -{ - uint64_t base = std::numeric_limits<uint64_t>::max(); - for (const auto& backingCache : backingCaches) - { - for (const auto& mapping : backingCache.mappings) - { - if (mapping.address < base) - { - base = mapping.address; - break; - } - } - } - return base; -} - -// Intentionally takes a copy to avoid modifying the cursor position in the original reader. -std::optional<ObjCOptimizationHeader> SharedCache::GetObjCOptimizationHeader(VMReader reader) const -{ - if (!m_cacheInfo->objcOptimizationDataRange) - return {}; - - ObjCOptimizationHeader header{}; - // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. - reader.Read(&header, m_cacheInfo->BaseAddress() + m_cacheInfo->objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader)); - - return header; -} - -uint64_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const -{ - if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) - return m_cacheInfo->BaseAddress() + header->relativeMethodSelectorBaseAddressOffset; - return 0; + for (const auto& [range, region] : m_regions) + if (address >= range.start && address < range.end) + return region; + return std::nullopt; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFile(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageAt(const uint64_t address) const { - uint64_t baseAddress = m_cacheInfo->BaseAddress(); - return MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), path, - [baseAddress, logger = m_logger](std::shared_ptr<MMappedFileAccessor> mmap) { - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }) - ->lock(); + const auto it = m_images.find(address); + if (it == m_images.end()) + return std::nullopt; + return it->second; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFileWithoutApplyingSlide(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageContaining(const uint64_t address) const { - return std::make_shared<MMappedFileAccessor>(path); + // TODO: What if we are using this on a shared region? Return a list of images? + auto region = GetRegionContaining(address); + if (region.has_value()) + return GetImageAt(*region->imageStart); + return std::nullopt; } -const std::string SharedCacheMetadata::Tag = "SHAREDCACHE-SharedCacheData"; -const std::string SharedCacheMetadata::CacheInfoTag = "SHAREDCACHE-CacheInfo"; -const std::string SharedCacheMetadata::ModifiedStateTagPrefix = "SHAREDCACHE-ModifiedState-"; -const std::string SharedCacheMetadata::ModifiedStateCountTag = "SHAREDCACHE-ModifiedState-Count"; - -SharedCacheMetadata::~SharedCacheMetadata() = default; -SharedCacheMetadata::SharedCacheMetadata(SharedCacheMetadata&&) = default; -SharedCacheMetadata& SharedCacheMetadata::operator=(SharedCacheMetadata&&) = default; - -SharedCacheMetadata::SharedCacheMetadata(SharedCache::CacheInfo cacheInfo, SharedCache::ModifiedState state) : - cacheInfo(std::make_unique<SharedCache::CacheInfo>(std::move(cacheInfo))), - state(std::make_unique<SharedCache::ModifiedState>(std::move(state))) -{} - - -// static -bool SharedCacheMetadata::ViewHasMetadata(BinaryView* view) +std::optional<CacheImage> SharedCache::GetImageWithName(const std::string& name) const { - return view->QueryMetadata(Tag); + for (const auto& [address, image] : m_images) + if (image.path == name) + return image; + return std::nullopt; } -std::optional<unsigned int> SharedCacheMetadata::ViewMetadataVersion(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolAt(uint64_t address) const { - // Whether the view has compatible metadata. I.e. if the version differs. - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - DeserializationContext context; - rapidjson::ParseResult result = context.doc.Parse(viewMetadata->GetString().c_str()); - if (!result) - return std::nullopt; - if (!context.doc.HasMember("metadataVersion")) + const auto it = m_symbols.find(address); + if (it == m_symbols.end()) return std::nullopt; - return context.doc["metadataVersion"].GetUint(); + return it->second; } -// static -std::optional<SharedCacheMetadata> SharedCacheMetadata::LoadFromView(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolWithName(const std::string& name) const { - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - - auto cacheInfo = SharedCache::CacheInfo::LoadFromString(viewMetadata->GetString()); - if (!cacheInfo) - return std::nullopt; - - auto modifiedState = SharedCache::ModifiedState::LoadAll(view, *cacheInfo); - return SharedCacheMetadata(std::move(*cacheInfo), std::move(modifiedState)); + for (const auto& [address, symbol] : m_symbols) + if (symbol.name == name) + return symbol; + return std::nullopt; } -const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& SharedCacheMetadata::ExportInfos() const +CacheProcessor::CacheProcessor(Ref<BinaryView> view) { - return state->exportInfos; + m_view = std::move(view); + m_logger = new Logger("CacheProcessor", m_view->GetFile()->GetSessionId()); } -std::string SharedCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const +bool CacheProcessor::ProcessCache(SharedCache& cache) { - auto it = std::find_if(cacheInfo->imageStarts.begin(), cacheInfo->imageStarts.end(), [=](auto& pair) { - return pair.second == baseAddress; - }); - - if (it == cacheInfo->imageStarts.end()) - return ""; - - return it->first; -} - -} // namespace SharedCacheCore - -void InitDSCViewType() { - MMappedFileAccessor::InitialVMSetup(); - std::atexit(VMShutdown); - - static DSCViewType type; - BinaryViewType::Register(&type); + // If we are in a project, use the project cache processor. + if (m_view->GetFile()->GetProjectFile()) + return ProcessProjectCache(cache); + return ProcessFileCache(cache); } -extern "C" +bool CacheProcessor::ProcessFileCache(SharedCache& cache) { - BNSharedCache* BNGetSharedCache(BNBinaryView* data) - { - if (!data) - return nullptr; - - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - if (auto cache = SharedCache::GetFromDSCView(view)) - { - cache->AddAPIRef(); - return cache->GetAPIObject(); - } - - return nullptr; - } - - BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return nullptr; - - cache->object->AddAPIRef(); - return cache; - } + // We assume that the binary view location has all the files we need. + // If we ever want to allow users to override the shared cache file location + // we should really make a cache processor constructor with entry file paths. + std::string baseFilePath = m_view->GetFile()->GetOriginalFilename(); + std::string baseFileName = BaseFileName(baseFilePath); - void BNFreeSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return; - - cache->object->ReleaseAPIRef(); - } - - bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name, bool skipObjC) - { - std::string imageName = std::string(name); - BNFreeString(name); - - if (cache->object) - return cache->object->LoadImageWithInstallName(imageName, skipObjC); - - return false; - } - - bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t addr) + // Add this file to the entries + try { - if (cache->object) - { - return cache->object->LoadSectionAtAddress(addr); - } + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; - return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); } - - bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address, bool skipObjC) + catch (const std::exception& e) { - if (cache->object) - { - return cache->object->LoadImageContainingAddress(address, skipObjC); - } - + // Just return false so the view init can continue. return false; } - void BNDSCViewProcessObjCSectionsForImageWithInstallName(BNSharedCache* cache, char* name, bool deallocName) - { - std::string imageName = std::string(name); - if (deallocName) - BNFreeString(name); - - if (cache->object) - cache->object->ProcessObjCSectionsForImageWithInstallName(imageName); - } - - void BNDSCViewProcessAllObjCSections(BNSharedCache* cache) - { - if (cache->object) - cache->object->ProcessAllObjCSections(); - } - - char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto value = cache->object->GetAvailableImages(); - *count = value.size(); - - std::vector<const char*> cstrings; - cstrings.reserve(value.size()); - for (size_t i = 0; i < value.size(); i++) - { - cstrings.push_back(value[i].c_str()); - } - return BNAllocStringList(cstrings.data(), cstrings.size()); - } - *count = 0; - return nullptr; - } - - BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count) + // Locate all possible related entry files and add them to the cache. + std::filesystem::path basePath = std::filesystem::path(baseFilePath).parent_path(); + for (const auto& entry : std::filesystem::directory_iterator(basePath)) { - if (cache->object) + if (!entry.is_regular_file()) + continue; + auto currentFilePath = entry.path().string(); + auto currentFileName = BaseFileName(currentFilePath); + // Skip our base file, obviously. + if (currentFilePath== baseFilePath) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (currentFilePath.find(baseFileName) == std::string::npos) + continue; + // Skip map files, they contain some nice information... we don't use. + if (entry.path().extension() == ".map") + continue; + try { - auto symbolsByImageName = cache->object->LoadAllSymbolsAndWait(); - size_t totalSymbolCount = 0; - for (const auto& [_, symbols] : symbolsByImageName) + auto additionalEntry = CacheEntry::FromFile(currentFilePath, currentFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - totalSymbolCount += symbols.size(); - } - *count = totalSymbolCount; - - BNDSCSymbolRep* outputSymbols = new BNDSCSymbolRep[totalSymbolCount]; - size_t i = 0; - for (const auto& [imageName, symbols] : symbolsByImageName) - { - for (const auto& symbol : symbols) - { - outputSymbols[i].address = symbol->GetAddress(); - outputSymbols[i].name = BNDuplicateStringRef(symbol->GetRawNameRef().GetObject()); - outputSymbols[i].image = BNAllocStringWithLength(imageName.c_str(), imageName.length()); - ++i; - } + m_logger->LogErrorF("Failed to load entry {}...", currentFileName); + continue; } - assert(i == totalSymbolCount); - return outputSymbols; - } - *count = 0; - return nullptr; - } - - void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeStringRef(symbols[i].name); - BNFreeString(symbols[i].image); - } - delete symbols; - } - - char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->NameForAddress(address).c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); - } - - return nullptr; - } - uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache) - { - // FIXME? - return 0; - } - - BNDSCViewState BNDSCViewGetState(BNSharedCache* cache) - { - if (cache->object) - { - return (BNDSCViewState)cache->object->ViewState(); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - - return BNDSCViewState::Unloaded; + catch (const std::exception& e) + {} } + return true; +} - BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto regions = cache->object->GetMappedRegions(); - *count = regions.size(); - BNDSCMappedMemoryRegion* mappedRegions = new BNDSCMappedMemoryRegion[regions.size()]; - for (size_t i = 0; i < regions.size(); i++) - { - mappedRegions[i].vmAddress = regions[i]->start; - mappedRegions[i].size = regions[i]->size; - mappedRegions[i].name = - BNAllocStringWithLength(regions[i]->prettyName.c_str(), regions[i]->prettyName.length()); - } - return mappedRegions; - } - *count = 0; - return nullptr; - } +bool CacheProcessor::ProcessProjectCache(SharedCache& cache) +{ + auto baseProjectFile = m_view->GetFile()->GetProjectFile(); + std::string baseFilePath = baseProjectFile->GetPathOnDisk(); + // TODO: I dont think the project file name will have anything other than the base name so this might be redundant. + std::string baseFileName = BaseFileName(baseProjectFile->GetName()); - void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeString(images[i].name); - } - delete images; - } + // Add this file to the entries + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); - BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count) + // Enumerate the project files folder to gather the necessary sub caches. + const auto project = baseProjectFile->GetProject(); + const auto folder = baseProjectFile->GetFolder(); + for (const auto& projectFile : project->GetFiles()) { - BNDSCBackingCache* caches = nullptr; - - if (cache->object) + auto projectFilePath = projectFile->GetPathOnDisk(); + auto projectFileName = projectFile->GetName(); + auto currentFolder = projectFile->GetFolder(); + // Skip our base project file, obviously. + if (projectFile->GetId() == baseProjectFile->GetId()) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (projectFileName.find(baseFileName) == std::string::npos) + continue; + // Filter out .map files, they contain some nice info for rebasing... that we don't do. + if (projectFileName.find(".map") != std::string::npos) + continue; + // If both top level, or we are in the same folder as the base project file add it. + if ((!folder && !currentFolder) || (folder && currentFolder)) { - auto viewCaches = cache->object->BackingCaches(); - *count = viewCaches.size(); - caches = new BNDSCBackingCache[viewCaches.size()]; - for (size_t i = 0; i < viewCaches.size(); i++) + try { - caches[i].path = BNAllocString(viewCaches[i].path.c_str()); - caches[i].cacheType = viewCaches[i].cacheType; - - BNDSCBackingCacheMapping* mappings; - mappings = new BNDSCBackingCacheMapping[viewCaches[i].mappings.size()]; - - size_t j = 0; - for (const auto& mapping : viewCaches[i].mappings) - { - mappings[j].vmAddress = mapping.address; - mappings[j].size = mapping.size; - mappings[j].fileOffset = mapping.fileOffset; - j++; - } - caches[i].mappings = mappings; - caches[i].mappingCount = viewCaches[i].mappings.size(); - } - } - - return caches; - } - - void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count) - { - for (size_t i = 0; i < count; i++) - { - delete[] caches[i].mappings; - BNFreeString(caches[i].path); - } - delete[] caches; - } - - void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) - { - if (cache->object) - { - cache->object->FindSymbolAtAddrAndApplyToAddr(symbolLocation, targetLocation, triggerReanalysis); - } - } - - BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - try { - auto vm = cache->object->GetVMMap(); - auto viewImageHeaders = cache->object->AllImageHeaders(); - *count = viewImageHeaders.size(); - BNDSCImage* images = new BNDSCImage[viewImageHeaders.size()]; - size_t i = 0; - for (const auto& [baseAddress, header] : viewImageHeaders) + auto additionalEntry = CacheEntry::FromFile(projectFilePath, projectFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - images[i].name = BNAllocString(header.installName.c_str()); - images[i].headerAddress = baseAddress; - images[i].mappingCount = header.sections.size(); - images[i].mappings = new BNDSCImageMemoryMapping[header.sections.size()]; - for (size_t j = 0; j < header.sections.size(); j++) - { - const auto sectionStart = header.sections[j].addr; - images[i].mappings[j].rawViewOffset = header.sections[j].offset; - images[i].mappings[j].vmAddress = sectionStart; - images[i].mappings[j].size = header.sections[j].size; - images[i].mappings[j].name = BNAllocString(header.sectionNames[j].c_str()); - auto fileAccessor = vm->MappingAtAddress(sectionStart).first.fileAccessor; - images[i].mappings[j].filePath = BNAllocStringWithLength(fileAccessor->filePath().data(), fileAccessor->filePath().length()); - images[i].mappings[j].loaded = cache->object->IsMemoryMapped(sectionStart); - } - i++; + m_logger->LogErrorF("Failed to load entry {}...", projectFileName); + continue; } - return images; - } - catch (...) - { - LogError("SharedCache: Failed to load image listing. Likely caused by a ser/deserialization error or load failure"); - *count = 0; - return nullptr; - } - } - *count = 0; - return nullptr; - } - void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - for (size_t j = 0; j < images[i].mappingCount; j++) - { - BNFreeString(images[i].mappings[j].name); - BNFreeString(images[i].mappings[j].filePath); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - delete[] images[i].mappings; - BNFreeString(images[i].name); + catch (const std::exception& e) + {} } - delete[] images; } - char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForAddress(address); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name) - { - std::string imageName = std::string(name); - BNFreeString(name); - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForName(imageName); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo() - { - BNDSCMemoryUsageInfo info; - info.mmapRefs = MMapCount(); - info.sharedCacheRefs = sharedCacheReferences.load(); - return info; - } - - BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID) - { - if (auto viewSpecificState = ViewSpecificStateForId(sessionID, false)) - return viewSpecificState->progress; - - return LoadProgressNotStarted; - } - - uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* data) - { - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - return SharedCache::FastGetBackingCacheCount(view); - } -}
\ No newline at end of file + return true; +} |
