summaryrefslogtreecommitdiff
path: root/qwadb.cpp
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-11-02 18:21:49 +0100
committerXavier Del Campo Romero <xavi92@disroot.org>2025-11-12 00:47:10 +0100
commitb16e2f67e7d392890c6835f98ca9b2a7bb44fe2e (patch)
treecf07afb610395dd182e1f243ffccf2a55a13effe /qwadb.cpp
First commitHEADmaster
Diffstat (limited to 'qwadb.cpp')
-rw-r--r--qwadb.cpp761
1 files changed, 761 insertions, 0 deletions
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 <QBrush>
+#include <QColor>
+#include <QCommandLineParser>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QTcpSocket>
+#include <cctype>
+#include <cstdint>
+
+#define invoke(o, p) ((*o).*(p))
+
+int QWadb::trap(const QList<QByteArray> &params)
+{
+ 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<WasmRoutine>();
+ 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<QByteArray> &params) const
+{
+ const auto s = params.at(0);
+ const auto g = lastreq.v.value<WasmGlobal>();
+ 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<QByteArray> &params) const
+{
+ const auto s = params.at(0);
+ const auto p = lastreq.v.value<WasmParam>();
+ /* 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<QByteArray> &params) const
+{
+ const auto s = params.at(0);
+ const auto p = lastreq.v.value<WasmLocal>();
+ 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<QByteArray> &params) const
+{
+ const auto s = params.at(0);
+ const auto type = lastreq.v.value<QWadbLinear>().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<uintmax_t>(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<QByteArray> &params)
+{
+ 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<QByteArray> &);
+
+ 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<WasmRoutine>().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<QWadbLinear>().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;
+ });
+}