Skip to content

Commit

Permalink
[SharedCache] Fix handling of relative selectors in macOS shared caches
Browse files Browse the repository at this point in the history
Find the relative selector base address in the Objective-C optimization
data pointed to by the shared cache header, rather than via
`__objc_scoffs`. This is only present on iOS, and not for every iOS
version that encodes selectors via direct offsets.

This also includes some related improvements:
1. Direct selectors get their own pointer type so they're rendered
   correctly in the view.
2. Method lists encoded as lists of lists are now handled.
3. The `dyld_cache_header` type added to the view is truncated to the
   length in the loaded cache. This ensures it is applied to the view.
4. A couple of methods that process method IMPs and selectors are
   updated to check whether the address is valid before attempting to
   process them. They would otherwise fail by throwing an exception if
   they proceed, but checking for validity is quicker and makes
   exception breakpoints usable.
  • Loading branch information
bdash committed Dec 11, 2024
1 parent 706d613 commit bf639fe
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 125 deletions.
18 changes: 16 additions & 2 deletions view/sharedcache/core/DSCView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ bool DSCView::Init()
"\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n"
"\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n"
"\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n"
"\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n"
"\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n"
"\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n"
"\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n"
"\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n"
"\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n"
"\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n"
"\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n"
"\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n"
"\t};", headerType, err);

Ref<Settings> settings = GetLoadSettings(GetTypeName());
Expand Down Expand Up @@ -732,8 +741,13 @@ bool DSCView::Init()
return false;
}

AddAutoSegment(primaryBase, 0x200, 0, 0x200, SegmentReadable);
AddAutoSection("__dsc_header", primaryBase, 0x200, ReadOnlyCodeSectionSemantics);
uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth());
// Truncate the `dyld_cache_header` structure to the structure present in the cache file.
auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize();
headerType.type = TypeBuilder::StructureType(newStructure).Finalize();

AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable);
AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics);
DefineType("dyld_cache_header", headerType.name, headerType.type);
DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type);

Expand Down
116 changes: 64 additions & 52 deletions view/sharedcache/core/ObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,22 +775,57 @@ void DSCObjCProcessor::LoadProtocols(VMReader* reader, Ref<Section> listSection)
}
}

void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start)
void DSCObjCProcessor::ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
{
reader->Seek(start);
method_list_t head;
head.entsizeAndFlags = reader->Read32();
head.count = reader->Read32();
if (head.count > 0x1000)
{
m_logger->LogError("List of method lists at 0x%llx has an invalid count of 0x%x", start, head.count);
return;
}

for (size_t i = 0; i < head.count; ++i) {
relative_list_list_entry_t list_entry;
reader->Read(&list_entry, sizeof(list_entry));

ReadMethodList(reader, cls, name, reader->GetOffset() - sizeof(list_entry) + list_entry.listOffset);
// Reset the cursor to immediately past the list entry.
reader->Seek(start + sizeof(method_list_t) + ((i + 1) * sizeof(relative_list_list_entry_t)));
}
}

void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
{
// Lower two bits indicate the type of method list.
switch (start & 0b11) {
case 0:
break;
case 1:
return ReadListOfMethodLists(reader, cls, name, start - 1);
default:
m_logger->LogDebug("ReadMethodList: Unknown method list type at 0x%llx: %d", start, start & 0x3);
return;
}

reader->Seek(start);
method_list_t head;
head.entsizeAndFlags = reader->Read32();
head.count = reader->Read32();

if (head.count > 0x1000)
{
m_logger->LogError("Method list at 0x%llx has an invalid count of 0x%x", start, head.count);
return;
}

uint64_t pointerSize = m_data->GetAddressSize();
bool relativeOffsets = (head.entsizeAndFlags & 0xFFFF0000) & 0x80000000;
bool directSelectors = (head.entsizeAndFlags & 0xFFFF0000) & 0x40000000;
auto methodSize = relativeOffsets ? 12 : pointerSize * 3;
DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + name, start, true);
DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + std::string(name), start, true);

for (unsigned i = 0; i < head.count; i++)
{
Expand All @@ -806,18 +841,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str
// --
if (relativeOffsets)
{
if (m_customRelativeMethodSelectorBase.has_value())
{
meth.name = m_customRelativeMethodSelectorBase.value() + reader->ReadS32();
meth.types = reader->GetOffset() + reader->ReadS32();
meth.imp = reader->GetOffset() + reader->ReadS32();
}
else
{
meth.name = reader->GetOffset() + reader->ReadS32();
meth.types = reader->GetOffset() + reader->ReadS32();
meth.imp = reader->GetOffset() + reader->ReadS32();
auto selectorBaseOffset = reader->GetOffset();
if (directSelectors && m_customRelativeMethodSelectorBase.has_value()) {
selectorBaseOffset = m_customRelativeMethodSelectorBase.value();
}

meth.name = selectorBaseOffset + reader->Read32();
meth.types = reader->GetOffset() + reader->ReadS32();
meth.imp = reader->GetOffset() + reader->ReadS32();
}
else
{
Expand Down Expand Up @@ -881,14 +912,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str
}
}

void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start)
void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
{
reader->Seek(start);
ivar_list_t head;
head.entsizeAndFlags = reader->Read32();
head.count = reader->Read32();
auto addressSize = m_data->GetAddressSize();
DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + name, start, true);
DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + std::string(name), start, true);
if (head.count > 0x1000)
{
m_logger->LogError("Ivar list at 0x%llx has an invalid count of 0x%llx", start, head.count);
Expand Down Expand Up @@ -1010,6 +1041,10 @@ void DSCObjCProcessor::GenerateClassTypes()

bool DSCObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod)
{
if (!method.imp || !m_data->IsValidOffset(method.imp)) {
return false;
}

std::stringstream r(method.name);

std::string token;
Expand Down Expand Up @@ -1221,6 +1256,19 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseN
m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false));
m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize));

Ref<Type> relativeSelectorPtr;
auto reader = VMReader(vm);
if (auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(reader)) {
m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", objCRelativeMethodsBaseAddr);
m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr;

auto type = TypeBuilder::PointerType(4, Type::PointerType(addrSize, Type::IntegerType(1, false)))
.SetPointerBase(RelativeToConstantPointerBaseType, objCRelativeMethodsBaseAddr)
.Finalize();
auto relativeSelectorPtrName = defineTypedef(m_data, {"relative_SEL"}, type);
relativeSelectorPtr = Type::NamedType(m_data, relativeSelectorPtrName);
}

// https://github.com/apple-oss-distributions/objc4/blob/196363c165b175ed925ef6b9b99f558717923c47/runtime/objc-abi.h
EnumerationBuilder imageInfoFlagBuilder;
imageInfoFlagBuilder.AddMemberWithValue("IsReplacement", 1 << 0);
Expand Down Expand Up @@ -1256,7 +1304,7 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseN
m_typeNames.imageInfo = imageInfoType.first;

StructureBuilder methodEntry;
methodEntry.AddMember(rptr_t, "name");
methodEntry.AddMember(relativeSelectorPtr ? relativeSelectorPtr : rptr_t, "name");
methodEntry.AddMember(rptr_t, "types");
methodEntry.AddMember(rptr_t, "imp");
auto type = finalizeStructureBuilder(m_data, methodEntry, "objc_method_entry_t");
Expand Down Expand Up @@ -1360,42 +1408,6 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseN
protocolBuilder.AddMember(Type::IntegerType(4, false), "flags");
m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first;

auto reader = VMReader(vm);

if (auto addr = m_cache->GetImageStart("/usr/lib/libobjc.A.dylib"))
{
auto header = m_cache->HeaderForAddress(addr.value());
uint64_t scoffs_addr = 0;
size_t scoffs_size = 0;

for (const auto& section : header->sections)
{
char name[17];
memcpy(name, section.sectname, 16);
name[16] = 0;
if (std::string(name) == "__objc_scoffs")
{
scoffs_addr = section.addr;
scoffs_size = section.size;
break;
}
}

if (scoffs_size && scoffs_addr)
{
if (scoffs_size == 0x20)
{
m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr);
}
else
{
m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr + 8);
}
m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", m_customRelativeMethodSelectorBase.value());
}
}


m_data->BeginBulkModifySymbols();
if (auto classList = m_data->GetSectionByName(baseName + "::__objc_classlist"))
LoadClasses(&reader, classList);
Expand Down
9 changes: 7 additions & 2 deletions view/sharedcache/core/ObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ namespace DSCObjC {
typedef struct {
uint64_t count;
} protocol_list_t;
struct relative_list_list_entry_t {
uint64_t imageIndex: 16;
int64_t listOffset: 48;
};
typedef struct {
view_ptr_t isa;
view_ptr_t mangledName;
Expand Down Expand Up @@ -214,8 +218,9 @@ namespace DSCObjC {
std::vector<QualifiedNameOrType> ParseEncodedType(const std::string& type);
void DefineObjCSymbol(BNSymbolType symbolType, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred);
void DefineObjCSymbol(BNSymbolType symbolType, Ref<Type> type, const std::string& name, uint64_t addr, bool deferred);
void ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start);
void ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start);
void ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
void ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
void ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
void LoadClasses(VMReader* reader, Ref<Section> listSection);
void LoadCategories(VMReader* reader, Ref<Section> listSection);
void LoadProtocols(VMReader* reader, Ref<Section> listSection);
Expand Down
44 changes: 44 additions & 0 deletions view/sharedcache/core/SharedCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ struct SharedCache::State
std::vector<MemoryRegion> dyldDataRegions;
std::vector<MemoryRegion> nonImageRegions;

std::optional<std::pair<size_t, size_t>> objcOptimizationDataRange;

std::string baseFilePath;
SharedCacheFormat cacheFormat;
DSCViewState viewState = DSCViewStateUnloaded;
Expand Down Expand Up @@ -277,6 +279,10 @@ void SharedCache::PerformInitialLoad()
MutableState().cacheFormat = iOS16CacheFormat;
}

if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) {
MutableState().objcOptimizationDataRange = {primaryCacheHeader.objcOptsOffset, primaryCacheHeader.objcOptsSize};
}

switch (State().cacheFormat)
{
case RegularCacheFormat:
Expand Down Expand Up @@ -3552,4 +3558,42 @@ const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImag
{
return State().headers;
}
size_t SharedCache::GetBaseAddress() const {
if (State().backingCaches.empty()) {
return 0;
}

const BackingCache& primaryCache = State().backingCaches[0];
if (!primaryCache.isPrimary) {
abort();
return 0;
}

if (primaryCache.mappings.empty()) {
return 0;
}

return primaryCache.mappings[0].address;
}

// 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) {
return {};
}

ObjCOptimizationHeader header{};
// Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior.
reader.Read(&header, GetBaseAddress() + State().objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader));

return header;
}

size_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const {
if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) {
return GetBaseAddress() + header->relativeMethodSelectorBaseAddressOffset;
}
return 0;
}

} // namespace SharedCacheCore
Loading

0 comments on commit bf639fe

Please sign in to comment.