plcrash_error_t plcrash_async_dwarf_expression_eval()

in Source/PLCrashAsyncDwarfExpression.cpp [77:705]


plcrash_error_t plcrash_async_dwarf_expression_eval (plcrash_async_mobject_t *mobj,
                                                     task_t task,
                                                     const plcrash_async_thread_state_t *thread_state,
                                                     const plcrash_async_byteorder_t *byteorder,
                                                     pl_vm_address_t address,
                                                     pl_vm_off_t offset,
                                                     pl_vm_size_t length,
                                                     machine_ptr initial_state[],
                                                     size_t initial_count,
                                                     machine_ptr *result)
{
    // TODO: Review the use of an up-to-800 byte stack allocation; we may want to replace this with
    // use of the new async-safe allocator.
    dwarf_stack<machine_ptr, 100> stack;
    dwarf_opstream opstream;
    plcrash_error_t err;

    /* Configure the opstream */
    if ((err = opstream.init(mobj, byteorder, address, offset, length)) != PLCRASH_ESUCCESS)
        return err;
    
    /*
     * Note that the below value macros all cast data to the appropriate target machine word size.
     * This will result in overflows, as defined in the DWARF specification; the unsigned overflow
     * behavior is defined, and as per DWARF and C, the signed overflow behavior is not.
     */
    
    /* A position-advancing read macro that uses GCC/clang's compound statement value extension, returning PLCRASH_EINVAL
     * if the read extends beyond the mapped range. */
#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 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; \
    } \
    (machine_ptr) 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; \
    } \
    (machine_ptr_s) v; \
})

    /* Macro to fetch register valeus; handles unsupported register numbers and missing registers values */
#define dw_thread_regval(dw_regnum) ({ \
    plcrash_regnum_t rn; \
	uint64_t _dw_regnum = dw_regnum; \
    if (!plcrash_async_thread_state_map_dwarf_to_reg(thread_state, _dw_regnum, &rn)) { \
        PLCF_DEBUG("Unsupported DWARF register value of 0x%" PRIx64, _dw_regnum);\
        return PLCRASH_ENOTSUP; \
    } \
\
    if (!plcrash_async_thread_state_has_reg(thread_state, rn)) { \
        PLCF_DEBUG("Register value of %s unavailable in the current frame.", plcrash_async_thread_state_get_reg_name(thread_state, rn)); \
        return PLCRASH_ENOTFOUND; \
    } \
\
    plcrash_greg_t val = plcrash_async_thread_state_get_reg(thread_state, rn); \
    (machine_ptr) val; \
})

    /* A push macro that handles reporting of stack overflow errors */
#define dw_expr_push(v) if (!stack.push((machine_ptr_s)v)) { \
    PLCF_DEBUG("Hit stack limit; cannot push further values"); \
    return PLCRASH_EINTERNAL; \
}
    
    /* A pop macro that handles reporting of stack underflow errors */
#define dw_expr_pop(v) if (!stack.pop(v)) { \
    PLCF_DEBUG("Pop on an empty stack"); \
    return PLCRASH_EINTERNAL; \
}
    
    /* Populate the initial state */
    for (size_t i = 0; i < initial_count; i++)
        dw_expr_push(initial_state[i]);

    uint8_t opcode;
    while (opstream.read_intU(&opcode)) {
        switch (opcode) {
            case DW_OP_lit0:
            case DW_OP_lit1:
            case DW_OP_lit2:
            case DW_OP_lit3:
            case DW_OP_lit4:
            case DW_OP_lit5:
            case DW_OP_lit6:
            case DW_OP_lit7:
            case DW_OP_lit8:
            case DW_OP_lit9:
            case DW_OP_lit10:
            case DW_OP_lit11:
            case DW_OP_lit12:
            case DW_OP_lit13:
            case DW_OP_lit14:
            case DW_OP_lit15:
            case DW_OP_lit16:
            case DW_OP_lit17:
            case DW_OP_lit18:
            case DW_OP_lit19:
            case DW_OP_lit20:
            case DW_OP_lit21:
            case DW_OP_lit22:
            case DW_OP_lit23:
            case DW_OP_lit24:
            case DW_OP_lit25:
            case DW_OP_lit26:
            case DW_OP_lit27:
            case DW_OP_lit28:
            case DW_OP_lit29:
            case DW_OP_lit30:
            case DW_OP_lit31:
                dw_expr_push(opcode-DW_OP_lit0);
                break;
                
            case DW_OP_const1u:
                dw_expr_push(dw_expr_read_int(uint8_t));
                break;

            case DW_OP_const1s:
                dw_expr_push(dw_expr_read_int(int8_t));
                break;
                
            case DW_OP_const2u:
                dw_expr_push(dw_expr_read_int(uint16_t));
                break;
                
            case DW_OP_const2s:
                dw_expr_push((int16_t)dw_expr_read_int(int16_t));
                break;
                
            case DW_OP_const4u:
                dw_expr_push(dw_expr_read_int(uint32_t));
                break;
                
            case DW_OP_const4s:
                dw_expr_push((int32_t) dw_expr_read_int(int32_t));
                break;
                
            case DW_OP_const8u:
                dw_expr_push(dw_expr_read_int(uint64_t));
                break;
                
            case DW_OP_const8s:
                dw_expr_push((int64_t) dw_expr_read_int(int64_t));
                break;
                
            case DW_OP_constu:
                dw_expr_push(dw_expr_read_uleb128());
                break;
                
            case DW_OP_consts:
                dw_expr_push(dw_expr_read_sleb128());
                break;
                
            case DW_OP_breg0:
			case DW_OP_breg1:
			case DW_OP_breg2:
			case DW_OP_breg3:
			case DW_OP_breg4:
			case DW_OP_breg5:
			case DW_OP_breg6:
			case DW_OP_breg7:
			case DW_OP_breg8:
			case DW_OP_breg9:
			case DW_OP_breg10:
			case DW_OP_breg11:
			case DW_OP_breg12:
			case DW_OP_breg13:
			case DW_OP_breg14:
			case DW_OP_breg15:
			case DW_OP_breg16:
			case DW_OP_breg17:
			case DW_OP_breg18:
			case DW_OP_breg19:
			case DW_OP_breg20:
			case DW_OP_breg21:
			case DW_OP_breg22:
			case DW_OP_breg23:
			case DW_OP_breg24:
			case DW_OP_breg25:
			case DW_OP_breg26:
			case DW_OP_breg27:
			case DW_OP_breg28:
			case DW_OP_breg29:
			case DW_OP_breg30:
			case DW_OP_breg31:
                dw_expr_push(dw_thread_regval(opcode - DW_OP_breg0) + dw_expr_read_sleb128());
                break;
                
            case DW_OP_bregx:
                dw_expr_push(dw_thread_regval(dw_expr_read_uleb128()) + dw_expr_read_sleb128());
                break;
                
            case DW_OP_dup:
                if (!stack.dup()) {
                    PLCF_DEBUG("DW_OP_dup on an empty stack");
                    return PLCRASH_EINVAL;
                }
                break;
                
            case DW_OP_drop: {
                if (!stack.drop()) {
                    PLCF_DEBUG("DW_OP_drop on an empty stack");
                    return PLCRASH_EINVAL;
                }
                break;
            }
                
            case DW_OP_pick:
                if (!stack.pick(dw_expr_read_int(uint8_t))) {
                    PLCF_DEBUG("DW_OP_pick on invalid index");
                    return PLCRASH_EINVAL;
                }
                break;

            case DW_OP_over:
                if (!stack.pick(1)) {
                    PLCF_DEBUG("DW_OP_over on stack with < 2 elements");
                    return PLCRASH_EINVAL;
                }
                break;
                
            case DW_OP_swap:
                if (!stack.swap()) {
                    PLCF_DEBUG("DW_OP_swap on stack with < 2 elements");
                    return PLCRASH_EINVAL;
                }
                break;
                
            case DW_OP_rot:
                if (!stack.rotate()) {
                    PLCF_DEBUG("DW_OP_rot on stack with < 3 elements");
                    return PLCRASH_EINVAL;
                }
                break;
            
                
            case DW_OP_xderef:
                /* This is identical to deref, except that it consumes an additional stack value
                 * containing the address space of the address. We don't support any systems with multiple
                 * address spaces, so we simply excise this value from the stack and fall through to the
                 * deref implementation */

                /* Move the address space value to the top of the stack, and then drop it */
                if (!stack.swap()) {
                    PLCF_DEBUG("DW_OP_xderef on stack with < 2 elements");
                    return PLCRASH_EINVAL;
                }
                
                /* This can't fail after the swap suceeded */
                stack.drop();
                PLCR_FALLTHROUGH;
                
            case DW_OP_deref: {
                machine_ptr addr;
                machine_ptr value;

                dw_expr_pop(&addr);
                if ((err = plcrash_async_task_memcpy(task, (pl_vm_address_t) addr, 0, &value, sizeof(value))) != PLCRASH_ESUCCESS) {
                    PLCF_DEBUG("DW_OP_deref referenced an invalid target address 0x%" PRIx64, (uint64_t) addr);
                    return err;
                }

                dw_expr_push(value);
                
                break;
            }
            case DW_OP_xderef_size:
                /* This is identical to deref_size, except that it consumes an additional stack value
                 * containing the address space of the address. We don't support any systems with multiple
                 * address spaces, so we simply excise this value from the stack and fall through to the
                 * deref implementation */
                
                /* Move the address space value to the top of the stack, and then drop it */
                if (!stack.swap()) {
                    PLCF_DEBUG("DW_OP_xderef_size on stack with < 2 elements");
                    return PLCRASH_EINVAL;
                }

                /* This can't fail after the swap suceeded */
                stack.drop();
                PLCR_FALLTHROUGH;

            case DW_OP_deref_size: {
                /* Fetch the target size */
                uint8_t size = dw_expr_read_int(uint8_t);
                if (size > sizeof(machine_ptr)) {
                    PLCF_DEBUG("DW_OP_deref_size specified a size larger than the native machine word");
                    return PLCRASH_EINVAL;
                }
                
                /* Pop the address from the stack */
                machine_ptr addr;
                dw_expr_pop(&addr);

                /* Perform the read */
                #define readval(_type) case sizeof(_type): { \
                    _type r; \
                    if ((err = plcrash_async_task_memcpy(task, (pl_vm_address_t)addr, 0, &r, sizeof(_type))) != PLCRASH_ESUCCESS) { \
                        PLCF_DEBUG("DW_OP_deref_size referenced an invalid target address 0x%" PRIx64, (uint64_t) addr); \
                        return err; \
                    } \
                    value = (machine_ptr)r; \
                    break; \
                }
                machine_ptr value = 0;
                switch (size) {
                    readval(uint8_t);
                    readval(uint16_t);
                    readval(uint32_t);
                    readval(uint64_t);

                    default:
                        PLCF_DEBUG("DW_OP_deref_size specified an unsupported size of %" PRIu8, size);
                        return PLCRASH_EINVAL;
                }
                #undef readval

                dw_expr_push(value);
                
                break;
            }
                
            case DW_OP_abs: {
                machine_ptr_s v;
                dw_expr_pop((machine_ptr *)&v);
                if (v < 0) {
                    dw_expr_push(-v);
                } else {
                    dw_expr_push(v);
                }
                break;
            }

            case DW_OP_and: {
                machine_ptr v1, v2;
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);                
                dw_expr_push(v1 & v2);
                break;
            }
                
            case DW_OP_div: {
                machine_ptr_s divisor;
                machine_ptr dividend;
                
                dw_expr_pop((machine_ptr *) &divisor);
                dw_expr_pop(&dividend);
                
                if (divisor == 0) {
                    PLCF_DEBUG("DW_OP_div attempted divide by zero");
                    return PLCRASH_EINVAL;
                }
                
                machine_ptr quotient = dividend / divisor;
                dw_expr_push(quotient);
                break;
            }
                
            case DW_OP_minus: {
                machine_ptr minuend, subtrahend;
                
                dw_expr_pop(&subtrahend);
                dw_expr_pop(&minuend);
                dw_expr_push(minuend - subtrahend);
                break;
            }
                
            case DW_OP_mod: {
                machine_ptr divisor;
                machine_ptr dividend;
                
                dw_expr_pop(&divisor);
                dw_expr_pop(&dividend);
                
                if (divisor == 0) {
                    PLCF_DEBUG("DW_OP_mod attempted divide by zero");
                    return PLCRASH_EINVAL;
                }
                
                machine_ptr remainder = dividend % divisor;
                dw_expr_push(remainder);
                break;
            }
                
            case DW_OP_mul: {
                machine_ptr v1, v2;
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                dw_expr_push(v1 * v2);
                break;
            }
                
            case DW_OP_neg: {
                machine_ptr_s svalue;
                dw_expr_pop((machine_ptr *) &svalue);
                dw_expr_push(0 - svalue);
                break;
            }
                
            case DW_OP_not: {
                machine_ptr v;
                dw_expr_pop(&v);
                dw_expr_push(~v);
                break;
            }
                
            case DW_OP_or: {
                machine_ptr v1, v2;
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                dw_expr_push(v1 | v2);
                break;
            }
                
            case DW_OP_plus: {
                machine_ptr v1, v2;
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                dw_expr_push(v1 + v2);
                break;
            }
                
            case DW_OP_plus_uconst: {
                machine_ptr v1 = dw_expr_read_uleb128();
                machine_ptr v2;
                
                dw_expr_pop(&v2);
                dw_expr_push(v1 + v2);
                break;
            }
                
            case DW_OP_shl: {
                machine_ptr shift;
                machine_ptr value;
                
                dw_expr_pop(&shift);
                dw_expr_pop(&value);
                
                dw_expr_push(value << shift);
                break;
            }
                
            case DW_OP_shr: {
                machine_ptr shift;
                machine_ptr value;
                
                dw_expr_pop(&shift);
                dw_expr_pop(&value);
                
                dw_expr_push(value >> shift);
                break;
            }
                
            case DW_OP_shra: {
                machine_ptr shift;
                machine_ptr_s value;
                
                dw_expr_pop(&shift);
                dw_expr_pop((machine_ptr *)&value);
                
                dw_expr_push(value >> shift);
                break;
            }
                
            case DW_OP_xor: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push(v1 ^ v2);
                break;
            }
                
            case DW_OP_le: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 <= v1));
                break;
            }

            case DW_OP_ge: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 >= v1));
                break;
            }
                
            case DW_OP_eq: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 == v1));
                break;
            }
    
            case DW_OP_lt: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 < v1));
                break;
            }

            case DW_OP_gt: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 > v1));
                break;
            }

            case DW_OP_ne: {
                machine_ptr v1, v2;
                
                dw_expr_pop(&v1);
                dw_expr_pop(&v2);
                
                dw_expr_push((v2 != v1));
                break;
            }
                
            case DW_OP_skip: {
                int16_t skipOffset = dw_expr_read_int(int16_t);
                if (!opstream.skip(skipOffset)) {
                    PLCF_DEBUG("DW_OP_skip offset %" PRId16 " falls outside of opcode range", skipOffset);
                    return PLCRASH_EINVAL;
                }
                break;
            }
                
            case DW_OP_bra: {
                int16_t skipOffset = dw_expr_read_int(int16_t);
                machine_ptr cond;

                dw_expr_pop(&cond);
                if (cond != 0) {
                    if (!opstream.skip(skipOffset)) {
                        PLCF_DEBUG("DW_OP_bra offset %" PRId16 " falls outside of opcode range", skipOffset);
                        return PLCRASH_EINVAL;
                    }
                }
                break;
            }

            case DW_OP_nop: // no-op
                break;
                
            // Not implemented -- fall through
            case DW_OP_fbreg:
                /* Unimplemented */
                
            case DW_OP_call2:
            case DW_OP_call4:
            case DW_OP_call_ref:
                /*
                 * As per DWARF 3, Section 6.4.2 Call Frame Instructions DW_OP_call2, DW_OP_call4 and DW_OP_call_ref operators
                 * are not meaningful in an operand of these instructions because there is no mapping from call frame information
                 * to any corresponding debugging compilation unit information, thus there is no way to interpret the call offset.
                 *
                 * If this implementation is further extended for use outside of CFI evaluation, this opcode should be implemented.
                */

            case DW_OP_push_object_address:
                /*
                 * As per DWARF 3, Section 6.4.2 Call Frame Instructions, DW_OP_push_object_address is not meaningful in an operand of these
                 * instructions because there is no object context to provide a value to push.
                 *
                 * If this implementation is further extended for use outside of CFI evaluation, this opcode should be implemented.
                 */

            case DW_OP_form_tls_address:
                /* The structure of TLS data on Darwin is implementation private. */
                
            case DW_OP_call_frame_cfa:
                /*
                 * As per DWARF 3, Section 6.4.2 Call Frame Instructions, DW_OP_call_frame_cfa is not meaningful in an operand of these
                 * instructions because its use would be circular.
                 *
                 * If this implementation is further extended for use outside of CFI evaluation, this opcode should be implemented.
                 */
                
            default:
                PLCF_DEBUG("Unsupported opcode 0x%" PRIx8, opcode);
                return PLCRASH_ENOTSUP;
        }
    }

    /* Provide the result */
    if (!stack.pop(result)) {
        PLCF_DEBUG("Expression did not provide a result value.");
        return PLCRASH_EINVAL;
    }

#undef dw_expr_read
#undef dw_expr_push
    return PLCRASH_ESUCCESS;
}