Source/PLCrashAsyncDwarfExpression.cpp (502 lines of code) (raw):
/*
* Copyright (c) 2013 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <inttypes.h>
#include "dwarf_stack.hpp"
#include "dwarf_opstream.hpp"
#include "PLCrashAsyncDwarfExpression.hpp"
#include "PLCrashAsyncDwarfPrimitives.hpp"
#include "PLCrashFeatureConfig.h"
#if PLCRASH_FEATURE_UNWIND_DWARF
using namespace plcrash::async;
PLCR_CPP_BEGIN_NS
namespace async {
/**
* @internal
* @ingroup plcrash_async_dwarf
* @{
*/
/**
* Evaluate a DWARF expression, as defined in the DWARF 4 Specification, Section 2.5. This
* internal implementation is templated to support 32-bit and 64-bit evaluation.
*
* @param mobj The memory object from which the expression opcodes will be read.
* @param task The task from which any DWARF expression memory loads will be performed.
* @param thread_state The thread state against which the expression will be evaluated.
* @param byteorder The byte order of the data referenced by @a mobj and @a thread_state.
* @param address The task-relative address within @a mobj at which the opcodes will be fetched.
* @param offset An offset to be applied to @a address.
* @param length The total length of the opcodes readable at @a address + @a offset.
* @param initial_state Initial set of values to be pushed onto the evaluation stack. The values will be pushed
* on their natural order; eg, the top of the stack will be the last value in this array. If the initial stack
* state should be empty, this value may be NULL, and @a initial_count should be 0.
* @param initial_count Number of values in the @a initial_state array.
* @param[out] result On success, the evaluation result. As per DWARF 3 section 2.5.1, this will be
* the top-most element on the evaluation stack. If the stack is empty, an error will be returned
* and no value will be written to this parameter.
*
* @return Returns PLCRASH_ESUCCESS on success, or an appropriate plcrash_error_t values
* on failure. If an invalid opcode is detected, PLCRASH_ENOTSUP will be returned. If the stack
* is empty upon termination of evaluation, PLCRASH_EINVAL will be returned.
*
* @todo Consider defining updated status codes or error handling to provide more structured
* error data on failure.
*/
template <typename machine_ptr, typename machine_ptr_s>
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(÷nd);
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(÷nd);
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;
}
/* Provide explicit 32/64-bit instantiations */
template plcrash_error_t plcrash_async_dwarf_expression_eval<uint32_t, int32_t> (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,
uint32_t initial_state[],
size_t initial_count,
uint32_t *result);
template plcrash_error_t plcrash_async_dwarf_expression_eval<uint64_t, int64_t> (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,
uint64_t initial_state[],
size_t initial_count,
uint64_t *result);
/*
* @}
*/
}
PLCR_CPP_END_NS
#endif /* PLCRASH_FEATURE_UNWIND_DWARF */