Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 46 additions & 38 deletions view/sharedcache/core/MachOProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,50 +84,58 @@ void SharedCacheMachOProcessor::ApplyHeader(const SharedCache& cache, SharedCach
}

// Apply symbols from the .symbols cache files.
for (const auto &entry: cache.GetEntries())
{
// NOTE: We check addr size as we only support 64bit .symbols files currently.
if (entry.GetType() != CacheEntryType::Symbols && m_vm->GetAddressSize() == 8)
continue;
const auto& entryHeader = entry.GetHeader();
ApplyUnmappedLocalSymbols(cache, header, std::move(typeLib));
}

// This is where we get the symbol and string table information from in the .symbols file.
dyld_cache_local_symbols_info localSymbolsInfo = {};
auto localSymbolsInfoAddr = entry.GetMappedAddress(entryHeader.localSymbolsOffset);
if (!localSymbolsInfoAddr.has_value())
continue;
m_vm->Read(&localSymbolsInfo, *localSymbolsInfoAddr, sizeof(dyld_cache_local_symbols_info));
void SharedCacheMachOProcessor::ApplyUnmappedLocalSymbols(const SharedCache& cache, const SharedCacheMachOHeader& header, Ref<TypeLibrary> typeLib)
{
const auto& localSymbolsCacheEntry = cache.GetLocalSymbolsEntry();
auto localSymbolsVM = cache.GetLocalSymbolsVM();
if (!localSymbolsCacheEntry || !localSymbolsVM)
return;

// NOTE: We check addr size as we only support 64bit .symbols files currently.
// TODO: Support 32-bit nlist
if (localSymbolsVM->GetAddressSize() != 8)
return;

const auto& entryHeader = localSymbolsCacheEntry->GetHeader();

// This is where we get the symbol and string table information from in the .symbols file.
dyld_cache_local_symbols_info localSymbolsInfo = {};
auto localSymbolsInfoAddr = entryHeader.localSymbolsOffset;

localSymbolsVM->Read(&localSymbolsInfo, localSymbolsInfoAddr, sizeof(dyld_cache_local_symbols_info));

// Read each symbols entry, looking for the current image entry.
uint64_t localEntriesAddr = *localSymbolsInfoAddr + localSymbolsInfo.entriesOffset;
uint64_t localSymbolsAddr = *localSymbolsInfoAddr + localSymbolsInfo.nlistOffset;
uint64_t localStringsAddr = *localSymbolsInfoAddr + localSymbolsInfo.stringsOffset;
// Read each symbols entry, looking for the current image entry.
uint64_t localEntriesAddr = localSymbolsInfoAddr + localSymbolsInfo.entriesOffset;
uint64_t localSymbolsAddr = localSymbolsInfoAddr + localSymbolsInfo.nlistOffset;
uint64_t localStringsAddr = localSymbolsInfoAddr + localSymbolsInfo.stringsOffset;

for (uint32_t i = 0; i < localSymbolsInfo.entriesCount; i++)
{
dyld_cache_local_symbols_entry_64 localSymbolsEntry = {};
for (uint32_t i = 0; i < localSymbolsInfo.entriesCount; i++)
localSymbolsVM->Read(&localSymbolsEntry, localEntriesAddr + i * sizeof(dyld_cache_local_symbols_entry_64),
sizeof(dyld_cache_local_symbols_entry_64));

// The dylibOffset is the offset from the cache base address to the image header.
const auto imageAddr = cache.GetBaseAddress() + localSymbolsEntry.dylibOffset;
if (imageAddr != header.textBase)
continue;

// We have found the entry to read!
uint64_t symbolTableStart = localSymbolsAddr + (localSymbolsEntry.nlistStartIndex * sizeof(nlist_64));
TableInfo symbolInfo = {symbolTableStart, localSymbolsEntry.nlistCount};
TableInfo stringInfo = {localStringsAddr, localSymbolsInfo.stringsSize};
m_view->BeginBulkModifySymbols();
const auto symbols = header.ReadSymbolTable(*localSymbolsVM, symbolInfo, stringInfo);
for (const auto &sym: symbols)
{
m_vm->Read(&localSymbolsEntry, localEntriesAddr + i * sizeof(dyld_cache_local_symbols_entry_64),
sizeof(dyld_cache_local_symbols_entry_64));
// The dylibOffset is the offset from the cache base address to the image header.
const auto imageAddr = cache.GetBaseAddress() + localSymbolsEntry.dylibOffset;
if (imageAddr == header.textBase)
{
// We have found the entry to read!
// TODO: Support 32bit nlist
uint64_t symbolTableStart = localSymbolsAddr + (localSymbolsEntry.nlistStartIndex * sizeof(nlist_64));
TableInfo symbolInfo = {symbolTableStart, localSymbolsEntry.nlistCount};
TableInfo stringInfo = {localStringsAddr, localSymbolsInfo.stringsSize};
m_view->BeginBulkModifySymbols();
const auto symbols = header.ReadSymbolTable(*m_vm, symbolInfo, stringInfo);
for (const auto &sym: symbols)
{
auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view);
ApplySymbol(m_view, typeLib, symbol, symbolType);
}
m_view->EndBulkModifySymbols();
break;
}
auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view);
ApplySymbol(m_view, typeLib, std::move(symbol), std::move(symbolType));
}
m_view->EndBulkModifySymbols();
return;
}
}

Expand Down
2 changes: 2 additions & 0 deletions view/sharedcache/core/MachOProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ class SharedCacheMachOProcessor
uint64_t ApplyHeaderSections(SharedCacheMachOHeader& header);

void ApplyHeaderDataVariables(SharedCacheMachOHeader& header);

void ApplyUnmappedLocalSymbols(const SharedCache& cache, const SharedCacheMachOHeader& header, BinaryNinja::Ref<BinaryNinja::TypeLibrary> typeLib);
};
37 changes: 36 additions & 1 deletion view/sharedcache/core/ObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,52 @@ std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cac
return header;
}

std::optional<std::pair<uint64_t, LegacyObjCOptimizationHeader>> GetLegacyObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader)
{
// In older versions the header lives in the `__TEXT,__objc_opt_ro` section within /usr/lib/libobjc.A.dylib
auto libObjC = cache.GetImageWithName("/usr/lib/libobjc.A.dylib");
if (!libObjC)
return std::nullopt;

// Convert the header's `char[16]` to a `string_view`.
auto AsStringView = []<size_t N>(const char (&arr)[N]) {
const char* end = std::find(arr, arr + N, '\0');
return std::string_view(arr, end - arr);
};

for (auto section : libObjC->header->sections) {
if (AsStringView(section.segname) != "__TEXT" || AsStringView(section.sectname) != "__objc_opt_ro")
continue;

LegacyObjCOptimizationHeader header = {};
reader.Read(&header, section.addr, sizeof(LegacyObjCOptimizationHeader));

// The `relativeMethodSelectorBaseAddressOffset` field was added in version 16 (the final version of this struct).
if (header.version >= 16)
return {{section.addr, header}};

break;
}

return std::nullopt;
}

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())
{
auto baseAddress = controller->GetCache().GetBaseAddress();
m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset;
}
else if (const auto info = GetLegacyObjCOptimizationHeader(controller->GetCache(), dangerReader); info.has_value())
{
const auto [optSectionAddr, header] = *info;
m_customRelativeMethodSelectorBase = optSectionAddr + header.relativeMethodSelectorBaseAddressOffset;
}
}

return m_customRelativeMethodSelectorBase.value_or(0);
Expand Down
16 changes: 16 additions & 0 deletions view/sharedcache/core/ObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ struct ObjCOptimizationHeader
uint64_t relativeMethodSelectorBaseAddressOffset;
};

// `objc_opt_t` from dyld/include/objc-shared-cache.h
struct LegacyObjCOptimizationHeader
{
uint32_t version;
uint32_t flags;
int32_t selopt_offset;
int32_t headeropt_ro_offset;
int32_t unused_clsopt_offset;
int32_t unused_protocolopt_offset;
int32_t headeropt_rw_offset;
int32_t unused_protocolopt2_offset;
int32_t largeSharedCachesClassOffset;
int32_t largeSharedCachesProtocolOffset;
int64_t relativeMethodSelectorBaseAddressOffset;
};

namespace DSCObjC {
class SharedCacheObjCReader : public BinaryNinja::ObjCReader
{
Expand Down
17 changes: 13 additions & 4 deletions view/sharedcache/core/SharedCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,8 @@ CacheEntry CacheEntry::FromFile(const std::string& filePath, const std::string&
{
// We found a single symbols cache entry file. Mark it as such!
type = CacheEntryType::Symbols;
// Adjust the mapping for the symbol file, they seem to be only for the header.
// If we do not adjust the mapping than we will not be able to read the symbol table through the virtual memory.
mappings[0].fileOffset = 0;
mappings[0].size = file->Length();
// Symbol files are not mapped into the address space.
mappings.clear();
}
else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0
&& header.imagesTextOffset == 0)
Expand Down Expand Up @@ -231,6 +229,17 @@ void SharedCache::AddEntry(CacheEntry entry)
// Get the file accessor to associate with the virtual memory region.
auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath());

if (entry.GetType() == CacheEntryType::Symbols)
{
m_localSymbolsEntry = std::move(entry);
// Map the entire file into its own virtual memory space.
// This is necessary due to code that processes symbols being written in terms of a `VirtualMemory`
Copy link
Member

@emesare emesare Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the fileAccessor in place of the m_localSymbolsVM, since it is just that file mapped at zero and it does not exist inside the regular virtual memory.

Just a suggestion, nothing blocking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the comment mentions, this uses a VirtualMemory as SharedCacheMachOHeader::ReadSymbolTable requires one. Since it is also used to process symbol tables inside images, such as in macOS shared caches, it cannot be updated to work with a FileAccessor. Some additional refactoring would be required to remove that use of VirtualMemory.

// rather than something more generic.
m_localSymbolsVM = std::make_shared<VirtualMemory>(m_vm->GetAddressSize());
m_localSymbolsVM->MapRegion(fileAccessor, {0, fileAccessor.lock()->Length()}, 0);
return;
}

// 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();
Expand Down
8 changes: 8 additions & 0 deletions view/sharedcache/core/SharedCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ class SharedCache
// NOTE: Wrapped in unique_ptr to keep SharedCache movable.
std::unique_ptr<std::shared_mutex> m_namedSymMutex;

// Local symbols entry and its mapping, used to read symbol tables from the .symbols file.
// These are handled separately as they are not mapped into the main virtual memory of the cache.
std::optional<CacheEntry> m_localSymbolsEntry;
std::shared_ptr<VirtualMemory> m_localSymbolsVM;

bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info);

// Add a region known not to overlap with another, otherwise use AddRegion.
Expand All @@ -211,6 +216,9 @@ class SharedCache
const std::unordered_map<uint64_t, CacheImage>& GetImages() const { return m_images; }
const std::unordered_map<uint64_t, CacheSymbol>& GetSymbols() const { return m_symbols; }

const std::optional<CacheEntry>& GetLocalSymbolsEntry() const { return m_localSymbolsEntry; }
std::shared_ptr<VirtualMemory> GetLocalSymbolsVM() const { return m_localSymbolsVM; }

void AddImage(CacheImage&& image);

// Add a region that may overlap with another.
Expand Down
3 changes: 3 additions & 0 deletions view/sharedcache/core/SharedCacheBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ bool SharedCacheBuilder::AddFile(
// Skip bndb files!
if (fileName.find(".bndb") != std::string::npos)
return false;
// Skip a2s files
if (fileName.find(".a2s") != std::string::npos)
return false;

try
{
Expand Down
Loading