plcrash_error_t plcrash_async_cfe_entry_apply()

in Source/PLCrashAsyncCompactUnwindEncoding.c [1043:1221]


plcrash_error_t plcrash_async_cfe_entry_apply (task_t task,
                                               pl_vm_address_t function_address,
                                               const plcrash_async_thread_state_t *thread_state,
                                               plcrash_async_cfe_entry_t *entry,
                                               plcrash_async_thread_state_t *new_thread_state)
{
    /* Set up register load target */
    size_t greg_size = plcrash_async_thread_state_get_greg_size(thread_state);
    bool x64 = (greg_size == sizeof(uint64_t));
    void *dest;
    union {
        /* Room for (frame pointer, return address) + saved registers */
        uint64_t greg64[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
        uint32_t greg32[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
    } regs;

    if (x64)
        dest = regs.greg64;
    else
        dest = regs.greg32;
    
    /* Sanity check: We'll use this buffer for popping the fp and pc, as well as restoring the saved registers. */
    PLCF_ASSERT(PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX >= 2);

    /* Initialize the new thread state */
    *new_thread_state = *thread_state;
    plcrash_async_thread_state_clear_volatile_regs(new_thread_state);

    pl_vm_address_t saved_reg_addr = 0x0;
    plcrash_async_cfe_entry_type_t entry_type = plcrash_async_cfe_entry_type(entry);
    switch (entry_type) {
        case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR: {
            plcrash_error_t err;

            /* Fetch the current frame pointer */
            if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_FP)) {
                PLCF_DEBUG("Can't apply FRAME_PTR unwind type without a valid frame pointer");
                return PLCRASH_ENOTFOUND;
            }

            plcrash_greg_t fp = plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_FP);
            
            /* Address of saved registers */
            saved_reg_addr = (pl_vm_address_t)(fp + entry->stack_offset);
            
            /* Restore the previous frame's stack pointer from the saved frame pointer. This is
             * the FP + saved FP + return address. */
            pl_vm_address_t new_sp;
            if (!plcrash_async_address_apply_offset((pl_vm_address_t) fp, greg_size * 2, &new_sp)) {
                PLCF_DEBUG("Current frame pointer falls outside of addressable bounds");
                return PLCRASH_EINVAL;
            }
    
            plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, new_sp);

            /* Read the saved fp and retaddr */
            err = plcrash_async_task_memcpy(task, (pl_vm_address_t) fp, 0, dest, greg_size * 2);
            if (err != PLCRASH_ESUCCESS) {
                PLCF_DEBUG("Failed to read frame data at address 0x%" PRIx64 ": %d", (uint64_t) fp, err);
                return err;
            }

            // XXX: This assumes downward stack growth.
            if (x64) {
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg64[0]);
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[1]);
            } else {
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg32[0]);
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[1]);
            }
            break;
        }
            
        case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT:
            // Fallthrough
            
        case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD: {
            plcrash_error_t err;

            /* Fetch the current stack pointer */
            if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_SP)) {
                PLCF_DEBUG("Can't apply FRAME_IMMD unwind type without a valid stack pointer");
                return PLCRASH_ENOTFOUND;
            }

            /* Extract the stack size */
            pl_vm_address_t stack_size = entry->stack_offset;
            if (entry_type == PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT) {
                /* Stack size is encoded as a 32-bit value within the target process' TEXT segment; the value
                 * provided from the entry is used as an offset from the start of the function to the actual
                 * stack size. */
                uint32_t indirect;

                err = plcrash_async_task_memcpy(task, function_address, stack_size, &indirect, sizeof(indirect));
                if (err != PLCRASH_ESUCCESS) {
                    PLCF_DEBUG("Failed to read indirect stack size from 0x%" PRIx64 " + 0x%" PRIx64 ": %d",
                               (uint64_t) function_address, (uint64_t)stack_size, err);
                    return err;
                }

                stack_size = indirect + entry->stack_adjust;
            }

            /* Compute the stack pointer address */
            plcrash_greg_t sp = stack_size + plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_SP);
            plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, sp);

            if (entry->return_address_register == PLCRASH_REG_INVALID) {
                /* Return address is on the stack */
                pl_vm_address_t retaddr = (pl_vm_address_t)(sp - greg_size);
                saved_reg_addr = retaddr - (greg_size * entry->register_count); /* retaddr - [saved registers] */

                /* Original SP is found just before the return address. */
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, retaddr + greg_size);

                /* Read the saved return address */
                err = plcrash_async_task_memcpy(task, (pl_vm_address_t) retaddr, 0, dest, greg_size);
                if (err != PLCRASH_ESUCCESS) {
                    PLCF_DEBUG("Failed to read return address from 0x%" PRIx64 ": %d", (uint64_t) retaddr, err);
                    return err;
                }
                
                if (x64) {
                    plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[0]);
                } else {
                    plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[0]);
                }
            } else {
                /* Return address is in a register; verify that the register is available */
                if (!plcrash_async_thread_state_has_reg(thread_state, entry->return_address_register)) {
                    PLCF_DEBUG("The specified return_address_register '%s' is not available", plcrash_async_thread_state_get_reg_name(thread_state, entry->return_address_register));
                    return PLCRASH_ENOTFOUND;
                }
                
                /* Copy the return address value to the new thread state's IP */
                plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, plcrash_async_thread_state_get_reg(thread_state, entry->return_address_register));
                
                /* Saved registers are found below the new stack pointer. */
                saved_reg_addr = (pl_vm_address_t) sp - (greg_size * entry->register_count); /* sp - [saved registers] */
            }

            break;
        }

            
        case PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF:
            return PLCRASH_ENOTSUP;
            
        case PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE:
            return PLCRASH_ENOTSUP;
    }

    /* Extract the saved registers */
    uint32_t register_count = plcrash_async_cfe_entry_register_count(entry);
    plcrash_regnum_t register_list[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
    plcrash_async_cfe_entry_register_list(entry, register_list);
    for (uint32_t i = 0; i < register_count; i++) {
        /* The register list may be sparse */
        if (register_list[i] == PLCRASH_REG_INVALID)
            continue;

        /* Fetch and save register data */
        plcrash_error_t err;
        err = plcrash_async_task_memcpy(task, (pl_vm_address_t) saved_reg_addr, i*greg_size, dest, greg_size);
        if (err != PLCRASH_ESUCCESS) {
            PLCF_DEBUG("Failed to read register data for index %s: %d", plcrash_async_thread_state_get_reg_name(thread_state, register_list[i]), err);
            return err;
        }

        if (x64) {
            plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg64[0]);
        } else {
            plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg32[0]);
        }
    }
    

    return PLCRASH_ESUCCESS;
}