summaryrefslogtreecommitdiff
path: root/view/sharedcache/core
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-03-10 11:05:40 -0400
committerMason Reed <mason@vector35.com>2025-04-02 05:36:54 -0400
commit25cc02431b61097b2adfc2fbc493b648b0300c3b (patch)
treea79d9c4f4f67234d3bf9bda413e8608f479a4cc8 /view/sharedcache/core
parentfa85bf28502286c4821427c5d0ed91a7ed46f8f6 (diff)
[SharedCache] Refactor Shared Cache
In absence of a better name, this commit refactors the shared cache code.
Diffstat (limited to 'view/sharedcache/core')
-rw-r--r--view/sharedcache/core/CMakeLists.txt2
-rw-r--r--view/sharedcache/core/DSCView.h40
-rw-r--r--view/sharedcache/core/Dyld.h267
-rw-r--r--view/sharedcache/core/FileAccessorCache.cpp112
-rw-r--r--view/sharedcache/core/FileAccessorCache.h84
-rw-r--r--view/sharedcache/core/MachO.cpp667
-rw-r--r--view/sharedcache/core/MachO.h76
-rw-r--r--view/sharedcache/core/MachOProcessor.cpp320
-rw-r--r--view/sharedcache/core/MachOProcessor.h23
-rw-r--r--view/sharedcache/core/MappedFile.cpp193
-rw-r--r--view/sharedcache/core/MappedFile.h86
-rw-r--r--view/sharedcache/core/MappedFileAccessor.cpp101
-rw-r--r--view/sharedcache/core/MappedFileAccessor.h103
-rw-r--r--view/sharedcache/core/MetadataSerializable.cpp552
-rw-r--r--view/sharedcache/core/MetadataSerializable.hpp254
-rw-r--r--view/sharedcache/core/ObjC.cpp113
-rw-r--r--view/sharedcache/core/ObjC.h65
-rw-r--r--view/sharedcache/core/SharedCache.cpp4154
-rw-r--r--view/sharedcache/core/SharedCache.h829
-rw-r--r--view/sharedcache/core/SharedCacheController.cpp261
-rw-r--r--view/sharedcache/core/SharedCacheController.h65
-rw-r--r--view/sharedcache/core/SharedCacheView.cpp (renamed from view/sharedcache/core/DSCView.cpp)481
-rw-r--r--view/sharedcache/core/SharedCacheView.h43
-rw-r--r--view/sharedcache/core/SlideInfo.cpp292
-rw-r--r--view/sharedcache/core/SlideInfo.h42
-rw-r--r--view/sharedcache/core/Utility.cpp131
-rw-r--r--view/sharedcache/core/Utility.h106
-rw-r--r--view/sharedcache/core/VM.cpp891
-rw-r--r--view/sharedcache/core/VM.h368
-rw-r--r--view/sharedcache/core/VirtualMemory.cpp375
-rw-r--r--view/sharedcache/core/VirtualMemory.h157
-rw-r--r--view/sharedcache/core/ffi.cpp429
-rw-r--r--view/sharedcache/core/ffi_global.h41
-rw-r--r--view/sharedcache/core/refcountobject.h223
34 files changed, 5196 insertions, 6750 deletions
diff --git a/view/sharedcache/core/CMakeLists.txt b/view/sharedcache/core/CMakeLists.txt
index 03766920..a9aaa1bc 100644
--- a/view/sharedcache/core/CMakeLists.txt
+++ b/view/sharedcache/core/CMakeLists.txt
@@ -9,7 +9,7 @@ if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD))
endif()
endif()
-file(GLOB SOURCES *.cpp *.h ../../../objectivec/*)
+file(GLOB_RECURSE SOURCES *.cpp *.h ../../../objectivec/*)
add_library(sharedcachecore OBJECT ${SOURCES})
diff --git a/view/sharedcache/core/DSCView.h b/view/sharedcache/core/DSCView.h
deleted file mode 100644
index bde2f7c2..00000000
--- a/view/sharedcache/core/DSCView.h
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Created by kat on 5/23/23.
-//
-
-#ifndef SHAREDCACHE_DSCVIEW_H
-#define SHAREDCACHE_DSCVIEW_H
-
-#include <binaryninjaapi.h>
-
-
-class DSCView : public BinaryNinja::BinaryView {
- bool m_parseOnly;
-public:
-
- DSCView(const std::string &typeName, BinaryView *data, bool parseOnly = false);
-
- ~DSCView() override;
-
- bool Init() override;
-};
-
-
-class DSCViewType : public BinaryNinja::BinaryViewType {
-
-public:
- DSCViewType();
-
- BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView *data) override;
-
- BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView *data) override;
-
- bool IsTypeValidForData(BinaryNinja::BinaryView *data) override;
-
- bool IsDeprecated() override { return false; }
-
- BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView *data) override;
-};
-
-
-#endif //SHAREDCACHE_DSCVIEW_H
diff --git a/view/sharedcache/core/Dyld.h b/view/sharedcache/core/Dyld.h
new file mode 100644
index 00000000..70397877
--- /dev/null
+++ b/view/sharedcache/core/Dyld.h
@@ -0,0 +1,267 @@
+#pragma once
+
+// These types will be parsed directly from the files.
+
+#include <stdint.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+ #define PACKED_STRUCT __attribute__((packed))
+#else
+ #define PACKED_STRUCT
+#endif
+
+struct PACKED_STRUCT dyld_cache_mapping_info
+{
+ uint64_t address;
+ uint64_t size;
+ uint64_t fileOffset;
+ uint32_t maxProt;
+ uint32_t initProt;
+};
+
+struct dyld_cache_slide_info
+{
+ uint32_t version;
+ uint32_t toc_offset;
+ uint32_t toc_count;
+ uint32_t entries_offset;
+ uint32_t entries_count;
+ uint32_t entries_size;
+ // uint16_t toc[toc_count];
+ // entrybitmap entries[entries_count];
+};
+
+struct dyld_cache_slide_info_entry
+{
+ uint8_t bits[4096 / (8 * 4)]; // 128-byte bitmap
+};
+
+struct PACKED_STRUCT dyld_cache_mapping_and_slide_info
+{
+ uint64_t address;
+ uint64_t size;
+ uint64_t fileOffset;
+ uint64_t slideInfoFileOffset;
+ uint64_t slideInfoFileSize;
+ uint64_t flags;
+ uint32_t maxProt;
+ uint32_t initProt;
+};
+
+struct PACKED_STRUCT dyld_cache_slide_info_v2
+{
+ uint32_t version;
+ uint32_t page_size;
+ uint32_t page_starts_offset;
+ uint32_t page_starts_count;
+ uint32_t page_extras_offset;
+ uint32_t page_extras_count;
+ uint64_t delta_mask;
+ uint64_t value_add;
+};
+#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array)
+#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing
+#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page
+
+#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
+
+struct PACKED_STRUCT dyld_cache_slide_info_v3
+{
+ uint32_t version;
+ uint32_t page_size;
+ uint32_t page_starts_count;
+ uint32_t pad_i_guess;
+ uint64_t auth_value_add;
+};
+
+
+// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
+struct dyld_chained_ptr_arm64e_shared_cache_rebase
+{
+ uint64_t runtimeOffset : 34, // offset from the start of the shared cache
+ high8 : 8, unused : 10,
+ next : 11, // 8-byte stide
+ auth : 1; // == 0
+};
+
+// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
+struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase
+{
+ uint64_t runtimeOffset : 34, // offset from the start of the shared cache
+ diversity : 16, addrDiv : 1,
+ keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA
+ next : 11, // 8-byte stide
+ auth : 1; // == 1
+};
+
+// TODO: dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now.
+
+#define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
+
+struct PACKED_STRUCT dyld_cache_slide_info_v5
+{
+ uint32_t version; // currently 5
+ uint32_t page_size; // currently 4096 (may also be 16384)
+ uint32_t page_starts_count;
+ uint32_t pad; // padding to ensure the value below is on an 8-byte boundary
+ uint64_t value_add;
+ // uint16_t page_starts[/* page_starts_count */];
+};
+
+
+struct PACKED_STRUCT dyld_cache_image_info
+{
+ uint64_t address;
+ uint64_t modTime;
+ uint64_t inode;
+ uint32_t pathFileOffset;
+ uint32_t pad;
+
+ bool operator==(const dyld_cache_image_info& image) const;
+};
+
+union dyld_cache_slide_pointer5
+{
+ uint64_t raw;
+ struct dyld_chained_ptr_arm64e_shared_cache_rebase regular;
+ struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth;
+};
+
+
+struct PACKED_STRUCT dyld_cache_local_symbols_info
+{
+ uint32_t nlistOffset; // offset into this chunk of nlist entries
+ uint32_t nlistCount; // count of nlist entries
+ uint32_t stringsOffset; // offset into this chunk of string pool
+ uint32_t stringsSize; // byte count of string pool
+ uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry
+ uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array
+};
+
+struct PACKED_STRUCT dyld_cache_local_symbols_entry
+{
+ uint32_t dylibOffset; // offset in cache file of start of dylib
+ uint32_t nlistStartIndex; // start index of locals for this dylib
+ uint32_t nlistCount; // number of local symbols for this dylib
+};
+
+struct PACKED_STRUCT dyld_cache_local_symbols_entry_64
+{
+ uint64_t dylibOffset; // offset in cache buffer of start of dylib
+ uint32_t nlistStartIndex; // start index of locals for this dylib
+ uint32_t nlistCount; // number of local symbols for this dylib
+};
+
+union dyld_cache_slide_pointer3
+{
+ uint64_t raw;
+ struct
+ {
+ uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2;
+ } plain;
+
+ struct
+ {
+ uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2,
+ offsetToNextPointer : 11, unused : 1,
+ authenticated : 1; // = 1;
+ } auth;
+};
+
+
+struct PACKED_STRUCT dyld_cache_header
+{
+ char magic[16]; // e.g. "dyld_v0 i386"
+ uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info
+ uint32_t mappingCount; // number of dyld_cache_mapping_info entries
+ uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing
+ uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing
+ uint64_t dyldBaseAddress; // base address of dyld when cache was built
+ uint64_t codeSignatureOffset; // file offset of code signature blob
+ uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file)
+ uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info
+ uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info
+ uint64_t localSymbolsOffset; // file offset of where local symbols are stored
+ uint64_t localSymbolsSize; // size of local symbols information
+ uint8_t uuid[16]; // unique value for each shared cache file
+ uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache
+ uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses
+ uint32_t branchPoolsCount; // number of uint64_t entries
+ uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache
+ uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache
+ uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info
+ uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries
+ uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info
+ uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info
+ uint64_t otherImageGroupAddrUnused; // unused
+ uint64_t otherImageGroupSizeUnused; // unused
+ uint64_t progClosuresAddr; // (unslid) address of list of program launch closures
+ uint64_t progClosuresSize; // size of list of program launch closures
+ uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures
+ uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures
+ uint32_t platform; // platform number (macOS=1, etc)
+ uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion
+ dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if
+ // cache is valid
+ simulator : 1, // for simulator of specified platform
+ locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache
+ builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used
+ // for overrides
+ padding : 20; // TBD
+ uint64_t sharedRegionStart; // base load address of cache if not slid
+ uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any
+ uint64_t maxSlide; // runtime slide of cache can be between zero and this value
+ uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache
+ uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache
+ uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs
+ uint64_t dylibsTrieSize; // size of trie of cached dylib paths
+ uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures
+ uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures
+ uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures
+ uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures
+ uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info
+ uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries
+ uint64_t dylibsPBLStateArrayAddrUnused; // unused
+ uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs
+ uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program
+ uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program
+ uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet
+ uint32_t programTrieSize;
+ uint32_t osVersion; // OS Version of dylibs in this cache for the main platform
+ uint32_t altPlatform; // e.g. iOSMac on macOS
+ uint32_t altOsVersion; // e.g. 14.0 for iOSMac
+ uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header
+ uint64_t swiftOptsSize; // size of Swift optimizations header
+ uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry
+ uint32_t subCacheArrayCount; // number of subCache entries
+ uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols
+ uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data
+ uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region
+ uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data
+ uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region
+ uint32_t imagesOffset; // file offset to first dyld_cache_image_info
+ uint32_t imagesCount; // number of dyld_cache_image_info entries
+ uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)
+ uint32_t padding2;
+ uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header
+ uint64_t objcOptsSize; // size of ObjC optimizations header
+ uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection
+ uint64_t cacheAtlasSize; // size of embedded cache atlas
+ uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header
+ uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data
+ uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info
+ uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries
+};
+
+struct PACKED_STRUCT dyld_subcache_entry
+{
+ char uuid[16];
+ uint64_t address;
+};
+
+struct PACKED_STRUCT dyld_subcache_entry2
+{
+ char uuid[16];
+ uint64_t address;
+ char fileExtension[32];
+}; \ No newline at end of file
diff --git a/view/sharedcache/core/FileAccessorCache.cpp b/view/sharedcache/core/FileAccessorCache.cpp
new file mode 100644
index 00000000..047bbe19
--- /dev/null
+++ b/view/sharedcache/core/FileAccessorCache.cpp
@@ -0,0 +1,112 @@
+#include "FileAccessorCache.h"
+
+#include <cassert>
+
+CacheAccessorID GetCacheAccessorID(const std::string& filePath)
+{
+ constexpr std::hash<std::string> hasher;
+ return static_cast<CacheAccessorID>(hasher(filePath));
+}
+
+FileAccessorCache::FileAccessorCache(size_t cacheSize)
+{
+ m_cacheSize = cacheSize;
+ m_accessors = {};
+}
+
+void FileAccessorCache::EvictLastUsed()
+{
+ if (m_cache.empty())
+ return;
+ // Evict the least recently used element.
+ const auto lruID = m_cache.front();
+ m_cache.pop_front();
+ // Ensure the least recently used ID actually exists in the accessors map.
+ assert(m_accessors.find(lruID) != m_accessors.end() && "Evicting non-existent ID from accessors map");
+ m_accessors.erase(lruID);
+}
+
+FileAccessorCache& FileAccessorCache::Global()
+{
+ static FileAccessorCache cache {};
+ return cache;
+}
+
+
+WeakFileAccessor FileAccessorCache::Open(const std::string& filePath)
+{
+ const auto id = GetCacheAccessorID(filePath);
+ std::unique_lock lock(m_mutex);
+
+ // Check if the file is already in the cache.
+ if (const auto it = m_accessors.find(id); it != m_accessors.end())
+ {
+ // Move the accessed ID to the back so we keep it in the cache.
+ auto pos = std::find(m_cache.begin(), m_cache.end(), id);
+ if (pos != m_cache.end())
+ m_cache.erase(pos);
+ m_cache.push_back(id);
+
+ return WeakFileAccessor(it->second, filePath);
+ }
+
+ // Evict if we are going to go above the limit.
+ if (m_cache.size() >= m_cacheSize)
+ EvictLastUsed();
+
+ // Create a new file accessor and add it to the cache.
+ auto accessor = MappedFileAccessor::Open(filePath);
+ if (accessor == nullptr)
+ {
+ // We failed to open the file, we must throw hard!
+ // TODO: Make this mechanism more thought out...
+ throw std::runtime_error("Failed to open file: " + filePath);
+ }
+ auto sharedAccessor = std::make_shared<MappedFileAccessor>(std::move(*accessor));
+ m_accessors.insert_or_assign(id, sharedAccessor);
+ m_cache.push_back(id);
+
+ return WeakFileAccessor(sharedAccessor, filePath);
+}
+
+void FileAccessorWriteLog::AddPointer(const size_t address, const size_t pointer)
+{
+ // TODO: A sharded map here would be better. see: rust dashmap
+ std::unique_lock<std::shared_mutex> lock(m_persistedMutex);
+ m_persistedPointers[address] = pointer;
+}
+
+void FileAccessorWriteLog::ApplyWrites(MappedFileAccessor& accessor)
+{
+ std::shared_lock<std::shared_mutex> lock(m_persistedMutex);
+ for (const auto& [address, pointer] : m_persistedPointers)
+ accessor.WritePointer(address, pointer);
+}
+
+std::shared_ptr<MappedFileAccessor> WeakFileAccessor::lock()
+{
+ auto sharedPtr = m_weakPtr.lock();
+ if (!sharedPtr)
+ {
+ // This will revive other weak pointers to the same shared ptr.
+ // Update the weak pointer to the newly created shared instance
+ m_weakPtr = FileAccessorCache::Global().Open(m_filePath).m_weakPtr;
+ sharedPtr = m_weakPtr.lock();
+
+ // Apply any previously written pointers back.
+ if (sharedPtr)
+ m_writeLog->ApplyWrites(*sharedPtr);
+ }
+
+ return sharedPtr;
+}
+
+void WeakFileAccessor::WritePointer(const size_t address, const size_t pointer)
+{
+ // Persist the pointer after the file accessor is revived.
+ m_writeLog->AddPointer(address, pointer);
+
+ // And then actually apply the written pointer...
+ if (auto sharedPtr = m_weakPtr.lock())
+ sharedPtr->WritePointer(address, pointer);
+}
diff --git a/view/sharedcache/core/FileAccessorCache.h b/view/sharedcache/core/FileAccessorCache.h
new file mode 100644
index 00000000..bb2f8084
--- /dev/null
+++ b/view/sharedcache/core/FileAccessorCache.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <shared_mutex>
+
+#include "MappedFileAccessor.h"
+
+typedef uint32_t CacheAccessorID;
+
+// TODO: We might want to make this more than just the path, for example
+// TODO: We might want to make it unique to a view session (session id).
+// Get a unique entry id for the given file path.
+CacheAccessorID GetCacheAccessorID(const std::string& filePath);
+
+class WeakFileAccessor;
+
+class FileAccessorCache
+{
+ size_t m_cacheSize;
+ std::mutex m_mutex;
+ // NOTE: If we end up wanting to handle 1000's of files we should consider std::list.
+ std::deque<CacheAccessorID> m_cache;
+ std::unordered_map<CacheAccessorID, std::shared_ptr<MappedFileAccessor>> m_accessors;
+
+ explicit FileAccessorCache(size_t cacheSize = 8);
+
+ void EvictLastUsed();
+
+public:
+ static FileAccessorCache& Global();
+
+ // Get a weak reference to a file accessor, the reference at this point is alive.
+ // The reference is always alive at this point either because it is in the cache or it has been inserted in.
+ // Subsequent calls to this might kill the backing file accessor resulting in the weak ref recreating the file
+ // accessor and inserting itself back into its related cache.
+ WeakFileAccessor Open(const std::string& filePath);
+
+ // Adjust the cache size limit.
+ // This will NOT evict current cache entries, as they are already available.
+ // Any subsequent call to `Open` will assume this cache size, evicting until the size is equal to the cache size.
+ void SetCacheSize(uint64_t size) { m_cacheSize = size; };
+};
+
+// Write log to be used in conjunction with `WeakFileAccessor` to re-apply written data to a "revived" file.
+struct FileAccessorWriteLog
+{
+ // To persist writes to a file accessor being revived (within the lock() function)
+ // we keep a list of writes that will be re-applied in the lock function.
+ std::shared_mutex m_persistedMutex;
+ std::unordered_map<size_t, uint64_t> m_persistedPointers;
+
+ FileAccessorWriteLog() = default;
+
+ // Add the pointer to the persisted pointers.
+ void AddPointer(size_t address, size_t pointer);
+
+ // Apply all logged writes to the given accessor.
+ void ApplyWrites(MappedFileAccessor& accessor);
+};
+
+class WeakFileAccessor
+{
+ // Weak pointer to the mapped file accessor, once this is expired we will re-open.
+ std::weak_ptr<MappedFileAccessor> m_weakPtr;
+ // File path for re-opening if needed
+ std::string m_filePath;
+
+ // Used to re-add writes once the file accessor is "revived".
+ std::shared_ptr<FileAccessorWriteLog> m_writeLog;
+
+ // TODO: Store a weak_ptr/shared_ptr to FileAccessorCache? That way we dont access Global()
+ // TODO: Only need to do the above if we want multiple caches.
+
+public:
+ explicit WeakFileAccessor(std::weak_ptr<MappedFileAccessor> weakPtr, std::string filePath) :
+ m_weakPtr(std::move(weakPtr)), m_filePath(std::move(filePath)),
+ m_writeLog(std::make_shared<FileAccessorWriteLog>())
+ {}
+
+ std::shared_ptr<MappedFileAccessor> lock();
+
+ // Persists the written pointer within this weak file accessor.
+ // This works as we expect the weak file accessor to be stored per virtual memory region.
+ void WritePointer(size_t address, size_t pointer);
+};
diff --git a/view/sharedcache/core/MachO.cpp b/view/sharedcache/core/MachO.cpp
new file mode 100644
index 00000000..14fffefd
--- /dev/null
+++ b/view/sharedcache/core/MachO.cpp
@@ -0,0 +1,667 @@
+#include "MachO.h"
+#include "Utility.h"
+
+#include "SharedCache.h"
+#include "VirtualMemory.h"
+
+using namespace BinaryNinja;
+
+std::vector<uint64_t> SharedCacheMachOHeader::ReadFunctionTable(VirtualMemory& vm) const
+{
+ // NOTE: The funcoff is relative to the file of the linkedit segment.
+ uint64_t funcStartsAddress = GetLinkEditFileBase() + functionStarts.funcoff;
+ auto funcStarts = vm.ReadBuffer(funcStartsAddress, functionStarts.funcsize);
+ uint64_t curfunc = textBase;
+ uint64_t curOffset = 0;
+
+ std::vector<uint64_t> functionTable = {};
+ auto current = static_cast<const uint8_t*>(funcStarts.GetData());
+ auto end = current + funcStarts.GetLength();
+ while (current != end)
+ {
+ curOffset = readLEB128(current, end);
+ // TODO: Verify this is the correct behavior.
+ // Skip unmapped functions.
+ if (curOffset == 0 || !vm.IsAddressMapped(curfunc))
+ continue;
+ curfunc += curOffset;
+ uint64_t target = curfunc;
+ functionTable.push_back(target);
+ }
+ return functionTable;
+}
+
+std::optional<SharedCacheMachOHeader> SharedCacheMachOHeader::ParseHeaderForAddress(
+ std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath)
+{
+ // Sanity check to make sure that the header is mapped.
+ // This should really only fail if we didn't grab all the required entries.
+ if (!vm->IsAddressMapped(address))
+ return std::nullopt;
+
+ SharedCacheMachOHeader header;
+
+ header.textBase = address;
+ header.installName = imagePath;
+ // The identifierPrefix is used for the display of the image name in the sections and segments.
+ header.identifierPrefix = BaseFileName(imagePath);
+
+ std::string errorMsg;
+ VirtualMemoryReader reader(vm);
+ reader.Seek(address);
+
+ header.ident.magic = reader.ReadUInt32();
+
+ BNEndianness endianness;
+ switch (header.ident.magic)
+ {
+ case MH_MAGIC:
+ case MH_MAGIC_64:
+ endianness = LittleEndian;
+ break;
+ case MH_CIGAM:
+ case MH_CIGAM_64:
+ endianness = BigEndian;
+ break;
+ default:
+ return {};
+ }
+
+ reader.SetEndianness(endianness);
+ header.ident.cputype = reader.ReadUInt32();
+ header.ident.cpusubtype = reader.ReadUInt32();
+ header.ident.filetype = reader.ReadUInt32();
+ header.ident.ncmds = reader.ReadUInt32();
+ header.ident.sizeofcmds = reader.ReadUInt32();
+ header.ident.flags = reader.ReadUInt32();
+ if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8
+ {
+ header.ident.reserved = reader.ReadUInt32();
+ }
+ header.loadCommandOffset = reader.GetOffset();
+
+ bool first = true;
+ // Parse segment commands
+ try
+ {
+ for (size_t i = 0; i < header.ident.ncmds; i++)
+ {
+ // BNLogInfo("of 0x%llx", reader.GetOffset());
+ load_command load;
+ segment_command_64 segment64;
+ section_64 sect = {};
+ size_t curOffset = reader.GetOffset();
+ load.cmd = reader.ReadUInt32();
+ load.cmdsize = reader.ReadUInt32();
+ size_t nextOffset = curOffset + load.cmdsize;
+ if (load.cmdsize < sizeof(load_command))
+ return {};
+
+ switch (load.cmd)
+ {
+ case LC_MAIN:
+ {
+ uint64_t entryPoint = reader.ReadUInt64();
+ header.entryPoints.push_back({entryPoint, true});
+ (void)reader.ReadUInt64(); // 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.ReadUInt32();
+ segment64.vmsize = reader.ReadUInt32();
+ segment64.fileoff = reader.ReadUInt32();
+ segment64.filesize = reader.ReadUInt32();
+ segment64.maxprot = reader.ReadUInt32();
+ segment64.initprot = reader.ReadUInt32();
+ segment64.nsects = reader.ReadUInt32();
+ segment64.flags = reader.ReadUInt32();
+ 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.ReadUInt32();
+ sect.size = reader.ReadUInt32();
+ sect.offset = reader.ReadUInt32();
+ sect.align = reader.ReadUInt32();
+ sect.reloff = reader.ReadUInt32();
+ sect.nreloc = reader.ReadUInt32();
+ sect.flags = reader.ReadUInt32();
+ sect.reserved1 = reader.ReadUInt32();
+ sect.reserved2 = reader.ReadUInt32();
+ // 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.ReadUInt64();
+ segment64.vmsize = reader.ReadUInt64();
+ segment64.fileoff = reader.ReadUInt64();
+ segment64.filesize = reader.ReadUInt64();
+ segment64.maxprot = reader.ReadUInt32();
+ segment64.initprot = reader.ReadUInt32();
+ segment64.nsects = reader.ReadUInt32();
+ segment64.flags = reader.ReadUInt32();
+ 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.ReadUInt64();
+ sect.size = reader.ReadUInt64();
+ sect.offset = reader.ReadUInt32();
+ sect.align = reader.ReadUInt32();
+ sect.reloff = reader.ReadUInt32();
+ sect.nreloc = reader.ReadUInt32();
+ sect.flags = reader.ReadUInt32();
+ sect.reserved1 = reader.ReadUInt32();
+ sect.reserved2 = reader.ReadUInt32();
+ sect.reserved3 = reader.ReadUInt32();
+ // 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.ReadUInt32();
+ header.routines64.init_module = reader.ReadUInt32();
+ header.routines64.reserved1 = reader.ReadUInt32();
+ header.routines64.reserved2 = reader.ReadUInt32();
+ header.routines64.reserved3 = reader.ReadUInt32();
+ header.routines64.reserved4 = reader.ReadUInt32();
+ header.routines64.reserved5 = reader.ReadUInt32();
+ header.routines64.reserved6 = reader.ReadUInt32();
+ header.routinesPresent = true;
+ break;
+ case LC_ROUTINES_64:
+ header.routines64.cmd = LC_ROUTINES_64;
+ header.routines64.init_address = reader.ReadUInt64();
+ header.routines64.init_module = reader.ReadUInt64();
+ header.routines64.reserved1 = reader.ReadUInt64();
+ header.routines64.reserved2 = reader.ReadUInt64();
+ header.routines64.reserved3 = reader.ReadUInt64();
+ header.routines64.reserved4 = reader.ReadUInt64();
+ header.routines64.reserved5 = reader.ReadUInt64();
+ header.routines64.reserved6 = reader.ReadUInt64();
+ header.routinesPresent = true;
+ break;
+ case LC_FUNCTION_STARTS:
+ header.functionStarts.funcoff = reader.ReadUInt32();
+ header.functionStarts.funcsize = reader.ReadUInt32();
+ header.functionStartsPresent = true;
+ break;
+ case LC_SYMTAB:
+ header.symtab.symoff = reader.ReadUInt32();
+ header.symtab.nsyms = reader.ReadUInt32();
+ header.symtab.stroff = reader.ReadUInt32();
+ header.symtab.strsize = reader.ReadUInt32();
+ break;
+ case LC_DYSYMTAB:
+ header.dysymtab.ilocalsym = reader.ReadUInt32();
+ header.dysymtab.nlocalsym = reader.ReadUInt32();
+ header.dysymtab.iextdefsym = reader.ReadUInt32();
+ header.dysymtab.nextdefsym = reader.ReadUInt32();
+ header.dysymtab.iundefsym = reader.ReadUInt32();
+ header.dysymtab.nundefsym = reader.ReadUInt32();
+ header.dysymtab.tocoff = reader.ReadUInt32();
+ header.dysymtab.ntoc = reader.ReadUInt32();
+ header.dysymtab.modtaboff = reader.ReadUInt32();
+ header.dysymtab.nmodtab = reader.ReadUInt32();
+ header.dysymtab.extrefsymoff = reader.ReadUInt32();
+ header.dysymtab.nextrefsyms = reader.ReadUInt32();
+ header.dysymtab.indirectsymoff = reader.ReadUInt32();
+ header.dysymtab.nindirectsyms = reader.ReadUInt32();
+ header.dysymtab.extreloff = reader.ReadUInt32();
+ header.dysymtab.nextrel = reader.ReadUInt32();
+ header.dysymtab.locreloff = reader.ReadUInt32();
+ header.dysymtab.nlocrel = reader.ReadUInt32();
+ header.dysymPresent = true;
+ break;
+ case LC_DYLD_CHAINED_FIXUPS:
+ header.chainedFixups.dataoff = reader.ReadUInt32();
+ header.chainedFixups.datasize = reader.ReadUInt32();
+ header.chainedFixupsPresent = true;
+ break;
+ case LC_DYLD_INFO:
+ case LC_DYLD_INFO_ONLY:
+ header.dyldInfo.rebase_off = reader.ReadUInt32();
+ header.dyldInfo.rebase_size = reader.ReadUInt32();
+ header.dyldInfo.bind_off = reader.ReadUInt32();
+ header.dyldInfo.bind_size = reader.ReadUInt32();
+ header.dyldInfo.weak_bind_off = reader.ReadUInt32();
+ header.dyldInfo.weak_bind_size = reader.ReadUInt32();
+ header.dyldInfo.lazy_bind_off = reader.ReadUInt32();
+ header.dyldInfo.lazy_bind_size = reader.ReadUInt32();
+ header.dyldInfo.export_off = reader.ReadUInt32();
+ header.dyldInfo.export_size = reader.ReadUInt32();
+ 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.ReadUInt32();
+ header.exportTrie.datasize = reader.ReadUInt32();
+ header.exportTriePresent = true;
+ break;
+ case LC_THREAD:
+ case LC_UNIXTHREAD:
+ /*while (reader.GetOffset() < nextOffset)
+ {
+
+ thread_command thread;
+ thread.flavor = reader.ReadUInt32();
+ thread.count = reader.ReadUInt32();
+ 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.ReadUInt32(), false});
+ (void)reader.ReadUInt32();
+ (void)reader.ReadUInt32();
+ //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.ReadUInt64(), false});
+ (void)reader.ReadUInt64();
+ (void)reader.ReadUInt64(); // 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.ReadUInt32();
+ 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.ReadUInt32();
+ header.buildVersion.minos = reader.ReadUInt32();
+ header.buildVersion.sdk = reader.ReadUInt32();
+ header.buildVersion.ntools = reader.ReadUInt32();
+ // 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.ReadUInt32();
+ uint32_t version = reader.ReadUInt32();
+ 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;
+ header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName);
+ }
+ }
+ catch (ReadException&)
+ {
+ return {};
+ }
+
+ return header;
+}
+
+// TODO: Support reading from .symbols file.
+// TODO: Replace view with address size?
+std::vector<CacheSymbol> SharedCacheMachOHeader::ReadSymbolTable(BinaryView& view, VirtualMemory& vm) const
+{
+ auto addressSize = view.GetAddressSize();
+ // NOTE: The symbol table will exist within the link edit segment, the table offsets are relative to the file not
+ // the linkedit segment.
+ uint64_t symbolsAddress = GetLinkEditFileBase() + symtab.symoff;
+ uint64_t stringsAddress = GetLinkEditFileBase() + symtab.stroff;
+
+ // TODO: This needs to be passed in as an optional argument.
+ // TODO: Sometimes symbol tables are shared and we have to offset into the table for a specific header.
+ // TODO: The "shared" symbol tables are stored in .symbols files.
+ int nlistStartIndex = 0;
+
+ std::vector<CacheSymbol> symbolList;
+ for (uint64_t i = 0; i < symtab.nsyms; i++)
+ {
+ uint64_t entryIndex = (nlistStartIndex + i);
+
+ nlist_64 nlist = {};
+ if (addressSize == 4)
+ {
+ // 32-bit DSC
+ struct nlist nlist32 = {};
+ vm.Read(&nlist, symbolsAddress + (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
+ vm.Read(&nlist, symbolsAddress + (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 >= symtab.strsize)
+ {
+ // TODO: where logger?
+ LogError(
+ "Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %u "
+ "for symbol table %x",
+ entryIndex, nlist.n_strx, symtab.strsize, symtab.stroff);
+ continue;
+ }
+
+ std::string symbolName = vm.ReadCString(stringsAddress + 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) < 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())
+ {
+ // TODO: Where logger?
+ LogError("Symbol %s at address %llx has unknown symbol type", symbolName.c_str(), symbolAddress);
+ continue;
+ }
+
+ std::optional<uint32_t> flags;
+ for (auto s : sections)
+ {
+ if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size)
+ {
+ // First section to contain the address we will use its flags.
+ flags = s.flags;
+ break;
+ }
+ }
+
+ if (symbolType != ExternalSymbol)
+ {
+ if (!flags.has_value())
+ {
+ // TODO: where logger?
+ LogError("Symbol %s at address %llx is not in any section", symbolName.c_str(), symbolAddress);
+ 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++;
+
+ CacheSymbol symbol;
+ symbol.address = symbolAddress;
+ symbol.name = std::move(symbolName);
+ symbol.type = symbolType.value();
+ symbolList.emplace_back(symbol);
+ }
+
+ return symbolList;
+}
+
+std::optional<CacheSymbol> SharedCacheMachOHeader::AddExportTerminalSymbol(
+ const std::string& symbolName, const uint8_t* current, const uint8_t* end) const
+{
+ uint64_t symbolFlags = readValidULEB128(current, end);
+ if (symbolFlags & EXPORT_SYMBOL_FLAGS_REEXPORT)
+ return std::nullopt;
+
+ uint64_t imageOffset = readValidULEB128(current, end);
+ uint64_t symbolAddress = textBase + imageOffset;
+ if (symbolName.empty() || symbolAddress == 0)
+ return std::nullopt;
+
+ // Tries to get the symbol type based off the section containing it.
+ auto sectionSymbolType = [&]() -> BNSymbolType {
+ uint32_t sectionFlags = 0;
+ for (const auto& section : sections)
+ {
+ if (symbolAddress >= section.addr && symbolAddress < section.addr + section.size)
+ {
+ // Take the flags from the first containing section.
+ sectionFlags = section.flags;
+ break;
+ }
+ }
+
+ // TODO: Is this enough to determine a function symbol?
+ // TODO: Might be the cause of https://github.com/Vector35/binaryninja-api/issues/6526
+ // Check the sections flags to see if we actually have a function symbol instead.
+ if (sectionFlags & S_ATTR_PURE_INSTRUCTIONS || sectionFlags & S_ATTR_SOME_INSTRUCTIONS)
+ return FunctionSymbol;
+
+ // By default, just return data symbol.
+ return DataSymbol;
+ };
+
+ switch (symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK)
+ {
+ case EXPORT_SYMBOL_FLAGS_KIND_REGULAR:
+ case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL:
+ return CacheSymbol(sectionSymbolType(), symbolAddress, symbolName);
+ case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE:
+ return CacheSymbol(DataSymbol, symbolAddress, symbolName);
+ default:
+ LogWarn("Unhandled export symbol kind: %llx", symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK);
+ return std::nullopt;
+ }
+}
+
+// TODO: This is like 90% of the runtime.
+bool SharedCacheMachOHeader::ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText,
+ const uint8_t* begin, const uint8_t* current, const uint8_t* end) const
+{
+ if (current >= end)
+ return false;
+
+ uint64_t terminalSize = readValidULEB128(current, end);
+ const uint8_t* child = current + terminalSize;
+
+ // The terminal is an export symbol.
+ if (terminalSize != 0)
+ {
+ // Add the export symbol is applicable.
+ auto symbol = AddExportTerminalSymbol(currentText, current, end);
+ if (symbol.has_value())
+ symbols.push_back(*symbol);
+ }
+
+ // TODO: Make this look better
+ current = child;
+ uint8_t childCount = *current++;
+ std::string childText = currentText;
+ for (uint8_t i = 0; i < childCount; ++i)
+ {
+ if (current >= end)
+ return false;
+ const auto it = std::find(current, end, 0);
+ childText.append(current, it);
+ current = it + 1;
+ if (current >= end)
+ return false;
+ const auto next = readValidULEB128(current, end);
+ if (next == 0)
+ return false;
+ if (!ProcessLinkEditTrie(symbols, childText, begin, begin + next, end))
+ return false;
+ childText.resize(currentText.size());
+ }
+
+ return true;
+}
+
+std::vector<CacheSymbol> SharedCacheMachOHeader::ReadExportSymbolTrie(VirtualMemory& vm) const
+{
+ if (exportTrie.datasize == 0)
+ return {};
+
+ uint64_t exportTrieAddress = GetLinkEditFileBase() + exportTrie.dataoff;
+ std::vector<CacheSymbol> symbols = {};
+ try
+ {
+ auto [begin, end] = vm.ReadSpan(exportTrieAddress, exportTrie.datasize);
+ ProcessLinkEditTrie(symbols, "", begin, begin, end);
+ }
+ catch (std::exception& e)
+ {
+ BNLogError("Failed to read Export Trie: %s", e.what());
+ }
+ return symbols;
+}
diff --git a/view/sharedcache/core/MachO.h b/view/sharedcache/core/MachO.h
new file mode 100644
index 00000000..37b82915
--- /dev/null
+++ b/view/sharedcache/core/MachO.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "VirtualMemory.h"
+
+// TODO: Including this adds a bunch of binary ninja specific stuff :ugh:
+#include "view/macho/machoview.h"
+
+struct CacheSymbol;
+
+struct SharedCacheMachOHeader
+{
+ uint64_t textBase = 0;
+ uint64_t loadCommandOffset = 0;
+ BinaryNinja::mach_header_64 ident;
+ // NOTE: This should never be empty.
+ std::string identifierPrefix;
+ std::string installName;
+
+ std::vector<std::pair<uint64_t, bool>> entryPoints;
+ std::vector<uint64_t> m_entryPoints; // list of entrypoints
+
+ BinaryNinja::symtab_command symtab;
+ BinaryNinja::dysymtab_command dysymtab;
+ BinaryNinja::dyld_info_command dyldInfo;
+ BinaryNinja::routines_command_64 routines64;
+ BinaryNinja::function_starts_command functionStarts;
+ std::vector<BinaryNinja::section_64> moduleInitSections;
+ BinaryNinja::linkedit_data_command exportTrie;
+ BinaryNinja::linkedit_data_command chainedFixups {};
+
+ uint64_t relocationBase;
+ // Section and program headers, internally use 64-bit form as it is a superset of 32-bit
+ std::vector<BinaryNinja::segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT
+ BinaryNinja::segment_command_64 linkeditSegment;
+ std::vector<BinaryNinja::section_64> sections;
+ std::vector<std::string> sectionNames;
+
+ std::vector<BinaryNinja::section_64> symbolStubSections;
+ std::vector<BinaryNinja::section_64> symbolPointerSections;
+
+ std::vector<std::string> dylibs;
+
+ BinaryNinja::build_version_command buildVersion;
+ std::vector<BinaryNinja::build_tool_version> buildToolVersions;
+
+ std::string exportTriePath;
+
+ bool linkeditPresent = false;
+ bool dysymPresent = false;
+ bool dyldInfoPresent = false;
+ bool exportTriePresent = false;
+ bool chainedFixupsPresent = false;
+ bool routinesPresent = false;
+ bool functionStartsPresent = false;
+ bool relocatable = false;
+
+ // The base address of the link edit file.
+ // Use this if you want to read offsets relative to the file containing the link edit segment.
+ uint64_t GetLinkEditFileBase() const { return linkeditSegment.vmaddr - linkeditSegment.fileoff; };
+
+ static std::optional<SharedCacheMachOHeader> ParseHeaderForAddress(
+ std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath);
+
+ // TODO: Replace view with address size?
+ std::vector<CacheSymbol> ReadSymbolTable(BinaryNinja::BinaryView& view, VirtualMemory& vm) const;
+
+ std::optional<CacheSymbol> AddExportTerminalSymbol(
+ const std::string& symbolName, const uint8_t* current, const uint8_t* end) const;
+
+ bool ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText, const uint8_t* begin,
+ const uint8_t* current, const uint8_t* end) const;
+
+ std::vector<CacheSymbol> ReadExportSymbolTrie(VirtualMemory& vm) const;
+
+ std::vector<uint64_t> ReadFunctionTable(VirtualMemory& vm) const;
+};
diff --git a/view/sharedcache/core/MachOProcessor.cpp b/view/sharedcache/core/MachOProcessor.cpp
new file mode 100644
index 00000000..be2e9007
--- /dev/null
+++ b/view/sharedcache/core/MachOProcessor.cpp
@@ -0,0 +1,320 @@
+#include "MachOProcessor.h"
+#include "SharedCache.h"
+
+using namespace BinaryNinja;
+
+SharedCacheMachOProcessor::SharedCacheMachOProcessor(Ref<BinaryView> view, std::shared_ptr<VirtualMemory> vm)
+{
+ m_view = view;
+ m_logger = new Logger("SharedCacheMachOProcessor", view->GetFile()->GetSessionId());
+ m_vm = std::move(vm);
+
+ // Adjust processor settings.
+ if (Ref<Settings> settings = m_view->GetLoadSettings(VIEW_NAME))
+ {
+ if (settings->Contains("loader.dsc.processFunctionStarts"))
+ m_applyFunctions = settings->Get<bool>("loader.dsc.processFunctionStarts", m_view);
+ }
+}
+
+void SharedCacheMachOProcessor::ApplyHeader(SharedCacheMachOHeader& header)
+{
+ // Add a section for the header itself.
+ std::string headerSection = fmt::format("{}::__macho_header", header.identifierPrefix);
+ // TODO: Support mach_header (non 64bit)
+ uint64_t headerSectionSize = sizeof(mach_header_64) + header.ident.sizeofcmds;
+ m_view->AddUserSection(headerSection, header.textBase, headerSectionSize, ReadOnlyDataSectionSemantics);
+
+ ApplyHeaderSections(header);
+ ApplyHeaderDataVariables(header);
+
+ if (header.linkeditPresent && m_vm->IsAddressMapped(header.linkeditSegment.vmaddr))
+ {
+ if (m_applyFunctions && header.functionStartsPresent)
+ {
+ auto targetPlatform = m_view->GetDefaultPlatform();
+ auto functions = header.ReadFunctionTable(*m_vm);
+ for (const auto& func : functions)
+ {
+ // TODO: Check to make sure the func exists in a loaded region?
+ // TODO: ^ this check existed prior so we should add it back.
+ m_view->AddFunctionForAnalysis(targetPlatform, func);
+ }
+ }
+
+ auto typeLib = m_view->GetTypeLibrary(header.installName);
+ m_view->BeginBulkModifySymbols();
+
+ // TODO: Why does this need to only happen in linkeditSegment?
+ // Apply symbols from symbol table.
+ if (header.symtab.symoff != 0)
+ {
+ // Mach-O View symtab processing with
+ // a ton of stuff cut out so it can work
+ // NOTE: This table is read relative to the link edit segment file base.
+ // TODO: Remove this and use the m_symbols in the cache?
+ const auto symbols = header.ReadSymbolTable(*m_view, *m_vm);
+ for (const auto& sym : symbols)
+ ApplySymbol(m_view, typeLib, sym.ToBNSymbol());
+ }
+
+ // Apply symbols from export trie.
+ if (header.exportTriePresent)
+ {
+ // NOTE: This table is read relative to the link edit segment file base.
+ // TODO: Remove this and use the m_symbols in the cache?
+ const auto exportSymbols = header.ReadExportSymbolTrie(*m_vm);
+ for (const auto& sym : exportSymbols)
+ ApplySymbol(m_view, typeLib, sym.ToBNSymbol());
+ }
+ m_view->EndBulkModifySymbols();
+ }
+}
+
+uint64_t SharedCacheMachOProcessor::ApplyHeaderSections(SharedCacheMachOHeader& header)
+{
+ auto initSection = [&](const section_64& section, const std::string& sectionName) {
+ if (!section.size)
+ return false;
+
+ std::string type;
+ BNSectionSemantics semantics = DefaultSectionSemantics;
+ switch (section.flags & 0xff)
+ {
+ case S_REGULAR:
+ if (section.flags & S_ATTR_PURE_INSTRUCTIONS)
+ {
+ type = "PURE_CODE";
+ semantics = ReadOnlyCodeSectionSemantics;
+ }
+ else if (section.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 (strncmp(section.sectname, "__text", sizeof(section.sectname)) == 0)
+ semantics = ReadOnlyCodeSectionSemantics;
+ if (strncmp(section.sectname, "__const", sizeof(section.sectname)) == 0)
+ semantics = ReadOnlyDataSectionSemantics;
+ if (strncmp(section.sectname, "__data", sizeof(section.sectname)) == 0)
+ semantics = ReadWriteDataSectionSemantics;
+ if (strncmp(section.segname, "__DATA_CONST", sizeof(section.segname)) == 0)
+ semantics = ReadOnlyDataSectionSemantics;
+ if (strncmp(section.segname, "__auth_got", sizeof(section.segname)) == 0)
+ semantics = ReadOnlyDataSectionSemantics;
+
+ m_view->AddUserSection(sectionName, section.addr, section.size, semantics, type, section.align);
+
+ return true;
+ };
+
+ uint64_t addedSections = 0;
+ for (size_t i = 0; i < header.sections.size() && i < header.sectionNames.size(); i++)
+ {
+ if (initSection(header.sections[i], header.sectionNames[i]))
+ addedSections++;
+ }
+ return addedSections;
+}
+
+void SharedCacheMachOProcessor::ApplyHeaderDataVariables(SharedCacheMachOHeader& header)
+{
+ // TODO: By using a binary reader we assume the sections have all been mapped.
+ // TODO: Maybe we should just use the virtual memory reader...
+ // TODO: We can define symbols and data variables even if there is no backing region FWIW
+ BinaryReader reader(m_view);
+ // TODO: Do we support non 64 bit header?
+ reader.Seek(header.textBase + sizeof(mach_header_64));
+
+ m_view->DefineDataVariable(header.textBase, Type::NamedType(m_view, QualifiedName("mach_header_64")));
+ m_view->DefineAutoSymbol(
+ new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding));
+
+ auto applyLoadCommand = [&](uint64_t cmdAddr, const load_command& load) {
+ switch (load.cmd)
+ {
+ case LC_SEGMENT:
+ {
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command")));
+ reader.SeekRelative(5 * 8);
+ size_t numSections = reader.Read32();
+ reader.SeekRelative(4);
+ for (size_t j = 0; j < numSections; j++)
+ {
+ m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section")));
+ auto sectionSymName =
+ fmt::format("__macho_section::{}_[{}]", header.identifierPrefix, std::to_string(j));
+ auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding);
+ m_view->DefineAutoSymbol(sectionSym);
+ reader.SeekRelative((8 * 8) + 4);
+ }
+ break;
+ }
+ case LC_SEGMENT_64:
+ {
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command_64")));
+ reader.SeekRelative(7 * 8);
+ size_t numSections = reader.Read32();
+ reader.SeekRelative(4);
+ for (size_t j = 0; j < numSections; j++)
+ {
+ m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section_64")));
+ auto sectionSymName =
+ fmt::format("__macho_section_64::{}_[{}]", header.identifierPrefix, std::to_string(j));
+ auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding);
+ m_view->DefineAutoSymbol(sectionSym);
+ reader.SeekRelative(10 * 8);
+ }
+ break;
+ }
+ case LC_SYMTAB:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("symtab")));
+ break;
+ case LC_DYSYMTAB:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dysymtab")));
+ break;
+ case LC_UUID:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_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:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dylib_command")));
+ if (load.cmdsize - 24 <= 150)
+ m_view->DefineDataVariable(
+ cmdAddr + 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:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("linkedit_data")));
+ break;
+ case LC_ENCRYPTION_INFO:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("encryption_info")));
+ break;
+ case LC_VERSION_MIN_MACOSX:
+ case LC_VERSION_MIN_IPHONEOS:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("version_min")));
+ break;
+ case LC_DYLD_INFO:
+ case LC_DYLD_INFO_ONLY:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dyld_info")));
+ break;
+ default:
+ m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("load_command")));
+ break;
+ }
+ };
+
+ try
+ {
+ for (size_t i = 0; i < header.ident.ncmds; i++)
+ {
+ load_command load {};
+ uint64_t curOffset = reader.GetOffset();
+ load.cmd = reader.Read32();
+ load.cmdsize = reader.Read32();
+
+ applyLoadCommand(curOffset, load);
+ m_view->DefineAutoSymbol(new Symbol(DataSymbol,
+ "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset,
+ LocalBinding));
+
+ uint64_t nextOffset = curOffset + load.cmdsize;
+ reader.Seek(nextOffset);
+ }
+ }
+ catch (ReadException&)
+ {
+ m_logger->LogError("Error when applying Mach-O header types at %llx", header.textBase);
+ }
+}
diff --git a/view/sharedcache/core/MachOProcessor.h b/view/sharedcache/core/MachOProcessor.h
new file mode 100644
index 00000000..0564a831
--- /dev/null
+++ b/view/sharedcache/core/MachOProcessor.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "MachO.h"
+
+// Process `SharedCacheMachOHeader`.
+class SharedCacheMachOProcessor
+{
+ BinaryNinja::Ref<BinaryNinja::BinaryView> m_view;
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+ std::shared_ptr<VirtualMemory> m_vm;
+
+ bool m_applyFunctions = true;
+
+public:
+ explicit SharedCacheMachOProcessor(
+ BinaryNinja::Ref<BinaryNinja::BinaryView> view, std::shared_ptr<VirtualMemory> vm);
+
+ // Initialize header information such as sections and symbols.
+ void ApplyHeader(SharedCacheMachOHeader& header);
+
+ uint64_t ApplyHeaderSections(SharedCacheMachOHeader& header);
+
+ void ApplyHeaderDataVariables(SharedCacheMachOHeader& header);
+};
diff --git a/view/sharedcache/core/MappedFile.cpp b/view/sharedcache/core/MappedFile.cpp
new file mode 100644
index 00000000..e0b805bc
--- /dev/null
+++ b/view/sharedcache/core/MappedFile.cpp
@@ -0,0 +1,193 @@
+#ifdef _MSC_VER
+ #include <windows.h>
+#else
+ #include <sys/mman.h>
+ #include <fcntl.h>
+ #include <cstdlib>
+ #include <sys/resource.h>
+#endif
+
+#include "MappedFile.h"
+#include "binaryninjaapi.h"
+
+#ifndef _MSC_VER
+uint64_t AdjustFileDescriptorLimit()
+{
+ // The soft file descriptor limit on Linux and Mac is a lot lower than
+ // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches
+ // have 60+ files which may not leave much headroom if a user opens
+ // more than one at a time. Attempt to increase the file descriptor
+ // limit to 1024, and limit ourselves to caching half of them as a
+ // memory vs performance trade-off (closing and re-opening a file
+ // requires parsing and applying the slide information again).
+ uint64_t maxFPLimit = 1024;
+
+ // check for BN_SHAREDCACHE_FP_MAX
+ // if it exists, set maxFPLimit to that value
+ if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env)
+ {
+ // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh.
+ maxFPLimit = strtoull(env, nullptr, 0);
+ if (maxFPLimit < 10)
+ {
+ BinaryNinja::LogWarn(
+ "BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis "
+ "on SharedCache Binaries.");
+ }
+ if (maxFPLimit == 0)
+ {
+ BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1");
+ maxFPLimit = 1;
+ }
+ }
+
+ rlimit rlim {};
+ getrlimit(RLIMIT_NOFILE, &rlim);
+ uint64_t previousLimit = rlim.rlim_cur;
+ uint64_t targetLimit = std::min(maxFPLimit, rlim.rlim_max);
+ if (rlim.rlim_cur < targetLimit)
+ {
+ rlim.rlim_cur = targetLimit;
+ if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
+ {
+ perror("setrlimit(RLIMIT_NOFILE)");
+ rlim.rlim_cur = previousLimit;
+ }
+ }
+
+ maxFPLimit = rlim.rlim_cur / 2;
+ return maxFPLimit;
+}
+
+MappedFile::~MappedFile()
+{
+ Unmap();
+ if (fd)
+ fclose(fd);
+}
+
+std::optional<MappedFile> MappedFile::OpenFile(const std::string& path)
+{
+ MappedFile file;
+ file._mmap = nullptr;
+ file.fd = fopen(path.c_str(), "r");
+ if (file.fd == nullptr)
+ return std::nullopt;
+
+ fseek(file.fd, 0L, SEEK_END);
+ file.len = ftell(file.fd);
+ fseek(file.fd, 0L, SEEK_SET);
+
+ return file;
+}
+
+MapStatus MappedFile::Map()
+{
+ if (_mmap)
+ return MapStatus::Success;
+
+ void* result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u);
+ if (result == MAP_FAILED)
+ {
+ BinaryNinja::LogError("mmap failed: %s", strerror(errno)); // Use errno to log the reason
+ return MapStatus::Error;
+ }
+ _mmap = static_cast<uint8_t*>(result);
+
+ return MapStatus::Success;
+}
+
+MapStatus MappedFile::Unmap()
+{
+ if (_mmap)
+ {
+ munmap(_mmap, len);
+ _mmap = nullptr;
+ }
+ return MapStatus::Success;
+}
+#else
+uint64_t AdjustFileDescriptorLimit()
+{
+ return 0x1000000;
+}
+
+MappedFile::~MappedFile()
+{
+ Unmap();
+ if (hFile)
+ CloseHandle(hFile);
+}
+
+std::optional<MappedFile> MappedFile::OpenFile(const std::string& path)
+{
+ MappedFile file;
+ file._mmap = nullptr;
+ file.hFile = CreateFile(path.c_str(), // file name
+ GENERIC_READ, // desired access (read-only)
+ FILE_SHARE_READ, // share mode
+ NULL, // security attributes
+ OPEN_EXISTING, // creation disposition
+ FILE_ATTRIBUTE_NORMAL, // flags and attributes
+ NULL); // template file
+
+ if (file.hFile == INVALID_HANDLE_VALUE)
+ return std::nullopt;
+
+ LARGE_INTEGER fileSize;
+ if (!GetFileSizeEx(file.hFile, &fileSize))
+ {
+ CloseHandle(file.hFile);
+ return std::nullopt;
+ }
+ file.len = static_cast<size_t>(fileSize.QuadPart);
+
+ return file;
+}
+
+MapStatus MappedFile::Map()
+{
+ if (_mmap)
+ return MapStatus::Success;
+
+ HANDLE hMapping = CreateFileMapping(hFile, // file handle
+ NULL, // security attributes
+ PAGE_WRITECOPY, // protection
+ 0, // maximum size (high-order DWORD)
+ 0, // maximum size (low-order DWORD)
+ NULL); // name of the mapping object
+
+ if (hMapping == NULL)
+ {
+ CloseHandle(hFile);
+ return MapStatus::Error;
+ }
+
+ _mmap = static_cast<uint8_t*>(MapViewOfFile(hMapping, // handle to the file mapping object
+ FILE_MAP_COPY, // desired access
+ 0, // file offset (high-order DWORD)
+ 0, // file offset (low-order DWORD)
+ 0)); // number of bytes to map (0 = entire file)
+
+ if (_mmap == nullptr)
+ {
+ CloseHandle(hMapping);
+ CloseHandle(hFile);
+ return MapStatus::Error;
+ }
+
+ CloseHandle(hMapping);
+ CloseHandle(hFile);
+
+ return MapStatus::Success;
+}
+
+MapStatus MappedFile::Unmap()
+{
+ if (_mmap)
+ {
+ UnmapViewOfFile(_mmap);
+ }
+ return MapStatus::Success;
+}
+#endif
diff --git a/view/sharedcache/core/MappedFile.h b/view/sharedcache/core/MappedFile.h
new file mode 100644
index 00000000..ced79f52
--- /dev/null
+++ b/view/sharedcache/core/MappedFile.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include <string>
+#include <cstdint>
+#include <cstdio>
+#include <optional>
+
+// Call this when initializing the plugin so that the process file descriptor limit is raised.
+uint64_t AdjustFileDescriptorLimit();
+
+enum MapStatus : int
+{
+ Success,
+ // TODO: Split this error out into more contextual errors.
+ Error,
+};
+
+struct MappedFile
+{
+ uint8_t* _mmap = nullptr;
+ size_t len = 0;
+
+#ifdef _MSC_VER
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+#else
+ FILE* fd = nullptr;
+#endif
+
+ MappedFile() = default;
+
+ ~MappedFile();
+
+ MappedFile(const MappedFile&) = delete;
+
+ MappedFile& operator=(const MappedFile&) = delete;
+
+ MappedFile(MappedFile&& other) noexcept : _mmap(other._mmap), len(other.len)
+ {
+#ifdef _MSC_VER
+ hFile = other.hFile;
+ // Don't close the hFile in the move.
+ other.hFile = nullptr;
+#else
+ fd = other.fd;
+ // Don't close the fd in the move.
+ other.fd = nullptr;
+#endif
+ other._mmap = nullptr;
+ }
+
+ // I hate C++
+ MappedFile& operator=(MappedFile&& other) noexcept
+ {
+ if (this != &other)
+ {
+ Unmap();
+#ifdef _MSC_VER
+ if (hFile != nullptr)
+ {
+ CloseHandle(hFile);
+ }
+ hFile = other.hFile;
+ // Don't close the hFile in the move.
+ other.hFile = nullptr;
+#else
+ if (fd != nullptr)
+ {
+ fclose(fd);
+ }
+ fd = other.fd;
+ // Don't close the fd in the move.
+ other.fd = nullptr;
+#endif
+ len = other.len;
+ _mmap = other._mmap;
+ other._mmap = nullptr;
+ }
+ return *this;
+ }
+
+ static std::optional<MappedFile> OpenFile(const std::string& path);
+
+ MapStatus Map();
+
+ MapStatus Unmap();
+};
diff --git a/view/sharedcache/core/MappedFileAccessor.cpp b/view/sharedcache/core/MappedFileAccessor.cpp
new file mode 100644
index 00000000..ddd6ce9b
--- /dev/null
+++ b/view/sharedcache/core/MappedFileAccessor.cpp
@@ -0,0 +1,101 @@
+#include "MappedFileAccessor.h"
+
+
+std::shared_ptr<MappedFileAccessor> MappedFileAccessor::Open(const std::string& filePath)
+{
+ auto file = MappedFile::OpenFile(filePath);
+ if (!file.has_value())
+ return nullptr;
+ if (file->Map() != MapStatus::Success)
+ return nullptr;
+ return std::make_shared<MappedFileAccessor>(std::move(*file));
+}
+
+// TODO: Will obviously not work on 32bit binaries, need to make WritePointer64 and 32 equiv.
+void MappedFileAccessor::WritePointer(size_t address, size_t pointer)
+{
+ m_dirty = true;
+ *reinterpret_cast<size_t*>(&m_file._mmap[address]) = pointer;
+}
+
+std::string MappedFileAccessor::ReadNullTermString(size_t address, size_t maxLength) const
+{
+ if (address > Length())
+ return "";
+ auto start = m_file._mmap + address;
+ auto endLen = (maxLength > 0) ? maxLength : m_file.len;
+ auto end = start + endLen;
+ auto nul = std::find(start, end, 0);
+ return {start, nul};
+}
+
+uint8_t MappedFileAccessor::ReadUInt8(size_t address)
+{
+ return Read<uint8_t>(address);
+}
+
+int8_t MappedFileAccessor::ReadInt8(size_t address)
+{
+ return Read<int8_t>(address);
+}
+
+uint16_t MappedFileAccessor::ReadUInt16(size_t address)
+{
+ return Read<uint16_t>(address);
+}
+
+int16_t MappedFileAccessor::ReadInt16(size_t address)
+{
+ return Read<int16_t>(address);
+}
+
+
+uint32_t MappedFileAccessor::ReadUInt32(size_t address)
+{
+ return Read<uint32_t>(address);
+}
+
+int32_t MappedFileAccessor::ReadInt32(size_t address)
+{
+ return Read<int32_t>(address);
+}
+
+uint64_t MappedFileAccessor::ReadUInt64(size_t address)
+{
+ return Read<uint64_t>(address);
+}
+
+int64_t MappedFileAccessor::ReadInt64(size_t address)
+{
+ return Read<int64_t>(address);
+}
+
+BinaryNinja::DataBuffer MappedFileAccessor::ReadBuffer(size_t addr, size_t length)
+{
+ if (addr + length > Length())
+ throw UnmappedAccessException(addr + length, Length());
+ return {&m_file._mmap[addr], length};
+}
+
+std::pair<const uint8_t*, const uint8_t*> MappedFileAccessor::ReadSpan(size_t addr, size_t length)
+{
+ if (addr + length > Length())
+ throw UnmappedAccessException(addr + length, Length());
+ const uint8_t* data = &m_file._mmap[addr];
+ return {data, data + length};
+}
+
+void MappedFileAccessor::Read(void* dest, size_t addr, size_t length) const
+{
+ if (addr + length > Length())
+ throw UnmappedAccessException(addr + length, Length());
+ memcpy(dest, &m_file._mmap[addr], length);
+}
+
+template <typename T>
+T MappedFileAccessor::Read(size_t address)
+{
+ T result;
+ Read(&result, address, sizeof(T));
+ return result;
+}
diff --git a/view/sharedcache/core/MappedFileAccessor.h b/view/sharedcache/core/MappedFileAccessor.h
new file mode 100644
index 00000000..7b01cb0d
--- /dev/null
+++ b/view/sharedcache/core/MappedFileAccessor.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "binaryninjaapi.h"
+#include "MappedFile.h"
+
+#include <cstdint>
+#include <list>
+
+class UnmappedAccessException : public std::exception
+{
+ uint64_t m_address;
+ uint64_t m_fileLen;
+
+public:
+ explicit UnmappedAccessException(uint64_t address, uint64_t fileLength) : m_address(address), m_fileLen(fileLength)
+ {}
+
+ virtual const char* what() const throw()
+ {
+ thread_local std::string message;
+ message =
+ fmt::format("Tried to access unmapped address {0:x} for file with length of {0:x}", m_address, m_fileLen);
+ return message.c_str();
+ }
+};
+
+// std::enable_shared_from_this allows weak pointers to "revive" the shared pointer in the FileAccessorCache.
+class MappedFileAccessor : public std::enable_shared_from_this<MappedFileAccessor>
+{
+ MappedFile m_file;
+ bool m_dirty = false;
+
+public:
+ explicit MappedFileAccessor(MappedFile file) : m_file(std::move(file)) {}
+
+ ~MappedFileAccessor() = default;
+
+ MappedFileAccessor(const MappedFileAccessor&) = delete;
+
+ MappedFileAccessor& operator=(const MappedFileAccessor&) = delete;
+
+ MappedFileAccessor(MappedFileAccessor&&) noexcept = default;
+
+ MappedFileAccessor& operator=(MappedFileAccessor&&) noexcept = default;
+
+ static std::shared_ptr<MappedFileAccessor> Open(const std::string& filePath);
+
+ size_t Length() const { return m_file.len; };
+
+ void* Data() const { return m_file._mmap; };
+
+ bool IsDirty() const { return m_dirty; }
+
+ /**
+ * Writes to files are implemented for performance reasons and should be treated with utmost care
+ *
+ * They _MAY_ disappear as _soon_ as you release the lock on the file.
+ * They may also NOT disappear for the lifetime of the application.
+ *
+ * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical
+ * scenario.
+ *
+ * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be
+ * used for anything else.
+ *
+ * \param address The address to write the pointer to
+ * \param pointer The pointer to be written
+ */
+ void WritePointer(size_t address, size_t pointer);
+
+ std::string ReadNullTermString(size_t address, size_t maxLength = -1) const;
+
+ uint8_t ReadUInt8(size_t address);
+
+ int8_t ReadInt8(size_t address);
+
+ uint16_t ReadUInt16(size_t address);
+
+ int16_t ReadInt16(size_t address);
+
+ uint32_t ReadUInt32(size_t address);
+
+ int32_t ReadInt32(size_t address);
+
+ uint64_t ReadUInt64(size_t address);
+
+ int64_t ReadInt64(size_t address);
+
+ BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length);
+
+ // Returns a range of pointers within the mapped memory region corresponding to
+ // {addr, length}.
+ // WARNING: The pointers returned by this method is only valid for the lifetime
+ // of this file accessor.
+ // TODO: This should use std::span<const uint8_t> once the minimum supported
+ // C++ version supports it.
+ std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length);
+
+ void Read(void* dest, size_t addr, size_t length) const;
+
+ template <typename T>
+ T Read(size_t address);
+};
diff --git a/view/sharedcache/core/MetadataSerializable.cpp b/view/sharedcache/core/MetadataSerializable.cpp
deleted file mode 100644
index eaf82c8b..00000000
--- a/view/sharedcache/core/MetadataSerializable.cpp
+++ /dev/null
@@ -1,552 +0,0 @@
-#include "MetadataSerializable.hpp"
-
-namespace SharedCacheCore {
-
-void Serialize(SerializationContext& context, std::string_view str) {
- context.writer.String(str.data(), str.length());
-}
-
-void Serialize(SerializationContext& context, const char* value) {
- Serialize(context, std::string_view(value));
-}
-
-void Serialize(SerializationContext& context, bool b)
-{
- context.writer.Bool(b);
-}
-
-void Serialize(SerializationContext& context, int8_t value) {
- context.writer.Int(value);
-}
-
-void Serialize(SerializationContext& context, uint8_t value) {
- context.writer.Uint(value);
-}
-
-void Serialize(SerializationContext& context, int16_t value) {
- context.writer.Int(value);
-}
-
-void Serialize(SerializationContext& context, uint16_t value) {
- context.writer.Uint(value);
-}
-
-void Serialize(SerializationContext& context, int32_t value) {
- context.writer.Int(value);
-}
-
-void Serialize(SerializationContext& context, uint32_t value) {
- context.writer.Uint(value);
-}
-
-void Serialize(SerializationContext& context, int64_t value) {
- context.writer.Int64(value);
-}
-
-void Serialize(SerializationContext& context, uint64_t value) {
- context.writer.Uint64(value);
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, bool& b) {
- b = context.doc[name.data()].GetBool();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b)
-{
- b = static_cast<uint8_t>(context.doc[name.data()].GetUint64());
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b)
-{
- b = static_cast<uint16_t>(context.doc[name.data()].GetUint64());
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b)
-{
- b = static_cast<uint32_t>(context.doc[name.data()].GetUint64());
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b)
-{
- b = context.doc[name.data()].GetUint64();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b)
-{
- b = context.doc[name.data()].GetInt64();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b)
-{
- b = context.doc[name.data()].GetInt64();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b)
-{
- b = context.doc[name.data()].GetInt();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b)
-{
- b = context.doc[name.data()].GetInt64();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::string& b)
-{
- b = context.doc[name.data()].GetString();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetUint64();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- std::string key = i.GetArray()[0].GetString();
- std::unordered_map<uint64_t, uint64_t> memArray;
- for (auto& member : i.GetArray()[1].GetArray())
- {
- memArray[member.GetArray()[0].GetUint64()] = member.GetArray()[1].GetUint64();
- }
- b[key] = memArray;
- }
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetString();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- b.emplace_back(i.GetString());
-}
-
-// Note: This flattens the pair into [first, second.first, second.second] with no nested arrays.
-void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value)
-{
- context.writer.StartArray();
- Serialize(context, value.first);
- Serialize(context, value.second.first);
- Serialize(context, value.second.second);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- std::pair<uint64_t, std::pair<uint64_t, uint64_t>> j;
- j.first = i.GetArray()[0].GetUint64();
- j.second.first = i.GetArray()[1].GetUint64();
- j.second.second = i.GetArray()[2].GetUint64();
- b.push_back(j);
- }
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- std::pair<uint64_t, bool> j;
- j.first = i.GetArray()[0].GetUint64();
- j.second = i.GetArray()[1].GetBool();
- b.push_back(j);
- }
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- b.push_back(i.GetUint64());
- }
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetUint64();
- }
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b)
-{
- for (auto& i : context.doc[name.data()].GetArray())
- {
- std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>> j;
- j.first = i.GetArray()[0].GetUint64();
- for (auto& k : i.GetArray()[1].GetArray())
- {
- j.second.push_back({k.GetArray()[0].GetUint64(), k.GetArray()[1].GetString()});
- }
- b.push_back(j);
- }
-}
-
-void Serialize(SerializationContext& context, const mach_header_64& value) {
- context.writer.StartArray();
- Serialize(context, value.magic);
- // cputype and cpusubtype are signed but were serialized as unsigned in
- // v4.2 (metadata version 2). We continue serializing them as unsigned
- // so we don't need to bump the metadata version.
- Serialize(context, static_cast<uint32_t>(value.cputype));
- Serialize(context, static_cast<uint32_t>(value.cpusubtype));
- Serialize(context, value.filetype);
- Serialize(context, value.ncmds);
- Serialize(context, value.sizeofcmds);
- Serialize(context, value.flags);
- Serialize(context, value.reserved);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, mach_header_64& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.magic = bArr[0].GetUint();
- b.cputype = bArr[1].GetUint();
- b.cpusubtype = bArr[2].GetUint();
- b.filetype = bArr[3].GetUint();
- b.ncmds = bArr[4].GetUint();
- b.sizeofcmds = bArr[5].GetUint();
- b.flags = bArr[6].GetUint();
- b.reserved = bArr[7].GetUint();
-}
-
-void Serialize(SerializationContext& context, const symtab_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.symoff);
- Serialize(context, value.nsyms);
- Serialize(context, value.stroff);
- Serialize(context, value.strsize);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, symtab_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.symoff = bArr[2].GetUint();
- b.nsyms = bArr[3].GetUint();
- b.stroff = bArr[4].GetUint();
- b.strsize = bArr[5].GetUint();
-}
-
-void Serialize(SerializationContext& context, const dysymtab_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.ilocalsym);
- Serialize(context, value.nlocalsym);
- Serialize(context, value.iextdefsym);
- Serialize(context, value.nextdefsym);
- Serialize(context, value.iundefsym);
- Serialize(context, value.nundefsym);
- Serialize(context, value.tocoff);
- Serialize(context, value.ntoc);
- Serialize(context, value.modtaboff);
- Serialize(context, value.nmodtab);
- Serialize(context, value.extrefsymoff);
- Serialize(context, value.nextrefsyms);
- Serialize(context, value.indirectsymoff);
- Serialize(context, value.nindirectsyms);
- Serialize(context, value.extreloff);
- Serialize(context, value.nextrel);
- Serialize(context, value.locreloff);
- Serialize(context, value.nlocrel);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, dysymtab_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.ilocalsym = bArr[2].GetUint();
- b.nlocalsym = bArr[3].GetUint();
- b.iextdefsym = bArr[4].GetUint();
- b.nextdefsym = bArr[5].GetUint();
- b.iundefsym = bArr[6].GetUint();
- b.nundefsym = bArr[7].GetUint();
- b.tocoff = bArr[8].GetUint();
- b.ntoc = bArr[9].GetUint();
- b.modtaboff = bArr[10].GetUint();
- b.nmodtab = bArr[11].GetUint();
- b.extrefsymoff = bArr[12].GetUint();
- b.nextrefsyms = bArr[13].GetUint();
- b.indirectsymoff = bArr[14].GetUint();
- b.nindirectsyms = bArr[15].GetUint();
- b.extreloff = bArr[16].GetUint();
- b.nextrel = bArr[17].GetUint();
- b.locreloff = bArr[18].GetUint();
- b.nlocrel = bArr[19].GetUint();
-}
-
-void Serialize(SerializationContext& context, const dyld_info_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.rebase_off);
- Serialize(context, value.rebase_size);
- Serialize(context, value.bind_off);
- Serialize(context, value.bind_size);
- Serialize(context, value.weak_bind_off);
- Serialize(context, value.weak_bind_size);
- Serialize(context, value.lazy_bind_off);
- Serialize(context, value.lazy_bind_size);
- Serialize(context, value.export_off);
- Serialize(context, value.export_size);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, dyld_info_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.rebase_off = bArr[2].GetUint();
- b.rebase_size = bArr[3].GetUint();
- b.bind_off = bArr[4].GetUint();
- b.bind_size = bArr[5].GetUint();
- b.weak_bind_off = bArr[6].GetUint();
- b.weak_bind_size = bArr[7].GetUint();
- b.lazy_bind_off = bArr[8].GetUint();
- b.lazy_bind_size = bArr[9].GetUint();
- b.export_off = bArr[10].GetUint();
- b.export_size = bArr[11].GetUint();
-}
-
-void Serialize(SerializationContext& context, const routines_command_64& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.init_address);
- Serialize(context, value.init_module);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, routines_command_64& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- // Because we might open databases that had not previously serialized this, we must allow
- // an empty array, otherwise we will crash!
- if (bArr.Size() < 4)
- return;
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.init_address = bArr[2].GetUint64();
- b.init_module = bArr[3].GetUint64();
-}
-
-void Serialize(SerializationContext& context, const function_starts_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.funcoff);
- Serialize(context, value.funcsize);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, function_starts_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.funcoff = bArr[2].GetUint();
- b.funcsize = bArr[3].GetUint();
-}
-
-void Serialize(SerializationContext& context, const section_64& value)
-{
- context.writer.StartArray();
-
- std::string_view sectname(value.sectname, 16);
- std::string_view segname(value.segname, 16);
-
- Serialize(context, sectname.substr(0, sectname.find('\0')));
- Serialize(context, segname.substr(0, segname.find('\0')));
- Serialize(context, value.addr);
- Serialize(context, value.size);
- Serialize(context, value.offset);
- Serialize(context, value.align);
- Serialize(context, value.reloff);
- Serialize(context, value.nreloc);
- Serialize(context, value.flags);
- Serialize(context, value.reserved1);
- Serialize(context, value.reserved2);
- Serialize(context, value.reserved3);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<section_64>& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- for (auto& s : bArr)
- {
- section_64 sec;
- auto s2 = s.GetArray();
- std::string sectNameStr = s2[0].GetString();
- memset(sec.sectname, 0, 16);
- memcpy(sec.sectname, sectNameStr.c_str(), sectNameStr.size());
- std::string segNameStr = s2[1].GetString();
- memset(sec.segname, 0, 16);
- memcpy(sec.segname, segNameStr.c_str(), segNameStr.size());
- sec.addr = s2[2].GetUint64();
- sec.size = s2[3].GetUint64();
- sec.offset = s2[4].GetUint();
- sec.align = s2[5].GetUint();
- sec.reloff = s2[6].GetUint();
- sec.nreloc = s2[7].GetUint();
- sec.flags = s2[8].GetUint();
- sec.reserved1 = s2[9].GetUint();
- sec.reserved2 = s2[10].GetUint();
- sec.reserved3 = s2[11].GetUint();
- b.push_back(std::move(sec));
- }
-}
-
-void Serialize(SerializationContext& context, const linkedit_data_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.dataoff);
- Serialize(context, value.datasize);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, linkedit_data_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.dataoff = bArr[2].GetUint();
- b.datasize = bArr[3].GetUint();
-}
-
-void Serialize(SerializationContext& context, const segment_command_64& value)
-{
- context.writer.StartArray();
- std::string_view segname(value.segname, 16);
- Serialize(context, segname.substr(0, segname.find('\0')));
- Serialize(context, value.vmaddr);
- Serialize(context, value.vmsize);
- Serialize(context, value.fileoff);
- Serialize(context, value.filesize);
- Serialize(context, value.maxprot);
- Serialize(context, value.initprot);
- Serialize(context, value.nsects);
- Serialize(context, value.flags);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, segment_command_64& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- std::string segNameStr = bArr[0].GetString();
- memset(b.segname, 0, 16);
- memcpy(b.segname, segNameStr.c_str(), segNameStr.size());
- b.vmaddr = bArr[1].GetUint64();
- b.vmsize = bArr[2].GetUint64();
- b.fileoff = bArr[3].GetUint64();
- b.filesize = bArr[4].GetUint64();
- b.maxprot = bArr[5].GetUint();
- b.initprot = bArr[6].GetUint();
- b.nsects = bArr[7].GetUint();
- b.flags = bArr[8].GetUint();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<segment_command_64>& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- for (auto& s : bArr)
- {
- segment_command_64 sec;
- auto s2 = s.GetArray();
- std::string segNameStr = s2[0].GetString();
- memset(sec.segname, 0, 16);
- memcpy(sec.segname, segNameStr.c_str(), segNameStr.size());
- sec.vmaddr = s2[1].GetUint64();
- sec.vmsize = s2[2].GetUint64();
- sec.fileoff = s2[3].GetUint64();
- sec.filesize = s2[4].GetUint64();
- sec.maxprot = s2[5].GetUint();
- sec.initprot = s2[6].GetUint();
- sec.nsects = s2[7].GetUint();
- sec.flags = s2[8].GetUint();
- b.push_back(std::move(sec));
- }
-}
-
-void Serialize(SerializationContext& context, const build_version_command& value)
-{
- context.writer.StartArray();
- Serialize(context, value.cmd);
- Serialize(context, value.cmdsize);
- Serialize(context, value.platform);
- Serialize(context, value.minos);
- Serialize(context, value.sdk);
- Serialize(context, value.ntools);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, build_version_command& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- b.cmd = bArr[0].GetUint();
- b.cmdsize = bArr[1].GetUint();
- b.platform = bArr[2].GetUint();
- b.minos = bArr[3].GetUint();
- b.sdk = bArr[4].GetUint();
- b.ntools = bArr[5].GetUint();
-}
-
-void Serialize(SerializationContext& context, const build_tool_version& value)
-{
- context.writer.StartArray();
- Serialize(context, value.tool);
- Serialize(context, value.version);
- context.writer.EndArray();
-}
-
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<build_tool_version>& b)
-{
- auto bArr = context.doc[name.data()].GetArray();
- for (auto& s : bArr)
- {
- build_tool_version sec;
- auto s2 = s.GetArray();
- sec.tool = s2[0].GetUint();
- sec.version = s2[1].GetUint();
- b.push_back(sec);
- }
-}
-
-} // namespace SharedCacheCore
diff --git a/view/sharedcache/core/MetadataSerializable.hpp b/view/sharedcache/core/MetadataSerializable.hpp
deleted file mode 100644
index 24d8c3fd..00000000
--- a/view/sharedcache/core/MetadataSerializable.hpp
+++ /dev/null
@@ -1,254 +0,0 @@
-//
-// Created by kat on 5/31/23.
-//
-
-/*
- * Welcome to, this file.
- *
- * This is a metadata serialization helper.
- *
- * Have you ever wished turning a complex datastructure into a Metadata object was as easy in C++ as it is in python?
- * Do you like macros and templates?
- *
- * Great news.
- *
- * Implement these on your `public MetadataSerializable<T>` subclass:
- * ```
- class MyClass : public MetadataSerializable<MyClass> {
- void Store(SerializationContext& context) const {
- MSS(m_someVariable);
- MSS(m_someOtherVariable);
- }
- void Load(DeserializationContext& context) {
- MSL(m_someVariable);
- MSL(m_someOtherVariable);
- }
- }
- ```
- * Then, you can turn your object into a Metadata object with `AsMetadata()`, and load it back with
- `LoadFromMetadata()`.
- *
- * Serialized fields will be automatically repopulated.
- *
- * Other ser/deser formats (rapidjson objects, strings) also exist. You can use these to achieve nesting, but probably
- avoid that.
- * */
-
-#ifndef SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP
-#define SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP
-
-#include <cassert>
-#include "binaryninjaapi.h"
-#include "rapidjson/document.h"
-#include "rapidjson/stringbuffer.h"
-#include "rapidjson/prettywriter.h"
-#include "../api/sharedcachecore.h"
-#include "view/macho/machoview.h"
-
-using namespace BinaryNinja;
-
-namespace SharedCacheCore {
-
-#define MSS(name) context.store(#name, name)
-#define MSS_CAST(name, type) context.store(#name, (type) name)
-#define MSS_SUBCLASS(name) Serialize(context, #name, name)
-#define MSL(name) name = context.load<decltype(name)>(#name)
-#define MSL_CAST(name, storedType, type) name = (type)context.load<storedType>(#name)
-
-struct DeserializationContext;
-
-struct SerializationContext {
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer;
-
- SerializationContext() : buffer(), writer(buffer) {
- }
-
- template <typename T>
- void store(std::string_view x, const T& y)
- {
- Serialize(*this, x, y);
- }
-};
-
-struct DeserializationContext {
- rapidjson::Document doc;
-
- template <typename T>
- T load(std::string_view x)
- {
- T value;
- Deserialize(*this, x, value);
- return value;
- }
-};
-
-template <typename Derived, typename LoadResult = Derived>
-class MetadataSerializable {
-public:
- template <typename... Args>
- std::string AsString(Args&&... args) const {
- SerializationContext context;
- Store(context, std::forward<Args>(args)...);
-
- return context.buffer.GetString();
- }
-
- static LoadResult LoadFromString(const std::string& s) {
- DeserializationContext context;
- [[maybe_unused]] rapidjson::ParseResult result = context.doc.Parse(s.c_str());
- assert(result);
- return Derived::Load(context);
- }
-
- static LoadResult LoadFromValue(rapidjson::Value& s) {
- DeserializationContext context;
- context.doc.CopyFrom(s, context.doc.GetAllocator());
- return Derived::Load(context);
- }
-
- template <typename... Args>
- Ref<Metadata> AsMetadata(Args&&... args) const {
- return new Metadata(AsString(std::forward<Args>(args)...));
- }
-
- template <typename... Args>
- void Store(SerializationContext& context, Args&&... args) const {
- context.writer.StartObject();
- AsDerived().Store(context, std::forward<Args>(args)...);
- context.writer.EndObject();
- }
-
-private:
- const Derived& AsDerived() const { return static_cast<const Derived&>(*this); }
- Derived& AsDerived() { return static_cast<Derived&>(*this); }
-};
-
-// The functions below are not part of the FFI API, but are exported so they can be shared with sharedcacheui.
-
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view str);
-
-template <typename T>
-inline void Serialize(SerializationContext& context, const MetadataSerializable<T>& value)
-{
- value.Store(context);
-}
-
-template <typename T>
-inline void Serialize(SerializationContext& context, std::string_view name, const T& value)
-{
- Serialize(context, name);
- Serialize(context, value);
-}
-
-template <typename First, typename Second>
-void Serialize(SerializationContext& context, const std::pair<First, Second>& value)
-{
- context.writer.StartArray();
- Serialize(context, value.first);
- Serialize(context, value.second);
- context.writer.EndArray();
-}
-
-template <typename K, typename V, typename L>
-void Serialize(SerializationContext& context, const std::map<K, V, L>& value)
-{
- context.writer.StartArray();
- for (auto& pair : value)
- {
- Serialize(context, pair);
- }
- context.writer.EndArray();
-}
-
-template <typename K, typename V>
-void Serialize(SerializationContext& context, const std::unordered_map<K, V>& value)
-{
- context.writer.StartArray();
- for (auto& pair : value)
- {
- Serialize(context, pair);
- }
- context.writer.EndArray();
-}
-
-template <typename T>
-void Serialize(SerializationContext& context, const std::vector<T>& values)
-{
- context.writer.StartArray();
- for (const auto& value : values)
- {
- Serialize(context, value);
- }
- context.writer.EndArray();
-}
-
-template <typename T>
-void Serialize(SerializationContext& context, const std::optional<T>& value)
-{
- if (value.has_value())
- Serialize(context, *value);
- else
- context.writer.Null();
-}
-
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const char*);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, bool b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, bool& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint8_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint16_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint32_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint64_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int8_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int16_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int32_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int64_t b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::string& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const mach_header_64& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, mach_header_64& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const symtab_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, symtab_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dysymtab_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dysymtab_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dyld_info_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dyld_info_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const routines_command_64& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, routines_command_64& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const function_starts_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, function_starts_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const section_64& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<section_64>& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const linkedit_data_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, linkedit_data_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const segment_command_64& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, segment_command_64& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<segment_command_64>& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_version_command& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, build_version_command& b);
-SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_tool_version& b);
-SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<build_tool_version>& b);
-
-} // namespace SharedCacheCore
-
-#endif // SHAREDCACHE_METADATASERIALIZABLE_HPP
diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp
index c763b321..bb483542 100644
--- a/view/sharedcache/core/ObjC.cpp
+++ b/view/sharedcache/core/ObjC.cpp
@@ -1,95 +1,98 @@
#include "ObjC.h"
+#include "SharedCacheController.h"
+
using namespace BinaryNinja;
using namespace DSCObjC;
-using namespace SharedCacheCore;
-DSCObjCReader::DSCObjCReader(SharedCache* cache, size_t addressSize) :
- m_reader(VMReader(cache->GetVMMap())), m_addressSize(addressSize)
-{
-}
+SharedCacheObjCReader::SharedCacheObjCReader(VirtualMemoryReader reader) : m_reader(reader) {}
-void DSCObjCReader::Read(void* dest, size_t len)
+void SharedCacheObjCReader::Read(void* dest, size_t len)
{
m_reader.Read(dest, len);
}
-std::string DSCObjCReader::ReadCString()
+std::string SharedCacheObjCReader::ReadCString(size_t maxLength)
{
- return m_reader.ReadCString(m_reader.GetOffset());
+ return m_reader.ReadCString(m_reader.GetOffset(), maxLength);
}
-uint8_t DSCObjCReader::Read8()
+uint8_t SharedCacheObjCReader::Read8()
{
- return m_reader.Read8();
+ return m_reader.ReadUInt8();
}
-uint16_t DSCObjCReader::Read16()
+uint16_t SharedCacheObjCReader::Read16()
{
- return m_reader.Read16();
+ return m_reader.ReadUInt16();
}
-uint32_t DSCObjCReader::Read32()
+uint32_t SharedCacheObjCReader::Read32()
{
- return m_reader.Read32();
+ return m_reader.ReadUInt32();
}
-uint64_t DSCObjCReader::Read64()
+uint64_t SharedCacheObjCReader::Read64()
{
- return m_reader.Read64();
+ return m_reader.ReadUInt64();
}
-int8_t DSCObjCReader::ReadS8()
+int8_t SharedCacheObjCReader::ReadS8()
{
- return m_reader.ReadS8();
+ return m_reader.ReadInt8();
}
-int16_t DSCObjCReader::ReadS16()
+int16_t SharedCacheObjCReader::ReadS16()
{
- return m_reader.ReadS16();
+ return m_reader.ReadInt16();
}
-int32_t DSCObjCReader::ReadS32()
+int32_t SharedCacheObjCReader::ReadS32()
{
- return m_reader.ReadS32();
+ return m_reader.ReadInt32();
}
-int64_t DSCObjCReader::ReadS64()
+int64_t SharedCacheObjCReader::ReadS64()
{
- return m_reader.ReadS64();
+ return m_reader.ReadInt64();
}
-uint64_t DSCObjCReader::ReadPointer()
+uint64_t SharedCacheObjCReader::ReadPointer()
{
return m_reader.ReadPointer();
}
-uint64_t DSCObjCReader::GetOffset() const
+uint64_t SharedCacheObjCReader::GetOffset() const
{
return m_reader.GetOffset();
}
-void DSCObjCReader::Seek(uint64_t offset)
+void SharedCacheObjCReader::Seek(uint64_t offset)
{
m_reader.Seek(offset);
}
-void DSCObjCReader::SeekRelative(int64_t offset)
+void SharedCacheObjCReader::SeekRelative(int64_t offset)
{
m_reader.SeekRelative(offset);
}
-VMReader& DSCObjCReader::GetVMReader()
+VirtualMemoryReader& SharedCacheObjCReader::GetVMReader()
{
return m_reader;
}
-std::shared_ptr<ObjCReader> DSCObjCProcessor::GetReader()
+std::shared_ptr<ObjCReader> SharedCacheObjCProcessor::GetReader()
{
- return std::make_shared<DSCObjCReader>(m_cache, m_data->GetAddressSize());
+ const auto controller = DSC::SharedCacheController::FromView(*m_data);
+ // TODO: This should never happen.
+ if (!controller)
+ throw std::runtime_error("SharedCacheController not found for SharedCacheObjCProcessor::GetReader!");
+ auto reader = VirtualMemoryReader(controller->GetCache().GetVirtualMemory(), m_data->GetAddressSize());
+ return std::make_shared<SharedCacheObjCReader>(reader);
}
-void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth)
+void SharedCacheObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth)
{
if (m_customRelativeMethodSelectorBase.has_value())
{
@@ -103,14 +106,48 @@ void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth)
}
}
-uint64_t DSCObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader)
+std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader)
{
- auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(static_cast<DSCObjCReader*>(reader)->GetVMReader());
- m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr;
- return objCRelativeMethodsBaseAddr;
+ // Find the first primary entry and use that header to read the obj opt header.
+ // Don't ask me why this is done like this...
+ std::optional<dyld_cache_header> primaryCacheHeader = std::nullopt;
+ for (const auto& [_, entry] : cache.GetEntries())
+ {
+ if (entry.GetType() == CacheEntryType::Primary)
+ {
+ primaryCacheHeader = entry.GetHeader();
+ break;
+ }
+ }
+
+ // Check if we even have the obj opt stuff.
+ if (!primaryCacheHeader || !primaryCacheHeader->objcOptsOffset || !primaryCacheHeader->objcOptsSize)
+ return std::nullopt;
+
+ ObjCOptimizationHeader header = {};
+ // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior.
+ // TODO: The base address is the lowest region, however is that going to be where the primary cache header resides?
+ reader.Read(&header, cache.GetBaseAddress() + primaryCacheHeader->objcOptsOffset, sizeof(ObjCOptimizationHeader));
+
+ return header;
}
-DSCObjCProcessor::DSCObjCProcessor(BinaryView* data, SharedCache* cache, bool isBackedByDatabase) :
- ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true), m_cache(cache)
+uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader)
{
+ // Try and retrieve the base address of the selector stuff.
+ if (const auto controller = DSC::SharedCacheController::FromView(*m_data))
+ {
+ auto baseAddress = controller->GetCache().GetBaseAddress();
+ auto dangerReader = dynamic_cast<SharedCacheObjCReader*>(reader)->GetVMReader();
+ if (const auto header = GetObjCOptimizationHeader(controller->GetCache(), dangerReader); header.has_value())
+ {
+ m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset;
+ }
+ }
+
+ return m_customRelativeMethodSelectorBase.value_or(0);
}
+
+SharedCacheObjCProcessor::SharedCacheObjCProcessor(BinaryView* data, bool isBackedByDatabase) :
+ ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true)
+{}
diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h
index 13790364..6576564b 100644
--- a/view/sharedcache/core/ObjC.h
+++ b/view/sharedcache/core/ObjC.h
@@ -1,52 +1,71 @@
-//
-// Created by kat on 5/23/23.
-//
-
-#ifndef SHAREDCACHE_OBJC_H
-#define SHAREDCACHE_OBJC_H
+#pragma once
#include <binaryninjaapi.h>
#include <objectivec/objc.h>
#include "SharedCache.h"
+struct ObjCOptimizationHeader
+{
+ uint32_t version;
+ uint32_t flags;
+ uint64_t headerInfoROCacheOffset;
+ uint64_t headerInfoRWCacheOffset;
+ uint64_t selectorHashTableCacheOffset;
+ uint64_t classHashTableCacheOffset;
+ uint64_t protocolHashTableCacheOffset;
+ uint64_t relativeMethodSelectorBaseAddressOffset;
+};
+
namespace DSCObjC {
- class DSCObjCReader : public ObjCReader {
- private:
- VMReader m_reader;
- size_t m_addressSize;
+ class SharedCacheObjCReader : public BinaryNinja::ObjCReader
+ {
+ VirtualMemoryReader m_reader;
public:
void Read(void* dest, size_t len) override;
- std::string ReadCString() override;
+
+ std::string ReadCString(size_t maxLength = -1) override;
+
uint8_t Read8() override;
+
uint16_t Read16() override;
+
uint32_t Read32() override;
+
uint64_t Read64() override;
+
int8_t ReadS8() override;
+
int16_t ReadS16() override;
+
int32_t ReadS32() override;
+
int64_t ReadS64() override;
+
uint64_t ReadPointer() override;
+
uint64_t GetOffset() const override;
+
void Seek(uint64_t offset) override;
+
void SeekRelative(int64_t offset) override;
- VMReader& GetVMReader();
+ VirtualMemoryReader& GetVMReader();
- DSCObjCReader(SharedCacheCore::SharedCache* cache, size_t addressSize);
+ SharedCacheObjCReader(VirtualMemoryReader reader);
};
- class DSCObjCProcessor : public ObjCProcessor {
+ class SharedCacheObjCProcessor : public BinaryNinja::ObjCProcessor
+ {
std::optional<uint64_t> m_customRelativeMethodSelectorBase = std::nullopt;
- SharedCacheCore::SharedCache* m_cache;
- std::shared_ptr<ObjCReader> GetReader() override;
- void GetRelativeMethod(ObjCReader* reader, method_t& meth) override;
-
+ std::shared_ptr<BinaryNinja::ObjCReader> GetReader() override;
+
+ void GetRelativeMethod(BinaryNinja::ObjCReader* reader, BinaryNinja::method_t& meth) override;
+
public:
- DSCObjCProcessor(BinaryView* data, SharedCacheCore::SharedCache* cache, bool isBackedByDatabase);
-
- uint64_t GetObjCRelativeMethodBaseAddress(ObjCReader* reader) override;
+ SharedCacheObjCProcessor(BinaryNinja::BinaryView* data, bool isBackedByDatabase);
+
+ uint64_t GetObjCRelativeMethodBaseAddress(BinaryNinja::ObjCReader* reader) override;
};
-}
-#endif //SHAREDCACHE_OBJC_H
+} // namespace DSCObjC
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;
+}
diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h
index fadcdd3a..54ec9dfd 100644
--- a/view/sharedcache/core/SharedCache.h
+++ b/view/sharedcache/core/SharedCache.h
@@ -1,705 +1,278 @@
-//
-// Created by kat on 5/19/23.
-//
+#pragma once
-#ifndef SHAREDCACHE_SHAREDCACHE_H
-#define SHAREDCACHE_SHAREDCACHE_H
+#include <vector>
+#include <Dyld.h>
-#include <binaryninjaapi.h>
-#include <cstdint>
-#include <memory>
-#include <mutex>
-#include <unordered_map>
-#include "VM.h"
-#include "view/macho/machoview.h"
-#include "MetadataSerializable.hpp"
-#include "../api/sharedcachecore.h"
+#include "binaryninjaapi.h"
+#include "MachO.h"
+#include "VirtualMemory.h"
-#include <optional>
+class SharedCache;
-DECLARE_SHAREDCACHE_API_OBJECT(BNSharedCache, SharedCache);
+struct CacheSymbol
+{
+ BNSymbolType type;
+ uint64_t address;
+ std::string name;
-namespace SharedCacheCore {
+ CacheSymbol() = default;
- enum DSCViewState
- {
- DSCViewStateUnloaded,
- DSCViewStateLoaded,
- DSCViewStateLoadedWithImages,
- };
-
- struct MemoryRegion : public MetadataSerializable<MemoryRegion>
- {
- enum class Type
- {
- Image,
- StubIsland,
- DyldData,
- NonImage,
- };
-
- std::string prettyName;
- uint64_t start;
- uint64_t size;
- // Start address of the image this region belongs to.
- // 0 if the region does not belong to any image.
- uint64_t imageStart = 0;
- BNSegmentFlag flags;
- Type type;
+ CacheSymbol(BNSymbolType type, uint64_t address, std::string name) :
+ type(type), address(address), name(std::move(name))
+ {}
+ ~CacheSymbol() = default;
- AddressRange AsAddressRange() const
- {
- return {start, start + size};
- }
+ CacheSymbol(const CacheSymbol& other) = default;
- void Store(SerializationContext& context) const
- {
- MSS(prettyName);
- MSS(start);
- MSS(size);
- MSS(imageStart);
- MSS_CAST(flags, uint64_t);
- MSS_CAST(type, uint8_t);
- }
+ CacheSymbol& operator=(const CacheSymbol& other) = default;
- static MemoryRegion Load(DeserializationContext& context)
- {
- MemoryRegion region;
- region.MSL(prettyName);
- region.MSL(start);
- region.MSL(size);
- region.MSL_CAST(flags, uint64_t, BNSegmentFlag);
- region.MSL_CAST(type, uint8_t, Type);
- if (context.doc.HasMember("imageStart"))
- region.MSL(imageStart);
+ CacheSymbol(CacheSymbol&& other) noexcept = default;
- return region;
- }
- };
+ CacheSymbol& operator=(CacheSymbol&& other) noexcept = default;
- struct CacheImage : public MetadataSerializable<CacheImage> {
- std::string installName;
- uint64_t headerLocation;
- // Start addresses of the memory regions in this image.
- std::vector<uint64_t> regionStarts;
+ // NOTE: you should really only call this when adding the symbol to the view.
+ BinaryNinja::Ref<BinaryNinja::Symbol> ToBNSymbol() const;
+};
- void Store(SerializationContext& context) const;
- static CacheImage Load(DeserializationContext& context);
- };
+enum class CacheRegionType
+{
+ Image,
+ StubIsland,
+ DyldData,
+ NonImage,
+};
- #if defined(__GNUC__) || defined(__clang__)
- #define PACKED_STRUCT __attribute__((packed))
- #else
- #define PACKED_STRUCT
- #endif
+struct CacheRegion
+{
+ CacheRegionType type;
+ std::string name;
+ uint64_t start;
+ uint64_t size;
+ // Associate this region with this image, this makes it easier to identify what image owns this region.
+ std::optional<uint64_t> imageStart;
+ BNSegmentFlag flags;
- #if defined(_MSC_VER)
- #pragma pack(push, 1)
- #else
+ CacheRegion() = default;
- #endif
+ ~CacheRegion() = default;
- struct PACKED_STRUCT dyld_cache_mapping_info
- {
- uint64_t address;
- uint64_t size;
- uint64_t fileOffset;
- uint32_t maxProt;
- uint32_t initProt;
- };
-
- struct BackingCache : public MetadataSerializable<BackingCache> {
- std::string path;
- BNBackingCacheType cacheType = BackingCacheTypeSecondary;
- std::vector<dyld_cache_mapping_info> mappings;
+ CacheRegion(const CacheRegion& other) = default;
- void Store(SerializationContext& context) const;
- static BackingCache Load(DeserializationContext& context);
- };
+ CacheRegion& operator=(const CacheRegion& other) = default;
- struct LoadedMapping
- {
- std::shared_ptr<MMappedFileAccessor> backingFile;
- dyld_cache_mapping_info mappingInfo;
- };
+ CacheRegion(CacheRegion&& other) noexcept = default;
- struct dyld_cache_slide_info
- {
- uint32_t version;
- uint32_t toc_offset;
- uint32_t toc_count;
- uint32_t entries_offset;
- uint32_t entries_count;
- uint32_t entries_size;
- // uint16_t toc[toc_count];
- // entrybitmap entries[entries_count];
- };
+ CacheRegion& operator=(CacheRegion&& other) noexcept = default;
- struct dyld_cache_slide_info_entry {
- uint8_t bits[4096/(8*4)]; // 128-byte bitmap
- };
+ AddressRange AsAddressRange() const { return {start, start + size}; }
- struct PACKED_STRUCT dyld_cache_mapping_and_slide_info
+ BNSectionSemantics SectionSemanticsForRegion() const
{
- uint64_t address;
- uint64_t size;
- uint64_t fileOffset;
- uint64_t slideInfoFileOffset;
- uint64_t slideInfoFileSize;
- uint64_t flags;
- uint32_t maxProt;
- uint32_t initProt;
- };
+ if ((flags & SegmentExecutable) && (flags & SegmentDenyWrite))
+ return ReadOnlyCodeSectionSemantics;
- struct PACKED_STRUCT dyld_cache_slide_info_v2
- {
- uint32_t version;
- uint32_t page_size;
- uint32_t page_starts_offset;
- uint32_t page_starts_count;
- uint32_t page_extras_offset;
- uint32_t page_extras_count;
- uint64_t delta_mask;
- uint64_t value_add;
- };
- #define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array)
- #define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing
- #define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page
-
- #define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
-
- struct PACKED_STRUCT dyld_cache_slide_info_v3
- {
- uint32_t version;
- uint32_t page_size;
- uint32_t page_starts_count;
- uint32_t pad_i_guess;
- uint64_t auth_value_add;
- };
-
-
- // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
- struct dyld_chained_ptr_arm64e_shared_cache_rebase
- {
- uint64_t runtimeOffset : 34, // offset from the start of the shared cache
- high8 : 8,
- unused : 10,
- next : 11, // 8-byte stide
- auth : 1; // == 0
- };
+ if (flags & SegmentExecutable)
+ return DefaultSectionSemantics;
- // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
- struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase
- {
- uint64_t runtimeOffset : 34, // offset from the start of the shared cache
- diversity : 16,
- addrDiv : 1,
- keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA
- next : 11, // 8-byte stide
- auth : 1; // == 1
- };
+ if (flags & SegmentDenyWrite)
+ return ReadOnlyDataSectionSemantics;
- // dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now.
+ return ReadWriteDataSectionSemantics;
+ }
+};
- #define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing
+// Represents a single image and its associated memory regions.
+struct CacheImage
+{
+ uint64_t headerAddress;
+ std::string path;
+ // A list to the start of memory regions associated with the image.
+ // This lets us load all regions for a given image easily.
+ std::vector<uint64_t> regionStarts;
+ std::shared_ptr<SharedCacheMachOHeader> header;
- struct PACKED_STRUCT dyld_cache_slide_info5
- {
- uint32_t version; // currently 5
- uint32_t page_size; // currently 4096 (may also be 16384)
- uint32_t page_starts_count;
- uint32_t pad; // padding to ensure the value below is on an 8-byte boundary
- uint64_t value_add;
- // uint16_t page_starts[/* page_starts_count */];
- };
+ CacheImage() = default;
+ ~CacheImage() = default;
- struct PACKED_STRUCT dyld_cache_image_info
- {
- uint64_t address;
- uint64_t modTime;
- uint64_t inode;
- uint32_t pathFileOffset;
- uint32_t pad;
- };
+ CacheImage(const CacheImage& other) = default;
- union dyld_cache_slide_pointer5
- {
- uint64_t raw;
- struct dyld_chained_ptr_arm64e_shared_cache_rebase regular;
- struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth;
- };
+ CacheImage& operator=(const CacheImage& other) = default;
+ CacheImage(CacheImage&& other) noexcept = default;
- struct PACKED_STRUCT dyld_cache_local_symbols_info
- {
- uint32_t nlistOffset; // offset into this chunk of nlist entries
- uint32_t nlistCount; // count of nlist entries
- uint32_t stringsOffset; // offset into this chunk of string pool
- uint32_t stringsSize; // byte count of string pool
- uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry
- uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array
- };
+ CacheImage& operator=(CacheImage&& other) noexcept = default;
- struct PACKED_STRUCT dyld_cache_local_symbols_entry
- {
- uint32_t dylibOffset; // offset in cache file of start of dylib
- uint32_t nlistStartIndex; // start index of locals for this dylib
- uint32_t nlistCount; // number of local symbols for this dylib
- };
+ // Get the file name from the path.
+ std::string GetName() const { return BaseFileName(path); }
- struct PACKED_STRUCT dyld_cache_local_symbols_entry_64
- {
- uint64_t dylibOffset; // offset in cache buffer of start of dylib
- uint32_t nlistStartIndex; // start index of locals for this dylib
- uint32_t nlistCount; // number of local symbols for this dylib
- };
+ // Get the names of the dependencies.
+ std::vector<std::string> GetDependencies() const;
+};
- union dyld_cache_slide_pointer3
- {
- uint64_t raw;
- struct
- {
- uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2;
- } plain;
+enum class CacheEntryType
+{
+ Primary,
+ Secondary,
+ // A special entry that holds symbols for other cache entries.
+ // TODO: We dont need this i think.
+ Symbols,
+ // If the type is marked as this then all mappings will be marked as such.
+ DyldData,
+ // A single stub mapping file.
+ Stub,
+};
- struct
- {
- uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2,
- offsetToNextPointer : 11, unused : 1,
- authenticated : 1; // = 1;
- } auth;
- };
+// Describes a single files cache information
+class CacheEntry
+{
+ CacheEntryType m_type;
+ std::string m_filePath;
+ std::string m_fileName;
+ dyld_cache_header m_header {};
+ // Mappings tell us _where_ to map the regions within the flat address space.
+ // Without this we wouldn't know where the entry is supposed to exist in the address space.
+ std::vector<dyld_cache_mapping_info> m_mappings {};
+ // TODO: We really should remove this methinks.
+ // TODO: Storing this here is basically useless? IDK
+ // Mapping of image path to image info, used within ProcessImagesAndRegions to add them to the cache.
+ std::unordered_map<std::string, dyld_cache_image_info> m_images {};
+public:
+ 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);
- struct PACKED_STRUCT dyld_cache_header
- {
- char magic[16]; // e.g. "dyld_v0 i386"
- uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info
- uint32_t mappingCount; // number of dyld_cache_mapping_info entries
- uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing
- uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing
- uint64_t dyldBaseAddress; // base address of dyld when cache was built
- uint64_t codeSignatureOffset; // file offset of code signature blob
- uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file)
- uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info
- uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info
- uint64_t localSymbolsOffset; // file offset of where local symbols are stored
- uint64_t localSymbolsSize; // size of local symbols information
- uint8_t uuid[16]; // unique value for each shared cache file
- uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache
- uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses
- uint32_t branchPoolsCount; // number of uint64_t entries
- uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache
- uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache
- uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info
- uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries
- uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info
- uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info
- uint64_t otherImageGroupAddrUnused; // unused
- uint64_t otherImageGroupSizeUnused; // unused
- uint64_t progClosuresAddr; // (unslid) address of list of program launch closures
- uint64_t progClosuresSize; // size of list of program launch closures
- uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures
- uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures
- uint32_t platform; // platform number (macOS=1, etc)
- uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion
- dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid
- simulator : 1, // for simulator of specified platform
- locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache
- builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides
- padding : 20; // TBD
- uint64_t sharedRegionStart; // base load address of cache if not slid
- uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any
- uint64_t maxSlide; // runtime slide of cache can be between zero and this value
- uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache
- uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache
- uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs
- uint64_t dylibsTrieSize; // size of trie of cached dylib paths
- uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures
- uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures
- uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures
- uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures
- uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info
- uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries
- uint64_t dylibsPBLStateArrayAddrUnused; // unused
- uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs
- uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program
- uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program
- uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet
- uint32_t programTrieSize;
- uint32_t osVersion; // OS Version of dylibs in this cache for the main platform
- uint32_t altPlatform; // e.g. iOSMac on macOS
- uint32_t altOsVersion; // e.g. 14.0 for iOSMac
- uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header
- uint64_t swiftOptsSize; // size of Swift optimizations header
- uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry
- uint32_t subCacheArrayCount; // number of subCache entries
- uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols
- uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data
- uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region
- uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data
- uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region
- uint32_t imagesOffset; // file offset to first dyld_cache_image_info
- uint32_t imagesCount; // number of dyld_cache_image_info entries
- uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)
- uint32_t padding2;
- uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header
- uint64_t objcOptsSize; // size of ObjC optimizations header
- uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection
- uint64_t cacheAtlasSize; // size of embedded cache atlas
- uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header
- uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data
- uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info
- uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries
- };
+ CacheEntry() = default;
- struct PACKED_STRUCT dyld_subcache_entry
- {
- char uuid[16];
- uint64_t address;
- };
+ CacheEntry(const CacheEntry&) = default;
- struct PACKED_STRUCT dyld_subcache_entry2
- {
- char uuid[16];
- uint64_t address;
- char fileExtension[32];
- };
+ CacheEntry(CacheEntry&&) = default;
- struct ObjCOptimizationHeader
- {
- uint32_t version;
- uint32_t flags;
- uint64_t headerInfoROCacheOffset;
- uint64_t headerInfoRWCacheOffset;
- uint64_t selectorHashTableCacheOffset;
- uint64_t classHashTableCacheOffset;
- uint64_t protocolHashTableCacheOffset;
- uint64_t relativeMethodSelectorBaseAddressOffset;
- };
+ CacheEntry& operator=(CacheEntry&&) = default;
- #if defined(_MSC_VER)
- #pragma pack(pop)
- #else
+ // Construct a cache entry from the file on disk.
+ // TODO: Seperate this out a bit more.
+ static std::optional<CacheEntry> FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type);
- #endif
-
- struct SharedCacheMachOHeader : public MetadataSerializable<SharedCacheMachOHeader>
- {
- uint64_t textBase = 0;
- uint64_t loadCommandOffset = 0;
- mach_header_64 ident;
- std::string identifierPrefix;
- std::string installName;
+ // TODO: From Project file?
- std::vector<std::pair<uint64_t, bool>> entryPoints;
- std::vector<uint64_t> m_entryPoints; // list of entrypoints
+ WeakFileAccessor GetAccessor() const;
- symtab_command symtab;
- dysymtab_command dysymtab;
- dyld_info_command dyldInfo;
- routines_command_64 routines64;
- function_starts_command functionStarts;
- std::vector<section_64> moduleInitSections;
- linkedit_data_command exportTrie;
- linkedit_data_command chainedFixups {};
+ // Get the headers virtual address.
+ // This is useful if you need to read relative to the start of the entry file.
+ std::optional<uint64_t> GetHeaderAddress() const;
- uint64_t relocationBase;
- // Section and program headers, internally use 64-bit form as it is a superset of 32-bit
- std::vector<segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT
- segment_command_64 linkeditSegment;
- std::vector<section_64> sections;
- std::vector<std::string> sectionNames;
+ // Get the mapped address for a given file offset.
+ // Ex. passing 0x0 will retrieve the mapped address for the start of the file (i.e. the header)
+ std::optional<uint64_t> GetMappedAddress(uint64_t fileOffset) const;
- std::vector<section_64> symbolStubSections;
- std::vector<section_64> symbolPointerSections;
+ CacheEntryType GetType() const { return m_type; }
+ // Ex. "/myuser/mypath/dyld_shared_cache_arm64e"
+ const std::string& GetFilePath() const { return m_filePath; }
+ // Ex. "dyld_shared_cache_arm64e"
+ const std::string GetFileName() const { return m_fileName; }
+ const dyld_cache_header& GetHeader() const { return m_header; }
+ const std::vector<dyld_cache_mapping_info>& GetMappings() const { return m_mappings; }
+ const std::unordered_map<std::string, dyld_cache_image_info>& GetImages() const { return m_images; }
+};
- std::vector<std::string> dylibs;
+// The ID for a given CacheEntry, use this instead of passing a pointer around to avoid complexity :V
+typedef uint32_t CacheEntryId;
- build_version_command buildVersion;
- std::vector<build_tool_version> buildToolVersions;
+// TODO: Add a "ViewCache" that keeps track of what has been added to the view.
- std::string exportTriePath;
+// The C in DSC.
+// This represents the entire cache, all regions and images are visible from here.
+// This is the dump for all the information, and what the workflow activities and the UI want.
+// Creating this is expensive, both in actual processing and just copying, so we only generate this
+// once every time the database is open.
+class SharedCache
+{
+ uint64_t m_addressSize = 8;
+ uint64_t m_baseAddress = 0;
+ // TODO: Figure out when to lock the mutex on this shit lmfao
+ // The shared cache can own the virtual memory, this is fine...
+ std::shared_ptr<VirtualMemory> m_vm;
+ std::unordered_map<CacheEntryId, CacheEntry> m_entries {};
+ // This information is used in tandem with the cache images to load memory regions into the binary view.
+ AddressRangeMap<CacheRegion> m_regions {};
+ // Describes the images of the cache.
+ std::unordered_map<uint64_t, CacheImage> m_images {};
+ // All the symbols for this cache. Both mapped and unmapped (not in the view).
+ std::unordered_map<uint64_t, CacheSymbol> m_symbols {};
- bool linkeditPresent = false;
- bool dysymPresent = false;
- bool dyldInfoPresent = false;
- bool exportTriePresent = false;
- bool chainedFixupsPresent = false;
- bool routinesPresent = false;
- bool functionStartsPresent = false;
- bool relocatable = false;
+ bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info);
- void Store(SerializationContext& context) const {
- MSS(textBase);
- MSS(loadCommandOffset);
- MSS_SUBCLASS(ident);
- MSS(identifierPrefix);
- MSS(installName);
- MSS(entryPoints);
- MSS(m_entryPoints);
- MSS_SUBCLASS(symtab);
- MSS_SUBCLASS(dysymtab);
- MSS_SUBCLASS(dyldInfo);
- MSS_SUBCLASS(routines64);
- MSS_SUBCLASS(functionStarts);
- MSS_SUBCLASS(moduleInitSections);
- MSS_SUBCLASS(exportTrie);
- MSS_SUBCLASS(chainedFixups);
- MSS(relocationBase);
- MSS_SUBCLASS(segments);
- MSS_SUBCLASS(linkeditSegment);
- MSS_SUBCLASS(sections);
- MSS(sectionNames);
- MSS_SUBCLASS(symbolStubSections);
- MSS_SUBCLASS(symbolPointerSections);
- MSS(dylibs);
- MSS_SUBCLASS(buildVersion);
- MSS_SUBCLASS(buildToolVersions);
- MSS(exportTriePath);
- MSS(linkeditPresent);
- MSS(dysymPresent);
- MSS(dyldInfoPresent);
- MSS(exportTriePresent);
- MSS(chainedFixupsPresent);
- MSS(routinesPresent);
- MSS(functionStartsPresent);
- MSS(relocatable);
- }
+ // Add a region known not to overlap with another, otherwise use AddRegion.
+ // returns whether the region was inserted.
+ bool AddNonOverlappingRegion(CacheRegion region);
- static SharedCacheMachOHeader Load(DeserializationContext& context) {
- SharedCacheMachOHeader header;
- header.MSL(textBase);
- header.MSL(loadCommandOffset);
- header.MSL(ident);
- header.MSL(identifierPrefix);
- header.MSL(installName);
- header.MSL(entryPoints);
- header.MSL(m_entryPoints);
- header.MSL(symtab);
- header.MSL(dysymtab);
- header.MSL(dyldInfo);
- header.MSL(routines64);
- header.MSL(functionStarts);
- header.MSL(moduleInitSections);
- header.MSL(exportTrie);
- header.MSL(chainedFixups);
- header.MSL(relocationBase);
- header.MSL(segments);
- header.MSL(linkeditSegment);
- header.MSL(sections);
- header.MSL(sectionNames);
- header.MSL(symbolStubSections);
- header.MSL(symbolPointerSections);
- header.MSL(dylibs);
- header.MSL(buildVersion);
- header.MSL(buildToolVersions);
- header.MSL(exportTriePath);
- header.MSL(linkeditPresent);
- header.MSL(dysymPresent);
- header.MSL(dyldInfoPresent);
- header.MSL(exportTriePresent);
- header.MSL(chainedFixupsPresent);
- header.MSL(routinesPresent);
- header.MSL(functionStartsPresent);
- header.MSL(relocatable);
- return header;
- }
- };
+public:
+ explicit SharedCache(uint64_t addressSize);
- struct MappingInfo
- {
- dyld_cache_mapping_info mappingInfo;
- uint32_t slideInfoVersion;
- dyld_cache_slide_info_v2 slideInfoV2;
- dyld_cache_slide_info_v3 slideInfoV3;
- dyld_cache_slide_info5 slideInfoV5;
- };
+ uint64_t GetBaseAddress() const { return m_baseAddress; }
+ std::shared_ptr<VirtualMemory> GetVirtualMemory() { return m_vm; }
+ const std::unordered_map<CacheEntryId, CacheEntry>& GetEntries() const { return m_entries; }
+ const AddressRangeMap<CacheRegion>& GetRegions() const { return m_regions; }
+ const std::unordered_map<uint64_t, CacheImage>& GetImages() const { return m_images; }
+ const std::unordered_map<uint64_t, CacheSymbol>& GetSymbols() const { return m_symbols; }
+ void AddImage(CacheImage image);
- class ScopedVMMapSession;
+ // Add a region that may overlap with another.
+ void AddRegion(CacheRegion region);
- static std::atomic<uint64_t> sharedCacheReferences = 0;
+ void AddSymbol(CacheSymbol symbol);
- class SharedCache
- {
- IMPLEMENT_SHAREDCACHE_API_OBJECT(BNSharedCache);
+ void AddSymbols(std::vector<CacheSymbol> symbols);
- std::atomic<int> m_refs = 0;
+ // Adds the cache entry and populates the virtual memory using the mapping information.
+ // After being added the entry is read only, there is nothing that can modify it.
+ CacheEntryId AddEntry(CacheEntry entry);
- public:
- virtual void AddRef() { m_refs.fetch_add(1); }
+ void ProcessEntryImages(const CacheEntry& entry);
- virtual void Release()
- {
- // undo actions will lock a file lock we hold and then wait for main thread
- // so we need to release the ref later.
- WorkerPriorityEnqueue([this]() {
- if (m_refs.fetch_sub(1) == 1)
- delete this;
- });
- }
+ void ProcessEntryRegions(const CacheEntry& entry);
- virtual void AddAPIRef() { AddRef(); }
+ void ProcessEntrySlideInfo(const CacheEntry& entry);
- virtual void ReleaseAPIRef() { Release(); }
+ std::optional<CacheEntry> GetEntryContaining(uint64_t address) const;
- public:
- enum SharedCacheFormat
- {
- RegularCacheFormat,
- SplitCacheFormat,
- LargeCacheFormat,
- iOS16CacheFormat,
- };
+ std::optional<CacheEntry> GetEntryWithImage(const CacheImage& image) const;
- struct CacheInfo;
- struct ModifiedState;
+ std::optional<CacheRegion> GetRegionAt(uint64_t address) const;
- struct ViewSpecificState;
+ std::optional<CacheRegion> GetRegionContaining(uint64_t address) const;
+ std::optional<CacheImage> GetImageAt(uint64_t address) const;
- private:
- Ref<Logger> m_logger;
- /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */
-
- // State that is initialized during `PerformInitialLoad` and does
- // not change thereafter.
- std::shared_ptr<const CacheInfo> m_cacheInfo;
-
- // Protects member variables below.
- mutable std::mutex m_mutex;
-
- // State that has been modified since this instance was created
- // or last saved to the view-specific state.
- // To get an accurate view of the current state, both these modifications
- // and the view-specific state must be consulted.
- std::unique_ptr<ModifiedState> m_modifiedState;
-
- // Serialized once by PerformInitialLoad and available after m_viewState == Loaded
- bool m_metadataValid = false;
-
- /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */
-
- /* API VIEW START */
- BinaryNinja::Ref<BinaryNinja::BinaryView> m_dscView;
- /* API VIEW END */
-
- std::shared_ptr<ViewSpecificState> m_viewSpecificState;
-
- private:
- void PerformInitialLoad(std::lock_guard<std::mutex>&);
- bool DeserializeFromRawView(std::lock_guard<std::mutex>&);
-
- public:
- std::shared_ptr<VM> GetVMMap();
- std::shared_ptr<VM> GetVMMap(const CacheInfo& staticState);
-
- static SharedCache* GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView);
- static uint64_t FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView);
- bool SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&);
- bool SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&);
-
- static void ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t baseAddress, Ref<Logger> logger);
- std::optional<uint64_t> GetImageStart(std::string_view installName);
- const SharedCacheMachOHeader* HeaderForAddress(uint64_t);
- bool LoadImageWithInstallName(std::string installName, bool skipObjC);
- bool LoadSectionAtAddress(uint64_t address);
- bool LoadImageContainingAddress(uint64_t address, bool skipObjC);
- void ProcessObjCSectionsForImageWithInstallName(std::string installName);
- void ProcessAllObjCSections();
- std::string NameForAddress(uint64_t address);
- std::string ImageNameForAddress(uint64_t address);
- std::vector<std::string> GetAvailableImages();
-
- std::vector<const MemoryRegion*> GetMappedRegions() const;
- bool IsMemoryMapped(uint64_t address);
-
- std::unordered_map<std::string, std::vector<Ref<Symbol>>> LoadAllSymbolsAndWait();
-
- const std::unordered_map<std::string, uint64_t>& AllImageStarts() const;
- const std::unordered_map<uint64_t, SharedCacheMachOHeader>& AllImageHeaders() const;
-
- std::string SerializedImageHeaderForAddress(uint64_t address);
- std::string SerializedImageHeaderForName(std::string name);
-
- void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis);
-
- const std::vector<BackingCache>& BackingCaches() const;
-
- DSCViewState ViewState() const;
-
- explicit SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> rawView);
- virtual ~SharedCache();
-
- uint64_t GetObjCRelativeMethodBaseAddress(const VMReader& reader) const;
-
-private:
- std::optional<SharedCacheMachOHeader> LoadHeaderForAddress(
- std::shared_ptr<VM> vm, uint64_t address, std::string installName);
- void InitializeHeader(
- std::lock_guard<std::mutex>&, Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header,
- std::vector<const MemoryRegion*> regionsToLoad);
- void 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);
- std::vector<Ref<Symbol>> ParseExportTrie(
- std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header);
- std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExportListForHeader(std::lock_guard<std::mutex>&, const SharedCacheMachOHeader& header,
- std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList = nullptr);
- std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const;
- void ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header,
- uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex = 0);
- void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol);
-
- void ProcessAllObjCSections(std::lock_guard<std::mutex>&);
- bool LoadImageWithInstallName(std::lock_guard<std::mutex>&, std::string installName, bool skipObjC);
-
- bool MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const;
- void SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region);
- bool MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const;
- void SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region);
-
- Ref<TypeLibrary> TypeLibraryForImage(const std::string& installName);
-
- std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(VMReader reader) const;
-
- std::shared_ptr<MMappedFileAccessor> MapFile(const std::string& path);
- static std::shared_ptr<MMappedFileAccessor> MapFileWithoutApplyingSlide(const std::string& path);
- };
-
- class SharedCacheMetadata
- {
- public:
- static std::optional<SharedCacheMetadata> LoadFromView(BinaryView*);
- static bool ViewHasMetadata(BinaryView*);
- static std::optional<unsigned int> ViewMetadataVersion(BinaryView*);
-
- const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& ExportInfos() const;
- std::string InstallNameForImageBaseAddress(uint64_t baseAddress) const;
+ std::optional<CacheImage> GetImageContaining(uint64_t address) const;
- ~SharedCacheMetadata();
- SharedCacheMetadata(SharedCacheMetadata&&);
- SharedCacheMetadata& operator=(SharedCacheMetadata&&);
+ // TODO: Rename to GetImageWithPath and then make another one for the image name.
+ std::optional<CacheImage> GetImageWithName(const std::string& name) const;
- private:
- SharedCacheMetadata(SharedCache::CacheInfo, SharedCache::ModifiedState);
+ std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const;
- std::unique_ptr<SharedCache::CacheInfo> cacheInfo;
- std::unique_ptr<SharedCache::ModifiedState> state;
+ std::optional<CacheSymbol> GetSymbolWithName(const std::string& name) const;
+};
- friend struct SharedCache::ModifiedState;
- friend class SharedCache;
+// This constructs a Cache, give it a file path, and it will add all relevant cache entries.
+class CacheProcessor
+{
+ BinaryNinja::Ref<BinaryNinja::BinaryView> m_view;
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
- static const std::string Tag;
- static const std::string CacheInfoTag;
- static const std::string ModifiedStateTagPrefix;
- static const std::string ModifiedStateCountTag;
- };
-}
+public:
+ explicit CacheProcessor(BinaryNinja::Ref<BinaryNinja::BinaryView> view);
-void InitDSCViewType();
+ // Construct a cache from the root file, this will parse the cache header and locate all
+ // applicable cache entries and add them as well.
+ bool ProcessCache(SharedCache& cache);
-#endif //SHAREDCACHE_SHAREDCACHE_H
+ // Process a cache on the file system, this is for when not using a project.
+ bool ProcessFileCache(SharedCache& cache);
+ // Process a cache using Binary Ninja's project system.
+ bool ProcessProjectCache(SharedCache& cache);
+};
diff --git a/view/sharedcache/core/SharedCacheController.cpp b/view/sharedcache/core/SharedCacheController.cpp
new file mode 100644
index 00000000..29c1192a
--- /dev/null
+++ b/view/sharedcache/core/SharedCacheController.cpp
@@ -0,0 +1,261 @@
+#include "SharedCacheController.h"
+
+#include <utility>
+
+#include "MachOProcessor.h"
+#include "ObjC.h"
+#include "SlideInfo.h"
+
+using namespace BinaryNinja;
+using namespace BinaryNinja::DSC;
+
+// Unique ID for a given Binary View.
+typedef uint64_t ViewId;
+
+std::shared_mutex GlobalControllersMutex;
+
+std::map<ViewId, DSCRef<SharedCacheController>>& GlobalControllers()
+{
+ // To make initialization order consistent we place the static in a function.
+ static std::map<ViewId, DSCRef<SharedCacheController>> g_dscViews = {};
+ return g_dscViews;
+}
+
+ViewId GetViewIdFromView(BinaryView& view)
+{
+ // Currently the view id is just the views session id.
+ // NOTE: If we want more than one shared cache controller per view we would need to make this more unique.
+ return view.GetFile()->GetSessionId();
+}
+
+void DeleteController(BinaryView& view)
+{
+ const auto id = GetViewIdFromView(view);
+ std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex);
+ auto& dscViews = GlobalControllers();
+ if (auto it = dscViews.find(id); it != dscViews.end())
+ {
+ // Someone is still holding the controller, lets warn about this.
+ if (it->second->m_refs > 1)
+ LogWarn("Deleting SharedCacheController for view %llx, but there are still %d references", id,
+ it->second->m_refs.load());
+ dscViews.erase(it);
+ LogDebug("Deleted SharedCacheController for view %s", view.GetFile()->GetFilename().c_str());
+ }
+}
+
+void RegisterSharedCacheControllerDestructor()
+{
+ BNObjectDestructionCallbacks callbacks = {};
+ callbacks.destructBinaryView = [](void* ctx, BNBinaryView* obj) -> void {
+ auto view = BinaryView(obj);
+ DeleteController(view);
+ };
+ BNRegisterObjectDestructionCallbacks(&callbacks);
+}
+
+SharedCacheController::SharedCacheController(SharedCache cache, Ref<Logger> logger) : m_cache(std::move(cache))
+{
+ INIT_DSC_API_OBJECT();
+ m_logger = std::move(logger);
+ m_loadedRegions = {};
+ m_loadedImages = {};
+}
+
+DSCRef<SharedCacheController> SharedCacheController::Initialize(BinaryView& view, SharedCache cache)
+{
+ auto id = GetViewIdFromView(view);
+ std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex);
+ auto logger = new Logger("SharedCacheController", view.GetFile()->GetSessionId());
+ DSCRef<SharedCacheController> dscView = new SharedCacheController(std::move(cache), logger);
+
+ // Pull the settings from the view.
+ if (Ref<Settings> settings = view.GetLoadSettings(VIEW_NAME))
+ {
+ if (settings->Contains("loader.dsc.processObjC"))
+ dscView->m_processObjC = settings->Get<bool>("loader.dsc.processObjC", &view);
+ if (settings->Contains("loader.dsc.processCFStrings"))
+ dscView->m_processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", &view);
+ if (settings->Contains("loader.dsc.regionFilter"))
+ dscView->m_regionFilter = std::regex(settings->Get<std::string>("loader.dsc.regionFilter", &view));
+ }
+
+ // Check the view auto metadata for shared cache information.
+ // This effectively restores the state of the opened database to when it was last saved.
+ auto metadata = view.GetAutoMetadata()->GetKeyValueStore();
+ if (metadata.find(METADATA_KEY) != metadata.end())
+ dscView->LoadMetadata(*metadata[METADATA_KEY]);
+
+ GlobalControllers().insert({id, dscView});
+ return dscView;
+}
+
+DSCRef<SharedCacheController> SharedCacheController::FromView(BinaryView& view)
+{
+ auto id = GetViewIdFromView(view);
+ std::shared_lock<std::shared_mutex> lock(GlobalControllersMutex);
+ auto& dscViews = GlobalControllers();
+ auto dscView = dscViews.find(id);
+ if (dscView == dscViews.end())
+ return nullptr;
+ return dscView->second;
+}
+
+bool SharedCacheController::ApplyRegionAtAddress(BinaryView& view, const uint64_t address)
+{
+ auto region = m_cache.GetRegionAt(address);
+ if (!region)
+ return false;
+ return ApplyRegion(view, *region);
+}
+
+bool SharedCacheController::ApplyRegion(BinaryView& view, const CacheRegion& region)
+{
+ // TODO: Check m_loadedRegions? This seems redundant...
+ // Loads the given region into the BinaryView and marks it as loaded.
+ // First check to make sure there isn't already a segment at the address in the view.
+ if (view.GetSegmentAt(region.start))
+ return false;
+
+ // Skip filtered regions, this defaults to just LINKEDIT regions.
+ if (std::regex_match(region.name, m_regionFilter))
+ {
+ LogDebug("Skipping filtered region at %llx", region.start);
+ return false;
+ }
+
+ auto vm = m_cache.GetVirtualMemory();
+ auto reader = VirtualMemoryReader(vm);
+ DataBuffer buffer = {};
+ try
+ {
+ buffer = reader.ReadBuffer(region.start, region.size);
+ }
+ catch (std::exception& e)
+ {
+ // This happens if we have not mapped in all the relevant entries.
+ m_logger->LogError("Failed to read region: %s", e.what());
+ return false;
+ }
+
+ // Unique memory region name so that we don't cause collisions.
+ // TODO: Better name? I dont really think so...
+ const auto memoryRegionName = fmt::format("{}_0x{:x}", region.name, region.start);
+
+ // NOTE: Adding a data memory region will store the entire contents of the region in the BNDB.
+ // TODO: We can use the AddRemoteMemoryRegion if we want to reload on view init.
+ // TODO: ^ The above is only useful if we assume that all files will be available across database loads.
+ // TODO: we might allow a user to select non-persisted memory regions as an option.
+ view.GetMemoryMap()->AddDataMemoryRegion(memoryRegionName, region.start, buffer, region.flags);
+ // TODO: We might want to make this auto if we decide to "reload" all loaded region in view init.
+ // If we are not associated with an image we can create a section here to set the semantics.
+ // This is important for stub regions, as they will deref non image data that we want to retrieve the value of.
+ if (region.type != CacheRegionType::Image)
+ view.AddUserSection(memoryRegionName, region.start, region.size, region.SectionSemanticsForRegion());
+
+ m_loadedRegions.insert(region.start);
+
+ // TODO: This needs to be done in a "database save" callback.
+ view.StoreMetadata(METADATA_KEY, GetMetadata());
+
+ return true;
+}
+
+bool SharedCacheController::IsRegionLoaded(const CacheRegion& region) const
+{
+ return std::any_of(m_loadedRegions.begin(), m_loadedRegions.end(), [&](const auto& loadedRegion) {
+ return loadedRegion == region.start;
+ });
+}
+
+bool SharedCacheController::ApplyImage(BinaryView& view, const CacheImage& image)
+{
+ // TODO: Check m_loadedImages? This seems redundant...
+ // Load all regions of an image and mark the image as loaded.
+ bool loadedRegion = false;
+ for (const auto& regionStart : image.regionStarts)
+ if (ApplyRegionAtAddress(view, regionStart))
+ loadedRegion = true;
+
+ // If there was no loaded regions than we just want to forgo loading the image.
+ if (!loadedRegion)
+ return false;
+
+ if (image.header)
+ {
+ // Header information is applied to the view here, such as sections.
+ auto machoProcessor = SharedCacheMachOProcessor(&view, m_cache.GetVirtualMemory());
+ machoProcessor.ApplyHeader(*image.header);
+
+ // TODO: Make this optional.
+ // Load objective-c information.
+ auto objcProcessor = DSCObjC::SharedCacheObjCProcessor(&view, false);
+ // TODO: Passing in an image name here is weird considering this is shared with the MACHO view.
+ // TODO: We should abstract out the "image" into an objc image type that represents what is required, which ig is the name?
+ try
+ {
+ if (m_processObjC)
+ objcProcessor.ProcessObjCData(image.GetName());
+ if (m_processObjC)
+ objcProcessor.ProcessCFStrings(image.GetName());
+ }
+ catch (std::exception& e)
+ {
+ // Let the user know there was an error in processing the objc stuff but let the image load
+ // regardless, as its non-critical.
+ m_logger->LogError("Failed to process ObjC information: %s", e.what());
+ }
+ }
+
+ m_loadedImages.insert(image.headerAddress);
+
+ // TODO: This needs to be done in a "database save" callback.
+ view.StoreMetadata(METADATA_KEY, GetMetadata());
+
+ // TODO: Partial failure state (i.e. 2 regions loaded, one failed)
+ return true;
+}
+
+bool SharedCacheController::IsImageLoaded(const CacheImage& image) const
+{
+ return std::any_of(m_loadedImages.begin(), m_loadedImages.end(), [&](const auto& loadedImage) {
+ return loadedImage == image.headerAddress;
+ });
+}
+
+Ref<Metadata> SharedCacheController::GetMetadata() const
+{
+ std::map<std::string, Ref<Metadata>> controllerMeta;
+
+ std::vector<uint64_t> loadedImages;
+ std::vector<uint64_t> loadedRegions;
+ loadedImages.reserve(m_loadedImages.size());
+ loadedRegions.reserve(m_loadedRegions.size());
+ for (const auto& loadedImage : m_loadedImages)
+ loadedImages.push_back(loadedImage);
+ for (const auto& loadedRegion : m_loadedRegions)
+ loadedRegions.push_back(loadedRegion);
+
+ controllerMeta["loadedImages"] = new Metadata(loadedImages);
+ controllerMeta["loadedRegions"] = new Metadata(loadedRegions);
+
+ return new Metadata(controllerMeta);
+}
+
+void SharedCacheController::LoadMetadata(const Metadata& metadata)
+{
+ auto controllerMeta = metadata.GetKeyValueStore();
+ if (controllerMeta.find("loadedImages") != controllerMeta.end())
+ {
+ const auto loadedImages = controllerMeta["loadedImages"]->GetUnsignedIntegerList();
+ for (const auto& image : loadedImages)
+ m_loadedImages.insert(image);
+ }
+
+ if (controllerMeta.find("loadedRegions") != controllerMeta.end())
+ {
+ const auto loadedRegions = controllerMeta["loadedRegions"]->GetUnsignedIntegerList();
+ for (const auto& region : loadedRegions)
+ m_loadedImages.insert(region);
+ }
+}
diff --git a/view/sharedcache/core/SharedCacheController.h b/view/sharedcache/core/SharedCacheController.h
new file mode 100644
index 00000000..354762eb
--- /dev/null
+++ b/view/sharedcache/core/SharedCacheController.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <regex>
+
+#include "SharedCache.h"
+#include "refcountobject.h"
+#include "ffi_global.h"
+
+DECLARE_DSC_API_OBJECT(BNSharedCacheController, SharedCacheController);
+
+void RegisterSharedCacheControllerDestructor();
+
+namespace BinaryNinja::DSC {
+ static const char* METADATA_KEY = "shared_cache";
+
+ // Represents the view state for a given `DSCache`
+ class SharedCacheController : public DSCRefCountObject
+ {
+ IMPLEMENT_DSC_API_OBJECT(BNSharedCacheController);
+ Ref<Logger> m_logger;
+
+ SharedCache m_cache;
+ // Store the open images.
+ // Things other than the cache here will be serialized.
+ std::unordered_set<uint64_t> m_loadedRegions;
+ std::unordered_set<uint64_t> m_loadedImages;
+
+ // Settings from the view.
+ std::regex m_regionFilter;
+ bool m_processObjC;
+ bool m_processCFStrings;
+
+ explicit SharedCacheController(SharedCache cache, Ref<Logger> logger);
+
+ public:
+ // Initialize the DSCacheView, this should be called from the view initialize function only!
+ static DSCRef<SharedCacheController> Initialize(BinaryView& view, SharedCache cache);
+
+ // NOTE: This will not create one if it does not exist. To create one for the view call `Initialize`.
+ static DSCRef<SharedCacheController> FromView(BinaryView& view);
+
+ SharedCache& GetCache() { return m_cache; };
+ const std::unordered_set<uint64_t>& GetLoadedRegions() { return m_loadedRegions; };
+ const std::unordered_set<uint64_t>& GetLoadedImages() { return m_loadedImages; };
+
+ // TODO: LoadResult type? AlreadyLoaded, Loaded, NotLoaded.
+ // NOTE: `address` should be the start of a region, not containing the address.
+ bool ApplyRegionAtAddress(BinaryView& view, uint64_t address);
+
+ bool ApplyRegion(BinaryView& view, const CacheRegion& region);
+
+ bool IsRegionLoaded(const CacheRegion& region) const;
+
+ // Loads the relevant image info into the view. This does not update analysis so if you
+ // call this make sure at some point you update analysis and likely with linear sweep.
+ bool ApplyImage(BinaryView& view, const CacheImage& image);
+
+ bool IsImageLoaded(const CacheImage& image) const;
+
+ // Get the metadata for saving the state of the shared cache.
+ Ref<Metadata> GetMetadata() const;
+
+ void LoadMetadata(const Metadata& metadata);
+ };
+} // namespace BinaryNinja::DSC
diff --git a/view/sharedcache/core/DSCView.cpp b/view/sharedcache/core/SharedCacheView.cpp
index add2faff..bf80c6eb 100644
--- a/view/sharedcache/core/DSCView.cpp
+++ b/view/sharedcache/core/SharedCacheView.cpp
@@ -1,82 +1,208 @@
-//
-// Created by kat on 5/23/23.
-//
+#include "SharedCacheView.h"
+#include <regex>
+#include "SharedCacheController.h"
+#include "FileAccessorCache.h"
+#include "MappedFileAccessor.h"
-/*
- * If you're here looking for the code to load caches, out of luck.
- *
- * The VIEW_NAME is essentially a blank slate that only knows how to deserialize and reserialize itself
- * based on some metadata encoded in it.
- *
- * The actual controller logic that does _all_ of the image loading is invoked via API -> SharedCache.cpp
- *
- * */
+using namespace BinaryNinja;
+using namespace BinaryNinja::DSC;
-#include "DSCView.h"
-#include "view/macho/machoview.h"
-#include "SharedCache.h"
+SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {}
-using namespace BinaryNinja;
+// We register all our one-shot stuff here, such as the object destructor.
+void SharedCacheViewType::Register()
+{
+ auto fdLimit = AdjustFileDescriptorLimit();
+ LogDebug("Shared Cache processing initialized with a max file descriptor limit of %lld", fdLimit);
+
+ // Adjust the global accessor cache to the fdlimit.
+ FileAccessorCache::Global().SetCacheSize(fdLimit);
+
+ // TODO: Register object destructor to clear accessor cache
+ RegisterSharedCacheControllerDestructor();
+
+ static SharedCacheViewType type;
+ BinaryViewType::Register(&type);
+}
+
+Ref<BinaryView> SharedCacheViewType::Create(BinaryView* data)
+{
+ return new SharedCacheView(VIEW_NAME, data, false);
+}
+
+Ref<Settings> SharedCacheViewType::GetLoadSettingsForData(BinaryView* data)
+{
+ Ref<BinaryView> viewRef = Parse(data);
+ if (!viewRef || !viewRef->Init())
+ {
+ LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str());
+ viewRef = data;
+ }
+
+ Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef);
+
+ // specify default load settings that can be overridden
+ std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"};
+ settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable.");
+
+ for (const auto& override : overrides)
+ {
+ if (settings->Contains(override))
+ settings->UpdateProperty(override, "readOnly", false);
+ }
+
+ Ref<Settings> programSettings = Settings::Instance();
+ programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", viewRef);
+
+ settings->RegisterSetting("loader.dsc.processCFStrings",
+ R"({
+ "title" : "Process CFString Metadata",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Processes CoreFoundation strings, applying string values from encoded metadata"
+ })");
+
+ settings->RegisterSetting("loader.dsc.autoLoadPattern",
+ R"({
+ "title" : "Image Auto-Load Regex Pattern",
+ "type" : "string",
+ "default" : ".*libsystem_c.dylib",
+ "description" : "A regex pattern to auto load matching images at the end of view init, defaults to the system_c image only."
+ })");
+
+ settings->RegisterSetting("loader.dsc.processObjC",
+ R"({
+ "title" : "Process Objective-C Metadata",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata"
+ })");
+
+ settings->RegisterSetting("loader.dsc.regionFilter",
+ R"({
+ "title" : "Region Regex Filter",
+ "type" : "string",
+ "default" : "LINKEDIT",
+ "description" : "Regex filter for region names to skip loading, by default this filters out the link edit region which is not necessary to be applied to the view."
+ })");
+
+ settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements",
+ R"({
+ "title" : "Auto-Load Objective-C Stub Requirements",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on."
+ })");
+
+ settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData",
+ R"({
+ "title" : "Auto-Load Stub Islands",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on."
+ })");
+
+ settings->RegisterSetting("loader.dsc.processFunctionStarts",
+ R"({
+ "title" : "Process Mach-O Function Starts Tables",
+ "type" : "boolean",
+ "default" : true,
+ "description" : "Add function starts sourced from the Function Starts tables to the core for analysis."
+ })");
+ // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O
+ // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This
+ // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema
+ // contains 'loader.macho.universalImageOffset'.
+ Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName());
+ if (loadSettings && !loadSettings->IsEmpty())
+ settings->DeserializeSchema(loadSettings->SerializeSchema());
-DSCView::DSCView(const std::string& typeName, BinaryView* data, bool parseOnly) :
+ return settings;
+}
+
+Ref<BinaryView> SharedCacheViewType::Parse(BinaryView* data)
+{
+ return new SharedCacheView(VIEW_NAME, data, true);
+}
+
+bool SharedCacheViewType::IsTypeValidForData(BinaryView* data)
+{
+ if (!data)
+ return false;
+
+ DataBuffer sig = data->ReadBuffer(data->GetStart(), 4);
+ if (sig.GetLength() != 4)
+ return false;
+
+ const char* magic = (char*)sig.GetData();
+ if (strncmp(magic, "dyld", 4) == 0)
+ return true;
+
+ return false;
+}
+
+SharedCacheView::SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly) :
BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly)
{
CreateLogger("SharedCache");
CreateLogger("SharedCache.ObjC");
}
-DSCView::~DSCView()
+enum DSCPlatform
{
-}
-
-enum DSCPlatform {
DSCPlatformMacOS = 1,
DSCPlatformiOS = 2,
DSCPlatformTVOS = 3,
DSCPlatformWatchOS = 4,
- DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS.
+ DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS.
// DSCPlatformMacCatalyst = 6,
DSCPlatformiOSSimulator = 7,
DSCPlatformTVOSSimulator = 8,
DSCPlatformWatchOSSimulator = 9,
- DSCPlatformVisionOS = 11, // Apple Vision Pro
- DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator
+ DSCPlatformVisionOS = 11, // Apple Vision Pro
+ DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator
};
-bool DSCView::Init()
+bool SharedCacheView::Init()
{
std::string os;
std::string arch;
+ auto logger = new Logger("SharedCacheView", GetFile()->GetSessionId());
+
uint32_t platform;
GetParentView()->Read(&platform, 0xd8, 4);
char magic[17];
GetParentView()->Read(&magic, 0, 16);
magic[16] = 0;
+
+ // TODO: Do we want to add any warnings about platform support here?
+ // TODO: Do we still consider macos experimental?
switch (platform)
{
- case DSCPlatformMacOS:
- case DSCPlatformTVOS:
- case DSCPlatformTVOSSimulator:
- os = "mac";
- break;
- case DSCPlatformiOS:
- case DSCPlatformiOSSimulator:
- case DSCPlatformVisionOS:
- case DSCPlatformVisionOSSimulator:
- os = "ios";
- break;
- // armv7 or slide info v1 (unsupported)
- case DSCPlatformWatchOS:
- case DSCPlatformWatchOSSimulator:
- case DSCPlatformBridgeOS:
- default:
- LogError("Unknown platform: %d", platform);
- return false;
+ case DSCPlatformMacOS:
+ case DSCPlatformTVOS:
+ case DSCPlatformTVOSSimulator:
+ os = "mac";
+ break;
+ case DSCPlatformiOS:
+ case DSCPlatformiOSSimulator:
+ case DSCPlatformVisionOS:
+ case DSCPlatformVisionOSSimulator:
+ os = "ios";
+ break;
+ // armv7 or slide info v1 (unsupported)
+ case DSCPlatformWatchOS:
+ case DSCPlatformWatchOSSimulator:
+ case DSCPlatformBridgeOS:
+ default:
+ logger->LogError("Unknown platform: %d", platform);
+ return false;
}
- if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" || std::string(magic) == "dyld_v1arm64_32")
+ if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e"
+ || std::string(magic) == "dyld_v1arm64_32")
{
arch = "aarch64";
}
@@ -86,24 +212,38 @@ bool DSCView::Init()
}
else
{
- LogError("Unknown magic: %s", magic);
+ logger->LogError("Unknown magic: %s", magic);
return false;
}
SetDefaultPlatform(Platform::GetByName(os + "-" + arch));
SetDefaultArchitecture(Architecture::GetByName(arch));
+ Ref<Settings> settings = GetLoadSettings(GetTypeName());
+
+ if (!settings)
+ {
+ Ref<Settings> programSettings = Settings::Instance();
+ programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", this);
+ }
+
+ if (m_parseOnly)
+ return true;
+
QualifiedNameAndType headerType;
std::string err;
- ParseTypeString("\n"
+ ParseTypeString(
+ "\n"
"\tstruct dyld_cache_header\n"
"\t{\n"
"\t\tchar magic[16];\t\t\t\t\t // e.g. \"dyld_v0 i386\"\n"
"\t\tuint32_t mappingOffset;\t\t\t // file offset to first dyld_cache_mapping_info\n"
"\t\tuint32_t mappingCount;\t\t\t // number of dyld_cache_mapping_info entries\n"
- "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing\n"
- "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing\n"
+ "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from "
+ "crashing\n"
+ "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from "
+ "crashing\n"
"\t\tuint64_t dyldBaseAddress;\t\t // base address of dyld when cache was built\n"
"\t\tuint64_t codeSignatureOffset;\t // file offset of code signature blob\n"
"\t\tuint64_t codeSignatureSize;\t\t // size of code signature blob (zero means to end of file)\n"
@@ -120,7 +260,8 @@ bool DSCView::Init()
"\t\tuint64_t imagesTextOffset;\t\t // file offset to first dyld_cache_image_text_info\n"
"\t\tuint64_t imagesTextCount;\t\t // number of dyld_cache_image_text_info entries\n"
"\t\tuint64_t patchInfoAddr;\t\t\t // (unslid) address of dyld_cache_patch_info\n"
- "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the dyld_cache_patch_info\n"
+ "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the "
+ "dyld_cache_patch_info\n"
"\t\tuint64_t otherImageGroupAddrUnused;\t // unused\n"
"\t\tuint64_t otherImageGroupSizeUnused;\t // unused\n"
"\t\tuint64_t progClosuresAddr;\t\t\t // (unslid) address of list of program launch closures\n"
@@ -129,10 +270,12 @@ bool DSCView::Init()
"\t\tuint64_t progClosuresTrieSize;\t\t // size of trie of indexes into program launch closures\n"
"\t\tuint32_t platform;\t\t\t\t\t // platform number (macOS=1, etc)\n"
"\t\tuint32_t formatVersion : 8,\t\t\t // dyld3::closure::kFormatVersion\n"
- "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid\n"
+ "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to "
+ "see if cache is valid\n"
"\t\t\tsimulator : 1,\t\t\t // for simulator of specified platform\n"
"\t\t\tlocallyBuiltCache : 1,\t // 0 for B&I built cache, 1 for locally built cache\n"
- "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables must be used for overrides\n"
+ "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables "
+ "must be used for overrides\n"
"\t\t\tpadding : 20;\t\t\t\t // TBD\n"
"\t\tuint64_t sharedRegionStart;\t\t // base load address of cache if not slid\n"
"\t\tuint64_t sharedRegionSize;\t\t // overall size required to map the cache and all subCaches, if any\n"
@@ -141,9 +284,11 @@ bool DSCView::Init()
"\t\tuint64_t dylibsImageArraySize;\t // size of ImageArray for dylibs in this cache\n"
"\t\tuint64_t dylibsTrieAddr;\t\t // (unslid) address of trie of indexes of all cached dylibs\n"
"\t\tuint64_t dylibsTrieSize;\t\t // size of trie of cached dylib paths\n"
- "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen closures\n"
+ "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen "
+ "closures\n"
"\t\tuint64_t otherImageArraySize;\t // size of ImageArray for dylibs and bundles with dlopen closures\n"
- "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures\n"
+ "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen "
+ "closures\n"
"\t\tuint64_t otherTrieSize;\t // size of trie of dylibs and bundles with dlopen closures\n"
"\t\tuint32_t mappingWithSlideOffset;\t\t // file offset to first dyld_cache_mapping_and_slide_info\n"
"\t\tuint32_t mappingWithSlideCount;\t\t\t // number of dyld_cache_mapping_and_slide_info entries\n"
@@ -160,49 +305,37 @@ bool DSCView::Init()
"\t\tuint64_t swiftOptsSize;\t\t\t// size of Swift optimizations header\n"
"\t\tuint32_t subCacheArrayOffset;\t// file offset to first dyld_subcache_entry\n"
"\t\tuint32_t subCacheArrayCount;\t// number of subCache entries\n"
- "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local symbols\n"
- "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add read-only/executable data\n"
+ "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local "
+ "symbols\n"
+ "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add "
+ "read-only/executable data\n"
"\t\tuint64_t rosettaReadOnlySize;\t// maximum size of the Rosetta read-only/executable region\n"
- "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write data\n"
+ "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write "
+ "data\n"
"\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n"
"\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n"
"\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n"
- "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n"
+ "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is "
+ "multi-cache(2)\n"
"\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n"
"\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n"
- "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n"
+ "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process "
+ "introspection\n"
"\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n"
- "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n"
+ "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of "
+ "dyld_cache_dynamic_data_header\n"
"\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n"
"\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n"
"\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n"
- "\t};", headerType, err);
+ "\t};",
+ headerType, err);
if (!err.empty() || !headerType.type)
{
- LogError("Failed to parse header type: %s", err.c_str());
+ logger->LogError("Failed to parse header type: %s", err.c_str());
return false;
}
- Ref<Settings> settings = GetLoadSettings(GetTypeName());
-
- if (!settings)
- {
- Ref<Settings> programSettings = Settings::Instance();
- programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", this);
- }
-
- if (m_parseOnly)
- return true;
-
- // Check the metadata version if there is one, so we can alert the user to why they have no loaded images.
- // TODO: Once the view is able to be exited early we should offer to close the view if the user does not want to upgrade.
- auto metadataVersion = SharedCacheCore::SharedCacheMetadata::ViewMetadataVersion(GetParentView());
- if (metadataVersion.has_value() && metadataVersion.value() != METADATA_VERSION)
- {
- ShowMessageBox("Invalid Shared Cache Metadata!", "The BNDB shared cache metadata was created with a different version of the Shared Cache view, to continue the metadata has to be recreated. You will need to add your images back again.");
- }
-
// Add Mach-O file header type info
EnumerationBuilder cpuTypeBuilder;
cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY);
@@ -335,17 +468,17 @@ bool DSCView::Init()
cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH); // (0x1c | LC_REQ_DYLD)
cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE);
cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO);
- cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD)
+ cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD)
cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB);
cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO);
cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO);
- cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD)
+ cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD)
cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB); // (0x23 | LC_REQ_DYLD)
cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX);
cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS);
cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS);
cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT);
- cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD)
+ cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD)
cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE);
cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION);
cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS);
@@ -622,14 +755,14 @@ bool DSCView::Init()
GetParentView()->Read(&basePointer, 16, 4);
if (basePointer == 0)
{
- LogError("Failed to read base pointer");
+ logger->LogError("Failed to read base pointer");
return false;
}
uint64_t primaryBase = 0;
GetParentView()->Read(&primaryBase, basePointer, 8);
if (primaryBase == 0)
{
- LogError("Failed to read primary base at 0x%llx", basePointer);
+ logger->LogError("Failed to read primary base at 0x%llx", basePointer);
return false;
}
@@ -641,131 +774,81 @@ bool DSCView::Init()
AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable);
AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics);
DefineType("dyld_cache_header", headerType.name, headerType.type);
- DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type);
+ DefineAutoSymbolAndVariableOrFunction(
+ GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type);
- return true;
-}
-
-
-DSCViewType::DSCViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME)
-{
-}
+ auto sharedCache = SharedCache(GetAddressSize());
-BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Create(BinaryNinja::BinaryView* data)
-{
- return new DSCView(VIEW_NAME, data, false);
-}
-
-
-Ref<Settings> DSCViewType::GetLoadSettingsForData(BinaryView* data)
-{
- Ref<BinaryView> viewRef = Parse(data);
- if (!viewRef || !viewRef->Init())
{
- LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str());
- viewRef = data;
+ // Add up all the cache entries using the cache processor.
+ // After this we should have all the mappings available as well.
+ CacheProcessor cacheProcessor = CacheProcessor(this);
+ auto startTime = std::chrono::high_resolution_clock::now();
+ if (!cacheProcessor.ProcessCache(sharedCache))
+ {
+ // Oh no, we failed to process the cache, this likely means the primary on-disk file was not able to be
+ // found.
+ // TODO: Prompt the user to select the primary cache file? We can still recover from here.
+ logger->LogError("Failed to process cache, likely missing cache files.");
+ // NOTE: An interaction handler headlessly could select yes to this, however for the purposes of this we will just let them continue by defualt.
+ if (IsUIEnabled() && ShowMessageBox("Continue opening", "This shared cache file was unable to be processed, would you like to open without shared cache information?", YesNoButtonSet, QuestionIcon) != YesButton)
+ return false;
+ }
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ logger->LogInfo("Processing %zu entries took %.3f seconds", sharedCache.GetEntries().size(), elapsed.count());
}
- Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef);
-
- // specify default load settings that can be overridden
- std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"};
- settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable.");
-
- for (const auto& override : overrides)
{
- if (settings->Contains(override))
- settings->UpdateProperty(override, "readOnly", false);
+ // Write all the slide info pointers to the virtual memory.
+ // This should be done before any other work begins, as the backing data will be altered by this process.
+ auto startTime = std::chrono::high_resolution_clock::now();
+ for (const auto& [_, entry] : sharedCache.GetEntries())
+ sharedCache.ProcessEntrySlideInfo(entry);
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ logger->LogInfo("Processing slide info took %.3f seconds", elapsed.count());
}
- Ref<Settings> programSettings = Settings::Instance();
- programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", viewRef);
-
- settings->RegisterSetting("loader.dsc.processCFStrings",
- R"({
- "title" : "Process CFString Metadata",
- "type" : "boolean",
- "default" : true,
- "description" : "Processes CoreFoundation strings, applying string values from encoded metadata"
- })");
-
- settings->RegisterSetting("loader.dsc.autoLoadLibSystem",
- R"({
- "title" : "Auto-Load libSystem",
- "type" : "boolean",
- "default" : true,
- "description" : "Whether to automatically load libsystem_c.dylib. This image contains frequently used noreturn symbols, and not loading it will result in frequently incorrect control flows."
- })");
-
- settings->RegisterSetting("loader.dsc.processObjC",
- R"({
- "title" : "Process Objective-C Metadata",
- "type" : "boolean",
- "default" : true,
- "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata"
- })");
-
- settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements",
- R"({
- "title" : "Auto-Load Objective-C Stub Requirements",
- "type" : "boolean",
- "default" : true,
- "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on."
- })");
-
- settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData",
- R"({
- "title" : "Auto-Load Stub Islands",
- "type" : "boolean",
- "default" : true,
- "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on."
- })");
-
- settings->RegisterSetting("loader.dsc.allowLoadingLinkeditSegments",
- R"({
- "title" : "Allow Loading __LINKEDIT Segments",
- "type" : "boolean",
- "default" : false,
- "description" : "Allow mapping __LINKEDIT segments. These are large regions of symbol data that are automatically processed by BinaryNinja without the need for mapping. On newer caches, __LINKEDIT for all images may end up merged and be >300MB in size. This will likely cause severe performance degradation with _zero_ benefit."
- })");
-
- settings->RegisterSetting("loader.dsc.processFunctionStarts",
- R"({
- "title" : "Process Mach-O Function Starts Tables",
- "type" : "boolean",
- "default" : true,
- "description" : "Add function starts sourced from the Function Starts tables to the core for analysis."
- })");
-
- // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O
- // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This
- // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema
- // contains 'loader.macho.universalImageOffset'.
- Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName());
- if (loadSettings && !loadSettings->IsEmpty())
- settings->DeserializeSchema(loadSettings->SerializeSchema());
-
- return settings;
-}
-
+ {
+ // Load up all images into the cache before adding extra regions.
+ // Currently, it is expected that a primary entry contain all relevant images, so we only check that one.
+ auto startTime = std::chrono::high_resolution_clock::now();
+ for (const auto& [_, entry] : sharedCache.GetEntries())
+ if (entry.GetType() == CacheEntryType::Primary)
+ sharedCache.ProcessEntryImages(entry);
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ auto images = sharedCache.GetImages();
+ logger->LogInfo("Processing %zu images took %.3f seconds", images.size(), elapsed.count());
+ // Warn if we found no images, and provide the likely explanation
+ if (images.empty())
+ logger->LogWarn("Failed to process any images, likely missing cache files.");
+ }
-BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Parse(BinaryNinja::BinaryView* data)
-{
- return new DSCView(VIEW_NAME, data, true);
-}
+ {
+ // TODO: Run this up on a separate thread maybe and have callback notifications?
+ // Load up all the regions into the cache.
+ auto startTime = std::chrono::high_resolution_clock::now();
+ for (const auto& [_, entry] : sharedCache.GetEntries())
+ sharedCache.ProcessEntryRegions(entry);
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ logger->LogInfo("Processing %zu regions took %.3f seconds", sharedCache.GetRegions().size(), elapsed.count());
+ }
-bool DSCViewType::IsTypeValidForData(BinaryNinja::BinaryView* data)
-{
- if (!data)
- return false;
+ auto cacheController = SharedCacheController::Initialize(*this, std::move(sharedCache));
- DataBuffer sig = data->ReadBuffer(data->GetStart(), 4);
- if (sig.GetLength() != 4)
- return false;
+ // Users can adjust which images are loaded by default using the `loader.dsc.autoLoadPattern` setting.
+ std::string autoLoadPattern = ".*libsystem_c.dylib";
+ if (settings && settings->Contains("loader.dsc.autoLoadPattern"))
+ autoLoadPattern = settings->Get<std::string>("loader.dsc.autoLoadPattern", this);
+ logger->LogDebug("Loading images using pattern: %s", autoLoadPattern.c_str());
- const char* magic = (char*)sig.GetData();
- if (strncmp(magic, "dyld", 4) == 0)
- return true;
+ std::regex autoLoadRegex(autoLoadPattern);
+ for (const auto& [_, image] : cacheController->GetCache().GetImages())
+ if (std::regex_match(image.GetName(), autoLoadRegex))
+ cacheController->ApplyImage(*this, image);
- return false;
+ return true;
}
diff --git a/view/sharedcache/core/SharedCacheView.h b/view/sharedcache/core/SharedCacheView.h
new file mode 100644
index 00000000..1ead0b33
--- /dev/null
+++ b/view/sharedcache/core/SharedCacheView.h
@@ -0,0 +1,43 @@
+//
+// Created by kat on 5/23/23.
+//
+
+#ifndef SHAREDCACHE_DSCVIEW_H
+#define SHAREDCACHE_DSCVIEW_H
+
+#include <binaryninjaapi.h>
+
+
+class SharedCacheView : public BinaryNinja::BinaryView
+{
+ bool m_parseOnly;
+
+public:
+ SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly = false);
+
+ ~SharedCacheView() override = default;
+
+ bool Init() override;
+};
+
+
+class SharedCacheViewType : public BinaryNinja::BinaryViewType
+{
+public:
+ SharedCacheViewType();
+
+ static void Register();
+
+ BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView* data) override;
+
+ BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView* data) override;
+
+ bool IsTypeValidForData(BinaryNinja::BinaryView* data) override;
+
+ bool IsDeprecated() override { return false; }
+
+ BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView* data) override;
+};
+
+
+#endif // SHAREDCACHE_DSCVIEW_H
diff --git a/view/sharedcache/core/SlideInfo.cpp b/view/sharedcache/core/SlideInfo.cpp
new file mode 100644
index 00000000..e258a90c
--- /dev/null
+++ b/view/sharedcache/core/SlideInfo.cpp
@@ -0,0 +1,292 @@
+#include "SlideInfo.h"
+
+#include "Dyld.h"
+#include "SharedCache.h"
+#include "Utility.h"
+
+SlideInfoProcessor::SlideInfoProcessor(uint64_t baseAddress)
+{
+ m_logger = new BinaryNinja::Logger("SlideInfoProcessor");
+ m_baseAddress = baseAddress;
+}
+
+void ApplySlideInfoV5(VirtualMemory& vm, const SlideMappingInfo& mapping)
+{
+ uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v5);
+ uint64_t pageStartCount = mapping.slideInfoV5.page_starts_count;
+ uint64_t pageSize = mapping.slideInfoV5.page_size;
+
+ uint64_t offset;
+ // Retrieve this once so we don't need to keep querying the region through the VM.
+ auto region = vm.GetRegionAtAddress(mapping.address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(mapping.address);
+
+ auto cursor = pageStartsOffset;
+ for (size_t i = 0; i < pageStartCount; i++)
+ {
+ uint16_t delta = vm.ReadUInt16(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);
+ dyld_cache_slide_pointer5 slideInfo = {vm.ReadUInt64(loc)};
+ delta = slideInfo.regular.next;
+ if (slideInfo.auth.auth)
+ {
+ uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset;
+ vm.WritePointer(loc, value);
+ }
+ else
+ {
+ uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset;
+ vm.WritePointer(loc, value);
+ }
+ } while (delta != 0);
+ }
+}
+
+void ApplySlideInfoV3(VirtualMemory& vm, const SlideMappingInfo& mapping)
+{
+ uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v3);
+ uint64_t pageStartCount = mapping.slideInfoV3.page_starts_count;
+ uint64_t pageSize = mapping.slideInfoV3.page_size;
+
+ auto cursor = pageStartsOffset;
+ for (size_t i = 0; i < pageStartCount; i++)
+ {
+ uint16_t delta = vm.ReadUInt16(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);
+ dyld_cache_slide_pointer3 slideInfo = {vm.ReadUInt64(loc)};
+ delta = slideInfo.plain.offsetToNextPointer;
+
+ if (slideInfo.auth.authenticated)
+ {
+ uint64_t value = slideInfo.auth.offsetFromSharedCacheBase;
+ value += mapping.slideInfoV3.auth_value_add;
+ vm.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;
+ vm.WritePointer(loc, value);
+ }
+ } while (delta != 0);
+ }
+}
+
+void ApplySlideInfoV2(VirtualMemory& vm, const SlideMappingInfo& mapping)
+{
+ auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) {
+ // TODO: This is always zero?
+ // TODO: This is probably something for runtime offsets provided to the shared cache.
+ uintptr_t slideAmount = 0;
+
+ auto deltaMask = slideInfo.delta_mask;
+ auto valueMask = ~deltaMask;
+ auto valueAdd = slideInfo.value_add;
+
+ auto deltaShift = CountTrailingZeros(deltaMask) - 2;
+
+ uint32_t pageOffset = startOffset;
+ uint32_t delta = 1;
+ while (delta != 0)
+ {
+ uint64_t loc = pageContent + pageOffset;
+ uintptr_t rawValue = vm.ReadUInt64(loc);
+ delta = (uint32_t)((rawValue & deltaMask) >> deltaShift);
+ uintptr_t value = (rawValue & valueMask);
+ if (value != 0)
+ {
+ value += valueAdd;
+ // TODO: slideAmount += value?
+ // TODO: slideAmount is always zero? What is this suppose to do?
+ value += slideAmount;
+ }
+ pageOffset += delta;
+ vm.WritePointer(loc, value);
+ }
+ };
+
+ uint64_t extrasOffset = mapping.address + mapping.slideInfoV2.page_extras_offset;
+ uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v2);
+ uint64_t pageStartCount = mapping.slideInfoV2.page_starts_count;
+ uint64_t pageSize = mapping.slideInfoV2.page_size;
+
+ auto cursor = pageStartsOffset;
+ for (size_t i = 0; i < pageStartCount; i++)
+ {
+ uint16_t start = vm.ReadUInt16(cursor);
+ cursor += sizeof(uint16_t);
+ if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE)
+ continue;
+
+ if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA)
+ {
+ int j = (start & 0x3FFF);
+ bool done = false;
+ do
+ {
+ uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t));
+ auto extra = vm.ReadUInt16(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;
+ } while (!done);
+ }
+ else
+ {
+ uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i);
+ uint16_t pageStartOffset = start * 4;
+ rebaseChain(mapping.slideInfoV2, page, pageStartOffset);
+ }
+ }
+}
+
+std::vector<SlideMappingInfo> SlideInfoProcessor::ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const
+{
+ auto& baseHeader = entry.GetHeader();
+
+ // Handle legacy, single mapping slide info.
+ if (baseHeader.slideInfoOffsetUnused)
+ {
+ auto slideInfoAddress = entry.GetMappedAddress(baseHeader.slideInfoOffsetUnused);
+ if (!slideInfoAddress.has_value())
+ {
+ m_logger->LogError("Unmapped slide info address %llx", slideInfoAddress);
+ return {};
+ }
+ auto slideInfoVersion = vm.ReadUInt32(*slideInfoAddress);
+ if (slideInfoVersion != 2 && slideInfoVersion != 3)
+ {
+ m_logger->LogError("Unsupported slide info version %d", slideInfoVersion);
+ return {};
+ }
+
+ SlideMappingInfo singleMapping = {};
+ singleMapping.address = *slideInfoAddress;
+
+ auto mappingAddress = entry.GetMappedAddress(baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info));
+ if (!mappingAddress.has_value())
+ {
+ m_logger->LogError("Unmapped mapping address %llx", mappingAddress);
+ return {};
+ }
+ vm.Read(&singleMapping.mappingInfo, *mappingAddress, sizeof(dyld_cache_mapping_info));
+ singleMapping.slideInfoVersion = slideInfoVersion;
+ if (singleMapping.slideInfoVersion == 2)
+ vm.Read(&singleMapping.slideInfoV2, *slideInfoAddress, sizeof(dyld_cache_slide_info_v2));
+ else if (singleMapping.slideInfoVersion == 3)
+ vm.Read(&singleMapping.slideInfoV3, *slideInfoAddress, sizeof(dyld_cache_slide_info_v3));
+
+ return {singleMapping};
+ }
+
+ std::vector<SlideMappingInfo> mappings = {};
+ for (auto i = 0; i < baseHeader.mappingWithSlideCount; i++)
+ {
+ dyld_cache_mapping_and_slide_info mappingAndSlideInfo = {};
+ auto mappingAndSlideInfoAddress =
+ entry.GetMappedAddress(baseHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info)));
+ if (!mappingAndSlideInfoAddress.has_value())
+ {
+ m_logger->LogError("Unmapped mapping and slide info address %llx", mappingAndSlideInfoAddress);
+ continue;
+ }
+
+ vm.Read(&mappingAndSlideInfo, *mappingAndSlideInfoAddress, sizeof(dyld_cache_mapping_and_slide_info));
+ if (mappingAndSlideInfo.size == 0 || mappingAndSlideInfo.slideInfoFileOffset == 0)
+ continue;
+
+ SlideMappingInfo map = {};
+ map.address = *entry.GetMappedAddress(mappingAndSlideInfo.slideInfoFileOffset);
+ map.slideInfoVersion = vm.ReadUInt32(map.address);
+ map.mappingInfo.address = mappingAndSlideInfo.address;
+ map.mappingInfo.size = mappingAndSlideInfo.size;
+ map.mappingInfo.fileOffset = *entry.GetMappedAddress(mappingAndSlideInfo.fileOffset);
+ if (map.slideInfoVersion == 2)
+ {
+ vm.Read(&map.slideInfoV2, map.address, sizeof(dyld_cache_slide_info_v2));
+ }
+ else if (map.slideInfoVersion == 3)
+ {
+ vm.Read(&map.slideInfoV3, map.address, sizeof(dyld_cache_slide_info_v3));
+ map.slideInfoV3.auth_value_add = m_baseAddress;
+ }
+ else if (map.slideInfoVersion == 5)
+ {
+ vm.Read(&map.slideInfoV5, map.address, sizeof(dyld_cache_slide_info_v5));
+ map.slideInfoV5.value_add = m_baseAddress;
+ }
+ else
+ {
+ m_logger->LogError("Unknown slide info version: %d", map.slideInfoVersion);
+ continue;
+ }
+
+ mappings.emplace_back(map);
+ m_logger->LogDebug("File: %s", entry.GetFilePath().c_str());
+ m_logger->LogDebug("Slide Info Address: 0x%llx", map.address);
+ m_logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address);
+ m_logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion);
+ }
+
+ return mappings;
+}
+
+void SlideInfoProcessor::ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings)
+{
+ // Apply the slide information to the mapped file.
+ for (const auto& mapping : mappings)
+ {
+ switch (mapping.slideInfoVersion)
+ {
+ case 2:
+ ApplySlideInfoV2(vm, mapping);
+ break;
+ case 3:
+ ApplySlideInfoV3(vm, mapping);
+ break;
+ case 5:
+ ApplySlideInfoV5(vm, mapping);
+ break;
+ default:
+ m_logger->LogError(
+ "Cannot apply slide info version: %d @ %llx", mapping.slideInfoVersion, mapping.mappingInfo.address);
+ break;
+ }
+ }
+}
+
+void SlideInfoProcessor::ProcessEntry(VirtualMemory& vm, const CacheEntry& entry)
+{
+ try
+ {
+ ApplyMappings(vm, ReadEntryInfo(vm, entry));
+ }
+ catch (const std::exception& e)
+ {
+ // Just log an error, we technically can continue as if the slide info is not applied for a given entry it does
+ // not necessarily mean we cannot do analysis on others.
+ m_logger->LogError("Error processing slide info for entry `%s`: %s", entry.GetFileName().c_str(), e.what());
+ }
+}
diff --git a/view/sharedcache/core/SlideInfo.h b/view/sharedcache/core/SlideInfo.h
new file mode 100644
index 00000000..bc165589
--- /dev/null
+++ b/view/sharedcache/core/SlideInfo.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "binaryninjaapi.h"
+#include "SharedCache.h"
+
+// Information required to apply slide (relocation) info to a mapping
+struct SlideMappingInfo
+{
+ dyld_cache_mapping_info mappingInfo;
+ // NOTE: Offset is relative to the beginning of the entry file.
+ uint64_t address;
+ uint16_t slideInfoVersion;
+
+ union
+ {
+ dyld_cache_slide_info_v2 slideInfoV2;
+ dyld_cache_slide_info_v3 slideInfoV3;
+ dyld_cache_slide_info_v5 slideInfoV5;
+ };
+};
+
+// Current usages of the slide info are:
+// - Reading export symbols requires slide info to be processed.
+// - Reading objc stuff
+// - Loading an image or region
+// - Reading branch island mappings????
+class SlideInfoProcessor
+{
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+ // Base address of the shared cache, NOT the base address of the entry.
+ uint64_t m_baseAddress;
+
+public:
+ explicit SlideInfoProcessor(uint64_t baseAddress);
+
+ std::vector<SlideMappingInfo> ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const;
+
+ // Write the slide information back to the entries memory mapped regions.
+ void ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings);
+
+ void ProcessEntry(VirtualMemory& vm, const CacheEntry& entry);
+};
diff --git a/view/sharedcache/core/Utility.cpp b/view/sharedcache/core/Utility.cpp
new file mode 100644
index 00000000..91729578
--- /dev/null
+++ b/view/sharedcache/core/Utility.cpp
@@ -0,0 +1,131 @@
+#include "Utility.h"
+#include "binaryninjaapi.h"
+#include "view/macho/machoview.h"
+
+using namespace BinaryNinja;
+
+BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt)
+{
+ 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 static_cast<BNSegmentFlag>(flags);
+}
+
+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;
+}
+
+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;
+ 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;
+}
+
+void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol)
+{
+ Ref<Function> func = nullptr;
+ auto symbolAddress = symbol->GetAddress();
+ auto symbolName = symbol->GetFullName();
+
+ if (symbol->GetType() == FunctionSymbol)
+ {
+ Ref<Platform> targetPlatform = view->GetDefaultPlatform();
+ func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress);
+ }
+
+ if (typeLib)
+ {
+ auto type = view->ImportTypeLibraryObject(typeLib, {symbolName});
+ // TODO: This is still auto
+ if (type)
+ view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type);
+ else
+ view->DefineAutoSymbol(symbol);
+ }
+ else
+ {
+ view->DefineAutoSymbol(symbol);
+ }
+
+ if (!func)
+ func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress);
+ if (func)
+ {
+ if (symbolName == "_objc_msgSend")
+ {
+ func->SetHasVariableArguments(false);
+ }
+ else if (symbolName.find("_objc_retain_x") != std::string::npos
+ || symbolName.find("_objc_release_x") != std::string::npos)
+ {
+ auto x = symbolName.rfind('x');
+ auto num = symbolName.substr(x + 1);
+
+ std::vector<FunctionParameter> callTypeParams;
+ auto cc = view->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num);
+
+ if (auto idType = view->GetTypeByName({"id"}))
+ {
+ callTypeParams.emplace_back("obj", idType, true, Variable());
+ auto funcType = Type::FunctionType(idType, cc, callTypeParams);
+ func->SetUserType(funcType);
+ }
+ else
+ {
+ LogWarn("Failed to find id type for %llx, objective-c processor not ran?", func->GetStart());
+ }
+ }
+ }
+}
+
+std::string BaseFileName(const std::string& path)
+{
+ auto lastSlashPos = path.find_last_of("/\\");
+ if (lastSlashPos != std::string::npos)
+ return path.substr(lastSlashPos + 1);
+ return path;
+}
diff --git a/view/sharedcache/core/Utility.h b/view/sharedcache/core/Utility.h
new file mode 100644
index 00000000..86715557
--- /dev/null
+++ b/view/sharedcache/core/Utility.h
@@ -0,0 +1,106 @@
+#pragma once
+
+#include <cstdint>
+#include <map>
+
+#include "binaryninjaapi.h"
+
+#ifdef _MSC_VER
+inline int CountTrailingZeros(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
+inline int CountTrailingZeros(uint64_t value)
+{
+ return value == 0 ? 64 : __builtin_ctzll(value);
+}
+#endif
+
+BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt);
+
+int64_t readSLEB128(const uint8_t*& current, const uint8_t* end);
+
+uint64_t readLEB128(const uint8_t*& current, const uint8_t* end);
+
+uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end);
+
+void ApplySymbol(BinaryNinja::Ref<BinaryNinja::BinaryView> view, BinaryNinja::Ref<BinaryNinja::TypeLibrary> typeLib,
+ BinaryNinja::Ref<BinaryNinja::Symbol> symbol);
+
+// Returns the on disk file for a given path, this is required to support project files.
+std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path);
+
+// Returns the "image name" for a given path.
+// /blah/foo/bar/libObjCThing.dylib -> libObjCThing.dylib
+std::string BaseFileName(const std::string& path);
+
+// Represents a range of addresses [start, end).
+// Note that `end` is not included within the range.
+struct AddressRange
+{
+ uint64_t start;
+ uint64_t end;
+
+ AddressRange(uint64_t start, uint64_t end) : start(start), end(end) {}
+
+ AddressRange() : start(0), end(0) {}
+
+ bool Overlaps(const AddressRange& b) const { return start < b.end && b.start < end; }
+
+ bool operator<(const AddressRange& b) const { return start < b.start || (start == b.start && end < b.end); }
+
+ friend bool operator<(const AddressRange& range, uint64_t address) { return range.end <= address; }
+
+ friend bool operator<(uint64_t address, const AddressRange& range) { return address < range.start; }
+};
+
+// A map keyed by address ranges that can be looked up via any
+// address within a range thanks to C++14's transparent comparators.
+template <typename Value>
+using AddressRangeMap = std::map<AddressRange, Value, std::less<>>;
+
+// TODO: Document this!
+template <typename T>
+class WeakAllocPtr
+{
+protected:
+ std::weak_ptr<T> m_weakPtr; // Weak reference to the object
+ std::function<std::shared_ptr<T>()> m_allocator; // Function to recreate the object
+
+public:
+ explicit WeakAllocPtr(std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator) {}
+
+ WeakAllocPtr(std::weak_ptr<T> weakPtr, std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator)
+ {
+ if (weakPtr == nullptr)
+ {
+ m_weakPtr = {};
+ }
+ else
+ {
+ m_weakPtr = weakPtr;
+ }
+ }
+
+ std::shared_ptr<T> lock()
+ {
+ std::shared_ptr<T> sharedPtr = m_weakPtr.lock();
+ if (!sharedPtr)
+ {
+ sharedPtr = m_allocator();
+ m_weakPtr = sharedPtr;
+ }
+ return sharedPtr;
+ }
+
+ std::shared_ptr<T> lock_no_allocate() { return m_weakPtr.lock(); }
+};
diff --git a/view/sharedcache/core/VM.cpp b/view/sharedcache/core/VM.cpp
deleted file mode 100644
index 711c2a88..00000000
--- a/view/sharedcache/core/VM.cpp
+++ /dev/null
@@ -1,891 +0,0 @@
-//
-// Created by kat on 5/23/23.
-//
-
-/*
- This is the cross-plat file buffering logic used for SharedCache processing.
- This is used for reading large amounts of large files in a performant manner.
-
- Here be no dragons, but this code is very complex, beware.
-
- We in _all_ cases memory map the files, as we hardly ever need more than a few pages per file for most intensive operations.
-
- Memory Map Implementation:
- Of interest is that on several platforms we have to account for very low file pointer limits, and when mapping
- 40+ files, these are trivially reachable.
-
- We handle this with a "SelfAllocatingWeakPtr":
- - Calling .lock() ALWAYS delivers a shared_ptr guaranteed to stay valid. This may block waiting for a free pointer
- - As soon as that lock is released, that file pointer MAY be freed if another thread wants to open a new one, and we are at our limit.
- - Calling .lock() again on this same theoretical object will then wait for another file pointer to be freeable.
-
- VM Implementation:
-
-
- Since the caches we're operating on are by nature page aligned, we are able to use nice optimizations under the hood to translate
- "VM Addresses" to their actual in-memory counterparts.
-
- We do this with a page table, which is a map of page -> file offset.
-
- We also implement a "VMReader" here, which is a drop-in replacement for BinaryReader that operates on the VM.
- see "ObjC.cpp" for where this is used.
-
-*/
-
-
-#include "VM.h"
-#include <utility>
-#include <memory>
-#include <cstring>
-#include <stdio.h>
-#include <filesystem>
-#include <binaryninjaapi.h>
-
-#ifdef _MSC_VER
- #include <windows.h>
-#else
- #include <sys/mman.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <sys/resource.h>
-#endif
-
-static std::atomic<uint64_t> mmapCount = 0;
-
-uint64_t MMapCount()
-{
- return mmapCount;
-}
-
-void VMShutdown() {}
-
-
-std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path)
-{
- auto dscProjectFile = dscView->GetFile()->GetProjectFile();
- BinaryNinja::LogDebugF(
- "ResolveFilePath:\n of: {}\n path: {}\n pfn: {}\n pfp: {}",
- dscView->GetFile()->GetOriginalFilename(),
- path,
- dscProjectFile ? dscProjectFile->GetName() : "",
- dscProjectFile ? dscProjectFile->GetPathOnDisk() : ""
- );
-
- // If we're not in a project, just return the path we were given
- if (!dscProjectFile)
- {
- return path;
- }
-
- // TODO: do we need to support looking in subfolders?
- // Replace project file path on disk with project file name for resolution
- std::string projectFilePathOnDisk = dscProjectFile->GetPathOnDisk();
- std::string originalFilePathOnDisk = dscView->GetFile()->GetOriginalFilename();
- std::string cleanPath = path;
- if (auto pindex = cleanPath.find(projectFilePathOnDisk); pindex != std::string::npos)
- {
- cleanPath.replace(pindex, projectFilePathOnDisk.size(), dscProjectFile->GetName());
- }
- else if (auto oindex = cleanPath.find(originalFilePathOnDisk); oindex != std::string::npos)
- {
- BinaryNinja::Ref<BinaryNinja::ProjectFile> originalProjectFile = dscProjectFile->GetProject()->GetFileByPathOnDisk(originalFilePathOnDisk);
- if (!originalProjectFile)
- {
- BinaryNinja::LogErrorF("Failed to resolve file path for {}: original file {} not found", path, originalFilePathOnDisk);
- return path;
- }
- cleanPath.replace(oindex, originalFilePathOnDisk.size(), originalProjectFile->GetName());
- }
-
- size_t lastSlashPos = cleanPath.find_last_of("/\\");
- std::string fileName;
-
- if (lastSlashPos != std::string::npos) {
- fileName = cleanPath.substr(lastSlashPos + 1);
- } else {
- fileName = cleanPath;
- }
-
- auto project = dscProjectFile->GetProject();
- auto dscProjectFolder = dscProjectFile->GetFolder();
- for (const auto& file : project->GetFiles())
- {
- auto fileFolder = file->GetFolder();
- bool isSibling = false;
- if (!dscProjectFolder && !fileFolder)
- {
- // Both top-level
- isSibling = true;
- }
- else if (dscProjectFolder && fileFolder)
- {
- // Have same parent folder
- isSibling = dscProjectFolder->GetId() == fileFolder->GetId();
- }
-
- if (isSibling && file->GetName() == fileName)
- {
- return file->GetPathOnDisk();
- }
- }
-
- BinaryNinja::LogErrorF("Failed to resolve file path for {}", path);
-
- // If we couldn't find a sibling filename, just return the path we were given
- return path;
-}
-
-
-void MMAP::Map()
-{
- if (mapped)
- return;
-#ifdef _MSC_VER
- LARGE_INTEGER fileSize;
- if (!GetFileSizeEx(hFile, &fileSize))
- {
- // Handle error
- CloseHandle(hFile);
- return;
- }
- len = static_cast<size_t>(fileSize.QuadPart);
-
- HANDLE hMapping = CreateFileMapping(
- hFile, // file handle
- NULL, // security attributes
- PAGE_WRITECOPY, // protection
- 0, // maximum size (high-order DWORD)
- 0, // maximum size (low-order DWORD)
- NULL); // name of the mapping object
-
- if (hMapping == NULL)
- {
- // Handle error
- CloseHandle(hFile);
- return;
- }
-
- _mmap = static_cast<uint8_t*>(MapViewOfFile(
- hMapping, // handle to the file mapping object
- FILE_MAP_COPY, // desired access
- 0, // file offset (high-order DWORD)
- 0, // file offset (low-order DWORD)
- 0)); // number of bytes to map (0 = entire file)
-
- if (_mmap == nullptr)
- {
- // Handle error
- CloseHandle(hMapping);
- CloseHandle(hFile);
- return;
- }
-
- mapped = true;
-
- CloseHandle(hMapping);
- CloseHandle(hFile);
-
-#else
- fseek(fd, 0L, SEEK_END);
- len = ftell(fd);
- fseek(fd, 0L, SEEK_SET);
-
- void *result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u);
- if (result == MAP_FAILED)
- {
- // Handle error
- return;
- }
-
- _mmap = static_cast<uint8_t*>(result);
- mapped = true;
-#endif
-}
-
-void MMAP::Unmap()
-{
-#ifdef _MSC_VER
- if (_mmap)
- {
- UnmapViewOfFile(_mmap);
- mapped = false;
- }
-#else
- if (mapped)
- {
- munmap(_mmap, len);
- mapped = false;
- }
-#endif
-}
-
-FileAccessorCache& FileAccessorCache::Shared()
-{
- static FileAccessorCache& shared = *new FileAccessorCache;
- return shared;
-};
-
-FileAccessorCache::FileAccessorCache() = default;
-
-void FileAccessorCache::SetCacheSize(uint64_t cacheSize)
-{
- m_cacheSize = cacheSize;
- EvictFromCacheIfNeeded();
-}
-
-void FileAccessorCache::RecordAccess(const std::string& path)
-{
- auto it = m_accessors.find(path);
- if (it == m_accessors.end())
- return;
-
- // Move the entry for `path` to the end of `m_leastRecentlyOpened`.
- m_leastRecentlyOpened.splice(m_leastRecentlyOpened.end(), m_leastRecentlyOpened, it->second.second);
-}
-
-void FileAccessorCache::EvictFromCacheIfNeeded()
-{
- std::lock_guard lock(m_mutex);
- while (m_accessors.size() > m_cacheSize)
- {
- m_accessors.erase(m_leastRecentlyOpened.front());
- m_leastRecentlyOpened.pop_front();
- }
-}
-
-std::shared_ptr<LazyMappedFileAccessor> FileAccessorCache::OpenLazily(
- const uint64_t sessionID, const std::string& path,
- std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine)
-{
- std::lock_guard lock(m_mutex);
- if (auto it = m_lazyAccessors.find(path); it != m_lazyAccessors.end())
- {
- RecordAccess(path);
- return it->second;
- }
-
- auto accessor = std::make_shared<LazyMappedFileAccessor>(path,
- [=, postAllocationRoutine = std::move(postAllocationRoutine)](
- const std::string& path) {
- auto accessor = Open(sessionID, path);
- if (postAllocationRoutine)
- {
- postAllocationRoutine(accessor);
- }
- return accessor;
- });
-
- m_lazyAccessors.insert_or_assign(path, accessor);
- return accessor;
-}
-
-std::shared_ptr<MMappedFileAccessor> FileAccessorCache::Open(
- const uint64_t sessionID, const std::string& path)
-{
- EvictFromCacheIfNeeded();
-
- mmapCount++;
- auto accessor = std::shared_ptr<MMappedFileAccessor>(new MMappedFileAccessor(path),
- [this](MMappedFileAccessor* accessor) { Close(accessor); });
-
- std::lock_guard lock(m_mutex);
- auto [it, inserted] = m_accessors.insert({path, {accessor, m_leastRecentlyOpened.end()}});
- if (inserted)
- {
- m_leastRecentlyOpened.push_back(path);
- it->second.second = std::prev(it->second.second);
- }
- else
- {
- RecordAccess(path);
- }
-
- return accessor;
-}
-
-void FileAccessorCache::Close(MMappedFileAccessor* accessor)
-{
- mmapCount--;
- delete accessor;
-}
-
-std::shared_ptr<LazyMappedFileAccessor> MMappedFileAccessor::Open(
- const uint64_t sessionID, const std::string& path,
- std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine)
-{
- return FileAccessorCache::Shared().OpenLazily(sessionID, path, std::move(postAllocationRoutine));
-}
-
-void MMappedFileAccessor::InitialVMSetup()
-{
- static std::once_flag once;
- std::call_once(once, []{
- // check for BN_SHAREDCACHE_FP_MAX
- // if it exists, set maxFPLimit to that value
- unsigned long long maxFPLimit = 0;
- if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env)
- {
- // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh.
- maxFPLimit = strtoull(env, nullptr, 0);
- if (maxFPLimit < 10)
- {
- BinaryNinja::LogWarn("BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis on SharedCache Binaries.");
- }
- if (maxFPLimit == 0)
- {
- BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1");
- maxFPLimit = 1;
- }
- }
- else
- {
-#ifdef _MSC_VER
- // It is not _super_ clear what the max file pointer limit is on windows,
- // but to my understanding, we are using the windows API to map files,
- // so we should have at least 2^24;
- // kind of funny to me that windows would be the most effecient OS to
- // parallelize sharedcache processing on in terms of FP usage concerns
- maxFPLimit = 0x1000000;
-#else
- // The soft file descriptor limit on Linux and Mac is a lot lower than
- // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches
- // have 60+ files which may not leave much headroom if a user opens
- // more than one at a time. Attempt to increase the file descriptor
- // limit to 1024, and limit ourselves to caching half of them as a
- // memory vs performance trade-off (closing and re-opening a file
- // requires parsing and applying the slide information again).
- constexpr rlim_t TargetFileDescriptorLimit = 1024;
- struct rlimit rlim;
- getrlimit(RLIMIT_NOFILE, &rlim);
- unsigned long long previousLimit = rlim.rlim_cur;
- if (rlim.rlim_cur < TargetFileDescriptorLimit)
- {
- rlim.rlim_cur = std::min(TargetFileDescriptorLimit, rlim.rlim_max);
- if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
- {
- perror("setrlimit(RLIMIT_NOFILE)");
- rlim.rlim_cur = previousLimit;
- }
- }
- maxFPLimit = rlim.rlim_cur / 2;
-#endif
- }
- BinaryNinja::LogInfo("Shared Cache processing initialized with a max file descriptor limit of %lld", maxFPLimit);
- FileAccessorCache::Shared().SetCacheSize(maxFPLimit);
- });
-}
-
-
-MMappedFileAccessor::MMappedFileAccessor(const std::string& path) : m_path(path)
-{
-#ifdef _MSC_VER
- m_mmap.hFile = CreateFile(
- path.c_str(), // file name
- GENERIC_READ, // desired access (read-only)
- FILE_SHARE_READ, // share mode
- NULL, // security attributes
- OPEN_EXISTING, // creation disposition
- FILE_ATTRIBUTE_NORMAL, // flags and attributes
- NULL); // template file
-
- if (m_mmap.hFile == INVALID_HANDLE_VALUE)
- {
- // BNLogInfo("Couldn't read file at %s", path.c_str());
- throw MissingFileException();
- }
-
-#else
-#ifdef ABORT_FAILURES
- if (path.empty())
- {
- cerr << "Path is empty." << endl;
- abort();
- }
-#endif
- m_mmap.fd = fopen(path.c_str(), "r");
- if (m_mmap.fd == nullptr)
- {
- BNLogError("Serious VM Error: Couldn't read file at %s", path.c_str());
-
-#ifndef _MSC_VER
- try {
- throw BinaryNinja::ExceptionWithStackTrace("Unable to Read file");
- }
- catch (ExceptionWithStackTrace &ex)
- {
- BNLogError("%s", ex.m_stackTrace.c_str());
- BNLogError("Error: %d (%s)", errno, strerror(errno));
- }
-#endif
- throw MissingFileException();
- }
-#endif
-
- m_mmap.Map();
-}
-
-MMappedFileAccessor::~MMappedFileAccessor()
-{
- // BNLogInfo("Unmapping %s", m_path.c_str());
- m_mmap.Unmap();
-
-#ifdef _MSC_VER
- if (m_mmap.hFile != INVALID_HANDLE_VALUE)
- {
- CloseHandle(m_mmap.hFile);
- }
-#else
- if (m_mmap.fd != nullptr)
- {
- fclose(m_mmap.fd);
- }
-#endif
-}
-
-void MMappedFileAccessor::WritePointer(size_t address, size_t pointer)
-{
- *(size_t*)&m_mmap._mmap[address] = pointer;
-}
-
-template <typename T>
-T MMappedFileAccessor::Read(size_t address)
-{
- T result;
- Read(&result, address, sizeof(T));
- return result;
-}
-
-std::string MMappedFileAccessor::ReadNullTermString(size_t address)
-{
- if (address > m_mmap.len)
- return "";
- auto start = m_mmap._mmap + address;
- auto end = m_mmap._mmap + m_mmap.len;
- auto nul = std::find(start, end, 0);
- return std::string(start, nul);
-}
-
-uint8_t MMappedFileAccessor::ReadUChar(size_t address)
-{
- return Read<uint8_t>(address);
-}
-
-int8_t MMappedFileAccessor::ReadChar(size_t address)
-{
- return Read<int8_t>(address);
-}
-
-uint16_t MMappedFileAccessor::ReadUShort(size_t address)
-{
- return Read<uint16_t>(address);
-}
-
-int16_t MMappedFileAccessor::ReadShort(size_t address)
-{
- return Read<int16_t>(address);
-}
-
-uint32_t MMappedFileAccessor::ReadUInt32(size_t address)
-{
- return Read<uint32_t>(address);
-}
-
-int32_t MMappedFileAccessor::ReadInt32(size_t address)
-{
- return Read<int32_t>(address);
-}
-
-uint64_t MMappedFileAccessor::ReadULong(size_t address)
-{
- return Read<uint64_t>(address);
-}
-
-int64_t MMappedFileAccessor::ReadLong(size_t address)
-{
- return Read<int64_t>(address);
-}
-
-BinaryNinja::DataBuffer MMappedFileAccessor::ReadBuffer(size_t address, size_t length)
-{
- if (m_mmap.len <= length || address > m_mmap.len - length)
- throw MappingReadException();
-
- return BinaryNinja::DataBuffer(&m_mmap._mmap[address], length);
-}
-
-std::pair<const uint8_t*, const uint8_t*> MMappedFileAccessor::ReadSpan(size_t address, size_t length)
-{
- if (address > m_mmap.len)
- throw MappingReadException();
- if (address + length > m_mmap.len)
- throw MappingReadException();
- const uint8_t* data = (&(((uint8_t*)m_mmap._mmap)[address]));
- return {data, data + length};
-}
-
-
-void MMappedFileAccessor::Read(void* dest, size_t address, size_t length)
-{
- if (m_mmap.len <= length || address > m_mmap.len - length)
- throw MappingReadException();
-
- memcpy(dest, &m_mmap._mmap[address], length);
-}
-
-
-VM::VM(size_t pageSize, bool safe) : m_pageSize(pageSize), m_safe(safe)
-{
-}
-
-VM::~VM()
-{
-}
-
-
-void VM::MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine)
-{
- // The mappings provided for shared caches will always be page aligned.
- // We can use this to our advantage and gain considerable performance via page tables.
- // This could probably be sped up if c++ were avoided?
- // We want to create a map of page -> file offset
-
- if (vm_address % m_pageSize != 0 || size % m_pageSize != 0)
- {
- throw MappingPageAlignmentException();
- }
-
- auto accessor =
- MMappedFileAccessor::Open(sessionID, ResolveFilePath(dscView, filePath), std::move(postAllocationRoutine));
- auto [it, inserted] = m_map.insert_or_assign({vm_address, vm_address + size}, PageMapping(std::move(accessor), fileoff));
- if (m_safe && !inserted)
- {
- BNLogWarn("Remapping page 0x%zx (f: 0x%zx)", vm_address, fileoff);
- throw MappingCollisionException();
- }
-}
-
-std::pair<PageMapping, size_t> VM::MappingAtAddress(size_t address)
-{
- if (auto it = m_map.find(address); it != m_map.end())
- {
- // The PageMapping object returned contains the page, and more importantly, the file pointer (there can be
- // multiple in newer caches) This is relevant for reading out the data in the rest of this file.
- // The second item in the returned pair is the offset of `address` within the file.
- auto& range = it->first;
- auto& mapping = it->second;
- return {mapping, mapping.fileOffset + (address - range.start)};
- }
-
- throw MappingReadException();
-}
-
-
-bool VM::AddressIsMapped(uint64_t address)
-{
- auto it = m_map.find(address);
- return it != m_map.end();
-}
-
-
-uint64_t VMReader::ReadULEB128(size_t limit)
-{
- uint64_t result = 0;
- int bit = 0;
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- auto fileCursor = mapping.second;
- auto fileLimit = fileCursor + (limit - m_cursor);
- auto fa = mapping.first.fileAccessor->lock();
- auto* fileBuff = (uint8_t*)fa->Data();
- do
- {
- if (fileCursor >= fileLimit)
- return -1;
- uint64_t slice = ((uint64_t*)&((fileBuff)[fileCursor]))[0] & 0x7f;
- if (bit > 63)
- return -1;
- else
- {
- result |= (slice << bit);
- bit += 7;
- }
- } while (((uint64_t*)&(fileBuff[fileCursor++]))[0] & 0x80);
- fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer
- return result;
-}
-
-
-int64_t VMReader::ReadSLEB128(size_t limit)
-{
- uint8_t cur;
- int64_t value = 0;
- size_t shift = 0;
-
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- auto fileCursor = mapping.second;
- auto fileLimit = fileCursor + (limit - m_cursor);
- auto fa = mapping.first.fileAccessor->lock();
- auto* fileBuff = (uint8_t*)fa->Data();
-
- while (fileCursor < fileLimit)
- {
- cur = ((uint64_t*)&((fileBuff)[fileCursor]))[0];
- fileCursor++;
- value |= (cur & 0x7f) << shift;
- shift += 7;
- if ((cur & 0x80) == 0)
- break;
- }
- value = (value << (64 - shift)) >> (64 - shift);
- fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer
- return value;
-}
-
-std::string VM::ReadNullTermString(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second);
-}
-
-uint8_t VM::ReadUChar(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second);
-}
-
-int8_t VM::ReadChar(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadChar(mapping.second);
-}
-
-uint16_t VM::ReadUShort(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second);
-}
-
-int16_t VM::ReadShort(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadShort(mapping.second);
-}
-
-uint32_t VM::ReadUInt32(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second);
-}
-
-int32_t VM::ReadInt32(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second);
-}
-
-uint64_t VM::ReadULong(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadULong(mapping.second);
-}
-
-int64_t VM::ReadLong(size_t address)
-{
- auto mapping = MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadLong(mapping.second);
-}
-
-BinaryNinja::DataBuffer VM::ReadBuffer(size_t addr, size_t length)
-{
- auto mapping = MappingAtAddress(addr);
- return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length);
-}
-
-
-void VM::Read(void* dest, size_t addr, size_t length)
-{
- auto mapping = MappingAtAddress(addr);
- mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length);
-}
-
-VMReader::VMReader(std::shared_ptr<VM> vm, size_t addressSize) : m_vm(vm), m_cursor(0), m_addressSize(addressSize) {}
-
-
-void VMReader::Seek(size_t address)
-{
- m_cursor = address;
-}
-
-void VMReader::SeekRelative(size_t offset)
-{
- m_cursor += offset;
-}
-
-std::string VMReader::ReadCString(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second);
-}
-
-uint8_t VMReader::ReadUChar(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 1;
- return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second);
-}
-
-int8_t VMReader::ReadChar(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 1;
- return mapping.first.fileAccessor->lock()->ReadChar(mapping.second);
-}
-
-uint16_t VMReader::ReadUShort(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 2;
- return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second);
-}
-
-int16_t VMReader::ReadShort(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 2;
- return mapping.first.fileAccessor->lock()->ReadShort(mapping.second);
-}
-
-uint32_t VMReader::ReadUInt32(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 4;
- return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second);
-}
-
-int32_t VMReader::ReadInt32(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 4;
- return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second);
-}
-
-uint64_t VMReader::ReadULong(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 8;
- return mapping.first.fileAccessor->lock()->ReadULong(mapping.second);
-}
-
-int64_t VMReader::ReadLong(size_t address)
-{
- auto mapping = m_vm->MappingAtAddress(address);
- m_cursor = address + 8;
- return mapping.first.fileAccessor->lock()->ReadLong(mapping.second);
-}
-
-
-size_t VMReader::ReadPointer(size_t address)
-{
- if (m_addressSize == 8)
- return ReadULong(address);
- else if (m_addressSize == 4)
- return ReadUInt32(address);
-
- // no idea what horrible arch we have, should probably die here.
- return 0;
-}
-
-
-size_t VMReader::ReadPointer()
-{
- if (m_addressSize == 8)
- return Read64();
- else if (m_addressSize == 4)
- return Read32();
-
- return 0;
-}
-
-BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t length)
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += length;
- return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length);
-}
-
-BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t addr, size_t length)
-{
- auto mapping = m_vm->MappingAtAddress(addr);
- m_cursor = addr + length;
- return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length);
-}
-
-void VMReader::Read(void* dest, size_t length)
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += length;
- mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length);
-}
-
-void VMReader::Read(void* dest, size_t addr, size_t length)
-{
- auto mapping = m_vm->MappingAtAddress(addr);
- m_cursor = addr + length;
- mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length);
-}
-
-
-uint8_t VMReader::Read8()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 1;
- return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second);
-}
-
-int8_t VMReader::ReadS8()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 1;
- return mapping.first.fileAccessor->lock()->ReadChar(mapping.second);
-}
-
-uint16_t VMReader::Read16()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 2;
- return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second);
-}
-
-int16_t VMReader::ReadS16()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 2;
- return mapping.first.fileAccessor->lock()->ReadShort(mapping.second);
-}
-
-uint32_t VMReader::Read32()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 4;
- return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second);
-}
-
-int32_t VMReader::ReadS32()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 4;
- return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second);
-}
-
-uint64_t VMReader::Read64()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 8;
- return mapping.first.fileAccessor->lock()->ReadULong(mapping.second);
-}
-
-int64_t VMReader::ReadS64()
-{
- auto mapping = m_vm->MappingAtAddress(m_cursor);
- m_cursor += 8;
- return mapping.first.fileAccessor->lock()->ReadLong(mapping.second);
-}
diff --git a/view/sharedcache/core/VM.h b/view/sharedcache/core/VM.h
deleted file mode 100644
index e245751a..00000000
--- a/view/sharedcache/core/VM.h
+++ /dev/null
@@ -1,368 +0,0 @@
-//
-// Created by kat on 5/23/23.
-//
-
-#ifndef SHAREDCACHE_VM_H
-#define SHAREDCACHE_VM_H
-
-#include <binaryninjaapi.h>
-
-#include <list>
-#include <mutex>
-#include <unordered_map>
-
-void VMShutdown();
-
-std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path);
-
-template <typename T>
-class SelfAllocatingWeakPtr {
-public:
- SelfAllocatingWeakPtr(std::function<std::shared_ptr<T>()> allocator) : allocator(allocator) {}
-
- std::shared_ptr<T> lock() {
- std::shared_ptr<T> sharedPtr = weakPtr.lock();
- if (!sharedPtr) {
- sharedPtr = allocator();
- weakPtr = sharedPtr;
- }
- return sharedPtr;
- }
-
- std::shared_ptr<T> lock_no_allocate() {
- return weakPtr.lock();
- }
-
-private:
- std::weak_ptr<T> weakPtr; // Weak reference to the object
- std::function<std::shared_ptr<T>()> allocator; // Function to recreate the object
-};
-
-
-class MissingFileException : public std::exception
-{
- virtual const char* what() const throw()
- {
- return "Missing File.";
- }
-};
-
-class MMappedFileAccessor;
-
-class MMAP {
- friend MMappedFileAccessor;
-
- uint8_t *_mmap;
- FILE *fd;
- size_t len;
-
-#ifdef _MSC_VER
- HANDLE hFile = INVALID_HANDLE_VALUE; // For Windows
-#endif
-
- bool mapped = false;
-
- void Map();
-
- void Unmap();
-};
-
-class LazyMappedFileAccessor : public SelfAllocatingWeakPtr<MMappedFileAccessor> {
-public:
- LazyMappedFileAccessor(
- std::string filePath, std::function<std::shared_ptr<MMappedFileAccessor>(const std::string&)> allocator) :
- SelfAllocatingWeakPtr([this, allocator = std::move(allocator)] { return allocator(m_filePath); }),
- m_filePath(std::move(filePath))
- {}
- ~LazyMappedFileAccessor() = default;
-
- std::string_view filePath() const { return m_filePath; }
-
-private:
- std::string m_filePath;
-};
-
-uint64_t MMapCount();
-
-class MMappedFileAccessor {
- std::string m_path;
- MMAP m_mmap;
- bool m_slideInfoWasApplied = false;
-
-public:
- MMappedFileAccessor(const std::string &path);
- ~MMappedFileAccessor();
-
- static std::shared_ptr<LazyMappedFileAccessor> Open(const uint64_t sessionID, const std::string &path, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine = nullptr);
-
- static void CloseAll(const uint64_t sessionID);
-
- static void InitialVMSetup();
-
- const std::string& Path() const { return m_path; };
-
- size_t Length() const { return m_mmap.len; };
-
- void *Data() const { return m_mmap._mmap; };
-
- bool SlideInfoWasApplied() const { return m_slideInfoWasApplied; }
-
- void SetSlideInfoWasApplied(bool slideInfoWasApplied) { m_slideInfoWasApplied = slideInfoWasApplied; }
-
- /**
- * Writes to files are implemented for performance reasons and should be treated with utmost care
- *
- * They _MAY_ disappear as _soon_ as you release the lock on the this file.
- * They may also NOT disappear for the lifetime of the application.
- *
- * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical scenario.
- *
- * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be used for anything else.
- *
- * \param address
- * \param pointer
- */
- void WritePointer(size_t address, size_t pointer);
-
- std::string ReadNullTermString(size_t address);
-
- uint8_t ReadUChar(size_t address);
-
- int8_t ReadChar(size_t address);
-
- uint16_t ReadUShort(size_t address);
-
- int16_t ReadShort(size_t address);
-
- uint32_t ReadUInt32(size_t address);
-
- int32_t ReadInt32(size_t address);
-
- uint64_t ReadULong(size_t address);
-
- int64_t ReadLong(size_t address);
-
- BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length);
-
- // Returns a range of pointers within the mapped memory region corresponding to
- // {addr, length}.
- // WARNING: The pointers returned by this method is only valid for the lifetime
- // of this file accessor.
- // TODO: This should use std::span<const uint8_t> once the minimum supported
- // C++ version supports it.
- std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length);
-
- void Read(void *dest, size_t addr, size_t length);
-
- template <typename T>
- T Read(size_t address);
-};
-
-class FileAccessorCache
-{
-public:
- static FileAccessorCache& Shared();
-
- std::shared_ptr<LazyMappedFileAccessor> OpenLazily(
- const uint64_t sessionID, const std::string& path,
- std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine);
-
- void SetCacheSize(uint64_t size);
-
-private:
- FileAccessorCache();
-
- std::shared_ptr<MMappedFileAccessor> Open(
- const uint64_t sessionID, const std::string& path);
-
- void Close(MMappedFileAccessor* accessor);
-
- void RecordAccess(const std::string& path);
- void EvictFromCacheIfNeeded();
-
- std::mutex m_mutex;
- std::unordered_map<std::string, std::shared_ptr<LazyMappedFileAccessor>> m_lazyAccessors;
-
- // Ordered from least recently opened (front) to most recently opened (end).
- std::list<std::string> m_leastRecentlyOpened;
- std::unordered_map<std::string, std::pair<std::shared_ptr<MMappedFileAccessor>, std::list<std::string>::iterator>>
- m_accessors;
-
- uint64_t m_cacheSize = 8;
-};
-
-struct PageMapping {
- std::shared_ptr<LazyMappedFileAccessor> fileAccessor;
- size_t fileOffset;
- PageMapping(std::shared_ptr<LazyMappedFileAccessor> fileAccessor, size_t fileOffset)
- : fileAccessor(std::move(fileAccessor)), fileOffset(fileOffset) {}
-};
-
-
-class VMException : public std::exception {
- virtual const char *what() const throw() {
- return "Generic VM Exception";
- }
-};
-
-class MappingPageAlignmentException : public VMException {
- virtual const char *what() const throw() {
- return "Tried to create a mapping not aligned to given page size";
- }
-};
-
-class MappingReadException : VMException {
- virtual const char *what() const throw() {
- return "Tried to access unmapped page";
- }
-};
-
-class MappingCollisionException : VMException {
- virtual const char *what() const throw() {
- return "Tried to remap a page";
- }
-};
-
-class VMReader;
-
-// Represents a range of addresses [start, end).
-// Note that `end` is not included within the range.
-struct AddressRange {
- uint64_t start;
- uint64_t end;
-
- bool operator<(const AddressRange& b) const {
- return start < b.start || (start == b.start && end < b.end);
- }
-
- friend bool operator<(const AddressRange& range, uint64_t address) {
- return range.end <= address;
- }
-
- friend bool operator<(uint64_t address, const AddressRange& range) {
- return address < range.start;
- }
-};
-
-// A map keyed by address ranges that can be looked up via any
-// address within a range thanks to C++14's transparent comparators.
-template <typename Value>
-using AddressRangeMap = std::map<AddressRange, Value, std::less<>>;
-
-class VM {
- // A map keyed by address ranges that can be looked up via any
- // address within a range thanks to C++14's transparent comparators.
- AddressRangeMap<PageMapping> m_map;
- size_t m_pageSize;
- bool m_safe;
-
- friend VMReader;
-
-public:
-
- VM(size_t pageSize, bool safe = true);
-
- ~VM();
-
- void MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine);
-
- bool AddressIsMapped(uint64_t address);
-
- std::pair<PageMapping, size_t> MappingAtAddress(size_t address);
-
- std::string ReadNullTermString(size_t address);
-
- uint8_t ReadUChar(size_t address);
-
- int8_t ReadChar(size_t address);
-
- uint16_t ReadUShort(size_t address);
-
- int16_t ReadShort(size_t address);
-
- uint32_t ReadUInt32(size_t address);
-
- int32_t ReadInt32(size_t address);
-
- uint64_t ReadULong(size_t address);
-
- int64_t ReadLong(size_t address);
-
- BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length);
-
- void Read(void *dest, size_t addr, size_t length);
-};
-
-
-class VMReader {
- std::shared_ptr<VM> m_vm;
- size_t m_cursor;
- size_t m_addressSize;
-
- BNEndianness m_endianness = LittleEndian;
-
-public:
- VMReader(std::shared_ptr<VM> vm, size_t addressSize = 8);
-
- void SetEndianness(BNEndianness endianness) { m_endianness = endianness; }
-
- BNEndianness GetEndianness() const { return m_endianness; }
-
- void Seek(size_t address);
-
- void SeekRelative(size_t offset);
-
- [[nodiscard]] size_t GetOffset() const { return m_cursor; }
-
- std::string ReadCString(size_t address);
-
- uint64_t ReadULEB128(size_t cursorLimit);
-
- int64_t ReadSLEB128(size_t cursorLimit);
-
- uint8_t Read8();
-
- int8_t ReadS8();
-
- uint16_t Read16();
-
- int16_t ReadS16();
-
- uint32_t Read32();
-
- int32_t ReadS32();
-
- uint64_t Read64();
-
- int64_t ReadS64();
-
- size_t ReadPointer();
-
- uint8_t ReadUChar(size_t address);
-
- int8_t ReadChar(size_t address);
-
- uint16_t ReadUShort(size_t address);
-
- int16_t ReadShort(size_t address);
-
- uint32_t ReadUInt32(size_t address);
-
- int32_t ReadInt32(size_t address);
-
- uint64_t ReadULong(size_t address);
-
- int64_t ReadLong(size_t address);
-
- size_t ReadPointer(size_t address);
-
- BinaryNinja::DataBuffer ReadBuffer(size_t length);
-
- BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length);
-
- void Read(void *dest, size_t length);
-
- void Read(void *dest, size_t addr, size_t length);
-};
-
-#endif //SHAREDCACHE_VM_H
diff --git a/view/sharedcache/core/VirtualMemory.cpp b/view/sharedcache/core/VirtualMemory.cpp
new file mode 100644
index 00000000..b55ddefe
--- /dev/null
+++ b/view/sharedcache/core/VirtualMemory.cpp
@@ -0,0 +1,375 @@
+#include "VirtualMemory.h"
+
+// TODO: Add back the ability to do relocations on this stuff.
+// TODO: ^ the above is currently handled only by the consumers of the VirtualMemory
+// TODO: however we might still want to have this be done here as well for persistence.
+
+void VirtualMemory::MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset)
+{
+ // Create a new VirtualMemoryRegion object
+ VirtualMemoryRegion region {fileOffset, std::move(fileAccessor)};
+
+ // TODO: How to handle overlapping regions?
+ for (const auto& [existingRange, existingRegion] : m_regions)
+ {
+ if (existingRange.Overlaps(mappedRange))
+ {
+ // Handle overlapping regions, e.g., throw an exception or skip the mapping
+ BinaryNinja::LogError("Overlapping memory region %llx", existingRange.start);
+ }
+ }
+
+ // Insert the region into the map
+ m_regions.insert_or_assign(mappedRange, region);
+}
+
+std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address, uint64_t& addressOffset)
+{
+ if (const auto& it = m_regions.find(address); it != m_regions.end())
+ {
+ // The VirtualMemoryRegion object returned contains the page, and more importantly, the file pointer (there can
+ // be multiple in newer caches) This is relevant for reading out the data in the rest of this file. The second
+ // item in the returned pair is the offset of `address` within the file.
+ const auto& range = it->first;
+ auto mapping = it->second;
+ addressOffset = mapping.fileOffset + (address - range.start);
+ return mapping;
+ }
+
+ return std::nullopt;
+}
+
+std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address)
+{
+ uint64_t offset;
+ return GetRegionAtAddress(address, offset);
+}
+
+bool VirtualMemory::IsAddressMapped(uint64_t address)
+{
+ return m_regions.find(address) != m_regions.end();
+}
+
+void VirtualMemory::WritePointer(size_t address, size_t pointer)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ // Unique to the `VirtualMemory::WritePointer` we actually have an interface on the WeakFileAccessor to use.
+ // this is the mechanism for persisting our written pointers.
+ region->fileAccessor.WritePointer(offset, pointer);
+}
+
+std::string VirtualMemory::ReadCString(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadNullTermString(offset);
+}
+
+uint8_t VirtualMemory::ReadUInt8(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadUInt8(offset);
+}
+
+int8_t VirtualMemory::ReadInt8(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadInt8(offset);
+}
+
+uint16_t VirtualMemory::ReadUInt16(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadUInt16(offset);
+}
+
+int16_t VirtualMemory::ReadInt16(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadInt16(offset);
+}
+
+uint32_t VirtualMemory::ReadUInt32(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadUInt32(offset);
+}
+
+int32_t VirtualMemory::ReadInt32(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadInt32(offset);
+}
+
+uint64_t VirtualMemory::ReadUInt64(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadUInt64(offset);
+}
+
+int64_t VirtualMemory::ReadInt64(uint64_t address)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadInt64(offset);
+}
+
+BinaryNinja::DataBuffer VirtualMemory::ReadBuffer(uint64_t address, size_t length)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadBuffer(offset, length);
+}
+
+std::pair<const uint8_t*, const uint8_t*> VirtualMemory::ReadSpan(size_t address, size_t length)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ return region->fileAccessor.lock()->ReadSpan(offset, length);
+}
+
+void VirtualMemory::Read(void* dest, uint64_t address, size_t length)
+{
+ uint64_t offset;
+ auto region = GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ region->fileAccessor.lock()->Read(dest, offset, length);
+}
+
+VirtualMemoryReader::VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize)
+{
+ m_memory = memory;
+ m_addressSize = addressSize;
+ m_cursor = 0;
+}
+
+std::string VirtualMemoryReader::ReadCString(uint64_t address, size_t maxLength)
+{
+ uint64_t offset;
+ auto region = m_memory->GetRegionAtAddress(address, offset);
+ if (!region.has_value())
+ throw UnmappedRegionException(address);
+ // TODO: Advance cursor?
+ return region->fileAccessor.lock()->ReadNullTermString(offset);
+}
+
+uint64_t VirtualMemoryReader::ReadULEB128(size_t cursorLimit)
+{
+ uint64_t result = 0;
+ int bit = 0;
+ uint64_t offset;
+ auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset);
+ auto fileLimit = offset + (cursorLimit - m_cursor);
+ auto fa = mapping->fileAccessor.lock();
+ auto* fileBuff = (uint8_t*)fa->Data();
+ do
+ {
+ if (offset >= fileLimit)
+ return -1;
+ uint64_t slice = ((uint64_t*)&((fileBuff)[offset]))[0] & 0x7f;
+ if (bit > 63)
+ return -1;
+ else
+ {
+ result |= (slice << bit);
+ bit += 7;
+ }
+ } while (((uint64_t*)&(fileBuff[offset++]))[0] & 0x80);
+ // TODO: There has got to be a better way to prevent this...
+ fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer
+ return result;
+}
+
+int64_t VirtualMemoryReader::ReadSLEB128(size_t cursorLimit)
+{
+ constexpr size_t BYTE_SIZE = 7; // Number of bits in each SLEB128 byte
+ constexpr size_t INT64_BITS = 64; // Total number of bits in an int64_t
+
+ int64_t value = 0;
+ size_t shift = 0;
+ uint64_t offset;
+
+ // Retrieve associated memory region and the file buffer
+ auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset);
+ auto fileLimit = offset + (cursorLimit - m_cursor);
+ auto fileAccessor = mapping->fileAccessor.lock();
+ auto* fileBuffer = static_cast<uint8_t*>(fileAccessor->Data());
+
+ // Loop through the SLEB128 encoded bytes
+ while (offset < fileLimit)
+ {
+ uint8_t currentByte = fileBuffer[offset++];
+ value |= (static_cast<int64_t>(currentByte & 0x7F) << shift);
+ shift += BYTE_SIZE;
+
+ if ((currentByte & 0x80) == 0) // If MSB is not set, we're done
+ break;
+ }
+
+ // Properly sign-extend the value according to its size
+ value = (value << (INT64_BITS - shift)) >> (INT64_BITS - shift);
+
+ // TODO: There has got to be a better way to prevent this...
+ // Prevent deallocation of the fileAccessor
+ fileAccessor->Data();
+
+ return value;
+}
+
+uint64_t VirtualMemoryReader::ReadPointer()
+{
+ return ReadPointer(m_cursor);
+}
+
+uint64_t VirtualMemoryReader::ReadPointer(uint64_t address)
+{
+ if (m_addressSize == 8)
+ return ReadUInt64(address);
+ if (m_addressSize == 4)
+ return ReadUInt32(address);
+ // TODO: Throw here or assert.
+ return 0;
+}
+
+uint8_t VirtualMemoryReader::ReadUInt8()
+{
+ return ReadUInt8(m_cursor);
+}
+
+uint8_t VirtualMemoryReader::ReadUInt8(uint64_t address)
+{
+ m_cursor = address + 1;
+ return m_memory->ReadUInt8(address);
+}
+
+int8_t VirtualMemoryReader::ReadInt8()
+{
+ return ReadUInt8(m_cursor);
+}
+
+int8_t VirtualMemoryReader::ReadInt8(uint64_t address)
+{
+ m_cursor = address + 1;
+ return m_memory->ReadInt8(address);
+}
+
+uint16_t VirtualMemoryReader::ReadUInt16()
+{
+ return ReadUInt16(m_cursor);
+}
+
+uint16_t VirtualMemoryReader::ReadUInt16(uint64_t address)
+{
+ m_cursor = address + 2;
+ return m_memory->ReadUInt16(address);
+}
+
+int16_t VirtualMemoryReader::ReadInt16()
+{
+ return ReadInt16(m_cursor);
+}
+
+int16_t VirtualMemoryReader::ReadInt16(uint64_t address)
+{
+ m_cursor = address + 2;
+ return m_memory->ReadInt16(address);
+}
+
+uint32_t VirtualMemoryReader::ReadUInt32()
+{
+ return ReadUInt32(m_cursor);
+}
+
+uint32_t VirtualMemoryReader::ReadUInt32(uint64_t address)
+{
+ m_cursor = address + 4;
+ return m_memory->ReadUInt32(address);
+}
+
+int32_t VirtualMemoryReader::ReadInt32()
+{
+ return ReadInt32(m_cursor);
+}
+
+int32_t VirtualMemoryReader::ReadInt32(uint64_t address)
+{
+ m_cursor = address + 4;
+ return m_memory->ReadInt32(address);
+}
+
+uint64_t VirtualMemoryReader::ReadUInt64()
+{
+ return ReadUInt64(m_cursor);
+}
+
+uint64_t VirtualMemoryReader::ReadUInt64(uint64_t address)
+{
+ m_cursor = address + 8;
+ return m_memory->ReadUInt64(address);
+}
+
+int64_t VirtualMemoryReader::ReadInt64()
+{
+ return ReadInt64(m_cursor);
+}
+
+int64_t VirtualMemoryReader::ReadInt64(uint64_t address)
+{
+ m_cursor = address + 8;
+ return m_memory->ReadInt64(address);
+}
+
+BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(size_t length)
+{
+ return ReadBuffer(m_cursor, length);
+}
+
+BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(uint64_t address, size_t length)
+{
+ m_cursor = address + length;
+ return m_memory->ReadBuffer(address, length);
+}
+
+void VirtualMemoryReader::Read(void* dest, size_t length)
+{
+ Read(dest, m_cursor, length);
+}
+
+void VirtualMemoryReader::Read(void* dest, uint64_t address, size_t length)
+{
+ m_cursor = address + length;
+ m_memory->Read(dest, address, length);
+}
diff --git a/view/sharedcache/core/VirtualMemory.h b/view/sharedcache/core/VirtualMemory.h
new file mode 100644
index 00000000..fb00ebaf
--- /dev/null
+++ b/view/sharedcache/core/VirtualMemory.h
@@ -0,0 +1,157 @@
+#pragma once
+#include "FileAccessorCache.h"
+#include "MappedFileAccessor.h"
+#include "Utility.h"
+
+class UnmappedRegionException : public std::exception
+{
+ uint64_t m_address;
+
+public:
+ explicit UnmappedRegionException(uint64_t address) : m_address(address) {}
+
+ virtual const char* what() const throw()
+ {
+ thread_local std::string message;
+ message = fmt::format("Tried to access unmapped region using address {0:x}", m_address);
+ return message.c_str();
+ }
+};
+
+// A region within the virtual memory
+struct VirtualMemoryRegion
+{
+ uint64_t fileOffset;
+ // Access the memory regions contents through this.
+ // NOTE: Any read through this should be seeked to `fileOffset`
+ WeakFileAccessor fileAccessor;
+
+ VirtualMemoryRegion(const VirtualMemoryRegion&) = default;
+
+ VirtualMemoryRegion& operator=(const VirtualMemoryRegion&) = default;
+
+ VirtualMemoryRegion(VirtualMemoryRegion&&) = default;
+
+ VirtualMemoryRegion& operator=(VirtualMemoryRegion&&) = default;
+};
+
+// Contains information to handle mapping of multiple mapped files into a single memory space.
+// This models how the loader of DYLD shared caches would operate, so that we can effectively query memory regions
+// and map them into Binary Ninja.
+class VirtualMemory
+{
+ std::shared_mutex m_regionMutex;
+ AddressRangeMap<VirtualMemoryRegion> m_regions;
+
+public:
+ // At no point do we ever store a strong pointer to a file accessor, that is the job of the `FileAccessorCache`.
+ void MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset);
+
+ // Returns the region in virtual memory, along with the offset into that region where the address is located.
+ // Using the regions file accessor and the address offset you can read a regions content.
+ std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address, uint64_t& addressOffset);
+
+ std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address);
+
+ bool IsAddressMapped(uint64_t address);
+
+ // Write a pointer at a given address. This pointer will be persisted
+ // for a given `VirtualMemoryRegion` region, unlike using the MappedFileAccessor directly.
+ // The persistence is provided through the WeakFileAccessor itself and thus is unique to the construction.
+ void WritePointer(size_t address, size_t pointer);
+
+ std::string ReadCString(uint64_t address);
+
+ uint8_t ReadUInt8(uint64_t address);
+
+ int8_t ReadInt8(uint64_t address);
+
+ uint16_t ReadUInt16(uint64_t address);
+
+ int16_t ReadInt16(uint64_t address);
+
+ uint32_t ReadUInt32(uint64_t address);
+
+ int32_t ReadInt32(uint64_t address);
+
+ uint64_t ReadUInt64(uint64_t address);
+
+ int64_t ReadInt64(uint64_t address);
+
+ BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length);
+
+ std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t address, size_t length);
+
+ void Read(void* dest, uint64_t address, size_t length);
+};
+
+class VirtualMemoryReader
+{
+ std::shared_ptr<VirtualMemory> m_memory;
+ uint64_t m_cursor;
+ uint64_t m_addressSize;
+ BNEndianness m_endianness = LittleEndian;
+
+public:
+ explicit VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize = 8);
+
+ void SetEndianness(BNEndianness endianness) { m_endianness = endianness; }
+
+ BNEndianness GetEndianness() const { return m_endianness; }
+
+ void Seek(const uint64_t address) { m_cursor = address; };
+
+ void SeekRelative(const size_t offset) { m_cursor += offset; };
+
+ size_t GetOffset() const { return m_cursor; }
+
+ std::string ReadCString(uint64_t address, size_t maxLength = -1);
+
+ uint64_t ReadULEB128(size_t cursorLimit);
+
+ int64_t ReadSLEB128(size_t cursorLimit);
+
+ uint64_t ReadPointer();
+
+ uint64_t ReadPointer(uint64_t address);
+
+ uint8_t ReadUInt8();
+
+ uint8_t ReadUInt8(uint64_t address);
+
+ int8_t ReadInt8();
+
+ int8_t ReadInt8(uint64_t address);
+
+ uint16_t ReadUInt16();
+
+ uint16_t ReadUInt16(uint64_t address);
+
+ int16_t ReadInt16();
+
+ int16_t ReadInt16(uint64_t address);
+
+ uint32_t ReadUInt32();
+
+ uint32_t ReadUInt32(uint64_t address);
+
+ int32_t ReadInt32();
+
+ int32_t ReadInt32(uint64_t address);
+
+ uint64_t ReadUInt64();
+
+ uint64_t ReadUInt64(uint64_t address);
+
+ int64_t ReadInt64();
+
+ int64_t ReadInt64(uint64_t address);
+
+ BinaryNinja::DataBuffer ReadBuffer(size_t length);
+
+ BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length);
+
+ void Read(void* dest, size_t length);
+
+ void Read(void* dest, uint64_t address, size_t length);
+};
diff --git a/view/sharedcache/core/ffi.cpp b/view/sharedcache/core/ffi.cpp
new file mode 100644
index 00000000..0b37196f
--- /dev/null
+++ b/view/sharedcache/core/ffi.cpp
@@ -0,0 +1,429 @@
+#include "SharedCacheController.h"
+#include "../api/sharedcachecore.h"
+
+using namespace BinaryNinja;
+using namespace BinaryNinja::DSC;
+
+BNSharedCacheImage ImageToApi(const CacheImage& image)
+{
+ BNSharedCacheImage apiImage;
+ apiImage.name = BNAllocString(image.path.c_str());
+ apiImage.headerAddress = image.headerAddress;
+ apiImage.regionStartCount = image.regionStarts.size();
+ uint64_t* regionStarts = new uint64_t[image.regionStarts.size()];
+ for (size_t i = 0; i < image.regionStarts.size(); i++)
+ regionStarts[i] = image.regionStarts[i];
+ apiImage.regionStarts = regionStarts;
+ return apiImage;
+}
+
+CacheImage ImageFromApi(const BNSharedCacheImage& image)
+{
+ CacheImage apiImage;
+ apiImage.path = image.name;
+ apiImage.headerAddress = image.headerAddress;
+ apiImage.regionStarts.reserve(image.regionStartCount);
+ for (size_t i = 0; i < image.regionStartCount; i++)
+ apiImage.regionStarts.push_back(image.regionStarts[i]);
+ apiImage.header = nullptr;
+ return apiImage;
+}
+
+BNSharedCacheRegionType RegionTypeToApi(const CacheRegionType& regionType)
+{
+ switch (regionType)
+ {
+ case CacheRegionType::Image:
+ return SharedCacheRegionTypeImage;
+ case CacheRegionType::StubIsland:
+ return SharedCacheRegionTypeStubIsland;
+ case CacheRegionType::DyldData:
+ return SharedCacheRegionTypeDyldData;
+ default:
+ case CacheRegionType::NonImage:
+ return SharedCacheRegionTypeNonImage;
+ }
+}
+
+CacheRegionType RegionTypeFromApi(const BNSharedCacheRegionType regionType)
+{
+ switch (regionType)
+ {
+ case SharedCacheRegionTypeImage:
+ return CacheRegionType::Image;
+ case SharedCacheRegionTypeStubIsland:
+ return CacheRegionType::StubIsland;
+ case SharedCacheRegionTypeDyldData:
+ return CacheRegionType::DyldData;
+ default:
+ case SharedCacheRegionTypeNonImage:
+ return CacheRegionType::NonImage;
+ }
+}
+
+BNSharedCacheRegion RegionToApi(const CacheRegion& region)
+{
+ BNSharedCacheRegion apiRegion;
+ apiRegion.vmAddress = region.start;
+ apiRegion.name = BNAllocString(region.name.c_str());
+ apiRegion.size = region.size;
+ apiRegion.flags = region.flags;
+ apiRegion.regionType = RegionTypeToApi(region.type);
+ // If not associated with image this will be zeroed.
+ apiRegion.imageStart = region.imageStart.value_or(0);
+ return apiRegion;
+}
+
+CacheRegion RegionFromApi(const BNSharedCacheRegion& apiRegion)
+{
+ CacheRegion region;
+ region.start = apiRegion.vmAddress;
+ region.name = apiRegion.name;
+ region.size = apiRegion.size;
+ region.flags = apiRegion.flags;
+ region.type = RegionTypeFromApi(apiRegion.regionType);
+ return region;
+}
+
+BNSharedCacheSymbol SymbolToApi(const CacheSymbol& symbol)
+{
+ BNSharedCacheSymbol apiSymbol;
+ apiSymbol.name = BNAllocString(symbol.name.c_str());
+ apiSymbol.address = symbol.address;
+ apiSymbol.symbolType = symbol.type;
+ return apiSymbol;
+}
+
+CacheSymbol SymbolFromApi(const BNSharedCacheSymbol& apiSymbol)
+{
+ CacheSymbol symbol;
+ symbol.name = apiSymbol.name;
+ symbol.address = apiSymbol.address;
+ symbol.type = apiSymbol.symbolType;
+ return symbol;
+}
+
+BNSharedCacheEntryType EntryTypeToApi(const CacheEntryType& entryType)
+{
+ switch (entryType)
+ {
+ case CacheEntryType::Primary:
+ return SharedCacheEntryTypePrimary;
+ case CacheEntryType::Stub:
+ return SharedCacheEntryTypeStub;
+ case CacheEntryType::Symbols:
+ return SharedCacheEntryTypeSymbols;
+ case CacheEntryType::DyldData:
+ return SharedCacheEntryTypeDyldData;
+ default:
+ case CacheEntryType::Secondary:
+ return SharedCacheEntryTypeSecondary;
+ }
+}
+
+BNSharedCacheMappingInfo MappingToApi(const dyld_cache_mapping_info& mapping)
+{
+ BNSharedCacheMappingInfo apiMapping;
+ apiMapping.vmAddress = mapping.address;
+ apiMapping.size = mapping.size;
+ apiMapping.fileOffset = mapping.fileOffset;
+ return apiMapping;
+}
+
+BNSharedCacheEntry EntryToApi(const CacheEntry& entry)
+{
+ BNSharedCacheEntry apiEntry;
+ apiEntry.path = BNAllocString(entry.GetFilePath().c_str());
+ apiEntry.entryType = EntryTypeToApi(entry.GetType());
+ const auto& mappings = entry.GetMappings();
+ apiEntry.mappingCount = mappings.size();
+ apiEntry.mappings = new BNSharedCacheMappingInfo[mappings.size()];
+ for (size_t i = 0; i < mappings.size(); i++)
+ apiEntry.mappings[i] = MappingToApi(mappings[i]);
+ return apiEntry;
+}
+
+extern "C"
+{
+ BNSharedCacheController* BNGetSharedCacheController(BNBinaryView* data)
+ {
+ Ref<BinaryView> view = new BinaryView(BNNewViewReference(data));
+ auto controller = SharedCacheController::FromView(*view);
+ if (!controller)
+ return nullptr;
+ return DSC_API_OBJECT_REF(controller);
+ }
+
+ BNSharedCacheController* BNNewSharedCacheControllerReference(BNSharedCacheController* controller)
+ {
+ return DSC_API_OBJECT_NEW_REF(controller);
+ }
+
+ void BNFreeSharedCacheControllerReference(BNSharedCacheController* controller)
+ {
+ DSC_API_OBJECT_FREE(controller);
+ }
+
+ bool BNSharedCacheControllerApplyImage(
+ BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheImage* image)
+ {
+ Ref<BinaryView> view = new BinaryView(BNNewViewReference(data));
+ // LoadImage will use the header, lets do everyone a favor and use the existing image!
+ if (const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress))
+ return controller->object->ApplyImage(*view, *realImage);
+ // They gave us an unknown image, we will not have header information.
+ return controller->object->ApplyImage(*view, ImageFromApi(*image));
+ }
+
+ bool BNSharedCacheControllerApplyRegion(
+ BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheRegion* region)
+ {
+ Ref<BinaryView> view = new BinaryView(BNNewViewReference(data));
+ return controller->object->ApplyRegion(*view, RegionFromApi(*region));
+ }
+
+ bool BNSharedCacheControllerIsRegionLoaded(BNSharedCacheController* controller, BNSharedCacheRegion* region)
+ {
+ return controller->object->IsRegionLoaded(RegionFromApi(*region));
+ }
+
+ bool BNSharedCacheControllerIsImageLoaded(BNSharedCacheController* controller, BNSharedCacheImage* image)
+ {
+ return controller->object->IsImageLoaded(ImageFromApi(*image));
+ }
+
+ bool BNSharedCacheControllerGetRegionAt(
+ BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion)
+ {
+ auto region = controller->object->GetCache().GetRegionAt(address);
+ if (!region)
+ return false;
+ *outRegion = RegionToApi(*region);
+ return true;
+ }
+
+ bool BNSharedCacheControllerGetRegionContaining(
+ BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion)
+ {
+ auto region = controller->object->GetCache().GetRegionContaining(address);
+ if (!region)
+ return false;
+ *outRegion = RegionToApi(*region);
+ return true;
+ }
+
+ BNSharedCacheRegion* BNSharedCacheControllerGetRegions(BNSharedCacheController* controller, size_t* count)
+ {
+ auto regions = controller->object->GetCache().GetRegions();
+ *count = regions.size();
+ BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count];
+ int idx = 0;
+ for (const auto& [_, region] : regions)
+ apiRegions[idx++] = RegionToApi(region);
+ return apiRegions;
+ }
+
+ BNSharedCacheRegion* BNSharedCacheControllerGetLoadedRegions(BNSharedCacheController* controller, size_t* count)
+ {
+ auto loadedRegionStarts = controller->object->GetLoadedRegions();
+
+ // TODO: This translation should likely exist in the core cache controller class?
+ std::vector<CacheRegion> loadedRegions;
+ for (auto start : loadedRegionStarts)
+ {
+ auto region = controller->object->GetCache().GetRegionAt(start);
+ if (region)
+ loadedRegions.push_back(*region);
+ }
+
+ *count = loadedRegions.size();
+ BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count];
+ // I am too lazy to add a real conversion here.
+ int idx = 0;
+ for (const auto& region : loadedRegions)
+ {
+ apiRegions[idx] = RegionToApi(region);
+ idx++;
+ }
+ return apiRegions;
+ }
+
+ uint64_t* BNSharedCacheAllocRegionList(uint64_t* list, size_t count)
+ {
+ uint64_t* newList = new uint64_t[count];
+ for (size_t i = 0; i < count; i++)
+ newList[i] = list[i];
+ return newList;
+ }
+
+ void BNSharedCacheFreeRegion(BNSharedCacheRegion region)
+ {
+ BNFreeString(region.name);
+ }
+
+ void BNSharedCacheFreeRegionList(BNSharedCacheRegion* regions, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNSharedCacheFreeRegion(regions[i]);
+ delete[] regions;
+ }
+
+ bool BNSharedCacheControllerGetImageAt(
+ BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage)
+ {
+ auto image = controller->object->GetCache().GetImageAt(address);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ bool BNSharedCacheControllerGetImageContaining(
+ BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage)
+ {
+ auto image = controller->object->GetCache().GetImageContaining(address);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ bool BNSharedCacheControllerGetImageWithName(
+ BNSharedCacheController* controller, const char* name, BNSharedCacheImage* outImage)
+ {
+ auto image = controller->object->GetCache().GetImageWithName(name);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ char** BNSharedCacheControllerGetImageDependencies(
+ BNSharedCacheController* controller, BNSharedCacheImage* image, size_t* count)
+ {
+ // GetDependencies will use the header, lets do everyone a favor and use the existing image!
+ const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress);
+ if (!realImage.has_value())
+ return nullptr;
+ const auto dependencies = realImage->GetDependencies();
+
+ std::vector<const char*> dependencyPtrs;
+ dependencyPtrs.reserve(dependencies.size());
+ for (const auto& dependency : dependencies)
+ dependencyPtrs.push_back(dependency.c_str());
+ *count = dependencyPtrs.size();
+ return BNAllocStringList(dependencyPtrs.data(), dependencyPtrs.size());
+ }
+
+ BNSharedCacheImage* BNSharedCacheControllerGetImages(BNSharedCacheController* controller, size_t* count)
+ {
+ auto images = controller->object->GetCache().GetImages();
+ *count = images.size();
+ BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count];
+ size_t idx = 0;
+ for (const auto& [_, image] : images)
+ apiImages[idx++] = ImageToApi(image);
+ return apiImages;
+ }
+
+ BNSharedCacheImage* BNSharedCacheControllerGetLoadedImages(BNSharedCacheController* controller, size_t* count)
+ {
+ auto loadedImageStarts = controller->object->GetLoadedImages();
+
+ // TODO: This translation should likely exist in the core cache controller class?
+ std::vector<CacheImage> loadedImages;
+ for (auto start : loadedImageStarts)
+ {
+ auto image = controller->object->GetCache().GetImageAt(start);
+ if (image)
+ loadedImages.push_back(*image);
+ }
+
+ *count = loadedImages.size();
+ BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count];
+ for (size_t i = 0; i < *count; i++)
+ apiImages[i] = ImageToApi(loadedImages[i]);
+ return apiImages;
+ }
+
+ void BNSharedCacheFreeImage(BNSharedCacheImage image)
+ {
+ BNFreeString(image.name);
+ delete[] image.regionStarts;
+ }
+
+ void BNSharedCacheFreeImageList(BNSharedCacheImage* images, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNSharedCacheFreeImage(images[i]);
+ delete[] images;
+ }
+
+ bool BNSharedCacheControllerGetSymbolAt(
+ BNSharedCacheController* controller, uint64_t address, BNSharedCacheSymbol* outSymbol)
+ {
+ auto symbol = controller->object->GetCache().GetSymbolAt(address);
+ if (!symbol)
+ return false;
+ *outSymbol = SymbolToApi(*symbol);
+ return true;
+ }
+
+ bool BNSharedCacheControllerGetSymbolWithName(
+ BNSharedCacheController* controller, const char* name, BNSharedCacheSymbol* outSymbol)
+ {
+ auto symbol = controller->object->GetCache().GetSymbolWithName(name);
+ if (!symbol)
+ return false;
+ *outSymbol = SymbolToApi(*symbol);
+ return true;
+ }
+
+ BNSharedCacheSymbol* BNSharedCacheControllerGetSymbols(BNSharedCacheController* controller, size_t* count)
+ {
+ auto symbols = controller->object->GetCache().GetSymbols();
+ *count = symbols.size();
+ BNSharedCacheSymbol* apiSymbols = new BNSharedCacheSymbol[*count];
+ size_t idx = 0;
+ for (const auto& [_, symbol] : symbols)
+ apiSymbols[idx++] = SymbolToApi(symbol);
+ return apiSymbols;
+ }
+
+
+ void BNSharedCacheFreeSymbol(BNSharedCacheSymbol symbol)
+ {
+ BNFreeString(symbol.name);
+ }
+
+ void BNSharedCacheFreeSymbolList(BNSharedCacheSymbol* symbols, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNSharedCacheFreeSymbol(symbols[i]);
+ delete[] symbols;
+ }
+
+ BNSharedCacheEntry* BNSharedCacheControllerGetEntries(BNSharedCacheController* controller, size_t* count)
+ {
+ auto entries = controller->object->GetCache().GetEntries();
+ *count = entries.size();
+ BNSharedCacheEntry* apiEntries = new BNSharedCacheEntry[*count];
+ size_t idx = 0;
+ for (const auto& [_, entry] : entries)
+ apiEntries[idx++] = EntryToApi(entry);
+ return apiEntries;
+ }
+
+ void BNSharedCacheFreeEntry(BNSharedCacheEntry entry)
+ {
+ BNFreeString(entry.path);
+ delete[] entry.mappings;
+ }
+
+ void BNSharedCacheFreeEntryList(BNSharedCacheEntry* entries, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNSharedCacheFreeEntry(entries[i]);
+ delete[] entries;
+ }
+};
diff --git a/view/sharedcache/core/ffi_global.h b/view/sharedcache/core/ffi_global.h
new file mode 100644
index 00000000..cd2727b9
--- /dev/null
+++ b/view/sharedcache/core/ffi_global.h
@@ -0,0 +1,41 @@
+/*
+Copyright 2020-2024 Vector 35 Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#pragma once
+
+// Define macros for defining objects exposed by the API
+#define DECLARE_DSC_API_OBJECT(handle, cls) \
+ namespace BinaryNinja::DSC { \
+ class cls; \
+ } \
+ struct handle \
+ { \
+ BinaryNinja::DSC::cls* object; \
+ }
+#define IMPLEMENT_DSC_API_OBJECT(handle) \
+\
+private: \
+ handle m_apiObject; \
+\
+public: \
+ typedef handle* APIHandle; \
+ handle* GetAPIObject() \
+ { \
+ return &m_apiObject; \
+ } \
+\
+private:
+#define INIT_DSC_API_OBJECT() m_apiObject.object = this;
diff --git a/view/sharedcache/core/refcountobject.h b/view/sharedcache/core/refcountobject.h
new file mode 100644
index 00000000..1b79f12f
--- /dev/null
+++ b/view/sharedcache/core/refcountobject.h
@@ -0,0 +1,223 @@
+/*
+Copyright 2020-2024 Vector 35 Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#pragma once
+
+#ifdef WIN32
+ #include <windows.h>
+#endif
+#include <stddef.h>
+#include <vector>
+#include <atomic>
+
+namespace BinaryNinja::DSC {
+ class DSCRefCountObject
+ {
+ // CORE_ALLOCATED_CLASS(RefCountObject)
+
+ public:
+ std::atomic<int> m_refs;
+
+ DSCRefCountObject() : m_refs(0) {}
+
+ virtual ~DSCRefCountObject() {}
+
+ virtual void AddRef() { m_refs.fetch_add(1); }
+
+ virtual void Release()
+ {
+ if (m_refs.fetch_sub(1) == 1)
+ delete this;
+ }
+
+ virtual void AddAPIRef() { AddRef(); }
+
+ virtual void ReleaseAPIRef() { Release(); }
+ };
+
+
+ template <class T>
+ class DSCRef
+ {
+ T* m_obj;
+#ifdef BN_REF_COUNT_DEBUG
+ void* m_assignmentTrace = nullptr;
+#endif
+
+ public:
+ DSCRef<T>() : m_obj(NULL) {}
+
+ DSCRef<T>(T* obj) : m_obj(obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ DSCRef<T>(const DSCRef<T>& obj) : m_obj(obj.m_obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ ~DSCRef<T>()
+ {
+ if (m_obj)
+ {
+ m_obj->Release();
+#ifdef BN_REF_COUNT_DEBUG
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+#endif
+ }
+ }
+
+ // move constructor
+ DSCRef<T>(DSCRef<T>&& other) : m_obj(other.m_obj)
+ {
+ other.m_obj = 0;
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = other.m_assignmentTrace;
+#endif
+ }
+
+ // move assignment (inefficient in this case)
+ // Ref<T>& operator=(Ref<T>&& other)
+ // {
+ // if (m_obj)
+ // {
+ // #ifdef BN_REF_COUNT_DEBUG
+ // BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+ // #endif
+ // m_obj->Release();
+ // }
+ // m_obj = other.m_obj;
+ // other.m_obj = 0;
+ // #ifdef BN_REF_COUNT_DEBUG
+ // m_assignmentTrace = other.m_assignmentTrace;
+ // #endif
+ // return *this;
+ // }
+
+ DSCRef<T>& operator=(const DSCRef<T>& obj)
+ {
+#ifdef BN_REF_COUNT_DEBUG
+ if (m_obj)
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+ if (obj.m_obj)
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ T* oldObj = m_obj;
+ m_obj = obj.m_obj;
+ if (m_obj)
+ m_obj->AddRef();
+ if (oldObj)
+ oldObj->Release();
+ return *this;
+ }
+
+ DSCRef<T>& operator=(T* obj)
+ {
+#ifdef BN_REF_COUNT_DEBUG
+ if (m_obj)
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+ if (obj)
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ T* oldObj = m_obj;
+ m_obj = obj;
+ if (m_obj)
+ m_obj->AddRef();
+ if (oldObj)
+ oldObj->Release();
+ return *this;
+ }
+
+ operator T*() const { return m_obj; }
+
+ T* operator->() const { return m_obj; }
+
+ T& operator*() const { return *m_obj; }
+
+ bool operator!() const { return m_obj == NULL; }
+
+ T* GetPtr() const { return m_obj; }
+
+ bool operator==(const DSCRef<T>& obj) const { return m_obj == obj.m_obj; }
+
+ bool operator!=(const DSCRef<T>& obj) const { return m_obj != obj.m_obj; }
+
+ bool operator<(const DSCRef<T>& obj) const { return m_obj < obj.m_obj; }
+
+ template <typename H>
+ friend H AbslHashValue(H h, const DSCRef<T>& value)
+ {
+ return AbslHashValue(std::move(h), value.m_obj);
+ }
+ };
+
+
+ // Macro-like functions to manage referenced objects for the external API
+ template <class T>
+ static typename T::APIHandle DSC_API_OBJECT_REF(T* obj)
+ {
+ if (obj == nullptr)
+ return nullptr;
+ obj->AddAPIRef();
+ return obj->GetAPIObject();
+ }
+
+ template <class T>
+ static typename T::APIHandle DSC_API_OBJECT_REF(const DSCRef<T>& obj)
+ {
+ if (!obj)
+ return nullptr;
+ obj->AddAPIRef();
+ return obj->GetAPIObject();
+ }
+
+ // template <class T>
+ // static typename T::APIHandle DSC_API_OBJECT_REF(const APIRef<T>& obj)
+ //{
+ // if (!obj)
+ // return nullptr;
+ // obj->AddAPIRef();
+ // return obj->GetAPIObject();
+ // }
+
+ template <class T>
+ static T* DSC_API_OBJECT_NEW_REF(T* obj)
+ {
+ if (obj)
+ obj->object->AddAPIRef();
+ return obj;
+ }
+
+ template <class T>
+ static void DSC_API_OBJECT_FREE(T* obj)
+ {
+ if (obj)
+ obj->object->ReleaseAPIRef();
+ }
+}; // namespace BinaryNinja::DSC