// PLFrameWalker API
plframe_error_t plframe_cursor_next (plframe_cursor_t *cursor) {
    kern_return_t kr;
    void *prevfp = cursor->fp[0];
    
    /* Fetch the next stack address */
    if (cursor->init_frame) {
        /* The first frame is already available, so there's nothing to do */
        cursor->init_frame = false;
        return PLFRAME_ESUCCESS;
    } else {
        if (cursor->fp[0] == NULL) {
            /* No frame data has been loaded, fetch it from register state */
            kr = plcrash_async_read_addr(mach_task_self(), cursor->uap->uc_mcontext->__ss.__rbp, cursor->fp, sizeof(cursor->fp));
        } else {
            /* Frame data loaded, walk the stack */
            kr = plcrash_async_read_addr(mach_task_self(), (pl_vm_address_t) cursor->fp[0], cursor->fp, sizeof(cursor->fp));
        }
    }
    
    /* Was the read successful? */
    if (kr != KERN_SUCCESS)
        return PLFRAME_EBADFRAME;
    
    /* Check for completion */
    if (cursor->fp[0] == NULL)
        return PLFRAME_ENOFRAME;
    
    /* Is the stack growing in the right direction? */
    if (!cursor->init_frame && prevfp > cursor->fp[0])
        return PLFRAME_EBADFRAME;
    
    /* New frame fetched */
    return PLFRAME_ESUCCESS;
}
/**
 * Initialize a new Mach-O binary image parser.
 *
 * @param image The image structure to be initialized.
 * @param name The file name or path for the Mach-O image.
 * @param header The task-local address of the image's Mach-O header.
 *
 * @return PLCRASH_ESUCCESS on success. PLCRASH_EINVAL will be returned in the Mach-O file can not be parsed,
 * or PLCRASH_EINTERNAL if an error occurs reading from the target task.
 *
 * @warning This method is not async safe.
 */
plcrash_error_t plcrash_nasync_macho_init (plcrash_async_macho_t *image, mach_port_t task, const char *name, pl_vm_address_t header) {
    plcrash_error_t ret;

    /* Defaults checked in the  error cleanup handler */
    bool mobj_initialized = false;
    bool task_initialized = false;
    image->name = NULL;

    /* Basic initialization */
    image->task = task;
    image->header_addr = header;
    image->name = strdup(name);

    mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, 1);
    task_initialized = true;

    /* Read in the Mach-O header */
    kern_return_t kt;
    if ((kt = plcrash_async_read_addr(image->task, image->header_addr, &image->header, sizeof(image->header))) != KERN_SUCCESS) {
        /* NOTE: The image struct must be fully initialized before returning here, as otherwise our _free() function
         * will crash */
        PLCF_DEBUG("Failed to read Mach-O header from 0x%" PRIx64 " for image %s, kern_error=%d", (uint64_t) image->header_addr, name, kt);
        ret = PLCRASH_EINTERNAL;
        goto error;
    }
    
    /* Set the default byte order*/
    image->byteorder = &plcrash_async_byteorder_direct;

    /* Parse the Mach-O magic identifier. */
    switch (image->header.magic) {
        case MH_CIGAM:
            // Enable byte swapping
            image->byteorder = &plcrash_async_byteorder_swapped;
            // Fall-through

        case MH_MAGIC:
            image->m64 = false;
            break;            
            
        case MH_CIGAM_64:
            // Enable byte swapping
            image->byteorder = &plcrash_async_byteorder_swapped;
            // Fall-through
            
        case MH_MAGIC_64:
            image->m64 = true;
            break;

        case FAT_CIGAM:
        case FAT_MAGIC:
            PLCF_DEBUG("%s called with an unsupported universal Mach-O archive in: %s", __func__, image->name);
            return PLCRASH_EINVAL;
            break;

        default:
            PLCF_DEBUG("Unknown Mach-O magic: 0x%" PRIx32 " in: %s", image->header.magic, image->name);
            return PLCRASH_EINVAL;
    }

    /* Save the header size */
    if (image->m64) {
        image->header_size = sizeof(struct mach_header_64);
    } else {
        image->header_size = sizeof(struct mach_header);
    }
    
    /* Map in header + load commands */
    pl_vm_size_t cmd_len = image->byteorder->swap32(image->header.sizeofcmds);
    pl_vm_size_t cmd_offset = image->header_addr + image->header_size;
    image->ncmds = image->byteorder->swap32(image->header.ncmds);

    ret = plcrash_async_mobject_init(&image->load_cmds, image->task, cmd_offset, cmd_len, true);
    if (ret != PLCRASH_ESUCCESS) {
        PLCF_DEBUG("Failed to map Mach-O load commands in image %s", image->name);
        goto error;
    } else {
        mobj_initialized = true;
    }

    /* Now that the image has been sufficiently initialized, determine the __TEXT segment size */
    void *cmdptr = NULL;
    image->text_size = 0x0;
    bool found_text_seg = false;
    while ((cmdptr = plcrash_async_macho_next_command_type(image, cmdptr, image->m64 ? LC_SEGMENT_64 : LC_SEGMENT)) != 0) {
        if (image->m64) {
            struct segment_command_64 *segment = cmdptr;
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) {
                PLCF_DEBUG("LC_SEGMENT command was too short");
                ret = PLCRASH_EINVAL;
                goto error;
            }
            
            if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0)
                continue;

            image->text_size = image->byteorder->swap64(segment->vmsize);
            image->text_vmaddr = image->byteorder->swap64(segment->vmaddr);
            found_text_seg = true;
            break;
        } else {
            struct segment_command *segment = cmdptr;
            if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) {
                PLCF_DEBUG("LC_SEGMENT command was too short");
                ret = PLCRASH_EINVAL;
                goto error;
            }
            
            if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0)
                continue;
            
            image->text_size = image->byteorder->swap32(segment->vmsize);
            image->text_vmaddr = image->byteorder->swap32(segment->vmaddr);
            found_text_seg = true;
            break;
        }
    }

    if (!found_text_seg) {
        PLCF_DEBUG("Could not find __TEXT segment!");
        ret = PLCRASH_EINVAL;
        goto error;
    }

    /* Compute the vmaddr slide */
    if (image->text_vmaddr < header) {
        image->vmaddr_slide = header - image->text_vmaddr;
    } else if (image->text_vmaddr > header) {
        image->vmaddr_slide = -((pl_vm_off_t) (image->text_vmaddr - header));
    } else {
        image->vmaddr_slide = 0;
    }

    return PLCRASH_ESUCCESS;
    
error:
    if (mobj_initialized)
        plcrash_async_mobject_free(&image->load_cmds);
    
    if (image->name != NULL)
        free(image->name);
    
    if (task_initialized)
        mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, -1);

    return ret;
}