#include "qwadb.h" #include "ui_qwadb.h" #include "wasm.h" #include #include #include #include #include #include #include #include #include #define invoke(o, p) ((*o).*(p)) int QWadb::trap(const QList ¶ms) { bool ok; const auto addr = params.at(0).toUInt(&ok, 0); if (!ok) return -1; const auto &globals = w.globals(); ui.pause->setEnabled(false); ui.globals->setRowCount(globals.size()); for (int i = 0; i < globals.size(); i++) { request req; req.type = request::type::global; req.index = req.rindex = i; req.v = QVariant::fromValue(globals.at(i)); push(req); } const auto pr = w.routine(addr); if (pr.isValid()) { const auto r = pr.value(); const auto &p = r.params; const auto &l = r.locals; ui.code->clear(); ui.code->setRowCount(r.instructions.size()); for (int i = 0; i < r.instructions.size(); i++) { const auto &in = r.instructions.at(i); const auto saddr = QString::number(in.address, 16); const auto addr_it = new QTableWidgetItem(saddr), instr = new QTableWidgetItem(in.instr); if (in.address == addr) { const auto brush = QBrush(QColor(255, 0, 0)); addr_it->setForeground(brush); instr->setForeground(brush); } ui.code->setItem(i, 0, addr_it); ui.code->setItem(i, 1, instr); } for (int i = 0; i < p.size(); i++) { request req; req.type = request::type::param; req.index = req.rindex = i; req.v = QVariant::fromValue(p.at(i)); push(req); } for (int i = 0; i < l.size(); i++) { request req; req.type = request::type::local; req.index = i; req.rindex = i + p.size(); req.v = QVariant::fromValue(l.at(i)); push(req); } ui.locals->setRowCount(l.size()); ui.params->setRowCount(p.size()); ui.routine->setText(r.name); } else { ui.params->clear(); ui.routine->setText("??"); } { QStringList headers; headers << tr("Address"); headers << tr("Instruction"); ui.code->setHorizontalHeaderLabels(headers); } { QStringList headers; headers << tr("Name"); headers << tr("Type"); headers << tr("Value"); ui.params->setHorizontalHeaderLabels(headers); } { QStringList headers; headers << tr("Index"); headers << tr("Name"); headers << tr("Type"); headers << tr("Value"); ui.params->setHorizontalHeaderLabels(headers); } { QStringList headers; headers << tr("Index"); headers << tr("Type"); headers << tr("Value"); ui.locals->setHorizontalHeaderLabels(headers); } ui.pc->setText("0x" + QString::number(addr, 16)); emit sendreq(); return 0; } int QWadb::parse_global(const QList ¶ms) const { const auto s = params.at(0); const auto g = lastreq.v.value(); const auto v = parse_value(s, g.type); if (v.isNull()) return -1; const auto uip = ui.globals; QString stype = to_string(g.type); if (!g.mutability) stype = "const " + stype; const auto index = new QTableWidgetItem(QString::number(lastreq.rindex)), type = new QTableWidgetItem(stype), value = new QTableWidgetItem(QString(s)); uip->setItem(lastreq.index, 0, index); uip->setItem(lastreq.index, 1, type); uip->setItem(lastreq.index, 2, value); return 0; } int QWadb::parse_param(const QList ¶ms) const { const auto s = params.at(0); const auto p = lastreq.v.value(); /* FIXME: WasmParam should have a valid type. */ const auto v = parse_value(s, WasmType::i64/* p.type */); if (v.isNull()) return -1; const auto uip = ui.params; const auto index = new QTableWidgetItem(QString::number(lastreq.rindex)), name = new QTableWidgetItem(p.name), type = new QTableWidgetItem(p.dwtype), value = new QTableWidgetItem(QString(s)); uip->setItem(lastreq.index, 0, index); uip->setItem(lastreq.index, 1, name); uip->setItem(lastreq.index, 2, type); uip->setItem(lastreq.index, 3, value); return 0; } const char *QWadb::to_fmt(const WasmType type) const { switch (type) { case WasmType::i32: return "i"; case WasmType::i64: return "I"; case WasmType::f32: return "f"; case WasmType::f64: return "F"; } return nullptr; } const char *QWadb::to_string(const WasmType type) const { switch (type) { case WasmType::i32: return "i32"; case WasmType::i64: return "i64"; case WasmType::f32: return "f32"; case WasmType::f64: return "f64"; } return nullptr; } int QWadb::from_string(const QString &s, WasmType &type) const { int ret = 0; if (s == "i32") type = WasmType::i32; else if (s == "i64") type = WasmType::i64; else if (s == "f32") type = WasmType::f32; else if (s == "f64") type = WasmType::f64; else ret = -1; return ret; } QVariant QWadb::parse_value(const QByteArray &v, const WasmType type) const { bool ok; QVariant ret; switch (type) { case WasmType::i32: ret = QVariant::fromValue(v.toUInt(&ok, 0)); break; case WasmType::i64: ret = QVariant::fromValue(v.toULongLong(&ok, 0)); break; case WasmType::f32: case WasmType::f64: ret = QVariant::fromValue(v.toFloat(&ok)); break; } return ok ? ret : QVariant(); } int QWadb::parse_local(const QList ¶ms) const { const auto s = params.at(0); const auto p = lastreq.v.value(); const auto v = parse_value(s, p.type); if (v.isNull()) return -1; const auto stype = to_string(p.type); const auto uil = ui.locals; const auto index = new QTableWidgetItem(QString::number(lastreq.rindex)), type = new QTableWidgetItem(stype), value = new QTableWidgetItem(QString(s)); uil->setItem(lastreq.index, 0, index); uil->setItem(lastreq.index, 1, type); uil->setItem(lastreq.index, 2, value); return 0; } int QWadb::parse_linear(const QList ¶ms) const { const auto s = params.at(0); const auto type = lastreq.v.value().type; const auto v = parse_value(s, type); struct item { QTableWidgetItem *addr, *value; }; if (v.isNull()) return -1; ui.linear_mem->clear(); ui.linear_value->setText(s); size_t n; switch (type) { case WasmType::i32: n = sizeof (int32_t); break; case WasmType::i64: n = sizeof (int64_t); break; case WasmType::f32: n = sizeof (float); break; case WasmType::f64: n = sizeof (double); break; } ui.linear_mem->setRowCount(n); for (size_t i = 0; i < n; i++) { auto it = new item; quint8 byte; switch (type) { case WasmType::i32: byte = v.toUInt() >> (i * 8); break; case WasmType::i64: byte = v.toULongLong() >> (i * 8); break; case WasmType::f32: case WasmType::f64: /* HACK */ byte = static_cast(v.toFloat()) >> (i * 8); break; } auto sbyte = QString::number(byte, 16); if (isprint(byte)) { char c[sizeof "a"]; snprintf(c, sizeof c, "%c", byte); sbyte += " '" + QString(c) + "'"; } it->addr = new QTableWidgetItem(QString::number(lastreq.index + i, 16)); it->value = new QTableWidgetItem(sbyte); ui.linear_mem->setItem(i, 0, it->addr); ui.linear_mem->setItem(i, 1, it->value); } return 0; } int QWadb::ok(const QList ¶ms) { int ret = 0; switch (lastreq.type) { case request::type::global: ret = parse_global(params); break; case request::type::param: ret = parse_param(params); break; case request::type::local: ret = parse_local(params); break; case request::type::linear: ret = parse_linear(params); break; case request::type::bkpt: break; } emit sendreq(); return ret; } int QWadb::parse(const QByteArray &frame) { typedef int (QWadb::*method)(const QList &); static const struct { const char *s; method fn; } methods[] = { {"trap", &QWadb::trap}, {"ok", &QWadb::ok} }; const auto tokens = frame.split(':'); for (const auto &m : methods) if (m.s == tokens.at(0)) return invoke(this, m.fn)(tokens.mid(1)); return -1; } int QWadb::push(const request &r) { reqs.enqueue(r); return 0; } int QWadb::pop(request &r) { if (reqs.isEmpty()) return -1; r = reqs.dequeue(); return 0; } int QWadb::process(const QByteArray &frame) { if (frame.contains(':')) return parse(frame); return 0; } int QWadb::getlf(const char b) { const int maxlen = 128; if (b == '\n') { const auto ret = process(frame); frame.clear(); step = &QWadb::getsync; return ret; } else if (frame.length() >= maxlen) return -1; frame.append(b); return 0; } int QWadb::getsync(const char b) { if (b == ';') step = &QWadb::getlf; return 0; } int QWadb::write(const QByteArray &ba) { auto rem = ba.size(); const char *p = ba.constData(); while (rem) { auto n = socket.write(p, rem); if (n < 0) return -1; rem -= n; p += n; } return 0; } void QWadb::disconnected(void) { connected = false; ui.run->setEnabled(false); ui.step->setEnabled(false); ui.pause->setEnabled(false); ui.linear_read->setEnabled(false); ui.connect->setEnabled(true); ui.connect->setText(tr("Connect")); } void QWadb::open(const QString &path) { Wasm w; QString error; if (w.open(path, error)) { QMessageBox::critical(this, tr("QWadb"), error); ui.modpath->clear(); return; } ui.modpath->setText(path); this->w = w; } QWadb::QWadb(const QCoreApplication &app, QWidget *parent) : QMainWindow(parent), connected(false), socket(this), step(&QWadb::getsync) { QCommandLineParser parser; QStringList args; ui.setupUi(this); parser.addHelpOption(); parser.addPositionalArgument("file", "WebAssembly file to debug"); parser.process(app); args = parser.positionalArguments(); if (args.size()) open(args.at(0)); connect(&socket, &QTcpSocket::connected, this, [this] { connected = true; ui.connect->setEnabled(true); ui.connect->setText(tr("Disconnect")); }); connect(&socket, &QTcpSocket::disconnected, this, &QWadb::disconnected); connect(&socket, &QTcpSocket::errorOccurred, this, [this] { socket.disconnect(); ui.run->setEnabled(false); ui.step->setEnabled(false); ui.pause->setEnabled(false); ui.linear_read->setEnabled(false); emit disconnected(); QMessageBox::critical(this, tr("QWadb"), socket.errorString()); }); connect(&socket, &QTcpSocket::readyRead, this, [this] { auto ba = socket.readAll(); for (auto b : ba) if (invoke(this, step)(b)) { frame.clear(); step = &QWadb::getsync; } }); connect(ui.open, &QPushButton::released, this, [this] { auto f = QFileDialog::getOpenFileName(this, tr("Open Wasm file")); if (f.isEmpty()) return; open(f); }); connect(ui.connect, &QPushButton::released, this, [this] { if (connected) { socket.disconnect(); emit disconnected(); return; } const auto host = ui.host->text(), port = ui.port->text(); if (host.isEmpty() || port.isEmpty()) { QMessageBox::critical(this, tr("QWadb"), tr("Missing hostname or port")); return; } ui.connect->setEnabled(false); ui.connect->setText(tr("Connecting...")); socket.connectToHost(host, port.toUInt()); }); connect(ui.step, &QPushButton::released, this, [this] { ui.run->setEnabled(false); ui.step->setEnabled(false); ui.pause->setEnabled(true); ui.toggle_bkpt->setEnabled(false); ui.linear_read->setEnabled(false); write(";s\n"); }); connect(ui.run, &QPushButton::released, this, [this] { ui.run->setEnabled(false); ui.step->setEnabled(false); ui.pause->setEnabled(true); ui.toggle_bkpt->setEnabled(false); write(";c\n"); }); connect(ui.toggle_bkpt, &QPushButton::released, this, [this] { const auto s = ui.bkpt->text(); QVariant r; request req; req.type = request::type::bkpt; if (s.isEmpty()) { QMessageBox::information(this, tr("QWadb"), tr("Missing function name or address")); return; } else if ((r = w.routine(s)).isValid()) req.index = r.value().lopc; else { bool ok; const auto addr = s.toUInt(&ok, 0); if (!ok) { QMessageBox::warning(this, tr("QWadb"), tr("Invalid function name or address: ") + s); return; } req.index = addr; } ui.bkpt->clear(); push(req); emit sendreq(); }); connect(ui.pause, &QPushButton::released, this, [this] { QByteArray ba; ui.pause->setEnabled(false); ba.push_back(-1); socket.write(ba); }); connect(ui.linear_read, &QPushButton::released, this, [this] { QWadbLinear l; request r; bool ok; const auto saddr = ui.linear_addr->text(), stype = ui.linear_type->currentText(); unsigned long addr = saddr.toULong(&ok, 0); if (!ok || addr > UINT32_MAX) { QMessageBox::warning(this, tr("QWadb"), tr("Invalid address: ") + saddr); return; } else if (from_string(stype, l.type)) { QMessageBox::warning(this, tr("QWadb"), tr("Invalid type: ") + stype); return; } r.type = request::type::linear; r.index = addr; r.v = QVariant::fromValue(l); ui.run->setEnabled(false); ui.step->setEnabled(false); ui.linear_read->setEnabled(false); push(r); emit sendreq(); }); connect(this, &QWadb::sendreq, this, [this] { request r; if (pop(r)) { ui.toggle_bkpt->setEnabled(true); ui.step->setEnabled(true); ui.run->setEnabled(true); ui.linear_read->setEnabled(true); return; } QByteArray ba = ";"; switch (r.type) { case request::type::global: ba.push_back("g:"); break; case request::type::bkpt: ba.push_back("b:"); break; case request::type::param: ba.push_back("p:"); break; case request::type::local: ba.push_back("l:"); break; case request::type::linear: { const auto type = r.v.value().type; ba.push_back("m:" + QByteArray(to_fmt(type)) + ":"); } break; } ba.push_back(QString::number(r.index).toUtf8()); ba.push_back('\n'); write(ba); lastreq = r; }); }