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(®num, &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;
}