summaryrefslogtreecommitdiff
path: root/view/sharedcache/core/SharedCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'view/sharedcache/core/SharedCache.cpp')
-rw-r--r--view/sharedcache/core/SharedCache.cpp1674
1 files changed, 968 insertions, 706 deletions
diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp
index 834ac988..9139d7ba 100644
--- a/view/sharedcache/core/SharedCache.cpp
+++ b/view/sharedcache/core/SharedCache.cpp
@@ -2,8 +2,6 @@
// Created by kat on 5/19/23.
//
-#include "binaryninjaapi.h"
-
/* ---
* This is the primary image loader logic for Shared Caches
*
@@ -26,20 +24,28 @@
#include "SharedCache.h"
+#include "binaryninjaapi.h"
#include "DSCView.h"
#include "ObjC.h"
+#include <algorithm>
+#include <fcntl.h>
#include <filesystem>
+#include <limits>
+#include <memory>
#include <mutex>
#include <optional>
#include <unordered_map>
#include <utility>
-#include <fcntl.h>
-#include <memory>
+#include <vector>
using namespace BinaryNinja;
using namespace SharedCacheCore;
+namespace SharedCacheCore {
+
+namespace {
+
#ifdef _MSC_VER
int count_trailing_zeros(uint64_t value) {
@@ -56,37 +62,70 @@ int count_trailing_zeros(uint64_t value) {
}
#endif
-namespace SharedCacheCore {
+struct MemoryRegionStatus
+{
+ bool loaded = false;
+ bool headerInitialized = false;
+};
-struct SharedCacheState {
- std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>
- exportInfos;
- std::unordered_map<uint64_t, std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>>
- symbolInfos;
+} // unnamed namespace
- std::unordered_map<std::string, uint64_t> imageStarts;
+// 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::vector<MemoryRegion> regionsMappedIntoMemory;
+ std::optional<std::pair<uint64_t, uint64_t>> objcOptimizationDataRange;
- std::vector<BackingCache> backingCaches;
+ std::string baseFilePath;
+ SharedCacheFormat cacheFormat = RegularCacheFormat;
- std::vector<MemoryRegion> stubIslandRegions; // TODO honestly both of these should be refactored into nonImageRegions. :p
- std::vector<MemoryRegion> dyldDataRegions;
- std::vector<MemoryRegion> nonImageRegions;
+ MemoryRegion* AddMemoryRegion(MemoryRegion region);
+ void AddPotentiallyOverlappingMemoryRegion(MemoryRegion region);
+#ifndef NDEBUG
+ void Verify() const;
+#endif
- std::optional<std::pair<size_t, size_t>> objcOptimizationDataRange;
+ uint64_t BaseAddress() const;
- std::string baseFilePath;
- SharedCache::SharedCacheFormat cacheFormat;
- DSCViewState viewState = DSCViewStateUnloaded;
+ void Store(SerializationContext&) const;
+ static std::optional<SharedCache::CacheInfo> Load(DeserializationContext&);
};
-} // namespace SharedCacheCore
+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&);
-struct SharedCache::ViewSpecificState {
+ void Merge(SharedCache::ModifiedState&& other);
+};
+
+struct SharedCache::ViewSpecificState
+{
std::mutex typeLibraryMutex;
std::unordered_map<std::string, Ref<TypeLibrary>> typeLibraries;
@@ -94,10 +133,17 @@ struct SharedCache::ViewSpecificState {
std::atomic<BNDSCViewLoadProgress> progress;
+ std::mutex cacheInfoMutex;
+ std::shared_ptr<const SharedCache::CacheInfo> cacheInfo;
+
std::mutex stateMutex;
- std::shared_ptr<SharedCacheState> cachedState;
+ struct State state;
+
+ std::atomic<DSCViewState> viewState;
+ uint64_t savedModifications = 0;
};
+namespace {
std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForId(uint64_t viewIdentifier, bool insertIfNeeded = true)
{
@@ -159,6 +205,20 @@ BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt)
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"
@@ -212,6 +272,7 @@ uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end)
return value;
}
+} // unnamed namespace
uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView)
{
@@ -275,8 +336,133 @@ uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::Bin
}
}
+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;
+}
+
+void SharedCache::SetMemoryRegionHeaderInitialized(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.headerInitialized = true;
+}
-void SharedCache::PerformInitialLoad()
+void SharedCache::PerformInitialLoad(std::lock_guard<std::mutex>& lock)
{
m_logger->LogInfo("Performing initial load of Shared Cache");
auto path = m_dscView->GetFile()->GetOriginalFilename();
@@ -284,9 +470,9 @@ void SharedCache::PerformInitialLoad()
m_viewSpecificState->progress = LoadProgressLoadingCaches;
- WillMutateState();
-
- MutableState().baseFilePath = path;
+ CacheInfo initialState;
+ initialState.baseFilePath = path;
+ initialState.cacheFormat = RegularCacheFormat;
DataBuffer sig = baseFile->ReadBuffer(0, 4);
if (sig.GetLength() != 4)
@@ -295,14 +481,12 @@ void SharedCache::PerformInitialLoad()
if (strncmp(magic, "dyld", 4) != 0)
abort();
- MutableState().cacheFormat = RegularCacheFormat;
-
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)
- MutableState().cacheFormat = RegularCacheFormat;
+ initialState.cacheFormat = RegularCacheFormat;
size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset);
size_t headerEnd = primaryCacheHeader.mappingOffset;
@@ -311,22 +495,23 @@ void SharedCache::PerformInitialLoad()
if (primaryCacheHeader.cacheType != 2)
{
if (std::filesystem::exists(ResolveFilePath(m_dscView, baseFile->Path() + ".01")))
- MutableState().cacheFormat = LargeCacheFormat;
+ initialState.cacheFormat = LargeCacheFormat;
else
- MutableState().cacheFormat = SplitCacheFormat;
+ initialState.cacheFormat = SplitCacheFormat;
}
else
- MutableState().cacheFormat = iOS16CacheFormat;
+ initialState.cacheFormat = iOS16CacheFormat;
}
if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize)
{
uint64_t objcOptsOffset = primaryCacheHeader.objcOptsOffset;
uint64_t objcOptsSize = primaryCacheHeader.objcOptsSize;
- MutableState().objcOptimizationDataRange = {objcOptsOffset, objcOptsSize};
+ initialState.objcOptimizationDataRange = {objcOptsOffset, objcOptsSize};
}
- switch (State().cacheFormat)
+ std::vector<MemoryRegion> nonImageMemoryRegions;
+ switch (initialState.cacheFormat)
{
case RegularCacheFormat:
{
@@ -340,7 +525,7 @@ void SharedCache::PerformInitialLoad()
baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping));
cache.mappings.push_back(mapping);
}
- MutableState().backingCaches.push_back(std::move(cache));
+ initialState.backingCaches.push_back(std::move(cache));
dyld_cache_image_info img {};
@@ -348,7 +533,7 @@ void SharedCache::PerformInitialLoad()
{
baseFile->Read(&img, primaryCacheHeader.imagesOffsetOld + (i * sizeof(img)), sizeof(img));
auto iname = baseFile->ReadNullTermString(img.pathFileOffset);
- MutableState().imageStarts[iname] = img.address;
+ initialState.imageStarts[iname] = img.address;
}
m_logger->LogInfo("Found %d images in the shared cache", primaryCacheHeader.imagesCountOld);
@@ -381,7 +566,8 @@ void SharedCache::PerformInitialLoad()
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);
- MutableState().stubIslandRegions.push_back(std::move(stubIslandRegion));
+ stubIslandRegion.type = MemoryRegion::Type::StubIsland;
+ nonImageMemoryRegions.push_back(std::move(stubIslandRegion));
}
}
}
@@ -405,7 +591,7 @@ void SharedCache::PerformInitialLoad()
baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping));
cache.mappings.push_back(mapping);
}
- MutableState().backingCaches.push_back(std::move(cache));
+ initialState.backingCaches.push_back(std::move(cache));
dyld_cache_image_info img {};
@@ -413,14 +599,14 @@ void SharedCache::PerformInitialLoad()
{
baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img));
auto iname = baseFile->ReadNullTermString(img.pathFileOffset);
- MutableState().imageStarts[iname] = img.address;
+ initialState.imageStarts[iname] = img.address;
}
if (primaryCacheHeader.branchPoolsCount)
{
for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++)
{
- MutableState().imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
+ initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()));
}
}
@@ -489,10 +675,11 @@ void SharedCache::PerformInitialLoad()
stubIslandRegion.size = size;
stubIslandRegion.prettyName = subCacheFilename + "::_stubs";
stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable);
- MutableState().stubIslandRegions.push_back(std::move(stubIslandRegion));
+ stubIslandRegion.type = MemoryRegion::Type::StubIsland;
+ nonImageMemoryRegions.push_back(std::move(stubIslandRegion));
}
- MutableState().backingCaches.push_back(std::move(subCache));
+ initialState.backingCaches.push_back(std::move(subCache));
}
break;
}
@@ -509,7 +696,7 @@ void SharedCache::PerformInitialLoad()
baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping));
cache.mappings.push_back(mapping);
}
- MutableState().backingCaches.push_back(std::move(cache));
+ initialState.backingCaches.push_back(std::move(cache));
dyld_cache_image_info img {};
@@ -517,14 +704,14 @@ void SharedCache::PerformInitialLoad()
{
baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img));
auto iname = baseFile->ReadNullTermString(img.pathFileOffset);
- MutableState().imageStarts[iname] = img.address;
+ initialState.imageStarts[iname] = img.address;
}
if (primaryCacheHeader.branchPoolsCount)
{
for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++)
{
- MutableState().imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
+ initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()));
}
}
@@ -565,7 +752,7 @@ void SharedCache::PerformInitialLoad()
subCache.mappings.push_back(subCacheMapping);
}
- MutableState().backingCaches.push_back(std::move(subCache));
+ initialState.backingCaches.push_back(std::move(subCache));
if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0
&& subCacheHeader.imagesTextOffset == 0)
@@ -578,7 +765,8 @@ void SharedCache::PerformInitialLoad()
stubIslandRegion.size = size;
stubIslandRegion.prettyName = subCacheFilename + "::_stubs";
stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable);
- MutableState().stubIslandRegions.push_back(std::move(stubIslandRegion));
+ stubIslandRegion.type = MemoryRegion::Type::StubIsland;
+ nonImageMemoryRegions.push_back(std::move(stubIslandRegion));
}
}
@@ -607,7 +795,7 @@ void SharedCache::PerformInitialLoad()
subCache.mappings.push_back(subCacheMapping);
}
- MutableState().backingCaches.push_back(std::move(subCache));
+ initialState.backingCaches.push_back(std::move(subCache));
}
catch (...)
{
@@ -629,7 +817,7 @@ void SharedCache::PerformInitialLoad()
cache.mappings.push_back(mapping);
}
- MutableState().backingCaches.push_back(std::move(cache));
+ initialState.backingCaches.push_back(std::move(cache));
dyld_cache_image_info img {};
@@ -637,14 +825,14 @@ void SharedCache::PerformInitialLoad()
{
baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img));
auto iname = baseFile->ReadNullTermString(img.pathFileOffset);
- MutableState().imageStarts[iname] = img.address;
+ initialState.imageStarts[iname] = img.address;
}
if (primaryCacheHeader.branchPoolsCount)
{
for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++)
{
- MutableState().imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
+ initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] =
baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()));
}
}
@@ -716,11 +904,12 @@ void SharedCache::PerformInitialLoad()
dyldDataRegion.size = size;
dyldDataRegion.prettyName = subCacheFilename + "::_data" + std::to_string(j);
dyldDataRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable);
- MutableState().dyldDataRegions.push_back(std::move(dyldDataRegion));
+ dyldDataRegion.type = MemoryRegion::Type::DyldData;
+ nonImageMemoryRegions.push_back(std::move(dyldDataRegion));
}
}
- MutableState().backingCaches.push_back(std::move(subCache));
+ initialState.backingCaches.push_back(std::move(subCache));
if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0
&& subCacheHeader.imagesTextOffset == 0)
@@ -733,7 +922,8 @@ void SharedCache::PerformInitialLoad()
stubIslandRegion.size = size;
stubIslandRegion.prettyName = subCacheFilename + "::_stubs";
stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable);
- MutableState().stubIslandRegions.push_back(std::move(stubIslandRegion));
+ stubIslandRegion.type = MemoryRegion::Type::StubIsland;
+ nonImageMemoryRegions.push_back(std::move(stubIslandRegion));
}
}
@@ -764,7 +954,7 @@ void SharedCache::PerformInitialLoad()
subCache.mappings.push_back(subCacheMapping);
}
- MutableState().backingCaches.push_back(std::move(subCache));
+ initialState.backingCaches.push_back(std::move(subCache));
}
catch (...)
{}
@@ -777,53 +967,67 @@ void SharedCache::PerformInitialLoad()
// We have set up enough metadata to map VM now.
- auto vm = GetVMMap();
+ auto vm = GetVMMap(initialState);
if (!vm)
{
m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal.");
return;
}
- for (const auto& start : State().imageStarts)
+ for (const auto& start : initialState.imageStarts)
{
- try {
+ try
+ {
auto imageHeader = SharedCache::LoadHeaderForAddress(vm, start.second, start.first);
- if (imageHeader)
+ if (!imageHeader)
{
- if (imageHeader->linkeditPresent && vm->AddressIsMapped(imageHeader->linkeditSegment.vmaddr))
+ 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")
{
- auto mapping = vm->MappingAtAddress(imageHeader->linkeditSegment.vmaddr);
- imageHeader->exportTriePath = mapping.first.fileAccessor->filePath();
+ if (auto it = initialState.memoryRegions.find(segment.vmaddr);
+ it != initialState.memoryRegions.end())
+ {
+ image.regionStarts.push_back(it->second.start);
+ continue;
+ }
}
- MutableState().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;
- MemoryRegion sectionRegion;
- sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName);
- sectionRegion.start = segment.vmaddr;
- sectionRegion.size = segment.vmsize;
- 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;
+ MemoryRegion sectionRegion;
+ sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName);
+ sectionRegion.start = segment.vmaddr;
+ sectionRegion.size = segment.vmsize;
+ uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot);
- sectionRegion.flags = (BNSegmentFlag)flags;
- image.regions.push_back(sectionRegion);
- }
- MutableState().images.push_back(image);
- }
- else
- {
- m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str());
+ // 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)
{
@@ -831,9 +1035,15 @@ void SharedCache::PerformInitialLoad()
}
}
- m_logger->LogInfo("Loaded %d Mach-O headers", State().headers.size());
+ m_logger->LogInfo("Loaded %d Mach-O headers", initialState.headers.size());
- for (const auto& cache : State().backingCaches)
+ for (auto& memoryRegion : nonImageMemoryRegions)
+ {
+ initialState.AddPotentiallyOverlappingMemoryRegion(std::move(memoryRegion));
+ }
+ m_logger->LogInfo("Loaded %zu stub island or dyld memory regions", nonImageMemoryRegions.size());
+
+ for (const auto& cache : initialState.backingCaches)
{
size_t i = 0;
for (const auto& mapping : cache.mappings)
@@ -841,135 +1051,16 @@ void SharedCache::PerformInitialLoad()
MemoryRegion region;
region.start = mapping.address;
region.size = mapping.size;
- region.prettyName = base_name(cache.path) + "::" + std::to_string(i);
+ region.prettyName = base_name(cache.path) + "::" + std::to_string(i++);
region.flags = SegmentFlagsFromMachOProtections(mapping.initProt, mapping.maxProt);
- MutableState().nonImageRegions.push_back(std::move(region));
- i++;
- }
- }
-
- // Iterate through each Mach-O header
- if (!State().dyldDataRegions.empty())
- {
- for (const auto& [headerKey, header] : State().headers)
- {
- // Iterate through each segment of the header
- for (const auto& segment : header.segments)
- {
- uint64_t segmentStart = segment.vmaddr;
- uint64_t segmentEnd = segmentStart + segment.vmsize;
-
- // Iterate through each region in m_dyldDataRegions
- for (auto it = State().dyldDataRegions.begin(); it != State().dyldDataRegions.end();)
- {
- uint64_t regionStart = it->start;
- uint64_t regionSize = it->size;
- uint64_t regionEnd = regionStart + regionSize;
-
- // Check if the region overlaps with the segment
- if (segmentStart < regionEnd && segmentEnd > regionStart)
- {
- // Split the region into two, removing the overlapped portion
- std::vector<MemoryRegion> newRegions;
-
- // Part before the overlap
- if (regionStart < segmentStart)
- {
- MemoryRegion newRegion(*it);
- newRegion.start = regionStart;
- newRegion.size = segmentStart - regionStart;
- newRegions.push_back(std::move(newRegion));
- }
-
- // Part after the overlap
- if (regionEnd > segmentEnd)
- {
- MemoryRegion newRegion(*it);
- newRegion.start = segmentEnd;
- newRegion.size = regionEnd - segmentEnd;
- newRegions.push_back(std::move(newRegion));
- }
-
- // Erase the original region
- it = MutableState().dyldDataRegions.erase(it);
-
- // Insert the new regions (if any)
- for (const auto& newRegion : newRegions)
- {
- it = MutableState().dyldDataRegions.insert(it, newRegion);
- ++it; // Move iterator to the next position
- }
- }
- else
- {
- ++it; // No overlap, move to the next region
- }
- }
- }
+ region.type = MemoryRegion::Type::NonImage;
+ initialState.AddPotentiallyOverlappingMemoryRegion(std::move(region));
}
}
- // Iterate through each Mach-O header
- if (!State().nonImageRegions.empty())
- {
- for (const auto& [headerKey, header] : State().headers)
- {
- // Iterate through each segment of the header
- for (const auto& segment : header.segments)
- {
- uint64_t segmentStart = segment.vmaddr;
- uint64_t segmentEnd = segmentStart + segment.vmsize;
-
- // Iterate through each region in m_dyldDataRegions
- for (auto it = State().nonImageRegions.begin(); it != State().nonImageRegions.end();)
- {
- uint64_t regionStart = it->start;
- uint64_t regionSize = it->size;
- uint64_t regionEnd = regionStart + regionSize;
-
- // Check if the region overlaps with the segment
- if (segmentStart < regionEnd && segmentEnd > regionStart)
- {
- // Split the region into two, removing the overlapped portion
- std::vector<MemoryRegion> newRegions;
-
- // Part before the overlap
- if (regionStart < segmentStart)
- {
- MemoryRegion newRegion(*it);
- newRegion.start = regionStart;
- newRegion.size = segmentStart - regionStart;
- newRegions.push_back(std::move(newRegion));
- }
-
- // Part after the overlap
- if (regionEnd > segmentEnd)
- {
- MemoryRegion newRegion(*it);
- newRegion.start = segmentEnd;
- newRegion.size = regionEnd - segmentEnd;
- newRegions.push_back(std::move(newRegion));
- }
-
- // Erase the original region
- it = MutableState().nonImageRegions.erase(it);
-
- // Insert the new regions (if any)
- for (const auto& newRegion : newRegions)
- {
- it = MutableState().nonImageRegions.insert(it, newRegion);
- ++it; // Move iterator to the next position
- }
- }
- else
- {
- ++it; // No overlap, move to the next region
- }
- }
- }
- }
- }
- SaveToDSCView();
+ m_cacheInfo = std::make_shared<CacheInfo>(std::move(initialState));
+ m_modifiedState->viewState = DSCViewStateLoaded;
+ SaveCacheInfoToDSCView(lock);
m_logger->LogDebug("Finished initial load of Shared Cache");
@@ -978,15 +1069,21 @@ void SharedCache::PerformInitialLoad()
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);
- for (const auto& cache : State().backingCaches)
+ uint64_t baseAddress = cacheInfo.BaseAddress();
+ for (const auto& cache : cacheInfo.backingCaches)
{
for (const auto& mapping : cache.mappings)
{
vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.address, mapping.fileOffset, mapping.size, cache.path,
- [this, vm=vm](std::shared_ptr<MMappedFileAccessor> mmap){
- ParseAndApplySlideInfoForFile(mmap);
+ [this, vm, baseAddress](std::shared_ptr<MMappedFileAccessor> mmap){
+ ParseAndApplySlideInfoForFile(mmap, baseAddress);
});
}
}
@@ -995,39 +1092,41 @@ std::shared_ptr<VM> SharedCache::GetVMMap()
}
-void SharedCache::DeserializeFromRawView()
+void SharedCache::DeserializeFromRawView(std::lock_guard<std::mutex>& lock)
{
- if (m_dscView->QueryMetadata(SharedCacheMetadataTag))
+ std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex);
+ if (m_viewSpecificState->cacheInfo)
{
- std::lock_guard lock(m_viewSpecificState->stateMutex);
- if (m_viewSpecificState->cachedState)
- {
- m_state = m_viewSpecificState->cachedState;
- m_stateIsShared = true;
- m_metadataValid = true;
- return;
- }
+ m_cacheInfo = m_viewSpecificState->cacheInfo;
+ m_modifiedState = std::make_unique<ModifiedState>();
+ m_metadataValid = true;
+ return;
+ }
- if (auto state = LoadFromString(m_dscView->GetStringMetadata(SharedCacheMetadataTag)))
+ if (SharedCacheMetadata::ViewHasMetadata(m_dscView))
+ {
+ auto metadata = SharedCacheMetadata::LoadFromView(m_dscView);
+ if (!metadata)
{
- m_state = std::make_shared<SharedCacheState>(std::move(*state));
- m_stateIsShared = false;
- m_metadataValid = true;
+ m_metadataValid = false;
+ m_logger->LogError("Failed to deserialize Shared Cache metadata");
return;
}
- m_state = nullptr;
- m_metadataValid = false;
- m_logger->LogError("Failed to deserialize Shared Cache metadata");
- WillMutateState();
- MutableState().viewState = DSCViewStateUnloaded;
+ m_viewSpecificState->viewState = metadata->state->viewState.value_or(DSCViewStateUnloaded);
+ m_viewSpecificState->state = std::move(*metadata->state);
+ m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo);
+
+ m_cacheInfo = m_viewSpecificState->cacheInfo;
+ m_modifiedState = std::make_unique<ModifiedState>();
+ m_metadataValid = true;
return;
}
+ m_cacheInfo = nullptr;
+ m_modifiedState = std::make_unique<ModifiedState>();
+ m_modifiedState->viewState = DSCViewStateUnloaded;
m_metadataValid = true;
- WillMutateState();
- MutableState().viewState = DSCViewStateUnloaded;
- MutableState().images.clear(); // fixme ??
}
@@ -1039,27 +1138,13 @@ std::string to_hex_string(uint64_t value)
}
-void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file)
+void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t base)
{
if (file->SlideInfoWasApplied())
return;
- WillMutateState();
-
dyld_cache_header baseHeader;
file->Read(&baseHeader, 0, sizeof(dyld_cache_header));
- uint64_t base = UINT64_MAX;
- for (const auto& backingCache : State().backingCaches)
- {
- for (const auto& mapping : backingCache.mappings)
- {
- if (mapping.address < base)
- {
- base = mapping.address;
- break;
- }
- }
- }
std::vector<std::pair<uint64_t, MappingInfo>> mappings;
@@ -1361,8 +1446,10 @@ void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAcces
}
-SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView))
+SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) :
+ m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView))
{
+ std::lock_guard lock(m_mutex);
m_logger = LogRegistry::GetLogger("SharedCache", dscView->GetFile()->GetSessionId());
if (dscView->GetTypeName() != VIEW_NAME)
{
@@ -1370,20 +1457,22 @@ SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : m_
m_logger->LogError("Attempted to create SharedCache object from non-Shared Cache view");
return;
}
+
sharedCacheReferences++;
INIT_SHAREDCACHE_API_OBJECT()
- DeserializeFromRawView();
+ DeserializeFromRawView(lock);
if (!m_metadataValid)
return;
- if (State().viewState != DSCViewStateUnloaded) {
+ if (m_modifiedState->viewState.value_or(m_viewSpecificState->viewState) != DSCViewStateUnloaded)
+ {
m_viewSpecificState->progress = LoadProgressFinished;
return;
}
- std::unique_lock lock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
+ std::unique_lock viewOperationsLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
try {
- PerformInitialLoad();
+ PerformInitialLoad(lock);
}
catch (...)
{
@@ -1398,20 +1487,17 @@ SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : m_
}
if (autoLoadLibsystem)
{
- for (const auto& [_, header] : State().headers)
+ for (const auto& [_, header] : m_cacheInfo->headers)
{
if (header.installName.find("libsystem_c.dylib") != std::string::npos)
{
- lock.unlock();
+ viewOperationsLock.unlock();
m_logger->LogInfo("Loading core libsystem_c.dylib library");
- LoadImageWithInstallName(header.installName, false);
+ LoadImageWithInstallName(lock, header.installName, false);
break;
}
}
}
-
- MutableState().viewState = DSCViewStateLoaded;
- SaveToDSCView();
}
SharedCache::~SharedCache() {
@@ -1431,31 +1517,34 @@ SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryVie
}
}
-std::optional<uint64_t> SharedCache::GetImageStart(std::string installName)
+std::optional<uint64_t> SharedCache::GetImageStart(const std::string_view installName)
{
- for (const auto& [name, start] : State().imageStarts)
- {
- if (name == installName)
- {
- return start;
- }
- }
- return {};
+ 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;
+
+ return std::nullopt;
}
-std::optional<SharedCacheMachOHeader> SharedCache::HeaderForAddress(uint64_t address)
+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;
+
// We _could_ mark each page with the image start? :grimacing emoji:
// But that'd require mapping pages :grimacing emoji: :grimacing emoji:
// There's not really any other hacks that could make this faster, that I can think of...
- for (const auto& [start, header] : State().headers)
+ for (const auto& [start, header] : m_cacheInfo->headers)
{
for (const auto& segment : header.segments)
{
if (segment.vmaddr <= address && segment.vmaddr + segment.vmsize > address)
- {
- return header;
- }
+ return &header;
}
}
return {};
@@ -1463,61 +1552,30 @@ std::optional<SharedCacheMachOHeader> SharedCache::HeaderForAddress(uint64_t add
std::string SharedCache::NameForAddress(uint64_t address)
{
- for (const auto& stubIsland : State().stubIslandRegions)
- {
- if (stubIsland.start <= address && stubIsland.start + stubIsland.size > address)
- {
- return stubIsland.prettyName;
- }
- }
- for (const auto& dyldData : State().dyldDataRegions)
- {
- if (dyldData.start <= address && dyldData.start + dyldData.size > address)
- {
- return dyldData.prettyName;
- }
- }
- for (const auto& nonImageRegion : State().nonImageRegions)
- {
- if (nonImageRegion.start <= address && nonImageRegion.start + nonImageRegion.size > address)
- {
- return nonImageRegion.prettyName;
- }
- }
- if (auto header = HeaderForAddress(address))
- {
- for (const auto& section : header->sections)
- {
- if (section.addr <= address && section.addr + section.size > address)
- {
- char sectionName[17];
- strncpy(sectionName, section.sectname, 16);
- sectionName[16] = '\0';
- return header->identifierPrefix + "::" + sectionName;
- }
- }
- }
+ if (auto it = m_cacheInfo->memoryRegions.find(address); it != m_cacheInfo->memoryRegions.end())
+ return it->second.prettyName;
+
return "";
}
std::string SharedCache::ImageNameForAddress(uint64_t address)
{
if (auto header = HeaderForAddress(address))
- {
return header->identifierPrefix;
- }
+
return "";
}
bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC)
{
- for (const auto& [start, header] : State().headers)
+ for (const auto& [start, header] : m_cacheInfo->headers)
{
for (const auto& segment : header.segments)
{
if (segment.vmaddr <= address && segment.vmaddr + segment.vmsize > address)
{
- return LoadImageWithInstallName(header.installName, skipObjC);
+ std::lock_guard lock(m_mutex);
+ return LoadImageWithInstallName(lock, header.installName, skipObjC);
}
}
}
@@ -1527,122 +1585,67 @@ bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC)
bool SharedCache::LoadSectionAtAddress(uint64_t address)
{
- std::unique_lock lock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
- DeserializeFromRawView();
- WillMutateState();
+ 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)
- {
+ if (!vm) {
m_logger->LogError("Failed to map VM pages for Shared Cache.");
return false;
}
SharedCacheMachOHeader targetHeader;
- MemoryRegion* targetSegment = nullptr;
+ const CacheImage* targetImage = nullptr;
+ const MemoryRegion* targetSegment = nullptr;
- for (auto& image : MutableState().images)
+ auto it = m_cacheInfo->memoryRegions.find(address);
+ if (it != m_cacheInfo->memoryRegions.end())
{
- for (auto& region : image.regions)
+ const MemoryRegion* region = &it->second;
+ for (auto& image : m_cacheInfo->images)
{
- if (region.start <= address && region.start + region.size > address)
- {
- targetHeader = MutableState().headers[image.headerLocation];
- targetSegment = &region;
- break;
- }
- }
- if (targetSegment)
+ 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;
+ }
}
+
if (!targetSegment)
{
- for (auto& stubIsland : MutableState().stubIslandRegions)
+ auto regionIt = m_cacheInfo->memoryRegions.find(address);
+ if (regionIt == m_cacheInfo->memoryRegions.end())
{
- if (stubIsland.start <= address && stubIsland.start + stubIsland.size > address)
- {
- if (stubIsland.loaded)
- {
- return true;
- }
- m_logger->LogInfo("Loading stub island %s @ 0x%llx", stubIsland.prettyName.c_str(), stubIsland.start);
- auto targetFile = vm->MappingAtAddress(stubIsland.start).first.fileAccessor->lock();
- auto reader = VMReader(vm);
- auto buff = reader.ReadBuffer(stubIsland.start, stubIsland.size);
- m_dscView->GetMemoryMap()->AddDataMemoryRegion(stubIsland.prettyName, stubIsland.start, buff, SegmentReadable | SegmentExecutable);
- m_dscView->AddUserSection(stubIsland.prettyName, stubIsland.start, stubIsland.size, ReadOnlyCodeSectionSemantics);
-
- stubIsland.loaded = true;
-
- MutableState().regionsMappedIntoMemory.push_back(stubIsland);
-
- SaveToDSCView();
-
- m_dscView->AddAnalysisOption("linearsweep");
- m_dscView->UpdateAnalysis();
-
- return true;
- }
+ m_logger->LogError("Failed to find a segment containing address 0x%llx", address);
+ return false;
}
- for (auto& dyldData : MutableState().dyldDataRegions)
- {
- if (dyldData.start <= address && dyldData.start + dyldData.size > address)
- {
- if (dyldData.loaded)
- {
- return true;
- }
- m_logger->LogInfo("Loading dyld data %s", dyldData.prettyName.c_str());
- auto targetFile = vm->MappingAtAddress(dyldData.start).first.fileAccessor->lock();
- auto reader = VMReader(vm);
- auto buff = reader.ReadBuffer(dyldData.start, dyldData.size);
- m_dscView->GetMemoryMap()->AddDataMemoryRegion(dyldData.prettyName, dyldData.start, buff, SegmentReadable);
- m_dscView->AddUserSection(dyldData.prettyName, dyldData.start, dyldData.size, ReadOnlyDataSectionSemantics);
-
- dyldData.loaded = true;
+ auto& region = regionIt->second;
+ if (MemoryRegionIsLoaded(lock, region))
+ return true;
- MutableState().regionsMappedIntoMemory.push_back(dyldData);
-
- SaveToDSCView();
-
- m_dscView->AddAnalysisOption("linearsweep");
- m_dscView->UpdateAnalysis();
-
- 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();
- for (auto& region : MutableState().nonImageRegions)
- {
- if (region.start <= address && region.start + region.size > address)
- {
- if (region.loaded)
- {
- return true;
- }
- m_logger->LogInfo("Loading non-image region %s", region.prettyName.c_str());
- 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, region.flags & SegmentDenyExecute ? ReadOnlyDataSectionSemantics : ReadOnlyCodeSectionSemantics);
+ auto reader = VMReader(vm);
+ auto buff = reader.ReadBuffer(region.start, region.size);
- region.loaded = true;
+ m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags);
+ m_dscView->AddUserSection(region.prettyName, region.start, region.size, SectionSemanticsForRegion(region));
- MutableState().regionsMappedIntoMemory.push_back(region);
+ SetMemoryRegionIsLoaded(lock, region);
- SaveToDSCView();
+ SaveModifiedStateToDSCView(lock);
- m_dscView->AddAnalysisOption("linearsweep");
- m_dscView->UpdateAnalysis();
+ m_dscView->AddAnalysisOption("linearsweep");
+ m_dscView->UpdateAnalysis();
- return true;
- }
- }
-
- m_logger->LogError("Failed to find a segment containing address 0x%llx", address);
- return false;
+ return true;
}
auto id = m_dscView->BeginUndoActions();
@@ -1654,16 +1657,12 @@ bool SharedCache::LoadSectionAtAddress(uint64_t address)
auto buff = reader.ReadBuffer(targetSegment->start, targetSegment->size);
m_dscView->GetMemoryMap()->AddDataMemoryRegion(targetSegment->prettyName, targetSegment->start, buff, targetSegment->flags);
- targetSegment->loaded = true;
+ SetMemoryRegionIsLoaded(lock, *targetSegment);
- MutableState().regionsMappedIntoMemory.push_back(*targetSegment);
+ if (!MemoryRegionIsHeaderInitialized(lock, *targetSegment))
+ SharedCache::InitializeHeader(lock, m_dscView, vm.get(), targetHeader, {targetSegment});
- SaveToDSCView();
-
- if (!targetSegment->headerInitialized)
- {
- SharedCache::InitializeHeader(m_dscView, vm.get(), targetHeader, {targetSegment});
- }
+ SaveModifiedStateToDSCView(lock);
m_dscView->AddAnalysisOption("linearsweep");
m_dscView->UpdateAnalysis();
@@ -1720,6 +1719,12 @@ void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string install
void SharedCache::ProcessAllObjCSections()
{
+ std::lock_guard lock(m_mutex);
+ ProcessAllObjCSections(lock);
+}
+
+void SharedCache::ProcessAllObjCSections(std::lock_guard<std::mutex>& lock)
+{
bool processCFStrings;
bool processObjCMetadata;
GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata);
@@ -1733,11 +1738,8 @@ void SharedCache::ProcessAllObjCSections()
std::set<uint64_t> processedImageHeaders;
for (auto region : GetMappedRegions())
{
- if (!region.loaded)
- continue;
-
// Don't repeat the same images multiple times
- auto header = HeaderForAddress(region.start);
+ auto header = HeaderForAddress(region->start);
if (!header)
continue;
if (processedImageHeaders.find(header->textBase) != processedImageHeaders.end())
@@ -1750,19 +1752,22 @@ void SharedCache::ProcessAllObjCSections()
bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC)
{
- auto settings = m_dscView->GetLoadSettings(VIEW_NAME);
+ std::lock_guard lock(m_mutex);
+ return LoadImageWithInstallName(lock, installName, skipObjC);
+}
- std::unique_lock lock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
+bool SharedCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName, bool skipObjC)
+{
+ auto settings = m_dscView->GetLoadSettings(VIEW_NAME);
- DeserializeFromRawView();
- WillMutateState();
+ std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
m_logger->LogInfo("Loading image %s", installName.c_str());
auto vm = GetVMMap();
- CacheImage* targetImage = nullptr;
+ const CacheImage* targetImage = nullptr;
- for (auto& cacheImage : MutableState().images)
+ for (auto& cacheImage : m_cacheInfo->images)
{
if (cacheImage.installName == installName)
{
@@ -1777,32 +1782,34 @@ bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObj
return false;
}
- auto it = State().headers.find(targetImage->headerLocation);
- if (it == State().headers.end())
+ 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();
- MutableState().viewState = DSCViewStateLoadedWithImages;
+ m_modifiedState->viewState = DSCViewStateLoadedWithImages;
auto reader = VMReader(vm);
reader.Seek(targetImage->headerLocation);
- std::vector<MemoryRegion*> regionsToLoad;
- regionsToLoad.reserve(targetImage->regions.size());
+ std::vector<const MemoryRegion*> regionsToLoad;
+ regionsToLoad.reserve(targetImage->regionStarts.size());
- for (auto& region : targetImage->regions)
+ 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 (region.loaded)
+ if (MemoryRegionIsLoaded(lock, region))
{
m_logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str());
continue;
@@ -1810,12 +1817,9 @@ bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObj
auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock();
auto buff = reader.ReadBuffer(region.start, region.size);
-
- region.loaded = true;
-
- MutableState().regionsMappedIntoMemory.push_back(region);
m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags);
+ SetMemoryRegionIsLoaded(lock, region);
regionsToLoad.push_back(&region);
}
@@ -1827,15 +1831,15 @@ bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObj
auto typeLib = TypeLibraryForImage(header.installName);
- SaveToDSCView();
-
auto h = SharedCache::LoadHeaderForAddress(vm, targetImage->headerLocation, installName);
- if (!h.has_value())
+ if (!h)
{
+ SaveModifiedStateToDSCView(lock);
return false;
}
- SharedCache::InitializeHeader(m_dscView, vm.get(), *h, regionsToLoad);
+ SharedCache::InitializeHeader(lock, m_dscView, vm.get(), *h, regionsToLoad);
+ SaveModifiedStateToDSCView(lock);
if (!skipObjC)
{
@@ -2263,10 +2267,9 @@ std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::sha
}
void SharedCache::InitializeHeader(
- Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<MemoryRegion*> regionsToLoad)
+ std::lock_guard<std::mutex>& lock,
+ Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<const MemoryRegion*> regionsToLoad)
{
- WillMutateState();
-
Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME);
bool applyFunctionStarts = true;
if (settings && settings->Contains("loader.dsc.processFunctionStarts"))
@@ -2279,10 +2282,8 @@ void SharedCache::InitializeHeader(
{
if (header.sections[i].addr >= region->start && header.sections[i].addr < region->start + region->size)
{
- if (region->headerInitialized)
- {
+ if (MemoryRegionIsHeaderInitialized(lock, *region))
skip = true;
- }
break;
}
}
@@ -2410,8 +2411,9 @@ void SharedCache::InitializeHeader(
{
if (header.textBase >= region->start && header.textBase < region->start + region->size)
{
- if (!region->headerInitialized)
+ if (!MemoryRegionIsHeaderInitialized(lock, *region))
applyHeaderTypes = true;
+
break;
}
}
@@ -2543,7 +2545,7 @@ void SharedCache::InitializeHeader(
{
if (curfunc >= region->start && curfunc < region->start + region->size)
{
- if (!region->headerInitialized)
+ if (!MemoryRegionIsHeaderInitialized(lock, *region))
addFunction = true;
}
}
@@ -2569,8 +2571,8 @@ void SharedCache::InitializeHeader(
nlist_64 sym;
memset(&sym, 0, sizeof(sym));
auto N_TYPE = 0xE; // idk
- std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> symbolInfos;
- symbolInfos.reserve(header.symtab.nsyms);
+ std::vector<Ref<Symbol>> symbols;
+ symbols.reserve(header.symtab.nsyms);
for (size_t i = 0; i < header.symtab.nsyms; i++)
{
@@ -2618,7 +2620,7 @@ void SharedCache::InitializeHeader(
if ((sym.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF)
sym.n_value++;
- auto symbolObj = new Symbol(type, symbol, sym.n_value, GlobalBinding);
+ Ref<Symbol> symbolObj = new Symbol(type, symbol, sym.n_value, GlobalBinding);
if (type == FunctionSymbol)
{
Ref<Platform> targetPlatform = view->GetDefaultPlatform();
@@ -2636,14 +2638,14 @@ void SharedCache::InitializeHeader(
}
else
view->DefineAutoSymbol(symbolObj);
- symbolInfos.emplace_back(sym.n_value, std::make_pair(type, std::move(symbol)));
+ symbols.push_back(std::move(symbolObj));
}
- MutableState().symbolInfos[header.textBase] = std::move(symbolInfos);
+ m_modifiedState->symbolInfos[header.textBase] = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbols));
}
if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr))
{
- auto symbols = GetExportListForHeader(header, [&]() {
+ auto symbols = GetExportListForHeader(lock, header, [&]() {
return vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock();
});
if (symbols)
@@ -2693,7 +2695,7 @@ void SharedCache::InitializeHeader(
for (auto region : regionsToLoad)
{
- region->headerInitialized = true;
+ SetMemoryRegionHeaderInitialized(lock, *region);
}
}
@@ -2781,14 +2783,28 @@ std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFil
}
}
+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(SharedCacheMachOHeader header, std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList)
+
+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 it = m_state->exportInfos.find(header.textBase); it != m_state->exportInfos.end())
+ if (auto exportList = GetExistingExportListForBaseAddress(lock, header.textBase))
{
if (didModifyExportList)
*didModifyExportList = false;
- return it->second;
+
+ return exportList;
}
std::shared_ptr<MMappedFileAccessor> linkeditFile = provideLinkeditFile();
@@ -2796,6 +2812,7 @@ std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExpor
{
if (didModifyExportList)
*didModifyExportList = false;
+
return nullptr;
}
@@ -2806,18 +2823,20 @@ std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExpor
{
exportMapping->insert_or_assign(sym->GetAddress(), std::move(sym));
}
- MutableState().exportInfos.emplace(header.textBase, exportMapping);
+
+ m_modifiedState->exportInfos.emplace(header.textBase, exportMapping);
if (didModifyExportList)
*didModifyExportList = true;
- return m_state->exportInfos[header.textBase];
+
+ return exportMapping;
}
std::vector<std::string> SharedCache::GetAvailableImages()
{
std::vector<std::string> installNames;
- installNames.reserve(State().headers.size());
- for (const auto& header : State().headers)
+ installNames.reserve(m_cacheInfo->headers.size());
+ for (const auto& header : m_cacheInfo->headers)
{
installNames.push_back(header.second.installName);
}
@@ -2825,22 +2844,19 @@ std::vector<std::string> SharedCache::GetAvailableImages()
}
-std::vector<std::pair<std::string, Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait()
+std::unordered_map<std::string, std::vector<Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait()
{
- WillMutateState();
-
- std::lock_guard initialLoadBlock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
+ 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::vector<std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>> exportLists;
- exportLists.reserve(State().images.size());
-
- size_t totalSymbolCount = 0;
+ std::unordered_map<std::string, std::vector<Ref<Symbol>>> symbolsByImageName(m_cacheInfo->images.size());
- for (const auto& img : State().images)
+ for (const auto& img : m_cacheInfo->images)
{
auto header = HeaderForAddress(img.headerLocation);
- auto exportList = GetExportListForHeader(*header, [&]() {
+ auto exportList = GetExportListForHeader(lock, *header, [&]() {
try {
return MapFile(header->exportTriePath);
}
@@ -2853,33 +2869,22 @@ std::vector<std::pair<std::string, Ref<Symbol>>> SharedCache::LoadAllSymbolsAndW
}
}, &doSave);
- exportLists.push_back(exportList);
- if (exportList)
- {
- totalSymbolCount += exportList->size();
- }
- }
-
- std::vector<std::pair<std::string, Ref<Symbol>>> symbols;
- symbols.reserve(totalSymbolCount);
-
- for (size_t i = 0; i < exportLists.size(); ++i)
- {
- if (!exportLists[i])
+ if (!exportList)
continue;
- const auto& img = State().images[i];
- for (const auto& [name, symbol] : *exportLists[i])
+ auto& symbols = symbolsByImageName[img.installName];
+ symbols.reserve(exportList->size());
+ for (const auto& [_, symbol] : *exportList)
{
- symbols.emplace_back(img.installName, symbol);
+ symbols.push_back(symbol);
}
}
// Only save to DSC view if a header was actually loaded
if (doSave)
- SaveToDSCView();
+ SaveModifiedStateToDSCView(lock);
- return symbols;
+ return symbolsByImageName;
}
@@ -2896,12 +2901,10 @@ std::string SharedCache::SerializedImageHeaderForAddress(uint64_t address)
std::string SharedCache::SerializedImageHeaderForName(std::string name)
{
- if (auto it = State().imageStarts.find(name); it != State().imageStarts.end())
+ if (auto it = m_cacheInfo->imageStarts.find(name); it != m_cacheInfo->imageStarts.end())
{
if (auto header = HeaderForAddress(it->second))
- {
return header->AsString();
- }
}
return "";
}
@@ -2930,6 +2933,8 @@ Ref<TypeLibrary> SharedCache::TypeLibraryForImage(const std::string& installName
void SharedCache::FindSymbolAtAddrAndApplyToAddr(
uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis)
{
+ std::lock_guard lock(m_mutex);
+
std::string prefix = "";
if (symbolLocation != targetLocation)
prefix = "j_";
@@ -2965,8 +2970,7 @@ void SharedCache::FindSymbolAtAddrAndApplyToAddr(
if (!header)
return;
- WillMutateState();
- auto exportList = GetExportListForHeader(*header, [&]() {
+ auto exportList = GetExportListForHeader(lock, *header, [&]() {
try {
return MapFile(header->exportTriePath);
} catch (...) {
@@ -3008,34 +3012,137 @@ void SharedCache::FindSymbolAtAddrAndApplyToAddr(
}
-bool SharedCache::SaveToDSCView()
+bool SharedCache::SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&)
{
- if (m_dscView)
+ 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);
+ m_dscView->GetParentView()->StoreMetadata(SharedCacheMetadata::Tag, data);
+
{
- auto data = AsMetadata();
- m_dscView->StoreMetadata(SharedCacheMetadataTag, data);
- m_dscView->GetParentView()->StoreMetadata(SharedCacheMetadataTag, data);
+ std::lock_guard lock(m_viewSpecificState->cacheInfoMutex);
+ if (m_cacheInfo && !m_viewSpecificState->cacheInfo)
+ m_viewSpecificState->cacheInfo = m_cacheInfo;
+ else if (m_cacheInfo != m_viewSpecificState->cacheInfo)
+ abort();
+ }
+
+ m_metadataValid = true;
+ return true;
+}
- // By moving our state the to cache we can avoid creating a copy in the case
- // that no further mutations are made to `this`. If we're not done being mutated,
- // the data will be copied on the first mutation.
- auto cachedState = std::make_shared<SharedCacheState>(std::move(*m_state));
- m_state = cachedState;
- m_stateIsShared = true;
+bool SharedCache::SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&)
+{
+ if (!m_dscView)
+ return false;
+ {
std::lock_guard lock(m_viewSpecificState->stateMutex);
- m_viewSpecificState->cachedState = std::move(cachedState);
- m_metadataValid = true;
+ 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);
- return true;
+ m_dscView->StoreMetadata(metadataKey, data);
+ m_dscView->GetParentView()->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);
+ m_dscView->GetParentView()->StoreMetadata(metadataKey, data);
+
+ Ref<Metadata> count = new Metadata(m_viewSpecificState->savedModifications);
+ m_dscView->StoreMetadata(SharedCacheMetadata::ModifiedStateCountTag, count);
+ m_dscView->GetParentView()->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)
+ {
+ m_viewSpecificState->state.memoryRegionStatus[region] = status;
+ }
+ 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;
+ }
}
- return false;
+
+ if (m_modifiedState->viewState)
+ {
+ m_viewSpecificState->viewState = m_modifiedState->viewState.value();
+ m_modifiedState->viewState = std::nullopt;
+ }
+
+ m_metadataValid = true;
+
+ return true;
}
-std::vector<MemoryRegion> SharedCache::GetMappedRegions() const
+
+
+std::vector<const MemoryRegion*> SharedCache::GetMappedRegions() const
{
- std::lock_guard lock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex);
- return State().regionsMappedIntoMemory;
+ 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)
@@ -3147,17 +3254,28 @@ extern "C"
{
if (cache->object)
{
- auto value = cache->object->LoadAllSymbolsAndWait();
- *count = value.size();
+ auto symbolsByImageName = cache->object->LoadAllSymbolsAndWait();
+ size_t totalSymbolCount = 0;
+ for (const auto& [_, symbols] : symbolsByImageName)
+ {
+ totalSymbolCount += symbols.size();
+ }
+ *count = totalSymbolCount;
- BNDSCSymbolRep* symbols = new BNDSCSymbolRep[value.size()];
- for (size_t i = 0; i < value.size(); i++)
+ BNDSCSymbolRep* outputSymbols = new BNDSCSymbolRep[totalSymbolCount];
+ size_t i = 0;
+ for (const auto& [imageName, symbols] : symbolsByImageName)
{
- symbols[i].address = value[i].second->GetAddress();
- symbols[i].name = BNDuplicateStringRef(value[i].second->GetRawNameRef().GetObject());
- symbols[i].image = BNAllocString(value[i].first.c_str());
+ 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;
+ }
}
- return symbols;
+ assert(i == totalSymbolCount);
+ return outputSymbols;
}
*count = 0;
return nullptr;
@@ -3219,9 +3337,10 @@ extern "C"
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 = BNAllocString(regions[i].prettyName.c_str());
+ 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;
}
@@ -3394,118 +3513,169 @@ extern "C"
}
}
-[[maybe_unused]] DSCViewType* g_dscViewType;
-
-void InitDSCViewType()
+void Serialize(SerializationContext& context, const dyld_cache_mapping_info& value)
{
- MMappedFileAccessor::InitialVMSetup();
- std::atexit(VMShutdown);
+ 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();
+}
- static DSCViewType type;
- BinaryViewType::Register(&type);
- g_dscViewType = &type;
+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);
+ }
}
-namespace SharedCacheCore {
+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;
+ }
-void Serialize(SerializationContext& context, const dyld_cache_mapping_info& value)
+ auto array = context.doc[name.data()].GetArray();
+ value = {array[0].GetUint64(), array[1].GetUint64()};
+}
+
+void Serialize(SerializationContext& context, const AddressRange& 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();
+ Serialize(context, std::make_pair(value.start, value.end));
}
-void Deserialize(DeserializationContext& context, std::string_view name, std::vector<dyld_cache_mapping_info>& b)
+void Deserialize(DeserializationContext& context, std::string_view name, AddressRange& value)
{
+ auto array = context.doc[name.data()].GetArray();
+ value = {array[0].GetUint64(), array[1].GetUint64()};
+}
- 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 Serialize(SerializationContext& context, const MemoryRegionStatus& status)
+{
+ context.writer.StartArray();
+ Serialize(context, status.loaded);
+ Serialize(context, status.headerInitialized);
+ context.writer.EndArray();
}
-void SharedCache::Store(SerializationContext& context) const
+void Deserialize(
+ DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, MemoryRegionStatus>& statuses)
{
- Serialize(context, "metadataVersion", METADATA_VERSION);
+ 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);
+ }
+}
- Serialize(context, "m_viewState", State().viewState);
- Serialize(context, "m_cacheFormat", State().cacheFormat);
- Serialize(context, "m_imageStarts", State().imageStarts);
- Serialize(context, "m_baseFilePath", State().baseFilePath);
+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();
+}
- Serialize(context, "headers");
+void Serialize(SerializationContext& context, const std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>& value)
+{
context.writer.StartArray();
- for (auto& [k, v] : State().headers)
+ for (const auto& [_, symbol] : *value)
{
- context.writer.StartObject();
- v.Store(context);
- context.writer.EndObject();
+ Serialize(context, symbol);
}
context.writer.EndArray();
+}
- Serialize(context, "exportInfos");
- context.writer.StartArray();
- for (const auto& pair1 : State().exportInfos)
+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::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& value)
+{
+ auto array = context.doc[name.data()].GetArray();
+ for (auto& pair : array)
{
- context.writer.StartObject();
- Serialize(context, "key", pair1.first);
- Serialize(context, "value");
- context.writer.StartArray();
- for (const auto& pair2 : *pair1.second)
+ auto symbols_array = pair[1].GetArray();
+ std::unordered_map<uint64_t, Ref<Symbol>> symbols;
+ for (auto& symbol_value : symbols_array)
{
- context.writer.StartObject();
- Serialize(context, "key", pair2.first);
- Serialize(context, "val1", pair2.second->GetType());
- Serialize(context, "val2", (std::string_view)pair2.second->GetRawNameRef());
- context.writer.EndObject();
+ auto symbol_array = symbol_value.GetArray();
+ std::string symbolName = symbol_array[0].GetString();
+ uint64_t address = symbol_array[1].GetUint64();
+ BNSymbolType type = (BNSymbolType)symbol_array[2].GetUint();
+ symbols.insert({address, new Symbol(type, symbolName, address)});
}
- context.writer.EndArray();
- context.writer.EndObject();
+ value[pair[0].GetUint64()] = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(std::move(symbols));
}
- context.writer.EndArray();
+}
- Serialize(context, "symbolInfos");
- context.writer.StartArray();
- for (const auto& pair1 : State().symbolInfos)
+void Deserialize(DeserializationContext& context, std::string_view name,
+ std::unordered_map<uint64_t, std::shared_ptr<std::vector<Ref<Symbol>>>>& value)
+{
+ auto array = context.doc[name.data()].GetArray();
+ for (auto& pair : array)
{
- context.writer.StartObject();
- Serialize(context, "key", pair1.first);
- Serialize(context, "value");
- context.writer.StartArray();
- for (const auto& pair2 : pair1.second)
+ auto symbols_array = pair[1].GetArray();
+ std::vector<Ref<Symbol>> symbols;
+ symbols.reserve(symbols_array.Size());
+ for (auto& symbol_value : symbols_array)
{
- context.writer.StartObject();
- Serialize(context, "key", pair2.first);
- Serialize(context, "val1", pair2.second.first);
- Serialize(context, "val2", pair2.second.second);
- context.writer.EndObject();
+ auto symbol_array = symbol_value.GetArray();
+ std::string symbolName = symbol_array[0].GetString();
+ uint64_t address = symbol_array[1].GetUint64();
+ BNSymbolType type = (BNSymbolType)symbol_array[2].GetUint();
+ symbols.push_back(new Symbol(type, symbolName, address));
}
- context.writer.EndArray();
- context.writer.EndObject();
+ value[pair[0].GetUint64()] = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbols));
}
- context.writer.EndArray();
+}
- Serialize(context, "backingCaches", State().backingCaches);
- Serialize(context, "stubIslands", State().stubIslandRegions);
- Serialize(context, "images", State().images);
- Serialize(context, "regionsMappedIntoMemory", State().regionsMappedIntoMemory);
- Serialize(context, "dyldDataSections", State().dyldDataRegions);
- Serialize(context, "nonImageRegions", State().nonImageRegions);
+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);
}
-std::optional<SharedCacheState> SharedCache::Load(DeserializationContext& context)
+// static
+std::optional<SharedCache::CacheInfo> SharedCache::CacheInfo::Load(DeserializationContext& context)
{
if (!context.doc.HasMember("metadataVersion"))
{
@@ -3519,67 +3689,62 @@ std::optional<SharedCacheState> SharedCache::Load(DeserializationContext& contex
return std::nullopt;
}
- SharedCacheState state;
-
- state.viewState = static_cast<DSCViewState>(context.load<uint8_t>("m_viewState"));
- state.cacheFormat = static_cast<SharedCacheFormat>(context.load<uint8_t>("m_cacheFormat"));
-
- for (auto& startAndHeader : context.doc["headers"].GetArray())
- {
- SharedCacheMachOHeader header = SharedCacheMachOHeader::LoadFromValue(startAndHeader);
- state.headers[header.textBase] = std::move(header);
- }
-
- Deserialize(context, "m_imageStarts", state.imageStarts);
- Deserialize(context, "m_baseFilePath", state.baseFilePath);
+ 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);
+ return cacheInfo;
+}
+void State::Store(SerializationContext& context, std::optional<DSCViewState> viewState) const
+{
+ MSS(memoryRegionStatus);
+ MSS(exportInfos);
+ MSS(symbolInfos);
+ MSS(viewState);
+}
- for (const auto& obj1 : context.doc["exportInfos"].GetArray())
- {
- std::unordered_map<uint64_t, Ref<Symbol>> innerVec;
- for (const auto& obj2 : obj1["value"].GetArray())
- {
- std::string raw = obj2["val2"].GetString();
- uint64_t addr = obj2["key"].GetUint64();
- innerVec[addr] = new Symbol(BNCreateSymbol((BNSymbolType)obj2["val1"].GetUint64(), raw.c_str(),
- raw.c_str(), raw.c_str(), addr, NoBinding, nullptr, 0));
- }
+void SharedCache::ModifiedState::Store(SerializationContext& context) const
+{
+ State::Store(context, viewState);
+}
- state.exportInfos[obj1["key"].GetUint64()] = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(innerVec);
- }
+SharedCache::ModifiedState SharedCache::ModifiedState::Load(DeserializationContext& context)
+{
+ SharedCache::ModifiedState state;
+ state.MSL(memoryRegionStatus);
+ state.MSL(exportInfos);
+ state.MSL(symbolInfos);
+ state.MSL(viewState);
+ return state;
+}
- for (auto& symbolInfo : context.doc["symbolInfos"].GetArray())
+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::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>>
- symbolInfos;
- auto symbolInfoArray = symbolInfo["value"].GetArray();
- symbolInfos.reserve(symbolInfoArray.Size());
- for (auto& si : symbolInfoArray)
- {
- symbolInfos.push_back({si["key"].GetUint64(),
- {static_cast<BNSymbolType>(si["val1"].GetUint64()), si["val2"].GetString()}});
- }
- state.symbolInfos[symbolInfo["key"].GetUint64()] = std::move(symbolInfos);
+ 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;
+}
- for (auto& bcV : context.doc["backingCaches"].GetArray())
- state.backingCaches.push_back(BackingCache::LoadFromValue(bcV));
-
- for (auto& imgV : context.doc["images"].GetArray())
- state.images.push_back(CacheImage::LoadFromValue(imgV));
-
- for (auto& rV : context.doc["regionsMappedIntoMemory"].GetArray())
- state.regionsMappedIntoMemory.push_back(MemoryRegion::LoadFromValue(rV));
-
- for (auto& siV : context.doc["stubIslands"].GetArray())
- state.stubIslandRegions.push_back(MemoryRegion::LoadFromValue(siV));
-
- for (auto& siV : context.doc["dyldDataSections"].GetArray())
- state.dyldDataRegions.push_back(MemoryRegion::LoadFromValue(siV));
-
- for (auto& siV : context.doc["nonImageRegions"].GetArray())
- state.nonImageRegions.push_back(MemoryRegion::LoadFromValue(siV));
+void SharedCache::ModifiedState::Merge(SharedCache::ModifiedState&& newer)
+{
+ memoryRegionStatus.merge(newer.memoryRegionStatus);
+ exportInfos.merge(newer.exportInfos);
+ symbolInfos.merge(newer.symbolInfos);
- return state;
+ if (newer.viewState)
+ viewState = newer.viewState;
}
void BackingCache::Store(SerializationContext& context) const
@@ -3598,89 +3763,118 @@ BackingCache BackingCache::Load(DeserializationContext& context)
return cache;
}
-#if defined(__GNUC__) || defined(__clang__)
-__attribute__((always_inline)) void SharedCache::AssertMutable() const
-#elif defined(_MSC_VER)
-__forceinline void SharedCache::AssertMutable() const
-#else
-#error "Unsupported compiler"
-#endif
+void CacheImage::Store(SerializationContext& context) const
+{
+ MSS(installName);
+ MSS(headerLocation);
+ MSS(regionStarts);
+}
+// static
+CacheImage CacheImage::Load(DeserializationContext& context)
{
- if (m_stateIsShared)
- {
- abort();
- }
+ CacheImage cacheImage;
+ cacheImage.MSL(installName);
+ cacheImage.MSL(headerLocation);
+ cacheImage.MSL(regionStarts);
+ return cacheImage;
}
-void SharedCache::WillMutateState()
+void Deserialize(DeserializationContext& context, std::string_view name, std::vector<BackingCache>& b)
{
- if (!m_state)
+ 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)
{
- m_state = std::make_shared<SharedCacheState>();
+ auto pair = pair_value.GetArray();
+ b[pair[0].GetUint64()] = SharedCacheMachOHeader::LoadFromValue(pair[1]);
}
- else if (m_stateIsShared)
+}
+
+void Deserialize(DeserializationContext& context, std::string_view name, AddressRangeMap<MemoryRegion>& b)
+{
+ auto array = context.doc[name.data()].GetArray();
+ for (auto& key_value : array)
{
- m_state = std::make_shared<SharedCacheState>(*m_state);
+ 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]);
}
- m_stateIsShared = false;
}
-
const std::vector<BackingCache>& SharedCache::BackingCaches() const
{
- return State().backingCaches;
+ return m_cacheInfo->backingCaches;
}
-DSCViewState SharedCache::ViewState() const
-{
- return State().viewState;
+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 State().imageStarts;
+ return m_cacheInfo->imageStarts;
}
const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImageHeaders() const
{
- return State().headers;
+ return m_cacheInfo->headers;
}
-size_t SharedCache::GetBaseAddress() const
-{
- if (State().backingCaches.empty())
- return 0;
- const BackingCache& primaryCache = State().backingCaches[0];
- if (!primaryCache.isPrimary)
+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)
{
- abort();
- return 0;
+ if (mapping.address < base)
+ {
+ base = mapping.address;
+ break;
+ }
+ }
}
-
- if (primaryCache.mappings.empty())
- return 0;
-
- return primaryCache.mappings[0].address;
+ 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 (!State().objcOptimizationDataRange)
+ if (!m_cacheInfo->objcOptimizationDataRange)
return {};
ObjCOptimizationHeader header{};
// Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior.
- reader.Read(&header, GetBaseAddress() + State().objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader));
+ reader.Read(&header, m_cacheInfo->BaseAddress() + m_cacheInfo->objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader));
return header;
}
-size_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const
+uint64_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const
{
if (auto header = GetObjCOptimizationHeader(reader); header.has_value())
- return GetBaseAddress() + header->relativeMethodSelectorBaseAddressOffset;
+ return m_cacheInfo->BaseAddress() + header->relativeMethodSelectorBaseAddressOffset;
return 0;
}
@@ -3688,7 +3882,7 @@ std::shared_ptr<MMappedFileAccessor> SharedCache::MapFile(const std::string& pat
{
return MMappedFileAccessor::
Open(m_dscView, m_dscView->GetFile()->GetSessionId(), path, [this](std::shared_ptr<MMappedFileAccessor> mmap) {
- ParseAndApplySlideInfoForFile(mmap);
+ ParseAndApplySlideInfoForFile(mmap, m_cacheInfo->BaseAddress());
})->lock();
}
@@ -3697,4 +3891,72 @@ std::shared_ptr<MMappedFileAccessor> SharedCache::MapFileWithoutApplyingSlide(co
return std::make_shared<MMappedFileAccessor>(path);
}
+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)
+{
+ return view->QueryMetadata(Tag);
+}
+
+// static
+std::optional<SharedCacheMetadata> SharedCacheMetadata::LoadFromView(BinaryView* view)
+{
+ 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));
+}
+
+const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& SharedCacheMetadata::ExportInfos() const
+{
+ return state->exportInfos;
+}
+
+std::string SharedCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const
+{
+ 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
+
+namespace {
+
+[[maybe_unused]] DSCViewType* g_dscViewType;
+
+}
+
+void InitDSCViewType() {
+ MMappedFileAccessor::InitialVMSetup();
+ std::atexit(VMShutdown);
+
+ static DSCViewType type;
+ BinaryViewType::Register(&type);
+ g_dscViewType = &type;
+}