plcrash_error_t dwarf_cfa_state::apply_state()

in Source/PLCrashAsyncDwarfCFAStateEvaluation.cpp [473:601]


plcrash_error_t dwarf_cfa_state<machine_ptr, machine_ptr_s>::apply_state (task_t task,
                                                                          plcrash_async_dwarf_cie_info_t *cie_info,
                                                                          const plcrash_async_thread_state_t *thread_state,
                                                                          const plcrash_async_byteorder_t *byteorder,
                                                                          plcrash_async_thread_state_t *new_thread_state)
{
    plcrash_error_t err;

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

    /*
     * Restore the canonical frame address
     */
    dwarf_cfa_rule<machine_ptr, machine_ptr_s> cfa_rule = get_cfa_rule();
    machine_ptr cfa_val;

    switch (cfa_rule.type()) {
        case DWARF_CFA_STATE_CFA_TYPE_UNDEFINED:
            /** Missing canonical frame address! */
            PLCF_DEBUG("No canonical frame address specified in the CFA state; can't apply state");
            return PLCRASH_EINVAL;

        case DWARF_CFA_STATE_CFA_TYPE_REGISTER:
        case DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED: {
            plcrash_regnum_t regnum;
            
            /* Map to a plcrash register number */
            if (!plcrash_async_thread_state_map_dwarf_to_reg(thread_state, cfa_rule.register_number(), &regnum)) {
                PLCF_DEBUG("CFA rule references an unsupported DWARF register: 0x%" PRIx32, cfa_rule.register_number());
                return PLCRASH_ENOTSUP;
            }
            
            /* Verify that the requested register is available */
            if (!plcrash_async_thread_state_has_reg(thread_state, regnum)) {
                PLCF_DEBUG("CFA rule references a register that is not available from the current thread state: %s", plcrash_async_thread_state_get_reg_name(thread_state, regnum));
                return PLCRASH_ENOTFOUND;
            }

            /* Fetch the current value, apply the offset, and save as the new thread's CFA. */
            cfa_val = (machine_ptr) plcrash_async_thread_state_get_reg(thread_state, regnum);
            if (cfa_rule.type() == DWARF_CFA_STATE_CFA_TYPE_REGISTER)
                cfa_val += cfa_rule.register_offset();
            else
                cfa_val += cfa_rule.register_offset_signed();
            break;
        }

        case DWARF_CFA_STATE_CFA_TYPE_EXPRESSION: {
            plcrash_async_mobject_t mobj;
            if ((err = plcrash_async_mobject_init(&mobj, task, cfa_rule.expression_address(), cfa_rule.expression_length(), true)) != PLCRASH_ESUCCESS) {
                PLCF_DEBUG("Could not map CFA expression range");
                return err;
            }
            
            if ((err = plcrash_async_dwarf_expression_eval<machine_ptr, machine_ptr_s>(&mobj, task, thread_state, byteorder, cfa_rule.expression_address(), 0x0, cfa_rule.expression_length(), NULL, 0, &cfa_val)) != PLCRASH_ESUCCESS) {
                PLCF_DEBUG("CFA eval_64 failed");
                return err;
            }

            /* Clean up the memory mapping */
            plcrash_async_mobject_free(&mobj);

            break;
        }
    }
    
    /* Apply the CFA to the new state */
    plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, cfa_val);
    
    /*
     * Restore register values
     */
    dwarf_cfa_state_iterator<machine_ptr, machine_ptr_s> iter = dwarf_cfa_state_iterator<machine_ptr, machine_ptr_s>(this);
    dwarf_cfa_state_regnum_t dw_regnum;
    plcrash_dwarf_cfa_reg_rule_t dw_rule;
    machine_ptr dw_value;
    
    while (iter.next(&dw_regnum, &dw_rule, &dw_value)) {
        /* Map the register number */
        plcrash_regnum_t pl_regnum;
        if (!plcrash_async_thread_state_map_dwarf_to_reg(thread_state, dw_regnum, &pl_regnum)) {
            /* Some DWARF ABIs (such as x86-64) define the return address using a pseudo-register. In that case, the
             * register will not have a vaid DWARF -> PLCrashReporter mapping; we simply target the IP in this case,
             * which results in the expected behavior of setting the IP in the new thread state. */
            if (cie_info->return_address_register == dw_regnum) {
                pl_regnum = PLCRASH_REG_IP;
            } else {
                PLCF_DEBUG("Register rule references an unsupported DWARF register: 0x%" PRIx64, (uint64_t) dw_regnum);
                return PLCRASH_EINVAL;
            }
        }
        
        /* Apply the register rule */
        if ((err = plcrash_async_dwarf_cfa_state_apply_register<machine_ptr, machine_ptr_s>(task, thread_state, byteorder, new_thread_state, cfa_val, pl_regnum, dw_rule, dw_value)) != PLCRASH_ESUCCESS)
            return err;
        
        /* If the target register is defined as the return address (and is not already the IP), copy the value to the IP.  */
        if (cie_info->return_address_register == dw_regnum && pl_regnum != PLCRASH_REG_IP) {
            PLCF_ASSERT(plcrash_async_thread_state_has_reg(new_thread_state, pl_regnum));
            plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, plcrash_async_thread_state_get_reg(new_thread_state, pl_regnum));
        }
    }

    /*
     * If the IP was not restored via a saved register above, the CIE's return_address_register may reference a live register in the
     * current thread state. This will occur in leaf frames on platforms where lr has not been saved to the stack.
     */
    if (!plcrash_async_thread_state_has_reg(new_thread_state, PLCRASH_REG_IP)) {
        /* Map the return_address_register number */
        plcrash_regnum_t pl_regnum;
        if (!plcrash_async_thread_state_map_dwarf_to_reg(thread_state, cie_info->return_address_register, &pl_regnum)) {
            PLCF_DEBUG("Return address register value references an unsupported DWARF register: 0x%" PRIx64, (uint64_t) cie_info->return_address_register);
            return PLCRASH_EINVAL;
        }
        
        /* Verify that the register is available */
        if (!plcrash_async_thread_state_has_reg(thread_state, pl_regnum)) {
            PLCF_DEBUG("CIE return_address_register references a register that is not available from the current thread state: %s", plcrash_async_thread_state_get_reg_name(thread_state, pl_regnum));
            return PLCRASH_EINVAL;
        }

        /* Copy the value to the new state's IP. */
        plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, plcrash_async_thread_state_get_reg(thread_state, pl_regnum));
    }

    return PLCRASH_ESUCCESS;
}