Source/PLCrashAsyncObjCSection.mm (722 lines of code) (raw):
/*
* Author: Mike Ash <mikeash@plausiblelabs.com>
*
* Copyright (c) 2012-2013 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "PLCrashAsyncObjCSection.h"
#include "PLCrashCompatConstants.h"
#include <Foundation/Foundation.h>
/**
* @internal
* @ingroup plcrash_async_image
* @defgroup plcrash_async_image_objc Objective-C Metadata Parsing
*
* Implements async-safe Objective-C binary parsing, for use at crash time when extracting binary information
* from the crashed process.
* @{
*/
static const char * const kObjCSegmentName = "__OBJC";
static const char * const kDataSegmentName = "__DATA";
static const char * const kDataConstSegmentName = "__DATA_CONST";
static const char * const kDataDirtySegmentName = "__DATA_DIRTY";
static const char * const kObjCModuleInfoSectionName = "__module_info";
static const char * const kClassListSectionName = "__objc_classlist";
static const char * const kCategoryListSectionName = "__objc_catlist";
static const char * const kObjCConstSectionName = "__objc_const";
static const char * const kObjCConstAxSectionName = "__objc_const_ax";
static const char * const kObjCDataSectionName = "__objc_data";
static const char * const kDataSectionName = "__data";
static uint32_t CLS_NO_METHOD_ARRAY = 0x4000;
static uint32_t END_OF_METHODS_LIST = -1;
/**
* @internal
* Flag set for non-ptr ISAs. This flag is not ABI stable, and may change.
*/
#define PLCRASH_ASYNC_OBJC_ISA_NONPTR_FLAG 0x1
/**
* Return true if the given CPU uses non-pointer isa values.
*
* On x64 architectures, isa pointers are masked to
* allow for refcounting and maintaining bit flags when the LSB is 0x1.
*
* @warning ISA tagging is handled entirely in libobjc, and could be changed in any future release. There
* are variables vended from libobjc that return the isa pointer mask; we validate these in our
* tests, but we can't validate the meaning of bitfield checked here.
*
* The tagged isa pointers seem to be used even within the writable class data; as such, we must
* perform masking here, as well. This is another reason we should migrate the code to
* work directly on the backing unmodified pages, as that provides us with a stable ABI. In
* the worst case scenario, we'll simply fail to symbolicate a class should the ABI
* change incompatibly.
*
* @sa http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
* @sa https://github.com/opensource-apple/objc4/blob/master/runtime/objc-config.h
*/
#if !__LP64__ || TARGET_IPHONE_SIMULATOR
#define PLCRASH_ASYNC_OBJC_SUPPORT_NONPTR_ISA 0
#else
#define PLCRASH_ASYNC_OBJC_SUPPORT_NONPTR_ISA 1
#endif
/**
* @internal
* The pointer mask for non-pointer ISAs. This flag is not ABI stable, and may change; it is validated
* at development time via our unit tests. As per our API invariants, if this flag becomes out-of-sync
* with the host OS, our symbolication implementation will either fail to find some symbols, or will
* return incorrect symbols, but it will not crash.
*
* @sa https://github.com/opensource-apple/objc4/blob/master/runtime/objc-private.h
*/
#if defined(__arm64__)
#define PLCRASH_ASYNC_OBJC_ISA_NONPTR_CLASS_MASK 0x0000000ffffffff8ULL
#elif defined(__x86_64__)
#define PLCRASH_ASYNC_OBJC_ISA_NONPTR_CLASS_MASK 0x00007ffffffffff8ULL
#else
#define PLCRASH_ASYNC_OBJC_ISA_NONPTR_CLASS_MASK ~1UL
#endif
/**
* @internal
* @see https://opensource.apple.com/source/objc4/objc4-750/runtime/objc-runtime-new.h
*
* Class's rw data structure has been realized.
** If set, data pointers to RW data instead of RO.
*/
static const uint32_t RW_REALIZED = (1U<<31);
/**
* @internal
* @see https://opensource.apple.com/source/objc4/objc4-750/runtime/objc-runtime-new.h
*
* A realized class' data pointer is a heap-copied copy of class_ro_t.
*/
static const uint32_t RW_COPIED_RO = (1<<27);
/**
* @internal
* @see https://opensource.apple.com/source/objc4/objc4-750/runtime/objc-runtime-new.h
*
* A class' data pointer mask.
*/
#if !__LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#else
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif
struct pl_objc1_module {
uint32_t version;
uint32_t size;
uint32_t name;
uint32_t symtab;
};
struct pl_objc1_symtab {
uint32_t sel_ref_cnt;
uint32_t refs;
uint16_t cls_def_count;
uint16_t cat_def_count;
};
struct pl_objc1_class {
uint32_t isa;
uint32_t super;
uint32_t name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
uint32_t ivars;
uint32_t methods;
uint32_t cache;
uint32_t protocols;
};
struct pl_objc1_method_list {
uint32_t obsolete;
uint32_t count;
};
struct pl_objc1_method {
uint32_t name;
uint32_t types;
uint32_t imp;
};
struct pl_objc2_class_32 {
uint32_t isa;
uint32_t superclass;
uint32_t cache;
uint32_t vtable;
uint32_t data_rw;
};
struct pl_objc2_class_64 {
uint64_t isa;
uint64_t superclass;
uint64_t cache;
uint64_t vtable;
uint64_t data_rw;
};
struct pl_objc2_class_data_rw_32 {
uint32_t flags;
uint32_t version;
uint32_t data_ro;
};
struct pl_objc2_class_data_rw_64 {
uint32_t flags;
uint32_t version;
uint64_t data_ro;
};
struct pl_objc2_class_data_ro_32 {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t ivarLayout;
uint32_t name;
uint32_t baseMethods;
uint32_t baseProtocols;
uint32_t ivars;
uint32_t weakIvarLayout;
uint32_t baseProperties;
};
struct pl_objc2_class_data_ro_64 {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
uint64_t ivarLayout;
uint64_t name;
uint64_t baseMethods;
uint64_t baseProtocols;
uint64_t ivars;
uint64_t weakIvarLayout;
uint64_t baseProperties;
};
/** @internal
* Category list entry (32-bit representation).
*/
struct pl_objc2_category_32 {
uint32_t name;
uint32_t cls;
uint32_t instanceMethods;
uint32_t classMethods;
uint32_t protocols;
uint32_t instanceProperties;
};
/** @internal
* Category list entry (64-bit representation).
*/
struct pl_objc2_category_64 {
uint64_t name;
uint64_t cls;
uint64_t instanceMethods;
uint64_t classMethods;
uint64_t protocols;
uint64_t instanceProperties;
};
struct pl_objc2_method_32 {
uint32_t name;
uint32_t types;
uint32_t imp;
};
struct pl_objc2_method_64 {
uint64_t name;
uint64_t types;
uint64_t imp;
};
struct pl_objc2_list_header {
uint32_t entsize;
uint32_t count;
};
/**
* Returns the pointer value for a non-pointer isa. This assumes that the lsb flag of 0x1 will continue to be
* used to designate a non-pointer isa; see the PLCRASH_ASYNC_OBJC_SUPPORT_NONPTR_ISA documentation for more details.
*/
static pl_vm_address_t plcrash_async_objc_isa_pointer (pl_vm_address_t isa) {
#if PLCRASH_ASYNC_OBJC_SUPPORT_NONPTR_ISA
if (isa & PLCRASH_ASYNC_OBJC_ISA_NONPTR_FLAG) {
return isa & PLCRASH_ASYNC_OBJC_ISA_NONPTR_CLASS_MASK;
}
#endif
return isa;
}
/**
* Get the index into the context's cache for the given key. Must only be called
* if the cache size has been set.
*
* @param context The context.
* @param key The key.
* @return The index.
*/
static size_t cache_index (plcrash_async_objc_cache_t *context, pl_vm_address_t key) {
return (key >> 2) % context->classCacheSize;
}
/**
* Get the context's cache's total memory allocation size, including both keys and values.
*
* @param context The context.
* @return The total number of bytes allocated for the cache.
*/
static size_t cache_allocation_size (plcrash_async_objc_cache_t *context) {
size_t size = context->classCacheSize;
return size * sizeof(*context->classCacheKeys) + size * sizeof(*context->classCacheValues);
}
/**
* Look up a key within the cache.
*
* @param context The context.
* @param key The key to look up.
* @return The value stored in the cache for that key, or 0 if none was found.
*/
static pl_vm_address_t cache_lookup (plcrash_async_objc_cache_t *context, pl_vm_address_t key) {
if (context->classCacheSize > 0) {
size_t index = cache_index(context, key);
if (context->classCacheKeys[index] == key) {
return context->classCacheValues[index];
}
}
return 0;
}
/**
* Store a key/value pair in the cache. The cache is not guaranteed storage so storing may
* silently fail, and the association can be evicted at any time. It's a CACHE.
*
* @param context The context.
* @param key The key to store.
* @param value The value to store.
*/
static void cache_set (plcrash_async_objc_cache_t *context, pl_vm_address_t key, pl_vm_address_t value) {
/* If nothing has used the cache yet, allocate the memory. */
if (context->classCacheKeys == NULL) {
size_t size = 1024;
context->classCacheSize = size;
size_t allocationSize = cache_allocation_size(context);
vm_address_t addr;
kern_return_t err = vm_allocate(mach_task_self_, &addr, allocationSize, VM_FLAGS_ANYWHERE);
/* If it fails, just bail out. We don't need the cache for correct operation. */
if (err != KERN_SUCCESS) {
PLCF_DEBUG("vm_allocate failed with error %x, the class cache could not be initialized and ObjC parsing will be substantially slower", err);
context->classCacheSize = 0;
return;
}
context->classCacheKeys = (pl_vm_address_t *)addr;
context->classCacheValues = (pl_vm_address_t *)(context->classCacheKeys + size);
}
/* Treat the cache as a simple hash table with no chaining whatsoever. If the bucket is already
* occupied, then don't do anything. The existing entry wins. */
size_t index = cache_index(context, key);
if (context->classCacheKeys[index] == 0) {
context->classCacheKeys[index] = key;
context->classCacheValues[index] = value;
}
}
/**
* Free any initialized memory objects in an ObjC context object.
*
* @param context The context.
*/
static void free_mapped_sections (plcrash_async_objc_cache_t *context) {
if (context->objcConstMobjInitialized) {
plcrash_async_mobject_free(&context->objcConstMobj);
context->objcConstMobjInitialized = false;
}
if (context->objcConstAxMobjInitialized) {
plcrash_async_mobject_free(&context->objcConstAxMobj);
context->objcConstAxMobjInitialized = false;
}
if (context->classMobjInitialized) {
plcrash_async_mobject_free(&context->classMobj);
context->classMobjInitialized = false;
}
if (context->catMobjInitialized) {
plcrash_async_mobject_free(&context->catMobj);
context->catMobjInitialized = false;
}
if (context->objcDataMobjInitialized) {
plcrash_async_mobject_free(&context->objcDataMobj);
context->objcDataMobjInitialized = false;
}
if (context->dataMobjInitialized) {
plcrash_async_mobject_free(&context->dataMobj);
context->dataMobjInitialized = false;
}
}
/**
* @internal
* @see https://opensource.apple.com/source/objc4/objc4-750/runtime/objc-file.mm
*
* Find and map a named section within a __DATA or __DATA_CONST or __DATA_DIRTY segment, initializing @a mobj.
* It is the caller's responsibility to dealloc @a mobj after a successful initialization.
*
* @param image The image to search for @a segname.
* @param sectname The name of the section to map.
* @param mobj The mobject to be initialized with a mapping of the section's data. It is the caller's responsibility to dealloc @a mobj after
* a successful initialization.
*
* @return Returns PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the section is not found, or an error result on failure.
*/
static plcrash_error_t map_data_section (plcrash_async_macho_t *image, const char **segname, const char *sectname, plcrash_async_mobject_t *mobj) {
*segname = kDataSegmentName;
plcrash_error_t err = plcrash_async_macho_map_section(image, *segname, sectname, mobj);
if (err == PLCRASH_ENOTFOUND) {
*segname = kDataConstSegmentName;
err = plcrash_async_macho_map_section(image, *segname, sectname, mobj);
}
if (err == PLCRASH_ENOTFOUND) {
*segname = kDataDirtySegmentName;
err = plcrash_async_macho_map_section(image, *segname, sectname, mobj);
}
return err;
}
/**
* Set up the memory objects in an ObjC context object for the given image. This will
* map the memory objects in the context to the appropriate sections in the image.
*
* @param image The MachO image to map.
* @param context The context.
* @return An error code.
*/
static plcrash_error_t map_sections (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *context) {
if (image == context->lastImage)
return PLCRASH_ESUCCESS;
/* Clean up the info from the previous image. Free the memory objects and reset the
* image pointer. The image pointer is reset so that it's not stale in case we return
* early due to an error. */
free_mapped_sections(context);
context->lastImage = NULL;
plcrash_error_t err;
const char *segname;
/* Map in the __objc_const section, which is where all the read-only class data lives. */
err = map_data_section(image, &segname, kObjCConstSectionName, &context->objcConstMobj);
if (err != PLCRASH_ESUCCESS) {
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%p, %s, %s, %p) failure %d", image, segname, kObjCConstSectionName, &context->objcConstMobj, err);
goto cleanup;
}
context->objcConstMobjInitialized = true;
/* Map in the __objc_const_ax section. */
err = plcrash_async_macho_map_section(image, kDataSegmentName, kObjCConstAxSectionName, &context->objcConstAxMobj);
if (err != PLCRASH_ESUCCESS && err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%s, %s, %s, %p) failure %d", PLCF_DEBUG_IMAGE_NAME(image), kDataSegmentName, kObjCConstAxSectionName, &context->objcConstAxMobj, err);
context->objcConstAxMobjInitialized = err == PLCRASH_ESUCCESS;
/* Map in the class list section. */
err = map_data_section(image, &segname, kClassListSectionName, &context->classMobj);
if (err != PLCRASH_ESUCCESS) {
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%s, %s, %s, %p) failure %d", PLCF_DEBUG_IMAGE_NAME(image), segname, kClassListSectionName, &context->classMobj, err);
goto cleanup;
}
context->classMobjInitialized = true;
/* Map in the category list section. */
err = map_data_section(image, &segname, kCategoryListSectionName, &context->catMobj);
if (err != PLCRASH_ESUCCESS) {
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%s, %s, %s, %p) failure %d", PLCF_DEBUG_IMAGE_NAME(image), segname, kCategoryListSectionName, &context->catMobj, err);
goto cleanup;
}
context->catMobjInitialized = true;
/* Map in the __objc_data section, which is where the actual classes live. */
err = plcrash_async_macho_map_section(image, kDataSegmentName, kObjCDataSectionName, &context->objcDataMobj);
if (err != PLCRASH_ESUCCESS) {
/* If the class list was found, the data section must also be found. */
PLCF_DEBUG("pl_async_macho_map_section(%s, %s, %s, %p) failure %d", PLCF_DEBUG_IMAGE_NAME(image), kDataSegmentName, kObjCDataSectionName, &context->objcDataMobj, err);
goto cleanup;
}
context->objcDataMobjInitialized = true;
/* Map in the __data section. */
err = plcrash_async_macho_map_section(image, kDataSegmentName, kDataSectionName, &context->dataMobj);
if (err != PLCRASH_ESUCCESS && err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%s, %s, %s, %p) failure %d", PLCF_DEBUG_IMAGE_NAME(image), kDataSegmentName, kDataSectionName, &context->dataMobj, err);
context->dataMobjInitialized = err == PLCRASH_ESUCCESS;
/* Only after all mappings succeed do we set the image. If any failed, the image won't be set,
* and any mappings that DO succeed will be cleaned up on the next call (or when freeing the
* context. */
context->lastImage = image;
cleanup:
return err;
}
static plcrash_error_t pl_async_parse_obj1_class(plcrash_async_macho_t *image, struct pl_objc1_class *cls, bool isMetaClass, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_error_t err;
/* Get the class's name. */
pl_vm_address_t namePtr = image->byteorder->swap32(cls->name);
bool classNameInitialized = false;
plcrash_async_macho_string_t className;
err = plcrash_async_macho_string_init(&className, image, namePtr);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_macho_string_init at 0x%llx error %d", (long long)namePtr, err);
return err;
}
classNameInitialized = true;
/* Grab the method list pointer. This is either a pointer to
* a single method_list structure, OR a pointer to an array
* of pointers to method_list structures, depending on the
* flag in the .info field. Argh. */
pl_vm_address_t methodListPtr = image->byteorder->swap32(cls->methods);
/* If CLS_NO_METHOD_ARRAY is set, then methodListPtr points to
* one method_list. If it's not set, then it points to an
* array of pointers to method lists. */
bool hasMultipleMethodLists = (image->byteorder->swap32(cls->info) & CLS_NO_METHOD_ARRAY) == 0;
pl_vm_address_t methodListCursor = methodListPtr;
while (true) {
/* Grab a method list pointer. How to do that depends on whether
* CLS_NO_METHOD_ARRAY is set. Once done, thisListPtr contains
* a pointer to the method_list structure to read. */
pl_vm_address_t thisListPtr;
if (hasMultipleMethodLists) {
/* If there are multiple method lists, then read the list pointer
* from the current cursor, and advance the cursor. */
uint32_t ptr;
err = plcrash_async_task_memcpy(image->task, methodListCursor, 0, &ptr, sizeof(ptr));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)methodListCursor, err);
goto cleanup;
}
thisListPtr = image->byteorder->swap32(ptr);
/* The end of the list is indicated with NULL or
* END_OF_METHODS_LIST (the ObjC runtime source checks both). */
if (thisListPtr == 0 || thisListPtr == END_OF_METHODS_LIST)
break;
methodListCursor += sizeof(ptr);
} else {
/* If CLS_NO_METHOD_ARRAY is set, then the single method_list
* is pointed to by the cursor. */
thisListPtr = methodListCursor;
/* The pointer may be NULL, in which case there are no methods. */
if (thisListPtr == 0)
break;
}
/* Read a method_list structure from the current list pointer. */
struct pl_objc1_method_list methodList;
err = plcrash_async_task_memcpy(image->task, thisListPtr, 0, &methodList, sizeof(methodList));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)methodListPtr, err);
goto cleanup;
}
/* Find out how many methods are in the list, and iterate. */
uint32_t count = image->byteorder->swap32(methodList.count);
for (uint32_t i = 0; i < count; i++) {
/* Method structures are laid out directly following the
* method_list structure. */
struct pl_objc1_method method;
pl_vm_address_t methodPtr = thisListPtr + sizeof(methodList) + i * sizeof(method);
err = plcrash_async_task_memcpy(image->task, methodPtr, 0, &method, sizeof(method));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)methodPtr, err);
goto cleanup;
}
/* Load the method name from the .name field pointer. */
pl_vm_address_t methodNamePtr = image->byteorder->swap32(method.name);
plcrash_async_macho_string_t methodName;
err = plcrash_async_macho_string_init(&methodName, image, methodNamePtr);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_macho_string_init at 0x%llx error %d", (long long)methodNamePtr, err);
goto cleanup;
}
/* Grab the method's IMP as well. */
pl_vm_address_t imp = image->byteorder->swap32(method.imp);
/* Callback! */
callback(isMetaClass, &className, &methodName, imp, ctx);
/* Clean up the method name object. */
plcrash_async_macho_string_free(&methodName);
}
/* Bail out of the loop after a single iteration if
* CLS_NO_METHOD_ARRAY is set, because there's no need
* to iterate in that case. */
if (!hasMultipleMethodLists)
break;
}
cleanup:
if (classNameInitialized)
plcrash_async_macho_string_free(&className);
return err;
}
/**
* Parse Objective-C class data from an old-style __module_info section containing
* ObjC1 metadata.
*
* @param image The Mach-O image to read from.
* @param callback The callback to invoke for each method found.
* @param ctx The context pointer to pass to the callback.
* @return PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the image doesn't
* contain ObjC1 metadata, or another error code if a different error occurred.
*/
static plcrash_error_t pl_async_objc_parse_from_module_info (plcrash_async_macho_t *image, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_error_t err = PLCRASH_EUNKNOWN;
struct pl_objc1_module *moduleData;
/* Map the __module_info section. */
bool moduleMobjInitialized = false;
plcrash_async_mobject_t moduleMobj;
err = plcrash_async_macho_map_section(image, kObjCSegmentName, kObjCModuleInfoSectionName, &moduleMobj);
if (err != PLCRASH_ESUCCESS) {
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_macho_map_section(%p, %s, %s, %p) failure %d", image, kObjCSegmentName, kObjCModuleInfoSectionName, &moduleMobj, err);
goto cleanup;
}
/* Successful mapping, so mark the memory object as needing cleanup. */
moduleMobjInitialized = true;
/* Get a pointer to the module info data. */
moduleData = (struct pl_objc1_module *) plcrash_async_mobject_remap_address(&moduleMobj, moduleMobj.task_address, 0, sizeof(*moduleData));
if (moduleData == NULL) {
PLCF_DEBUG("Failed to obtain pointer from %s memory object", kObjCModuleInfoSectionName);
err = PLCRASH_ENOTFOUND;
goto cleanup;
}
/* Read successive module structs from the section until we run out of data. */
for (unsigned moduleIndex = 0; moduleIndex < moduleMobj.length / sizeof(*moduleData); moduleIndex++) {
/* Grab the pointer to the symtab for this module struct. */
pl_vm_address_t symtabPtr = image->byteorder->swap32(moduleData[moduleIndex].symtab);
if (symtabPtr == 0)
continue;
/* Read a symtab struct from that pointer. */
struct pl_objc1_symtab symtab;
err = plcrash_async_task_memcpy(image->task, symtabPtr, 0, &symtab, sizeof(symtab));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)symtabPtr, err);
goto cleanup;
}
/* Iterate over the classes in the symtab. */
uint16_t classCount = image->byteorder->swap16(symtab.cls_def_count);
for (unsigned i = 0; i < classCount; i++) {
/* Classes are indicated by pointers laid out sequentially after the
* symtab structure. */
uint32_t classPtr;
pl_vm_address_t cursor = symtabPtr + sizeof(symtab) + i * sizeof(classPtr);
err = plcrash_async_task_memcpy(image->task, cursor, 0, &classPtr, sizeof(classPtr));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)cursor, err);
goto cleanup;
}
classPtr = image->byteorder->swap32(classPtr);
/* Read a class structure from the class pointer. */
struct pl_objc1_class cls;
err = plcrash_async_task_memcpy(image->task, classPtr, 0, &cls, sizeof(cls));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)classPtr, err);
goto cleanup;
}
err = pl_async_parse_obj1_class(image, &cls, false, callback, ctx);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("pl_async_parse_obj1_class error %d while parsing class", err);
goto cleanup;
}
/* Read a class structure for the metaclass. */
pl_vm_address_t isa = plcrash_async_objc_isa_pointer(image->byteorder->swap(cls.isa));
struct pl_objc1_class metaclass;
err = plcrash_async_task_memcpy(image->task, isa, 0, &metaclass, sizeof(metaclass));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)isa, err);
goto cleanup;
}
err = pl_async_parse_obj1_class(image, &metaclass, true, callback, ctx);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("pl_async_parse_obj1_class error %d while parsing metaclass", err);
goto cleanup;
}
}
}
cleanup:
/* Clean up the memory objects before returning if they're initialized. */
if (moduleMobjInitialized)
plcrash_async_mobject_free(&moduleMobj);
return err;
}
/**
* Parse an ObjC 2.0 method_list_t structure at @a method_list_addr and call @a callback with all
* parsed methods.
*
* @param image The image to read from.
* @param objc_cache The Objective-C cache object.
* @param class_name The name of the class being parsed.
* @param is_meta_class true if this is a metaclass.
* @param method_list_addr The address of the method list data.
* @param callback The callback to be invoked for each method found.
* @param ctx A context pointer to pass to the callback.
* @return Returns PLCRASH_ESUCCESS on success, or an appropriate error on failure.
*/
static plcrash_error_t pl_async_objc_parse_objc2_method_list (plcrash_async_macho_t *image,
plcrash_async_objc_cache_t *objc_cache,
plcrash_async_macho_string_t *class_name,
bool is_meta_class,
pl_vm_address_t method_list_addr,
plcrash_async_objc_found_method_cb callback,
void *ctx)
{
PLCF_ASSERT(method_list_addr != 0);
/* Read the method list header. */
struct pl_objc2_list_header *header;
plcrash_async_mobject_t *section = &objc_cache->objcConstMobj;
header = (struct pl_objc2_list_header *) plcrash_async_mobject_remap_address(section, method_list_addr, 0, sizeof(*header));
if (header == NULL && objc_cache->objcConstAxMobjInitialized) {
section = &objc_cache->objcConstAxMobj;
header = (struct pl_objc2_list_header *) plcrash_async_mobject_remap_address(section, method_list_addr, 0, sizeof(*header));
}
if (header == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_const and __objc_const_ax failed to map methods pointer 0x%llx", (long long) method_list_addr);
return PLCRASH_EINVALID_DATA;
}
/* Extract the entry size and count from the list header. */
uint32_t entsize = image->byteorder->swap32(header->entsize) & ~(uint32_t)3;
uint32_t count = image->byteorder->swap32(header->count);
/* Compute the method list start position and length. */
pl_vm_address_t method_list_start = method_list_addr + sizeof(*header);
pl_vm_size_t method_list_length = (pl_vm_size_t)entsize * count;
const char *cursor = (const char *) plcrash_async_mobject_remap_address(section, method_list_start, 0, method_list_length);
if (cursor == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address at 0x%llx length %llu returned NULL", (long long)method_list_start, (unsigned long long)method_list_length);
return PLCRASH_EINVALID_DATA;
}
/* Extract methods from the list. */
for (uint32_t i = 0; i < count; i++) {
plcrash_error_t err;
/* Read an architecture-appropriate method structure from the
* current cursor. */
const struct pl_objc2_method_32 *method_32 = (const struct pl_objc2_method_32 *)cursor;
const struct pl_objc2_method_64 *method_64 = (const struct pl_objc2_method_64 *)cursor;
/* Extract the method name pointer. */
pl_vm_address_t methodNamePtr = (image->m64
? (pl_vm_address_t) image->byteorder->swap64(method_64->name)
: image->byteorder->swap32(method_32->name));
/* Read the method name. */
plcrash_async_macho_string_t method_name;
if ((err = plcrash_async_macho_string_init(&method_name, image, methodNamePtr)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_macho_string_init at 0x%llx error %d", (long long)methodNamePtr, err);
return err;
}
/* Extract the method IMP. */
pl_vm_address_t imp = (image->m64
? (pl_vm_address_t) image->byteorder->swap64(method_64->imp)
: image->byteorder->swap32(method_32->imp));
/* Call the callback. */
callback(is_meta_class, class_name, &method_name, imp, ctx);
/* Clean up the method name. */
plcrash_async_macho_string_free(&method_name);
/* Increment the cursor by the entry size for the next iteration of the loop. */
cursor += entsize;
}
return PLCRASH_ESUCCESS;
}
/**
* Parse a single ObjC 2.0 class structure and return the results.
*
* @param image The image to read from.
* @param objc_cache The Objective-C cache object.
* @param cls A pointer to the class structure to be parsed
* @param[out] class_name On success, will be initialized with the class name. It is the caller's responsibility to free
* the returned value via plcrash_async_macho_string_free(). If the function returns an error, no class_name value
* will be provided and the caller is not responsible for freeing any associated resources.
* @param[out] cls_data_ro A buffer to which the class_ro data will be written. Must be at least sizeof(class_ro_t).
*
* @tparam class_t The class type, one of pl_objc2_class_32 or pl_objc2_class_64.
* @tparam class_ro_t The read-only class type, one of pl_objc2_class_data_ro_32 or pl_objc2_class_data_ro_64.
* @tparam class_rw_t The read-write class type, one of pl_objc2_class_data_rw_32 or pl_objc2_class_data_rw_64.
*
* @return Returns PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the class is unrealized, PLCRASH_EINVALID_DATA if
* the input format is invalid (including failed pointer dereferencing), or another appropriate error.
*/
template<typename class_t, typename class_ro_t, typename class_rw_t, typename machine_ptr_t>
static plcrash_error_t pl_async_objc_parse_objc2_class (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *objc_cache, class_t *cls, plcrash_async_macho_string_t *class_name, class_ro_t *cls_data_ro) {
plcrash_error_t err;
/* Grab the class's data_rw pointer. This needs masking because it also
* can contain flags. */
pl_vm_address_t data_ptr = (pl_vm_address_t) image->byteorder->swap(cls->data_rw);
data_ptr &= FAST_DATA_MASK;
/* Grab the data RO pointer from the cache. If unavailable, we'll fetch the data and populate the class. */
pl_vm_address_t cached_data_ro_addr = cache_lookup(objc_cache, data_ptr);
if (cached_data_ro_addr == 0) {
class_rw_t cls_data_rw;
/* Read the class_rw structure. */
err = plcrash_async_task_memcpy(image->task, data_ptr, 0, &cls_data_rw, sizeof(cls_data_rw));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)data_ptr, err);
return PLCRASH_EINVALID_DATA;
}
/* Check the flags. If it's not yet realized, then we need to skip the class. */
uint32_t flags = image->byteorder->swap(cls_data_rw.flags);
if ((flags & RW_REALIZED) == 0) {
// PLCF_DEBUG("Found unrealized class with RO data at 0x%llx, skipping it", (long long)data_ptr);
return PLCRASH_ENOTFOUND;
}
/* Grab the data_ro pointer. The RO data (read-only) contains the class name
* and method list. */
cached_data_ro_addr = (pl_vm_address_t) image->byteorder->swap(cls_data_rw.data_ro);
/* When objc_class_abi_version >= 1, it's a tagged union based on the low bit:
* 0: class_ro_t 1: class_rw_ext_t
* @see https://opensource.apple.com/source/objc4/objc4-781/runtime/objc-runtime-new.h */
if (cached_data_ro_addr & 0x1) {
cached_data_ro_addr &= ~0x1;
/* class_rw_ext_t has a link to R/O data without any offset, so just grab this pointer. */
machine_ptr_t data_ro_addr;
err = plcrash_async_task_memcpy(image->task, cached_data_ro_addr, 0, &data_ro_addr, sizeof(data_ro_addr));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx error %d", (long long)cached_data_ro_addr, err);
return PLCRASH_EINVALID_DATA;
}
cached_data_ro_addr = (pl_vm_address_t) image->byteorder->swap(data_ro_addr);
}
/* Copy the R/O data; it will either be heap allocated (RW_COPIED_RO), found within the __objc_const section of the current cached image,
* or found within the __objc_const section of a non-cached image. */
if ((flags & RW_COPIED_RO) != 0 || !plcrash_async_macho_contains_address(image, cached_data_ro_addr)) {
if ((err = plcrash_async_task_memcpy(image->task, cached_data_ro_addr, 0, cls_data_ro, sizeof(*cls_data_ro))) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy at 0x%llx returned NULL", (long long)cached_data_ro_addr);
return PLCRASH_EINVALID_DATA;
}
} else {
void *ptr = plcrash_async_mobject_remap_address(&objc_cache->objcConstMobj, cached_data_ro_addr, 0, sizeof(*cls_data_ro));
if (ptr == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_const for pointer 0x%llx returned NULL", (long long)cached_data_ro_addr);
return PLCRASH_EINVALID_DATA;
}
plcrash_async_memcpy(cls_data_ro, ptr, sizeof(*cls_data_ro));
}
/* Add a new cache entry. */
cache_set(objc_cache, data_ptr, cached_data_ro_addr);
} else {
void *ptr;
/* We know that the address is valid (it wouldn't be in the cache otherwise). We try the cheaper memory mapping first,
* and then fall back to a memory copy. */
if ((ptr = plcrash_async_mobject_remap_address(&objc_cache->objcConstMobj, cached_data_ro_addr, 0, sizeof(*cls_data_ro))) != NULL) {
plcrash_async_memcpy(cls_data_ro, ptr, sizeof(*cls_data_ro));
} else if (plcrash_async_task_memcpy(image->task, cached_data_ro_addr, 0, cls_data_ro, sizeof(*cls_data_ro)) == PLCRASH_ESUCCESS) {
/* Do nothing -- success! */
} else {
PLCF_DEBUG("Failed to read validated class_ro data at 0x%llx", (long long)cached_data_ro_addr);
return PLCRASH_EINVALID_DATA;
}
}
/* Fetch the pointer to the class name, and make the string. */
pl_vm_address_t class_name_ptr = (pl_vm_address_t) image->byteorder->swap(cls_data_ro->name);
err = plcrash_async_macho_string_init(class_name, image, class_name_ptr);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_macho_string_init at 0x%llx error %d", (long long)class_name_ptr, err);
return PLCRASH_EINVALID_DATA;
}
return err;
}
/**
* Parse a single ObjC 2.0 class structure and call @a callback with all parsed methods.
*
* @param image The image to read from.
* @param objc_cache The Objective-C cache object.
* @param cls A pointer to the class structure to be parsed
* @param is_meta_class true if this is a metaclass.
* @param callback The callback to invoke for each method found.
* @param ctx A context pointer to pass to the callback.
*
* @tparam class_t The class type, one of pl_objc2_class_32 or pl_objc2_class_64.
* @tparam class_ro_t The read-only class type, one of pl_objc2_class_data_ro_32 or pl_objc2_class_data_ro_64.
* @tparam class_rw_t The read-write class type, one of pl_objc2_class_data_rw_32 or pl_objc2_class_data_rw_64.
*
* @return An error code.
*/
template<typename class_t, typename class_ro_t, typename class_rw_t, typename machine_ptr_t>
static plcrash_error_t pl_async_objc_parse_objc2_class_methods (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *objc_cache, class_t *cls, bool is_meta_class, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_async_macho_string_t class_name;
class_ro_t cls_data_ro;
plcrash_error_t err;
/* Parse the class */
if ((err = pl_async_objc_parse_objc2_class<class_t, class_ro_t, class_rw_t, machine_ptr_t>(image, objc_cache, cls, &class_name, &cls_data_ro)) != PLCRASH_ESUCCESS)
return err;
/* Fetch and parse the method list. */
pl_vm_address_t methods_ptr = (pl_vm_address_t) image->byteorder->swap(cls_data_ro.baseMethods);
if (methods_ptr != 0) {
err = pl_async_objc_parse_objc2_method_list(image, objc_cache, &class_name, is_meta_class, methods_ptr, callback, ctx);
} else {
/* The base method list will be NULL if no methods are defined for the class/metaclass; in that case, we simply skip the class. */
err = PLCRASH_ESUCCESS;
}
/* Clean up */
plcrash_async_macho_string_free(&class_name);
return err;
}
/**
* Parse a single ObjC 2.0 category_t structure and call @a callback with all parsed methods.
*
* @param image The image to read from.
* @param objc_cache The Objective-C cache object.
* @param category A pointer to a category structure.
* @param callback The callback to invoke for each method found.
* @param ctx A context pointer to pass to the callback.
*
* @tparam category_t The category type, one of pl_objc2_category_32 or pl_objc2_category_64.
* @tparam class_t The class type, one of pl_objc2_class_32 or pl_objc2_class_64.
* @tparam class_ro_t The read-only class type, one of pl_objc2_class_data_ro_32 or pl_objc2_class_data_ro_64.
* @tparam class_rw_t The read-write class type, one of pl_objc2_class_data_rw_32 or pl_objc2_class_data_rw_64.
*
* @return Returns PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the class referenced by the category is unrealized, PLCRASH_EINVALID_DATA if
* the input format is invalid (including failed pointer dereferencing), or another appropriate error.
*/
template <typename category_t, typename class_t, typename class_ro_t, typename class_rw_t, typename machine_ptr_t>
static plcrash_error_t pl_async_objc_parse_objc2_category_methods (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *objc_cache, category_t *category, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_async_macho_string_t class_name;
class_ro_t cls_data_ro;
plcrash_error_t err;
/* Grab the class reference and parse the class. We try to simply remap the pointer, but if that fails, we perform a more
* expensive read. */
pl_vm_address_t ptr = (pl_vm_address_t) image->byteorder->swap(category->cls);
class_t *classPtr = (class_t *) plcrash_async_mobject_remap_address(&objc_cache->objcDataMobj, ptr, 0, sizeof(*classPtr));
class_t class_data;
if (classPtr == NULL) {
if ((err = plcrash_async_task_memcpy(image->task, ptr, 0, &class_data, sizeof(class_data))) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("plcrash_async_task_memcpy() for pointer 0x%llx returned NULL", (long long)ptr);
return PLCRASH_EINVALID_DATA;
}
classPtr = &class_data;
}
if ((err = pl_async_objc_parse_objc2_class<class_t, class_ro_t, class_rw_t, machine_ptr_t>(image, objc_cache, classPtr, &class_name, &cls_data_ro)) != PLCRASH_ESUCCESS)
return err;
/* Fetch and parse the instance and class method lists. The method list will be NULL if no methods are defined for the category; in that case, we simply skip the category. */
pl_vm_address_t methods_ptr = (pl_vm_address_t) image->byteorder->swap(category->instanceMethods);
if (methods_ptr != 0) {
if ((err = pl_async_objc_parse_objc2_method_list(image, objc_cache, &class_name, false, methods_ptr, callback, ctx)) != PLCRASH_ESUCCESS)
goto cleanup;
}
methods_ptr = (pl_vm_address_t) image->byteorder->swap(category->classMethods);
if (methods_ptr != 0) {
if ((err = pl_async_objc_parse_objc2_method_list(image, objc_cache, &class_name, true, methods_ptr, callback, ctx)) != PLCRASH_ESUCCESS)
goto cleanup;
}
err = PLCRASH_ESUCCESS;
cleanup:
/* Clean up */
plcrash_async_macho_string_free(&class_name);
return err;
}
/**
* Parse ObjC2 class data from a __objc_classlist section.
*
* @param image The Mach-O image to parse.
* @param objcContext An ObjC context object.
* @param callback The callback to invoke for each method found.
* @param ctx A context pointer to pass to the callback.
* @return PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if no ObjC2 data exists in the image, and another error code if a different error occurred.
*
* @tparam class_t The class type, one of pl_objc2_class_32 or pl_objc2_class_64.
* @tparam class_ro_t The read-only class type, one of pl_objc2_class_data_ro_32 or pl_objc2_class_data_ro_64.
* @tparam class_rw_t The read-write class type, one of pl_objc2_class_data_rw_32 or pl_objc2_class_data_rw_64.
* @tparam category_t The category type, one of pl_objc2_category_32 or pl_objc2_category_64.
* @tparam machine_ptr_t The target's pointer type (eg, uint64_t).
*/
template<typename class_t, typename class_ro_t, typename class_rw_t, typename category_t, typename machine_ptr_t>
static plcrash_error_t pl_async_objc_parse_from_data_section (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *objcContext, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_error_t err;
/* Map memory objects. */
err = map_sections(image, objcContext);
if (err != PLCRASH_ESUCCESS) {
/* Don't log an error if ObjC data was simply not found */
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("Unable to map relevant sections in %s for ObjC2 class parsing, error %d", PLCF_DEBUG_IMAGE_NAME(image), err);
return err;
}
/* Get a pointer out of the mapped class list. */
machine_ptr_t *classPtrs = (machine_ptr_t *) plcrash_async_mobject_remap_address(&objcContext->classMobj, objcContext->classMobj.task_address, 0, objcContext->classMobj.length);
if (classPtrs == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_classlist for pointer 0x%llx returned NULL", (long long)objcContext->classMobj.address);
return PLCRASH_EINVALID_DATA;
}
/* Figure out how many classes are in the class list based on its length and
* the size of a pointer in the image. */
pl_vm_size_t classCount = objcContext->classMobj.length / sizeof(machine_ptr_t);
/* Iterate over all classes. */
for(unsigned i = 0; i < classCount; i++) {
/* Read the class structure */
pl_vm_address_t ptr = (pl_vm_address_t) classPtrs[i];
class_t *classPtr = (class_t *) plcrash_async_mobject_remap_address(&objcContext->objcDataMobj, ptr, 0, sizeof(*classPtr));
if (classPtr == NULL) {
classPtr = (class_t *) plcrash_async_mobject_remap_address(&objcContext->dataMobj, ptr, 0, sizeof(*classPtr));
}
if (classPtr == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_data and __data for pointer 0x%llx returned NULL", (long long)ptr);
return PLCRASH_EINVALID_DATA;
}
/* Parse the class. */
err = pl_async_objc_parse_objc2_class_methods<class_t, class_ro_t, class_rw_t, machine_ptr_t>(image, objcContext, classPtr, false, callback, ctx);
if (err != PLCRASH_ESUCCESS) {
/* Skip unrealized classes; they'll never appear in a live backtrace. */
if (err == PLCRASH_ENOTFOUND) {
/* We should reset the error variable to avoid return it from the function. */
err = PLCRASH_ESUCCESS;
continue;
}
PLCF_DEBUG("pl_async_objc_parse_objc2_class error %d while parsing class", err);
return err;
}
/* Read an architecture-appropriate class structure for the metaclass. */
pl_vm_address_t isa = plcrash_async_objc_isa_pointer((pl_vm_address_t) image->byteorder->swap(classPtr->isa));
class_t *metaclass = (class_t *) plcrash_async_mobject_remap_address(&objcContext->objcDataMobj, isa, 0, sizeof(*metaclass));
if (metaclass == NULL) {
metaclass = (class_t *) plcrash_async_mobject_remap_address(&objcContext->dataMobj, isa, 0, sizeof(*metaclass));
}
if (metaclass == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_data and __data for pointer 0x%llx returned NULL", (long long)isa);
return PLCRASH_EINVALID_DATA;
}
/* Parse the metaclass. */
err = pl_async_objc_parse_objc2_class_methods<class_t, class_ro_t, class_rw_t, machine_ptr_t>(image, objcContext, metaclass, true, callback, ctx);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("pl_async_objc_parse_objc2_class_methods error %d while parsing metaclass", err);
return err;
}
}
/* Get a pointer out of the mapped category list. */
machine_ptr_t *catPtrs = (machine_ptr_t *) plcrash_async_mobject_remap_address(&objcContext->catMobj, objcContext->catMobj.task_address, 0, objcContext->catMobj.length);
if (catPtrs == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_catlist for pointer 0x%llx returned NULL", (long long)objcContext->catMobj.address);
return PLCRASH_EINVALID_DATA;
}
/* Figure out how many categories are in the category list based on its length and the size of a pointer in the image. */
pl_vm_size_t catCount = objcContext->catMobj.length / sizeof(*catPtrs);
/* Iterate over all classes. */
for(unsigned i = 0; i < catCount; i++) {
/* Read a category pointer at the current index from the appropriate pointer. */
pl_vm_address_t ptr = (pl_vm_address_t) image->byteorder->swap(catPtrs[i]);
/* Read the category structure. */
category_t *category = (category_t *) plcrash_async_mobject_remap_address(&objcContext->objcConstMobj, ptr, 0, sizeof(*category));
if (category == NULL) {
PLCF_DEBUG("plcrash_async_mobject_remap_address in __objc_const for pointer 0x%llx returned NULL", (long long)ptr);
return PLCRASH_EINVALID_DATA;
}
/* Parse the category. */
err = pl_async_objc_parse_objc2_category_methods<category_t, class_t, class_ro_t, class_rw_t, machine_ptr_t>(image, objcContext, category, callback, ctx);
if (err != PLCRASH_ESUCCESS) {
/* Skip unrealized classes; they'll never appear in a live backtrace. */
if (err == PLCRASH_ENOTFOUND)
continue;
PLCF_DEBUG("pl_async_objc_parse_objc2_class error %d while parsing class", err);
return err;
}
}
return err;
}
/**
* Initialize an ObjC cache object.
*
* @param cache A pointer to the cache object to initialize.
* @return An error code.
*/
plcrash_error_t plcrash_async_objc_cache_init (plcrash_async_objc_cache_t *cache) {
cache->gotObjC2Info = false;
cache->lastImage = NULL;
cache->objcConstMobjInitialized = false;
cache->objcConstAxMobjInitialized = false;
cache->classMobjInitialized = false;
cache->catMobjInitialized = false;
cache->objcDataMobjInitialized = false;
cache->dataMobjInitialized = false;
cache->classCacheSize = 0;
cache->classCacheKeys = NULL;
cache->classCacheValues = NULL;
return PLCRASH_ESUCCESS;
}
/**
* Free an ObjC cache object.
*
* @param cache A pointer to the cache object to free.
*/
void plcrash_async_objc_cache_free (plcrash_async_objc_cache_t *cache) {
free_mapped_sections(cache);
if (cache->classCacheKeys != NULL)
vm_deallocate(mach_task_self(), (vm_address_t)cache->classCacheKeys, cache_allocation_size(cache));
}
/**
* @internal
*
* Parse Objective-C class data from a Mach-O image, invoking a callback
* for each method found in the data. This tries both old-style ObjC1
* class data and new-style ObjC2 data.
*
* @param image The image to read class data from.
* @param cache An ObjC context object.
* @param callback The callback to invoke for each method.
* @param ctx The context pointer to pass to the callback.
* @return An error code.
*/
static plcrash_error_t plcrash_async_objc_parse (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *cache, plcrash_async_objc_found_method_cb callback, void *ctx) {
plcrash_error_t err;
if (cache == NULL)
return PLCRASH_EACCESS;
if (!cache->gotObjC2Info) {
/* Try ObjC1 data. */
err = pl_async_objc_parse_from_module_info(image, callback, ctx);
} else {
/* If it couldn't be found before, don't even bother to try again. */
err = PLCRASH_ENOTFOUND;
}
/* If there wasn't any, try ObjC2 data. */
if (err == PLCRASH_ENOTFOUND) {
if (image->m64)
err = pl_async_objc_parse_from_data_section<pl_objc2_class_64, pl_objc2_class_data_ro_64, pl_objc2_class_data_rw_64, pl_objc2_category_64, uint64_t>(image, cache, callback, ctx);
else
err = pl_async_objc_parse_from_data_section<pl_objc2_class_32, pl_objc2_class_data_ro_32, pl_objc2_class_data_rw_32, pl_objc2_category_32, uint32_t>(image, cache, callback, ctx);
if (err == PLCRASH_ESUCCESS) {
/* ObjC2 info successfully obtained, note that so we can stop trying ObjC1 next time around. */
cache->gotObjC2Info = true;
}
}
return err;
}
struct pl_async_objc_find_method_search_context {
pl_vm_address_t searchIMP;
pl_vm_address_t bestIMP;
};
struct pl_async_objc_find_method_call_context {
pl_vm_address_t searchIMP;
plcrash_async_objc_found_method_cb outerCallback;
void *outerCallbackCtx;
};
/**
* Callback used to search for the method IMP that best matches a search target.
* The context pointer is a pointer to pl_async_objc_find_method_search_context.
* The searchIMP field should be set to the IMP to search for. The bestIMP field
* should be initialized to 0, and will be updated with the best-matching IMP
* found.
*/
static void pl_async_objc_find_method_search_callback (bool isClassMethod, plcrash_async_macho_string_t *className, plcrash_async_macho_string_t *methodName, pl_vm_address_t imp, void *ctx) {
struct pl_async_objc_find_method_search_context *ctxStruct = (struct pl_async_objc_find_method_search_context *) ctx;
if (imp >= ctxStruct->bestIMP && imp <= ctxStruct->searchIMP) {
ctxStruct->bestIMP = imp;
}
}
/**
* Callback used to find the method that precisely matches a search target.
* The context pointer is a pointer to pl_async_objc_find_method_call_context.
* The searchIMP field should be set to the IMP to search for. The outerCallback
* will be invoked, passing outerCalblackCtx and the method data for a precise
* match, if any is found.
*/
static void pl_async_objc_find_method_call_callback (bool isClassMethod, plcrash_async_macho_string_t *className, plcrash_async_macho_string_t *methodName, pl_vm_address_t imp, void *ctx) {
struct pl_async_objc_find_method_call_context *ctxStruct = (struct pl_async_objc_find_method_call_context *) ctx;
if (imp == ctxStruct->searchIMP && ctxStruct->outerCallback != NULL) {
ctxStruct->outerCallback(isClassMethod, className, methodName, imp, ctxStruct->outerCallbackCtx);
ctxStruct->outerCallback = NULL;
}
}
/**
* Search for the method that best matches the given code address.
*
* @param image The image to search.
* @param objcContext A pointer to an ObjC context object. Must not be NULL, and must (obviously) be initialized.
* @param imp The address to search for.
* @param callback The callback to invoke when the best match is found.
* @param ctx The context pointer to pass to the callback.
* @return An error code.
*/
plcrash_error_t plcrash_async_objc_find_method (plcrash_async_macho_t *image, plcrash_async_objc_cache_t *objcContext, pl_vm_address_t imp, plcrash_async_objc_found_method_cb callback, void *ctx) {
struct pl_async_objc_find_method_search_context searchCtx = {
.searchIMP = imp
};
plcrash_error_t err = plcrash_async_objc_parse(image, objcContext, pl_async_objc_find_method_search_callback, &searchCtx);
if (err != PLCRASH_ESUCCESS) {
/* Don't log an error if ObjC data was simply not found */
if (err != PLCRASH_ENOTFOUND)
PLCF_DEBUG("pl_async_objc_parse of %p (%s) failure %d", image, PLCF_DEBUG_IMAGE_NAME(image), err);
return err;
}
if (searchCtx.bestIMP == 0)
return PLCRASH_ENOTFOUND;
struct pl_async_objc_find_method_call_context callCtx = {
.searchIMP = searchCtx.bestIMP,
.outerCallback = callback,
.outerCallbackCtx = ctx
};
return plcrash_async_objc_parse(image, objcContext, pl_async_objc_find_method_call_callback, &callCtx);
}