Skip to content

Commit aa408cf

Browse files
committed
[SharedCache] Fix handling of relative selectors in macOS shared caches
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.
1 parent a44b783 commit aa408cf

File tree

6 files changed

+227
-125
lines changed

6 files changed

+227
-125
lines changed

view/sharedcache/core/DSCView.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ bool DSCView::Init()
200200
"\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n"
201201
"\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n"
202202
"\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n"
203+
"\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n"
204+
"\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n"
205+
"\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n"
206+
"\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n"
207+
"\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n"
208+
"\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n"
209+
"\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n"
210+
"\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n"
211+
"\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n"
203212
"\t};", headerType, err);
204213

205214
Ref<Settings> settings = GetLoadSettings(GetTypeName());
@@ -732,8 +741,13 @@ bool DSCView::Init()
732741
return false;
733742
}
734743

735-
AddAutoSegment(primaryBase, 0x200, 0, 0x200, SegmentReadable);
736-
AddAutoSection("__dsc_header", primaryBase, 0x200, ReadOnlyCodeSectionSemantics);
744+
uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth());
745+
// Truncate the `dyld_cache_header` structure to the structure present in the cache file.
746+
auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize();
747+
headerType.type = TypeBuilder::StructureType(newStructure).Finalize();
748+
749+
AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable);
750+
AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics);
737751
DefineType("dyld_cache_header", headerType.name, headerType.type);
738752
DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type);
739753

view/sharedcache/core/ObjC.cpp

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -775,22 +775,57 @@ void DSCObjCProcessor::LoadProtocols(VMReader* reader, Ref<Section> listSection)
775775
}
776776
}
777777

778-
void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start)
778+
void DSCObjCProcessor::ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
779779
{
780780
reader->Seek(start);
781781
method_list_t head;
782782
head.entsizeAndFlags = reader->Read32();
783783
head.count = reader->Read32();
784+
if (head.count > 0x1000)
785+
{
786+
m_logger->LogError("List of method lists at 0x%llx has an invalid count of 0x%x", start, head.count);
787+
return;
788+
}
789+
790+
for (size_t i = 0; i < head.count; ++i) {
791+
relative_list_list_entry_t list_entry;
792+
reader->Read(&list_entry, sizeof(list_entry));
793+
794+
ReadMethodList(reader, cls, name, reader->GetOffset() - sizeof(list_entry) + list_entry.listOffset);
795+
// Reset the cursor to immediately past the list entry.
796+
reader->Seek(start + sizeof(method_list_t) + ((i + 1) * sizeof(relative_list_list_entry_t)));
797+
}
798+
}
799+
800+
void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
801+
{
802+
// Lower two bits indicate the type of method list.
803+
switch (start & 0b11) {
804+
case 0:
805+
break;
806+
case 1:
807+
return ReadListOfMethodLists(reader, cls, name, start - 1);
808+
default:
809+
m_logger->LogDebug("ReadMethodList: Unknown method list type at 0x%llx: %d", start, start & 0x3);
810+
return;
811+
}
812+
813+
reader->Seek(start);
814+
method_list_t head;
815+
head.entsizeAndFlags = reader->Read32();
816+
head.count = reader->Read32();
817+
784818
if (head.count > 0x1000)
785819
{
786820
m_logger->LogError("Method list at 0x%llx has an invalid count of 0x%x", start, head.count);
787821
return;
788822
}
823+
789824
uint64_t pointerSize = m_data->GetAddressSize();
790825
bool relativeOffsets = (head.entsizeAndFlags & 0xFFFF0000) & 0x80000000;
791826
bool directSelectors = (head.entsizeAndFlags & 0xFFFF0000) & 0x40000000;
792827
auto methodSize = relativeOffsets ? 12 : pointerSize * 3;
793-
DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + name, start, true);
828+
DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + std::string(name), start, true);
794829

795830
for (unsigned i = 0; i < head.count; i++)
796831
{
@@ -806,18 +841,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str
806841
// --
807842
if (relativeOffsets)
808843
{
809-
if (m_customRelativeMethodSelectorBase.has_value())
810-
{
811-
meth.name = m_customRelativeMethodSelectorBase.value() + reader->ReadS32();
812-
meth.types = reader->GetOffset() + reader->ReadS32();
813-
meth.imp = reader->GetOffset() + reader->ReadS32();
814-
}
815-
else
816-
{
817-
meth.name = reader->GetOffset() + reader->ReadS32();
818-
meth.types = reader->GetOffset() + reader->ReadS32();
819-
meth.imp = reader->GetOffset() + reader->ReadS32();
844+
auto selectorBaseOffset = reader->GetOffset();
845+
if (directSelectors && m_customRelativeMethodSelectorBase.has_value()) {
846+
selectorBaseOffset = m_customRelativeMethodSelectorBase.value();
820847
}
848+
849+
meth.name = selectorBaseOffset + reader->Read32();
850+
meth.types = reader->GetOffset() + reader->ReadS32();
851+
meth.imp = reader->GetOffset() + reader->ReadS32();
821852
}
822853
else
823854
{
@@ -881,14 +912,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str
881912
}
882913
}
883914

884-
void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start)
915+
void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start)
885916
{
886917
reader->Seek(start);
887918
ivar_list_t head;
888919
head.entsizeAndFlags = reader->Read32();
889920
head.count = reader->Read32();
890921
auto addressSize = m_data->GetAddressSize();
891-
DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + name, start, true);
922+
DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + std::string(name), start, true);
892923
if (head.count > 0x1000)
893924
{
894925
m_logger->LogError("Ivar list at 0x%llx has an invalid count of 0x%llx", start, head.count);
@@ -1010,6 +1041,10 @@ void DSCObjCProcessor::GenerateClassTypes()
10101041

10111042
bool DSCObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod)
10121043
{
1044+
if (!method.imp || !m_data->IsValidOffset(method.imp)) {
1045+
return false;
1046+
}
1047+
10131048
std::stringstream r(method.name);
10141049

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

1259+
Ref<Type> relativeSelectorPtr;
1260+
auto reader = VMReader(vm);
1261+
if (auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(reader)) {
1262+
m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", objCRelativeMethodsBaseAddr);
1263+
m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr;
1264+
1265+
auto type = TypeBuilder::PointerType(4, Type::PointerType(addrSize, Type::IntegerType(1, false)))
1266+
.SetPointerBase(RelativeToConstantPointerBaseType, objCRelativeMethodsBaseAddr)
1267+
.Finalize();
1268+
auto relativeSelectorPtrName = defineTypedef(m_data, {"relative_SEL"}, type);
1269+
relativeSelectorPtr = Type::NamedType(m_data, relativeSelectorPtrName);
1270+
}
1271+
12241272
// https://github.com/apple-oss-distributions/objc4/blob/196363c165b175ed925ef6b9b99f558717923c47/runtime/objc-abi.h
12251273
EnumerationBuilder imageInfoFlagBuilder;
12261274
imageInfoFlagBuilder.AddMemberWithValue("IsReplacement", 1 << 0);
@@ -1256,7 +1304,7 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseN
12561304
m_typeNames.imageInfo = imageInfoType.first;
12571305

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

1363-
auto reader = VMReader(vm);
1364-
1365-
if (auto addr = m_cache->GetImageStart("/usr/lib/libobjc.A.dylib"))
1366-
{
1367-
auto header = m_cache->HeaderForAddress(addr.value());
1368-
uint64_t scoffs_addr = 0;
1369-
size_t scoffs_size = 0;
1370-
1371-
for (const auto& section : header->sections)
1372-
{
1373-
char name[17];
1374-
memcpy(name, section.sectname, 16);
1375-
name[16] = 0;
1376-
if (std::string(name) == "__objc_scoffs")
1377-
{
1378-
scoffs_addr = section.addr;
1379-
scoffs_size = section.size;
1380-
break;
1381-
}
1382-
}
1383-
1384-
if (scoffs_size && scoffs_addr)
1385-
{
1386-
if (scoffs_size == 0x20)
1387-
{
1388-
m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr);
1389-
}
1390-
else
1391-
{
1392-
m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr + 8);
1393-
}
1394-
m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", m_customRelativeMethodSelectorBase.value());
1395-
}
1396-
}
1397-
1398-
13991411
m_data->BeginBulkModifySymbols();
14001412
if (auto classList = m_data->GetSectionByName(baseName + "::__objc_classlist"))
14011413
LoadClasses(&reader, classList);

view/sharedcache/core/ObjC.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ namespace DSCObjC {
5858
typedef struct {
5959
uint64_t count;
6060
} protocol_list_t;
61+
struct relative_list_list_entry_t {
62+
uint64_t imageIndex: 16;
63+
int64_t listOffset: 48;
64+
};
6165
typedef struct {
6266
view_ptr_t isa;
6367
view_ptr_t mangledName;
@@ -214,8 +218,9 @@ namespace DSCObjC {
214218
std::vector<QualifiedNameOrType> ParseEncodedType(const std::string& type);
215219
void DefineObjCSymbol(BNSymbolType symbolType, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred);
216220
void DefineObjCSymbol(BNSymbolType symbolType, Ref<Type> type, const std::string& name, uint64_t addr, bool deferred);
217-
void ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start);
218-
void ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start);
221+
void ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
222+
void ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
223+
void ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start);
219224
void LoadClasses(VMReader* reader, Ref<Section> listSection);
220225
void LoadCategories(VMReader* reader, Ref<Section> listSection);
221226
void LoadProtocols(VMReader* reader, Ref<Section> listSection);

view/sharedcache/core/SharedCache.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ struct SharedCache::State
7575
std::vector<MemoryRegion> dyldDataRegions;
7676
std::vector<MemoryRegion> nonImageRegions;
7777

78+
std::optional<std::pair<size_t, size_t>> objcOptimizationDataRange;
79+
7880
std::string baseFilePath;
7981
SharedCacheFormat cacheFormat;
8082
DSCViewState viewState = DSCViewStateUnloaded;
@@ -311,6 +313,10 @@ void SharedCache::PerformInitialLoad()
311313
MutableState().cacheFormat = iOS16CacheFormat;
312314
}
313315

316+
if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) {
317+
MutableState().objcOptimizationDataRange = {primaryCacheHeader.objcOptsOffset, primaryCacheHeader.objcOptsSize};
318+
}
319+
314320
switch (State().cacheFormat)
315321
{
316322
case RegularCacheFormat:
@@ -3673,4 +3679,42 @@ const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImag
36733679
{
36743680
return State().headers;
36753681
}
3682+
size_t SharedCache::GetBaseAddress() const {
3683+
if (State().backingCaches.empty()) {
3684+
return 0;
3685+
}
3686+
3687+
const BackingCache& primaryCache = State().backingCaches[0];
3688+
if (!primaryCache.isPrimary) {
3689+
abort();
3690+
return 0;
3691+
}
3692+
3693+
if (primaryCache.mappings.empty()) {
3694+
return 0;
3695+
}
3696+
3697+
return primaryCache.mappings[0].address;
3698+
}
3699+
3700+
// Intentionally takes a copy to avoid modifying the cursor position in the original reader.
3701+
std::optional<ObjCOptimizationHeader> SharedCache::GetObjCOptimizationHeader(VMReader reader) const {
3702+
if (!State().objcOptimizationDataRange) {
3703+
return {};
3704+
}
3705+
3706+
ObjCOptimizationHeader header{};
3707+
// Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior.
3708+
reader.Read(&header, GetBaseAddress() + State().objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader));
3709+
3710+
return header;
3711+
}
3712+
3713+
size_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const {
3714+
if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) {
3715+
return GetBaseAddress() + header->relativeMethodSelectorBaseAddressOffset;
3716+
}
3717+
return 0;
3718+
}
3719+
36763720
} // namespace SharedCacheCore

0 commit comments

Comments
 (0)