gctf2023/pwn/flipper/dist/utils/add-debug/dwarf/expr.cc
2023-11-24 13:11:34 -05:00

419 lines
17 KiB
C++

#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<taddr> &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<taddr, 8> 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<section> subsec
(make_shared<section>(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<ubyte>();
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<uint8_t>());
break;
case DW_OP::const2u:
stack.push_back(cur.fixed<uint16_t>());
break;
case DW_OP::const4u:
stack.push_back(cur.fixed<uint32_t>());
break;
case DW_OP::const8u:
stack.push_back(cur.fixed<uint64_t>());
break;
case DW_OP::const1s:
stack.push_back(cur.fixed<int8_t>());
break;
case DW_OP::const2s:
stack.push_back(cur.fixed<int16_t>());
break;
case DW_OP::const4s:
stack.push_back(cur.fixed<int32_t>());
break;
case DW_OP::const8s:
stack.push_back(cur.fixed<int64_t>());
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<uint8_t>();
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<uint8_t>();
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<uint8_t>();
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<int16_t>();
goto skip_common;
case DW_OP::bra:
tmp1.s = cur.fixed<int16_t>();
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