#include "internal.hh" using namespace std; DWARFPP_BEGIN_NAMESPACE expr_context no_expr_context; expr::expr(const unit *cu, section_offset offset, section_length len) : cu(cu), offset(offset), len(len) { } expr_result expr::evaluate(expr_context *ctx) const { return evaluate(ctx, {}); } expr_result expr::evaluate(expr_context *ctx, taddr argument) const { return evaluate(ctx, {argument}); } expr_result expr::evaluate(expr_context *ctx, const std::initializer_list &arguments) const { // The stack machine's stack. The top of the stack is // stack.back(). // XXX This stack must be in target machine representation, // since I see both (DW_OP_breg0 (eax): -28; DW_OP_stack_value) // and (DW_OP_lit1; DW_OP_stack_value). small_vector stack; // Create the initial stack. arguments are in reverse order // (that is, element 0 is TOS), so reverse it. stack.reserve(arguments.size()); for (const taddr *elt = arguments.end() - 1; elt >= arguments.begin(); elt--) stack.push_back(*elt); // Create a subsection for just this expression so we can // easily detect the end (including premature end). auto cusec = cu->data(); shared_ptr
subsec (make_shared
(cusec->type, cusec->begin + offset, len, cusec->fmt, cusec->addr_size)); cursor cur(subsec); // Prepare the expression result. Some location descriptions // create the result directly, rather than using the top of // stack. expr_result result; // 2.6.1.1.4 Empty location descriptions if (cur.end()) { result.location_type = expr_result::type::empty; result.value = 0; return result; } // Assume the result is an address for now and should be // grabbed from the top of stack at the end. result.location_type = expr_result::type::address; // Execute! while (!cur.end()) { #define CHECK() do { if (stack.empty()) goto underflow; } while (0) #define CHECKN(n) do { if (stack.size() < n) goto underflow; } while (0) union { uint64_t u; int64_t s; } tmp1, tmp2, tmp3; static_assert(sizeof(tmp1) == sizeof(taddr), "taddr is not 64 bits"); // Tell GCC to warn us about missing switch cases, // even though we have a default case. #pragma GCC diagnostic push #pragma GCC diagnostic warning "-Wswitch-enum" DW_OP op = (DW_OP)cur.fixed(); switch (op) { // 2.5.1.1 Literal encodings case DW_OP::lit0...DW_OP::lit31: stack.push_back((unsigned)op - (unsigned)DW_OP::lit0); break; case DW_OP::addr: stack.push_back(cur.address()); break; case DW_OP::const1u: stack.push_back(cur.fixed()); break; case DW_OP::const2u: stack.push_back(cur.fixed()); break; case DW_OP::const4u: stack.push_back(cur.fixed()); break; case DW_OP::const8u: stack.push_back(cur.fixed()); break; case DW_OP::const1s: stack.push_back(cur.fixed()); break; case DW_OP::const2s: stack.push_back(cur.fixed()); break; case DW_OP::const4s: stack.push_back(cur.fixed()); break; case DW_OP::const8s: stack.push_back(cur.fixed()); break; case DW_OP::constu: stack.push_back(cur.uleb128()); break; case DW_OP::consts: stack.push_back(cur.sleb128()); break; // 2.5.1.2 Register based addressing case DW_OP::fbreg: // XXX throw runtime_error("DW_OP_fbreg not implemented"); case DW_OP::breg0...DW_OP::breg31: tmp1.u = (unsigned)op - (unsigned)DW_OP::breg0; tmp2.s = cur.sleb128(); stack.push_back((int64_t)ctx->reg(tmp1.u) + tmp2.s); break; case DW_OP::bregx: tmp1.u = cur.uleb128(); tmp2.s = cur.sleb128(); stack.push_back((int64_t)ctx->reg(tmp1.u) + tmp2.s); break; // 2.5.1.3 Stack operations case DW_OP::dup: CHECK(); stack.push_back(stack.back()); break; case DW_OP::drop: CHECK(); stack.pop_back(); break; case DW_OP::pick: tmp1.u = cur.fixed(); CHECKN(tmp1.u); stack.push_back(stack.revat(tmp1.u)); break; case DW_OP::over: CHECKN(2); stack.push_back(stack.revat(1)); break; case DW_OP::swap: CHECKN(2); tmp1.u = stack.back(); stack.back() = stack.revat(1); stack.revat(1) = tmp1.u; break; case DW_OP::rot: CHECKN(3); tmp1.u = stack.back(); stack.back() = stack.revat(1); stack.revat(1) = stack.revat(2); stack.revat(2) = tmp1.u; break; case DW_OP::deref: tmp1.u = subsec->addr_size; goto deref_common; case DW_OP::deref_size: tmp1.u = cur.fixed(); if (tmp1.u > subsec->addr_size) throw expr_error("DW_OP_deref_size operand exceeds address size"); deref_common: CHECK(); stack.back() = ctx->deref_size(stack.back(), tmp1.u); break; case DW_OP::xderef: tmp1.u = subsec->addr_size; goto xderef_common; case DW_OP::xderef_size: tmp1.u = cur.fixed(); if (tmp1.u > subsec->addr_size) throw expr_error("DW_OP_xderef_size operand exceeds address size"); xderef_common: CHECKN(2); tmp2.u = stack.back(); stack.pop_back(); stack.back() = ctx->xderef_size(tmp2.u, stack.back(), tmp1.u); break; case DW_OP::push_object_address: // XXX throw runtime_error("DW_OP_push_object_address not implemented"); case DW_OP::form_tls_address: CHECK(); stack.back() = ctx->form_tls_address(stack.back()); break; case DW_OP::call_frame_cfa: // XXX throw runtime_error("DW_OP_call_frame_cfa not implemented"); // 2.5.1.4 Arithmetic and logical operations #define UBINOP(binop) \ do { \ CHECKN(2); \ tmp1.u = stack.back(); \ stack.pop_back(); \ tmp2.u = stack.back(); \ stack.back() = tmp2.u binop tmp1.u; \ } while (0) case DW_OP::abs: CHECK(); tmp1.u = stack.back(); if (tmp1.s < 0) tmp1.s = -tmp1.s; stack.back() = tmp1.u; break; case DW_OP::and_: UBINOP(&); break; case DW_OP::div: CHECKN(2); tmp1.u = stack.back(); stack.pop_back(); tmp2.u = stack.back(); tmp3.s = tmp1.s / tmp2.s; stack.back() = tmp3.u; break; case DW_OP::minus: UBINOP(-); break; case DW_OP::mod: UBINOP(%); break; case DW_OP::mul: UBINOP(*); break; case DW_OP::neg: CHECK(); tmp1.u = stack.back(); tmp1.s = -tmp1.s; stack.back() = tmp1.u; break; case DW_OP::not_: CHECK(); stack.back() = ~stack.back(); break; case DW_OP::or_: UBINOP(|); break; case DW_OP::plus: UBINOP(+); break; case DW_OP::plus_uconst: tmp1.u = cur.uleb128(); CHECK(); stack.back() += tmp1.u; break; case DW_OP::shl: CHECKN(2); tmp1.u = stack.back(); stack.pop_back(); tmp2.u = stack.back(); // C++ does not define what happens if you // shift by more bits than the width of the // type, so we handle this case specially if (tmp1.u < sizeof(tmp2.u)*8) stack.back() = tmp2.u << tmp1.u; else stack.back() = 0; break; case DW_OP::shr: CHECKN(2); tmp1.u = stack.back(); stack.pop_back(); tmp2.u = stack.back(); // Same as above if (tmp1.u < sizeof(tmp2.u)*8) stack.back() = tmp2.u >> tmp1.u; else stack.back() = 0; break; case DW_OP::shra: CHECKN(2); tmp1.u = stack.back(); stack.pop_back(); tmp2.u = stack.back(); // Shifting a negative number is // implementation-defined in C++. tmp3.u = (tmp2.s < 0); if (tmp3.u) tmp2.s = -tmp2.s; if (tmp1.u < sizeof(tmp2.u)*8) tmp2.u >>= tmp1.u; else tmp2.u = 0; // DWARF implies that over-shifting a negative // number should result in 0, not ~0. if (tmp3.u) tmp2.s = -tmp2.s; stack.back() = tmp2.u; break; case DW_OP::xor_: UBINOP(^); break; #undef UBINOP // 2.5.1.5 Control flow operations #define SRELOP(relop) \ do { \ CHECKN(2); \ tmp1.u = stack.back(); \ stack.pop_back(); \ tmp2.u = stack.back(); \ stack.back() = (tmp2.s <= tmp1.s) ? 1 : 0; \ } while (0) case DW_OP::le: SRELOP(<=); break; case DW_OP::ge: SRELOP(>=); break; case DW_OP::eq: SRELOP(==); break; case DW_OP::lt: SRELOP(<); break; case DW_OP::gt: SRELOP(>); break; case DW_OP::ne: SRELOP(!=); break; case DW_OP::skip: tmp1.s = cur.fixed(); goto skip_common; case DW_OP::bra: tmp1.s = cur.fixed(); CHECK(); tmp2.u = stack.back(); stack.pop_back(); if (tmp2.u == 0) break; skip_common: cur = cursor(subsec, (int64_t)cur.get_section_offset() + tmp1.s); break; case DW_OP::call2: case DW_OP::call4: case DW_OP::call_ref: // XXX throw runtime_error(to_string(op) + " not implemented"); #undef SRELOP // 2.5.1.6 Special operations case DW_OP::nop: break; // 2.6.1.1.2 Register location descriptions case DW_OP::reg0...DW_OP::reg31: result.location_type = expr_result::type::reg; result.value = (unsigned)op - (unsigned)DW_OP::reg0; break; case DW_OP::regx: result.location_type = expr_result::type::reg; result.value = cur.uleb128(); break; // 2.6.1.1.3 Implicit location descriptions case DW_OP::implicit_value: result.location_type = expr_result::type::implicit; result.implicit_len = cur.uleb128(); cur.ensure(result.implicit_len); result.implicit = cur.pos; break; case DW_OP::stack_value: CHECK(); result.location_type = expr_result::type::literal; result.value = stack.back(); break; // 2.6.1.2 Composite location descriptions case DW_OP::piece: case DW_OP::bit_piece: // XXX throw runtime_error(to_string(op) + " not implemented"); case DW_OP::lo_user...DW_OP::hi_user: // XXX We could let the context evaluate this, // but it would need access to the cursor. throw expr_error("unknown user op " + to_string(op)); default: throw expr_error("bad operation " + to_string(op)); } #pragma GCC diagnostic pop #undef CHECK #undef CHECKN } if (result.location_type == expr_result::type::address) { // The result type is still and address, so we should // fetch it from the top of stack. if (stack.empty()) throw expr_error("final stack is empty; no result given"); result.value = stack.back(); } return result; underflow: throw expr_error("stack underflow evaluating DWARF expression"); } DWARFPP_END_NAMESPACE