plcrash_error_t dwarf_cfa_state::eval_program()

in Source/PLCrashAsyncDwarfCFAStateEvaluation.cpp [84:458]


plcrash_error_t dwarf_cfa_state<machine_ptr, machine_ptr_s>::eval_program (plcrash_async_mobject_t *mobj,
                                                                           machine_ptr pc,
                                                                           machine_ptr initial_pc_value,
                                                                           plcrash_async_dwarf_cie_info_t *cie_info,
                                                                           gnu_ehptr_reader<machine_ptr> *ptr_reader,
                                                                           const plcrash_async_byteorder_t *byteorder,
                                                                           pl_vm_address_t address,
                                                                           pl_vm_off_t offset,
                                                                           pl_vm_size_t length)
{
    plcrash::async::dwarf_opstream opstream;
    plcrash_error_t err;
    machine_ptr location = initial_pc_value;

    /* Save the initial state; this is needed for DW_CFA_restore, et al. */
    // TODO - It would be preferrable to only allocate the number of registers actually required here.
    dwarf_cfa_state<machine_ptr, machine_ptr_s> initial_state;
    {
        dwarf_cfa_state_regnum_t regnum;
        plcrash_dwarf_cfa_reg_rule_t rule;
        machine_ptr value;

        dwarf_cfa_state_iterator<machine_ptr, machine_ptr_s> iter = dwarf_cfa_state_iterator<machine_ptr, machine_ptr_s>(this);
        while (iter.next(&regnum, &rule, &value)) {
            if (!initial_state.set_register(regnum, rule, value)) {
                PLCF_DEBUG("Hit register allocation limit while saving initial state");
                return PLCRASH_ENOMEM;
            }
        }
    }

    /* Default to reading as a standard machine word */
    DW_EH_PE_t gnu_eh_ptr_encoding = DW_EH_PE_absptr;
    if (cie_info->has_eh_augmentation && cie_info->eh_augmentation.has_pointer_encoding && ptr_reader != NULL) {
        gnu_eh_ptr_encoding = (DW_EH_PE_t) cie_info->eh_augmentation.pointer_encoding;
    }
    
    /* Calculate the absolute (target-relative) address of the start of the stream */
    pl_vm_address_t opstream_target_address;
    if (!plcrash_async_address_apply_offset(address, offset, &opstream_target_address)) {
        PLCF_DEBUG("Offset overflows base address");
        return PLCRASH_EINVAL;
    }

    /* Configure the opstream */
    if ((err = opstream.init(mobj, byteorder, address, offset, length)) != PLCRASH_ESUCCESS)
        return err;
    
#define dw_expr_read_int(_type) ({ \
    _type v; \
    if (!opstream.read_intU<_type>(&v)) { \
        PLCF_DEBUG("Read of size %zu exceeds mapped range", sizeof(v)); \
        return PLCRASH_EINVAL; \
    } \
    v; \
})
    
    /* A position-advancing DWARF uleb128 register read macro that uses GCC/clang's compound statement value extension, returning an error
     * if the read fails, or the register value exceeds DWARF_CFA_STATE_REGNUM_MAX */
#define dw_expr_read_uleb128_regnum() ({ \
    uint64_t v; \
    if (!opstream.read_uleb128(&v)) { \
        PLCF_DEBUG("Read of ULEB128 value failed"); \
        return PLCRASH_EINVAL; \
    } \
    if (v > DWARF_CFA_STATE_REGNUM_MAX) { \
        PLCF_DEBUG("Register number %" PRIu64 " exceeds DWARF_CFA_STATE_REGNUM_MAX", v); \
        return PLCRASH_ENOTSUP; \
    } \
    (uint32_t) v; \
})
    
    /* A position-advancing uleb128 read macro that uses GCC/clang's compound statement value extension, returning an error
     * if the read fails. */
#define dw_expr_read_uleb128() ({ \
    uint64_t v; \
    if (!opstream.read_uleb128(&v)) { \
        PLCF_DEBUG("Read of ULEB128 value failed"); \
        return PLCRASH_EINVAL; \
    } \
    v; \
})

    /* A position-advancing sleb128 read macro that uses GCC/clang's compound statement value extension, returning an error
     * if the read fails. */
#define dw_expr_read_sleb128() ({ \
    int64_t v; \
    if (!opstream.read_sleb128(&v)) { \
        PLCF_DEBUG("Read of SLEB128 value failed"); \
        return PLCRASH_EINVAL; \
    } \
    v; \
})
    
    /* Handle error checking when setting a register on the CFA state */
#define dw_expr_set_register(_regnum, _rule, _value) do { \
    if (!set_register(_regnum, _rule, _value)) { \
        PLCF_DEBUG("Exhausted available register slots while evaluating CFA opcodes"); \
        return PLCRASH_ENOMEM; \
    } \
} while (0)

    /* Iterate the opcode stream until the pc_offset is hit */
    uint8_t opcode;
    while ((pc == 0 || location <= pc) && opstream.read_intU(&opcode)) {
        uint8_t const_operand = 0;

        /* Check for opcodes encoded in the top two bits, with an operand
         * in the bottom 6 bits. */
        
        if ((opcode & 0xC0) != 0) {
            const_operand = opcode & 0x3F;
            opcode &= 0xC0;
        }
        
        switch (opcode) {
            case DW_CFA_set_loc:
                if (cie_info->segment_size != 0) {
                    PLCF_DEBUG("Segment support has not been implemented");
                    return PLCRASH_ENOTSUP;
                }

                /* Try reading an eh_frame encoded pointer */
                if (!opstream.read_gnueh_ptr(ptr_reader, gnu_eh_ptr_encoding, &location)) {
                    PLCF_DEBUG("DW_CFA_set_loc failed to read the target pointer value");
                    return PLCRASH_EINVAL;
                }
                break;
                
            case DW_CFA_advance_loc:
                location += const_operand * cie_info->code_alignment_factor;
                break;
                
            case DW_CFA_advance_loc1:
                location += dw_expr_read_int(uint8_t) * cie_info->code_alignment_factor;
                break;
                
            case DW_CFA_advance_loc2:
                location += dw_expr_read_int(uint16_t) * cie_info->code_alignment_factor;
                break;
                
            case DW_CFA_advance_loc4:
                location += dw_expr_read_int(uint32_t) * cie_info->code_alignment_factor;
                break;
                
            case DW_CFA_def_cfa:
                set_cfa_register(dw_expr_read_uleb128_regnum(), (machine_ptr)dw_expr_read_uleb128());
                break;
                
            case DW_CFA_def_cfa_sf:
                set_cfa_register_signed(dw_expr_read_uleb128_regnum(), (machine_ptr_s)(dw_expr_read_sleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_def_cfa_register: {
                dwarf_cfa_rule<machine_ptr, machine_ptr_s> rule = get_cfa_rule();
                
                switch (rule.type()) {
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER:
                        set_cfa_register(dw_expr_read_uleb128_regnum(), rule.register_offset());
                        break;
                        
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED:
                        set_cfa_register_signed(dw_expr_read_uleb128_regnum(), rule.register_offset_signed());
                        break;
                        
                    case DWARF_CFA_STATE_CFA_TYPE_EXPRESSION:
                    case DWARF_CFA_STATE_CFA_TYPE_UNDEFINED:
                        PLCF_DEBUG("DW_CFA_def_cfa_register emitted for a non-register CFA rule state");
                        return PLCRASH_EINVAL;
                }
                break;
            }
                
            case DW_CFA_def_cfa_offset: {
                dwarf_cfa_rule<machine_ptr, machine_ptr_s> rule = get_cfa_rule();
                switch (rule.type()) {
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER:
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED:
                        /* Our new offset is unsigned, so all register rules are converted to unsigned here */
                        set_cfa_register(rule.register_number(), (machine_ptr)dw_expr_read_uleb128());
                        break;
                        
                    case DWARF_CFA_STATE_CFA_TYPE_EXPRESSION:
                    case DWARF_CFA_STATE_CFA_TYPE_UNDEFINED:
                        PLCF_DEBUG("DW_CFA_def_cfa_register emitted for a non-register CFA rule state");
                        return PLCRASH_EINVAL;
                }
                break;
            }
                
                
            case DW_CFA_def_cfa_offset_sf: {
                dwarf_cfa_rule<machine_ptr, machine_ptr_s> rule = get_cfa_rule();
                switch (rule.type()) {
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER:
                    case DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED:
                        /* Our new offset is signed, so all register rules are converted to signed here */
                        set_cfa_register_signed(rule.register_number(), (machine_ptr_s)(dw_expr_read_sleb128() * cie_info->data_alignment_factor));
                        break;

                    case DWARF_CFA_STATE_CFA_TYPE_EXPRESSION:
                    case DWARF_CFA_STATE_CFA_TYPE_UNDEFINED:
                        PLCF_DEBUG("DW_CFA_def_cfa_register emitted for a non-register CFA rule state");
                        return PLCRASH_EINVAL;
                }
                break;
            }

            case DW_CFA_def_cfa_expression: {                
                /* Fetch the DW_FORM_block length header; we need this to skip the over the DWARF expression. */
                uint64_t blockLength = dw_expr_read_uleb128();
                
                /* Fetch the opstream position of the DWARF expression */
                uintptr_t pos = opstream.get_position();

                /* The returned sizes should always fit within the VM types in valid DWARF data; if they don't, how
                 * are we debugging the target? */
                if (blockLength > PL_VM_SIZE_MAX || blockLength > PL_VM_OFF_MAX) {
                    PLCF_DEBUG("DWARF expression length exceeds PL_VM_SIZE_MAX/PL_VM_OFF_MAX in DW_CFA_def_cfa_expression operand");
                    return PLCRASH_ENOTSUP;
                }
                
                // This issue triggers clang's new 'tautological' warnings on some host platforms with some types of pl_vm_off_t/pl_vm_address_t.
                // Testing tautological correctness and *documenting* the issue is the whole point of the check, even though it
                // may always be true on some hosts.
                // Since older versions of clang do not support -Wtautological, we have to enable -Wunknown-pragmas first
                PLCR_PRAGMA_CLANG("clang diagnostic push");
                PLCR_PRAGMA_CLANG("clang diagnostic ignored \"-Wunknown-pragmas\"");
                PLCR_PRAGMA_CLANG("clang diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"");
                if (pos > PL_VM_ADDRESS_MAX || pos > PL_VM_OFF_MAX) {
                    PLCF_DEBUG("DWARF expression position exceeds PL_VM_ADDRESS_MAX/PL_VM_OFF_MAX in CFA opcode stream");
                    return PLCRASH_ENOTSUP;
                }
                PLCR_PRAGMA_CLANG("clang diagnostic pop");
                
                /* Calculate the absolute address of the expression opcodes. */
                pl_vm_address_t abs_addr;
                if (!plcrash_async_address_apply_offset(opstream_target_address, pos, &abs_addr)) {
                    PLCF_DEBUG("Offset overflows base address");
                    return PLCRASH_EINVAL;
                }

                /* Save the position */
                set_cfa_expression(abs_addr, (pl_vm_size_t) blockLength);
                
                /* Skip the expression opcodes */
                opstream.skip((pl_vm_off_t) blockLength);
                break;
            }
                
            case DW_CFA_undefined:
                remove_register(dw_expr_read_uleb128_regnum());
                break;
                
            case DW_CFA_same_value:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_SAME_VALUE, 0);
                break;
                
            case DW_CFA_offset:
                dw_expr_set_register(const_operand, PLCRASH_DWARF_CFA_REG_RULE_OFFSET, (machine_ptr)(dw_expr_read_uleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_offset_extended:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_OFFSET, (machine_ptr)(dw_expr_read_uleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_offset_extended_sf:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_OFFSET, (machine_ptr)(dw_expr_read_sleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_val_offset:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_VAL_OFFSET, (machine_ptr)(dw_expr_read_uleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_val_offset_sf:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_VAL_OFFSET, (machine_ptr)(dw_expr_read_sleb128() * cie_info->data_alignment_factor));
                break;
                
            case DW_CFA_register:
                dw_expr_set_register(dw_expr_read_uleb128_regnum(), PLCRASH_DWARF_CFA_REG_RULE_REGISTER, (machine_ptr)dw_expr_read_uleb128());
                break;
            
            case DW_CFA_expression:
            case DW_CFA_val_expression: {
                dwarf_cfa_state_regnum_t regnum = dw_expr_read_uleb128_regnum();
                uintptr_t pos = opstream.get_position();
                
                /* Fetch the DW_FORM_BLOCK length header; we need this to skip the over the DWARF expression. */
                uint64_t blockLength = dw_expr_read_uleb128();

                /* Calculate the absolute address of the expression opcodes (including verifying that pos won't overflow when applying the offset). */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
                if (pos > PL_VM_ADDRESS_MAX || pos > PL_VM_OFF_MAX) {
                    PLCF_DEBUG("DWARF expression position exceeds PL_VM_ADDRESS_MAX/PL_VM_OFF_MAX in DW_CFA_expression evaluation");
                    return PLCRASH_ENOTSUP;
                }
#pragma clang diagnostic pop
                
                pl_vm_address_t abs_addr;
                if (!plcrash_async_address_apply_offset(opstream_target_address, pos, &abs_addr)) {
                    PLCF_DEBUG("Offset overflows base address");
                    return PLCRASH_EINVAL;
                }
                
                /* Save the position */
                if (opcode == DW_CFA_expression) {
                    dw_expr_set_register(regnum, PLCRASH_DWARF_CFA_REG_RULE_EXPRESSION, (machine_ptr)abs_addr);
                } else {
                    PLCF_ASSERT(opcode == DW_CFA_val_expression); // If not _expression, must be _val_expression.
                    dw_expr_set_register(regnum, PLCRASH_DWARF_CFA_REG_RULE_VAL_EXPRESSION, (machine_ptr)abs_addr);
                }

                /* Skip the expression opcodes */
                opstream.skip((pl_vm_off_t) blockLength);
                break;
            }
                
            case DW_CFA_restore: {
                plcrash_dwarf_cfa_reg_rule_t rule;
                machine_ptr value;
                
                /* Either restore the value specified in the initial state, or remove the register
                 * if the initial state has no associated value */
                if (initial_state.get_register_rule(const_operand, &rule, &value)) {
                    dw_expr_set_register(const_operand, rule, value);
                } else {
                    remove_register(const_operand);
                }
        
                break;
            }
                
            case DW_CFA_restore_extended: {
                dwarf_cfa_state_regnum_t regnum = dw_expr_read_uleb128_regnum();
                plcrash_dwarf_cfa_reg_rule_t rule;
                machine_ptr value;
                
                /* Either restore the value specified in the initial state, or remove the register
                 * if the initial state has no associated value */
                if (initial_state.get_register_rule(regnum, &rule, &value)) {
                    dw_expr_set_register(regnum, rule, value);
                } else {
                    remove_register(const_operand);
                }
                
                break;
            }
                
            case DW_CFA_remember_state:
                if (!push_state()) {
                    PLCF_DEBUG("DW_CFA_remember_state exeeded the allocated CFA stack size");
                    return PLCRASH_ENOMEM;
                }
                break;
                
            case DW_CFA_restore_state:
                if (!pop_state()) {
                    PLCF_DEBUG("DW_CFA_restore_state was issued on an empty CFA stack");
                    return PLCRASH_EINVAL;
                }
                break;

            case DW_CFA_nop:
                break;
                
            default:
                PLCF_DEBUG("Unsupported opcode 0x%" PRIx8, opcode);
                return PLCRASH_ENOTSUP;
        }
    }

    return PLCRASH_ESUCCESS;
}