static plcrash_error_t pl_async_objc_parse_objc2_class()

in Source/PLCrashAsyncObjCSection.mm [824:913]


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;
}