summaryrefslogtreecommitdiff
path: root/view/sharedcache/core/SharedCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'view/sharedcache/core/SharedCache.cpp')
-rw-r--r--view/sharedcache/core/SharedCache.cpp4154
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(&currentMapping, 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(
+ &currentImg, 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(
+ &currentImg, 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 = &image;
- 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(&region);
- }
-
- // 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(&sect, 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(&sect.sectname, 16);
- reader.Read(&sect.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(&sect.sectname, 16);
- reader.Read(&sect.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;
+}