blob: a01bce150592fa4374675188e304ba3a8c4a4b2b [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "GdbExpression.h"
#include "GdbServer.h"
#include "Task.h"
#include "core.h"
using namespace std;
namespace rr {
#define WORKAROUND_GDB_BUGS
// Extracted from
// https://sourceware.org/gdb/current/onlinedocs/gdb/Bytecode-Descriptions.html
enum Opcode {
OP_float = 0x01,
OP_add = 0x02,
OP_sub = 0x03,
OP_mul = 0x04,
OP_div_signed = 0x05,
OP_div_unsigned = 0x06,
OP_rem_signed = 0x07,
OP_rem_unsigned = 0x08,
OP_lsh = 0x09,
OP_rsh_signed = 0x0a,
OP_rsh_unsigned = 0x0b,
OP_trace = 0x0c,
OP_trace_quick = 0x0d,
OP_log_not = 0x0e,
OP_bit_and = 0x0f,
OP_bit_or = 0x10,
OP_bit_xor = 0x11,
OP_bit_not = 0x12,
OP_equal = 0x13,
OP_less_signed = 0x14,
OP_less_unsigned = 0x15,
OP_ext = 0x16,
OP_ref8 = 0x17,
OP_ref16 = 0x18,
OP_ref32 = 0x19,
OP_ref64 = 0x1a,
OP_ref_float = 0x1b,
OP_ref_double = 0x1c,
OP_ref_long_double = 0x1d,
OP_l_to_d = 0x1e,
OP_d_to_l = 0x1f,
OP_if_goto = 0x20,
OP_goto = 0x21,
OP_const8 = 0x22,
OP_const16 = 0x23,
OP_const32 = 0x24,
OP_const64 = 0x25,
OP_reg = 0x26,
OP_end = 0x27,
OP_dup = 0x28,
OP_pop = 0x29,
OP_zero_ext = 0x2a,
OP_swap = 0x2b,
OP_getv = 0x2c,
OP_setv = 0x2d,
OP_tracev = 0x2e,
OP_tracenz = 0x2f,
OP_trace16 = 0x30,
OP_pick = 0x32,
OP_rot = 0x33,
OP_printf = 0x34,
};
struct ExpressionState {
typedef GdbExpression::Value Value;
ExpressionState(const vector<uint8_t>& bytecode)
: bytecode(bytecode), pc(0), error(false), end(false) {}
void set_error() { error = true; }
// Methods set error to true if there's an error and return some sentinel
// Value.
Value pop() {
if (stack.empty()) {
set_error();
return Value(-1);
}
Value v = stack.back();
stack.pop_back();
return v;
}
struct BinaryOperands {
BinaryOperands(int64_t a = 0, int64_t b = 0) : a(a), b(b) {}
int64_t a;
int64_t b;
};
BinaryOperands pop_a_b() {
int64_t b = pop().i;
return BinaryOperands(pop().i, b);
}
int64_t nonzero(int64_t v) {
if (!v) {
set_error();
return 1;
}
return v;
}
int64_t pop_a() { return pop().i; }
void push(int64_t i) { stack.push_back(Value(i)); }
template <typename T> T fetch() {
if (pc + sizeof(T) > bytecode.size()) {
set_error();
return T(-1);
}
T v = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
v = (v << 8) | bytecode[pc + i];
}
pc += sizeof(T);
return v;
}
template <typename T> void load(Task* t) {
uint64_t addr = pop().i;
if (error) {
// Don't do unnecessary syscalls if we're already in an error state.
return;
}
bool ok = true;
T v = t->read_mem(remote_ptr<T>(addr), &ok);
if (!ok) {
set_error();
return;
}
push(v);
}
void pick(size_t offset) {
if (offset >= stack.size()) {
set_error();
return;
}
push(stack[stack.size() - 1 - offset].i);
}
void step(Task* t) {
DEBUG_ASSERT(!error);
BinaryOperands operands;
switch (fetch<uint8_t>()) {
case OP_add:
operands = pop_a_b();
return push(operands.a + operands.b);
case OP_sub:
operands = pop_a_b();
return push(operands.a - operands.b);
case OP_mul:
operands = pop_a_b();
return push(operands.a * operands.b);
case OP_div_signed:
operands = pop_a_b();
return push(operands.a / nonzero(operands.b));
case OP_div_unsigned:
operands = pop_a_b();
return push(uint64_t(operands.a) / uint64_t(nonzero(operands.b)));
case OP_rem_signed:
operands = pop_a_b();
return push(operands.a % nonzero(operands.b));
case OP_rem_unsigned:
operands = pop_a_b();
return push(uint64_t(operands.a) % uint64_t(nonzero(operands.b)));
case OP_lsh:
operands = pop_a_b();
return push(operands.a << operands.b);
case OP_rsh_signed:
operands = pop_a_b();
return push(operands.a >> operands.b);
case OP_rsh_unsigned:
operands = pop_a_b();
return push(uint64_t(operands.a) >> operands.b);
case OP_log_not:
return push(!pop_a());
case OP_bit_and:
operands = pop_a_b();
return push(operands.a & operands.b);
case OP_bit_or:
operands = pop_a_b();
return push(operands.a | operands.b);
case OP_bit_xor:
operands = pop_a_b();
return push(operands.a ^ operands.b);
case OP_bit_not:
return push(~pop_a());
case OP_equal:
operands = pop_a_b();
return push(operands.a == operands.b);
case OP_less_signed:
operands = pop_a_b();
return push(operands.a < operands.b);
case OP_less_unsigned:
operands = pop_a_b();
return push(uint64_t(operands.a) < uint64_t(operands.b));
case OP_ext: {
int64_t n = nonzero(fetch<uint8_t>());
if (n >= 64) {
return;
}
int64_t a = pop_a();
int64_t n_mask = (int64_t(1) << n) - 1;
int sign_bit = (a >> (n - 1)) & 1;
return push((sign_bit * ~n_mask) | (a & n_mask));
}
case OP_zero_ext: {
int64_t n = fetch<uint8_t>();
if (n >= 64) {
return;
}
int64_t a = pop_a();
int64_t n_mask = (int64_t(1) << n) - 1;
return push(a & n_mask);
}
case OP_ref8:
return load<uint8_t>(t);
case OP_ref16:
return load<uint16_t>(t);
case OP_ref32:
return load<uint32_t>(t);
case OP_ref64:
return load<uint64_t>(t);
case OP_dup:
return pick(0);
case OP_swap:
operands = pop_a_b();
push(operands.b);
return push(operands.a);
case OP_pop:
pop_a();
return;
case OP_pick:
return pick(fetch<uint8_t>());
case OP_rot: {
int64_t c = pop_a();
int64_t b = pop_a();
int64_t a = pop_a();
push(c);
push(b);
return push(a);
}
case OP_if_goto: {
uint16_t offset = fetch<uint16_t>();
if (pop_a()) {
pc = offset;
}
return;
}
case OP_goto:
pc = fetch<uint16_t>();
return;
case OP_const8:
return push(fetch<uint8_t>());
case OP_const16:
return push(fetch<uint16_t>());
case OP_const32:
return push(fetch<uint32_t>());
case OP_const64:
return push(fetch<uint64_t>());
case OP_reg: {
GdbRegisterValue v = GdbServer::get_reg(t->regs(), t->extra_regs(),
GdbRegister(fetch<uint16_t>()));
if (!v.defined) {
set_error();
return;
}
switch (v.size) {
case 1:
return push(v.value1);
case 2:
return push(v.value2);
case 4:
return push(v.value4);
case 8:
return push(v.value8);
}
set_error();
return;
}
case OP_end:
end = true;
return;
default:
set_error();
return;
}
}
const vector<uint8_t>& bytecode;
vector<Value> stack;
size_t pc;
bool error;
bool end;
};
#ifdef WORKAROUND_GDB_BUGS
/* https://sourceware.org/bugzilla/show_bug.cgi?id=18617 means that
* gdb generates incorrect operands for OP_ext and OP_zero_ext.
* We work around this bug by generating all the alternative programs that gdb
* maybe should have generated, and evaluating all of them. If they agree on
* the result, we return that as the correct result, otherwise we return
* failure.
*/
static int count_variants(int bits) {
int result = 1;
if (bits > 8) {
++result;
}
if (bits > 16) {
++result;
}
if (bits > 32) {
++result;
}
return result;
}
template <typename T>
static T fetch(const uint8_t* data, size_t size, size_t pc) {
if (pc + sizeof(T) > size) {
return T(-1);
}
T v = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
v = (v << 8) | data[pc + i];
}
return v;
}
GdbExpression::GdbExpression(const uint8_t* data, size_t size) {
vector<bool> instruction_starts;
instruction_starts.resize(size);
fill(instruction_starts.begin(), instruction_starts.end(), false);
int64_t num_variants = 1;
vector<size_t> unvisited;
unvisited.push_back(0);
while (!unvisited.empty()) {
size_t pc = unvisited.back();
unvisited.pop_back();
if (pc >= instruction_starts.size() || instruction_starts[pc]) {
continue;
}
instruction_starts[pc] = true;
switch (data[pc]) {
case OP_ext:
case OP_zero_ext:
if (pc + 1 < size) {
num_variants *= count_variants(data[pc + 1]);
if (num_variants > 64) {
// Too many variants, giving up on this expression
return;
}
}
unvisited.push_back(pc + 2);
break;
case OP_pick:
case OP_const8:
unvisited.push_back(pc + 2);
break;
case OP_if_goto:
unvisited.push_back(fetch<uint16_t>(data, size, pc + 1));
unvisited.push_back(pc + 3);
break;
case OP_goto:
unvisited.push_back(fetch<uint16_t>(data, size, pc + 1));
break;
case OP_const16:
case OP_reg:
unvisited.push_back(pc + 3);
break;
case OP_const32:
unvisited.push_back(pc + 5);
break;
case OP_const64:
unvisited.push_back(pc + 9);
break;
case OP_end:
break;
default:
unvisited.push_back(pc + 1);
break;
}
}
bytecode_variants.push_back(vector<uint8_t>(data, data + size));
for (size_t i = 0; i < size; ++i) {
if (!instruction_starts[i]) {
continue;
}
if ((data[i] == OP_ext || data[i] == OP_zero_ext) && i + 1 < size) {
uint8_t bits = data[i + 1];
vector<vector<uint8_t>> variants;
for (auto& b : bytecode_variants) {
// gdb perhaps should have used a smaller type width here --- 8, 16 or
// 32 bits.
if (bits > 8) {
vector<uint8_t> v = b;
v[i + 1] = 8;
variants.push_back(std::move(v));
}
if (bits > 16) {
vector<uint8_t> v = b;
v[i + 1] = 16;
variants.push_back(std::move(v));
}
if (bits > 32) {
vector<uint8_t> v = b;
v[i + 1] = 32;
variants.push_back(std::move(v));
}
variants.push_back(std::move(b));
}
bytecode_variants = std::move(variants);
}
}
}
#else
GdbExpression::GdbExpression(const uint8_t* data, size_t size) {
bytecode_variants.push_back(vector<uint8_t>(data, data + size));
}
#endif
bool GdbExpression::evaluate(Task* t, Value* result) const {
if (bytecode_variants.empty()) {
return false;
}
bool first = true;
for (auto& b : bytecode_variants) {
ExpressionState state(b);
for (int steps = 0; !state.end; ++steps) {
if (steps >= 10000 || state.error) {
return false;
}
state.step(t);
}
Value v = state.pop();
if (state.error) {
return false;
}
if (first) {
*result = v;
first = false;
} else if (*result != v) {
return false;
}
}
return true;
}
} // namespace rr