From b16e2f67e7d392890c6835f98ca9b2a7bb44fe2e Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Sun, 2 Nov 2025 18:21:49 +0100 Subject: First commit --- qwadb.cpp | 761 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 761 insertions(+) create mode 100644 qwadb.cpp (limited to 'qwadb.cpp') diff --git a/qwadb.cpp b/qwadb.cpp new file mode 100644 index 0000000..db3e2ec --- /dev/null +++ b/qwadb.cpp @@ -0,0 +1,761 @@ +#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; + }); +} -- cgit v1.2.3