Files
zer0condition 00a3adf890 initial commit
2026-08-19 01:39:41 +05:30

1747 lines
69 KiB
C++

#include "pages.h"
#include <windows.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QFormLayout>
#include <QSplitter>
#include <QHeaderView>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
#include <QScrollBar>
#include <QTabWidget>
#include <QFrame>
#include <QGroupBox>
#include <QTextStream>
#include <QFile>
#include <QCoreApplication>
#include <QHash>
#include <QDataStream>
#include <QRegularExpression>
#include <QFileInfo>
#include <QShortcut>
#include <QKeySequence>
static QLabel* section_label(const QString& t)
{
auto* l = new QLabel(t); l->setObjectName("sectionLabel"); return l;
}
static QLabel* h2(const QString& t)
{
auto* l = new QLabel(t); l->setObjectName("h2"); return l;
}
static QFrame* card()
{
auto* f = new QFrame; f->setObjectName("card"); f->setFrameShape(QFrame::NoFrame); return f;
}
static QLabel* pill(const QString& t, const QString& kind = "")
{
auto* l = new QLabel(t);
l->setObjectName("pill");
if (!kind.isEmpty()) l->setProperty("kind", kind);
return l;
}
// ModulesPage
// WorkbenchPage
static const char* CAP_NAMES[] = {
"ALLOC","READ_KMEM","WRITE_KMEM","MSR_R","MSR_W","PHYSMEM","CPUID_TSC","CALLBACKS","HOSTCALL","INTROSPECT"
};
WorkbenchPage::WorkbenchPage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(14);
// load bar - full width, minimal
auto* loadRow = new QHBoxLayout;
loadRow->setSpacing(8);
loadPath = new QLineEdit;
loadPath->setPlaceholderText("path to .wasm (or drag file here / Ctrl+O)");
auto* browseBtn = new QPushButton("Browse");
auto* loadBtn = new QPushButton("Load"); loadBtn->setObjectName("btnPrimary");
loadBudget = new QSpinBox;
loadBudget->setRange(0, 1 << 30);
loadBudget->setToolTip("pool budget in bytes, 0 = 4MB default");
loadBudget->setFixedWidth(120);
loadBudget->setSpecialValueText("default pool");
loadRow->addWidget(loadPath, 1);
loadRow->addWidget(loadBudget);
loadRow->addWidget(browseBtn);
loadRow->addWidget(loadBtn);
root->addLayout(loadRow);
connect(browseBtn, &QPushButton::clicked, this, [this]{
QString p = QFileDialog::getOpenFileName(this, "Select .wasm", QString(), "WebAssembly (*.wasm);;All (*.*)");
if (!p.isEmpty()) loadPath->setText(p);
});
connect(loadBtn, &QPushButton::clicked, this, [this]{
if (loadPath->text().isEmpty()) { emit toast("pick a .wasm first", 1); return; }
quint32 id = 0; QString err;
if (drv->load_module(loadPath->text(), loadBudget->value(), id, err)) {
pending_select = id;
emit toast(QString("loaded module %1").arg(id), 2);
emit moduleLoaded(id);
} else {
emit toast(QString("load failed: %1").arg(err), 1);
}
});
connect(loadPath, &QLineEdit::returnPressed, loadBtn, &QPushButton::click);
// horizontal split: module list left, inspector+invoke right
auto* mainSplit = new QSplitter(Qt::Horizontal);
mainSplit->setChildrenCollapsible(false);
mainSplit->setObjectName("wbSplit");
// LEFT: module list
auto* leftCol = new QWidget;
auto* lcv = new QVBoxLayout(leftCol);
lcv->setContentsMargins(0, 0, 0, 0);
lcv->setSpacing(8);
auto* leftHead = new QHBoxLayout; leftHead->setContentsMargins(2, 0, 2, 0);
auto* leftLbl = new QLabel("Loaded"); leftLbl->setObjectName("h2");
moduleCount = new QLabel("0"); moduleCount->setObjectName("dim");
auto* unloadAll = new QPushButton("Unload all");
unloadAll->setToolTip("Unload every module");
leftHead->addWidget(leftLbl);
leftHead->addWidget(moduleCount);
leftHead->addStretch();
leftHead->addWidget(unloadAll);
lcv->addLayout(leftHead);
moduleList = new QListWidget;
moduleList->setObjectName("nav"); // reuse muted nav style
moduleList->setFocusPolicy(Qt::StrongFocus);
moduleList->setFrameShape(QFrame::NoFrame);
lcv->addWidget(moduleList, 1);
mainSplit->addWidget(leftCol);
connect(unloadAll, &QPushButton::clicked, this, [this]{
drv->unload_all();
clearSelection();
emit toast("unloaded all modules", 2);
});
connect(moduleList, &QListWidget::currentItemChanged, this, [this](QListWidgetItem* cur, QListWidgetItem*){
if (!cur) return;
quint32 id = cur->data(Qt::UserRole).toUInt();
emit moduleSelected(id);
});
// RIGHT: inspector + invoke wrapper (empty state OR infoPanel)
auto* rightCol = new QWidget;
auto* rcv = new QVBoxLayout(rightCol);
rcv->setContentsMargins(0, 0, 0, 0);
rcv->setSpacing(0);
emptyPanel = new QWidget;
auto* el = new QVBoxLayout(emptyPanel);
el->setContentsMargins(0, 60, 0, 0);
auto* emptyLbl = new QLabel("Select a module from the list on the left,\nor load a .wasm above.");
emptyLbl->setAlignment(Qt::AlignCenter);
emptyLbl->setObjectName("emptyState");
el->addWidget(emptyLbl);
rcv->addWidget(emptyPanel);
infoPanel = new QWidget;
auto* ipl = new QVBoxLayout(infoPanel);
ipl->setContentsMargins(0, 0, 0, 0);
ipl->setSpacing(14);
// module header card with unload button
auto* headCard = card();
auto* hl = new QHBoxLayout(headCard);
hl->setContentsMargins(20, 14, 16, 14);
hl->setSpacing(10);
auto* headText = new QVBoxLayout; headText->setSpacing(2);
modTitle = new QLabel; modTitle->setObjectName("moduleTitle");
modMeta = new QLabel; modMeta->setObjectName("mono"); modMeta->setStyleSheet("color:#7b818c;");
headText->addWidget(modTitle);
headText->addWidget(modMeta);
hl->addLayout(headText, 1);
auto* unloadOne = new QPushButton("Unload");
unloadOne->setObjectName("btnDanger");
hl->addWidget(unloadOne, 0, Qt::AlignTop);
ipl->addWidget(headCard);
connect(unloadOne, &QPushButton::clicked, this, [this]{
if (!current.valid) return;
quint32 id = current.base.id;
drv->unload(id);
clearSelection();
emit toast(QString("unloaded module %1").arg(id), 2);
});
// caps card
auto* capCard = card();
auto* cl = new QVBoxLayout(capCard);
cl->setContentsMargins(20, 14, 20, 14);
cl->setSpacing(8);
cl->addWidget(section_label("Capabilities"));
capsBar = new QWidget;
auto* capsLay = new QHBoxLayout(capsBar);
capsLay->setContentsMargins(0, 0, 0, 0);
capsLay->setSpacing(6);
capsLay->addStretch();
cl->addWidget(capsBar);
ipl->addWidget(capCard);
// inner split: exports/imports left, invoke right
auto* split = new QSplitter(Qt::Horizontal);
split->setChildrenCollapsible(false);
split->setObjectName("wbSplit");
split->setHandleWidth(3);
// exports/imports tabs card
auto* eiCard = card();
auto* eil = new QVBoxLayout(eiCard);
eil->setContentsMargins(0, 0, 0, 0);
auto* tabs = new QTabWidget;
tabs->setObjectName("wbTabs");
expTable = new QTableWidget(0, 2);
expTable->setObjectName("dataTable");
expTable->setHorizontalHeaderLabels({"#", "Name"});
expTable->horizontalHeader()->setStretchLastSection(true);
expTable->setColumnWidth(0, 40);
expTable->verticalHeader()->setVisible(false);
expTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
expTable->setSelectionBehavior(QAbstractItemView::SelectRows);
expTable->setShowGrid(false);
expTable->setAlternatingRowColors(true);
tabs->addTab(expTable, "Exports");
impTable = new QTableWidget(0, 2);
impTable->setObjectName("dataTable");
impTable->setHorizontalHeaderLabels({"#", "Name"});
impTable->horizontalHeader()->setStretchLastSection(true);
impTable->setColumnWidth(0, 40);
impTable->verticalHeader()->setVisible(false);
impTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
impTable->setShowGrid(false);
impTable->setAlternatingRowColors(true);
tabs->addTab(impTable, "Imports");
eil->addWidget(tabs);
split->addWidget(eiCard);
connect(expTable, &QTableWidget::itemDoubleClicked, this, [this](QTableWidgetItem* it){
if (it->column() != 1) return;
callExport->setText(it->text());
});
// invoke card
auto* invCard = card();
auto* il = new QVBoxLayout(invCard);
il->setContentsMargins(20, 16, 20, 16);
il->setSpacing(10);
il->addWidget(h2("Invoke export"));
auto* form = new QFormLayout;
form->setLabelAlignment(Qt::AlignRight);
form->setSpacing(8);
callExport = new QLineEdit;
callExport->setPlaceholderText("pick from Exports list, or type name");
callArgs = new QLineEdit; callArgs->setPlaceholderText("hex/dec, comma-separated");
callArgs->setFont(QFont("JetBrainsMono NF", 10));
callTimeout = new QSpinBox; callTimeout->setRange(0, 60000); callTimeout->setSuffix(" ms");
form->addRow("Export", callExport);
form->addRow("Args", callArgs);
form->addRow("Timeout", callTimeout);
il->addLayout(form);
auto* callBtn = new QPushButton("Call"); callBtn->setObjectName("btnPrimary");
il->addWidget(callBtn);
il->addWidget(section_label("Last result"));
callResult = new QLabel("no calls yet");
callResult->setObjectName("resultLabel");
callResult->setTextInteractionFlags(Qt::TextSelectableByMouse);
il->addWidget(callResult);
il->addWidget(section_label("Call history"));
callHist = new QTableWidget(0, 3);
callHist->setObjectName("dataTable");
callHist->setHorizontalHeaderLabels({"Time", "Export", "Result"});
callHist->horizontalHeader()->setStretchLastSection(false);
callHist->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
callHist->setColumnWidth(0, 80);
callHist->setColumnWidth(2, 160);
callHist->verticalHeader()->setVisible(false);
callHist->setEditTriggers(QAbstractItemView::NoEditTriggers);
callHist->setShowGrid(false);
callHist->setAlternatingRowColors(true);
il->addWidget(callHist, 1);
split->addWidget(invCard);
split->setStretchFactor(0, 55);
split->setStretchFactor(1, 45);
split->setSizes({560, 440});
ipl->addWidget(split, 1);
rcv->addWidget(infoPanel);
infoPanel->hide();
mainSplit->addWidget(rightCol);
mainSplit->setStretchFactor(0, 0);
mainSplit->setStretchFactor(1, 1);
mainSplit->setSizes({260, 900});
root->addWidget(mainSplit, 1);
connect(callBtn, &QPushButton::clicked, this, [this]{
if (!current.valid) return;
QVector<quint64> args;
for (const QString& tok : callArgs->text().split(',', Qt::SkipEmptyParts)) {
QString s = tok.trimmed();
if (s.isEmpty()) continue;
bool ok = false;
quint64 v = s.startsWith("0x", Qt::CaseInsensitive)
? s.mid(2).toULongLong(&ok, 16)
: s.toULongLong(&ok, 0);
if (ok) args.push_back(v);
}
quint64 rv = 0; QString err;
bool ok = drv->call_export(current.base.id, callExport->text(), callTimeout->value(), args, rv, err);
int row = callHist->rowCount();
callHist->insertRow(0);
callHist->setItem(0, 0, new QTableWidgetItem(QDateTime::currentDateTime().toString("HH:mm:ss")));
callHist->setItem(0, 1, new QTableWidgetItem(callExport->text()));
QString rs = ok ? QString::asprintf("0x%llx", (unsigned long long)rv) : ("err: " + err);
auto* ri = new QTableWidgetItem(rs);
ri->setForeground(ok ? QColor("#ffffff") : QColor("#e06c6c"));
callHist->setItem(0, 2, ri);
if (callHist->rowCount() > 100) callHist->removeRow(100);
if (ok) {
callResult->setText(QString::asprintf("0x%016llx", (unsigned long long)rv));
callResult->setStyleSheet("color:#f3f4f6; font-family:'JetBrainsMono NF'; font-size:15px; font-weight:500;");
emit toast(QString("call ok: 0x%1").arg(rv, 0, 16), 2);
} else {
callResult->setText(err);
callResult->setStyleSheet("color:#e06c6c; font-family:'JetBrainsMono NF'; font-size:13px;");
emit toast(QString("call failed: %1").arg(err), 1);
}
(void)row;
});
}
void WorkbenchPage::refreshModules(const QVector<ModuleRow>& mods)
{
// remember which module was selected, restore selection after rebuild
quint32 prev = current.valid ? current.base.id : (quint32)-1;
if (pending_select) { prev = pending_select; pending_select = 0; }
// hide the toolkit guest from this list. it's a GUI-internal module the
// Explorer tab drives; if the user sees it here alongside their own wasm
// it just looks like something they didn't load.
QVector<ModuleRow> visible;
for (const auto& m : mods) {
QString base = QFileInfo(m.name).fileName();
if (base.compare("toolkit.wasm", Qt::CaseInsensitive) == 0) continue;
visible.push_back(m);
}
moduleList->blockSignals(true);
moduleList->clear();
for (const auto& m : visible) {
QString base = QFileInfo(m.name).fileName();
if (base.isEmpty()) base = m.name;
auto* it = new QListWidgetItem(QString("%1 %2").arg(m.id, 2, 10, QChar('0')).arg(base));
it->setData(Qt::UserRole, m.id);
it->setToolTip(QString("%1\nhash %2\nwasm %3 B, mem %4 KB, pool %5 B")
.arg(m.name)
.arg(m.hash, 16, 16, QChar('0'))
.arg(m.wasm_size).arg(m.mem_pages * 64).arg(m.pool_bytes));
moduleList->addItem(it);
if (m.id == prev) moduleList->setCurrentItem(it);
}
moduleList->blockSignals(false);
moduleCount->setText(QString("(%1)").arg(visible.size()));
if (visible.isEmpty()) clearSelection();
else if (prev != (quint32)-1) {
// trigger the same signal path so info reloads even if selection didn't change
for (int i = 0; i < moduleList->count(); i++) {
if (moduleList->item(i)->data(Qt::UserRole).toUInt() == prev) {
emit moduleSelected(prev);
break;
}
}
}
}
void WorkbenchPage::setSelected(quint32 id, const ModuleInfo& info)
{
(void)id;
current = info;
if (!info.valid) { clearSelection(); return; }
emptyPanel->hide();
infoPanel->show();
modTitle->setText(QFileInfo(info.base.name).fileName().isEmpty() ? info.base.name : QFileInfo(info.base.name).fileName());
modMeta->setText(QString::asprintf("id %u %016llx %.1f KB wasm %u KB mem %llu B pool",
info.base.id, (unsigned long long)info.base.hash, info.base.wasm_size/1024.0,
info.base.mem_pages*64, (unsigned long long)info.base.pool_bytes));
refreshCaps(info.caps);
// filter out internal exports (__gvm_caps) so the user sees only what they wrote
QStringList user_exports;
for (const auto& e : info.exports) if (!e.startsWith("__gvm_")) user_exports << e;
expTable->setRowCount(user_exports.size());
QFont mono("JetBrainsMono NF", 10);
for (int i = 0; i < user_exports.size(); i++) {
auto* n = new QTableWidgetItem(QString::number(i)); n->setFont(mono); n->setForeground(QColor("#7b818c"));
expTable->setItem(i, 0, n);
auto* e = new QTableWidgetItem(user_exports[i]); e->setFont(mono);
expTable->setItem(i, 1, e);
}
impTable->setRowCount(info.imports.size());
for (int i = 0; i < info.imports.size(); i++) {
auto* n = new QTableWidgetItem(QString::number(i)); n->setFont(mono); n->setForeground(QColor("#7b818c"));
impTable->setItem(i, 0, n);
auto* e = new QTableWidgetItem(info.imports[i]); e->setFont(mono); e->setForeground(QColor("#a8adb8"));
impTable->setItem(i, 1, e);
}
// auto-populate export field with first user export, but don't clobber
// whatever the user typed unless it doesn't match anything from this module
if (!user_exports.isEmpty() && !user_exports.contains(callExport->text()))
callExport->setText(user_exports.first());
// highlight the corresponding row in the list without re-emitting signals
moduleList->blockSignals(true);
for (int i = 0; i < moduleList->count(); i++) {
if (moduleList->item(i)->data(Qt::UserRole).toUInt() == info.base.id) {
moduleList->setCurrentRow(i);
break;
}
}
moduleList->blockSignals(false);
}
void WorkbenchPage::clearSelection()
{
current = {};
infoPanel->hide();
emptyPanel->show();
}
void WorkbenchPage::refreshCaps(quint32 caps)
{
auto* lay = static_cast<QHBoxLayout*>(capsBar->layout());
while (auto* it = lay->takeAt(0)) {
if (it->widget()) it->widget()->deleteLater();
delete it;
}
for (int i = 0; i < 10; i++) {
bool on = (caps >> i) & 1;
lay->addWidget(pill(CAP_NAMES[i], on ? "on" : "off"));
}
lay->addStretch();
}
// MemoryPage
MemoryPage::MemoryPage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(14);
auto* c = card();
auto* cl = new QVBoxLayout(c);
cl->setContentsMargins(20, 16, 20, 16);
cl->setSpacing(10);
cl->addWidget(h2("Read"));
auto* form = new QFormLayout;
form->setSpacing(8);
modId = new QSpinBox; modId->setRange(-1, 999); modId->setValue(-1); modId->setSpecialValueText("(not set)");
exp = new QLineEdit; exp->setText("toolkit_read_kmem");
addr = new QLineEdit; addr->setText("0xfffff800`00000000"); addr->setFont(QFont("JetBrainsMono NF", 10));
length = new QSpinBox; length->setRange(16, 4096); length->setValue(256); length->setSuffix(" bytes");
form->addRow("Toolkit module id", modId);
form->addRow("Export", exp);
form->addRow("Address", addr);
form->addRow("Length", length);
cl->addLayout(form);
auto* readBtn = new QPushButton("Read"); readBtn->setObjectName("btnPrimary");
cl->addWidget(readBtn, 0, Qt::AlignLeft);
status = new QLabel(""); status->setObjectName("dim");
cl->addWidget(status);
root->addWidget(c);
hex = new QPlainTextEdit;
hex->setReadOnly(true);
hex->setFont(QFont("JetBrainsMono NF", 10));
hex->setObjectName("hexView");
hex->setPlaceholderText("Hex output will appear here after a successful read.");
root->addWidget(hex, 1);
connect(readBtn, &QPushButton::clicked, this, [this]{
status->clear();
if (modId->value() < 0) { status->setText("set toolkit mod id first"); return; }
QString a = addr->text(); a.remove('`').remove(' ');
bool ok = false;
quint64 va = a.startsWith("0x", Qt::CaseInsensitive) ? a.mid(2).toULongLong(&ok, 16) : a.toULongLong(&ok, 0);
if (!ok) { status->setText("bad address"); return; }
QVector<quint64> argv = { va, 0, (quint64)length->value() };
quint64 rv = 0; QString err;
if (drv->call_export(modId->value(), exp->text(), 2000, argv, rv, err))
status->setText(QString::asprintf("toolkit rv=0x%llx bytes read. hex readback needs the new IOCTL.", (unsigned long long)rv));
else
status->setText("call failed: " + err);
});
}
// EventsPage
static const char* evt_tag(int k)
{
switch (k) {
case EVT_PROC: return "PROC"; case EVT_EXIT: return "EXIT"; case EVT_IMAGE: return "IMAGE";
case EVT_CALL: return "CALL"; case EVT_LOAD: return "LOAD"; case EVT_UNLOAD: return "UNLD";
case EVT_ERROR: return "ERR"; default: return "MSG";
}
}
static QColor evt_col(int k)
{
switch (k) {
case EVT_PROC: case EVT_LOAD: return QColor("#8fbf5c");
case EVT_EXIT: case EVT_ERROR: return QColor("#cd5c5c");
case EVT_IMAGE: return QColor("#7cb1f0");
case EVT_CALL: return QColor("#d4a259");
case EVT_UNLOAD: return QColor("#b96060");
default: return QColor("#d4d4d4");
}
}
EventsPage::EventsPage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(10);
// control card: load + start + stop + status
auto* ctlCard = card();
auto* cl = new QHBoxLayout(ctlCard);
cl->setContentsMargins(20, 12, 20, 12);
cl->setSpacing(8);
loadBtn = new QPushButton("Load tracer");
loadBtn->setToolTip("Load samples/process_tracer.wasm");
startBtn = new QPushButton("Start (poll)");
startBtn->setToolTip("Enable callbacks. Events log via driver notify handlers.");
reactiveBtn = new QPushButton("Start reactive");
reactiveBtn->setObjectName("btnPrimary");
reactiveBtn->setToolTip("Enable callbacks and spawn kernel dispatch worker that invokes guest exports.");
stopBtn = new QPushButton("Stop");
stopBtn->setObjectName("btnDanger");
stopBtn->setToolTip("Unload the tracer module (removes registered callbacks)");
statusLbl = new QLabel;
cl->addWidget(loadBtn);
cl->addWidget(startBtn);
cl->addWidget(reactiveBtn);
cl->addWidget(stopBtn);
cl->addSpacing(8);
cl->addWidget(statusLbl, 1);
root->addWidget(ctlCard);
set_status("no tracer loaded", "#8b8b8b");
startBtn->setEnabled(false);
reactiveBtn->setEnabled(false);
stopBtn->setEnabled(false);
connect(loadBtn, &QPushButton::clicked, this, &EventsPage::on_load_tracer);
connect(startBtn, &QPushButton::clicked, this, [this]{ on_start(false); });
connect(reactiveBtn, &QPushButton::clicked, this, [this]{ on_start(true); });
connect(stopBtn, &QPushButton::clicked, this, &EventsPage::on_stop);
// filter bar
auto* barCard = card();
auto* bl = new QHBoxLayout(barCard);
bl->setContentsMargins(20, 12, 20, 12);
bl->setSpacing(12);
cbProc = new QCheckBox("Proc"); cbProc->setChecked(true);
cbExit = new QCheckBox("Exit"); cbExit->setChecked(true);
cbImage = new QCheckBox("Image"); cbImage->setChecked(true);
cbCall = new QCheckBox("Call"); cbCall->setChecked(true);
cbError = new QCheckBox("Error"); cbError->setChecked(true);
filter = new QLineEdit; filter->setPlaceholderText("Substring filter"); filter->setFixedWidth(260);
bl->addWidget(cbProc); bl->addWidget(cbExit); bl->addWidget(cbImage); bl->addWidget(cbCall); bl->addWidget(cbError);
bl->addStretch();
bl->addWidget(filter);
root->addWidget(barCard);
table = new QTableWidget(0, 3);
table->setObjectName("dataTable");
table->setHorizontalHeaderLabels({"Time", "Kind", "Line"});
table->horizontalHeader()->setStretchLastSection(true);
table->setColumnWidth(0, 110);
table->setColumnWidth(1, 80);
table->verticalHeader()->setVisible(false);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setShowGrid(false);
table->setAlternatingRowColors(true);
root->addWidget(table, 1);
}
void EventsPage::append(const QVector<LogEntry>& entries)
{
for (const auto& e : entries) {
bool show =
(e.kind == EVT_PROC && cbProc->isChecked()) ||
(e.kind == EVT_EXIT && cbExit->isChecked()) ||
(e.kind == EVT_IMAGE && cbImage->isChecked()) ||
((e.kind == EVT_CALL || e.kind == EVT_LOAD || e.kind == EVT_UNLOAD) && cbCall->isChecked()) ||
(e.kind == EVT_ERROR && cbError->isChecked());
if (!show) continue;
QString f = filter->text();
if (!f.isEmpty() && !e.line.contains(f)) continue;
table->insertRow(0);
auto* ts = new QTableWidgetItem(e.ts);
ts->setFont(QFont("JetBrainsMono NF", 10));
ts->setForeground(QColor("#8a8a8a"));
table->setItem(0, 0, ts);
auto* kd = new QTableWidgetItem(evt_tag(e.kind));
kd->setFont(QFont("JetBrainsMono NF", 10));
kd->setForeground(evt_col(e.kind));
table->setItem(0, 1, kd);
auto* ln = new QTableWidgetItem(e.line);
ln->setFont(QFont("JetBrainsMono NF", 10));
table->setItem(0, 2, ln);
if (table->rowCount() > 2000) table->removeRow(2000);
}
}
void EventsPage::clear() { table->setRowCount(0); }
void EventsPage::set_status(const QString& s, const QString& color)
{
statusLbl->setText(s);
statusLbl->setStyleSheet(QString("color:%1; font-size:12px;").arg(color));
}
void EventsPage::on_load_tracer()
{
QString here = QCoreApplication::applicationDirPath();
QStringList candidates = {
here + "/../features/process_tracer.wasm",
here + "/features/process_tracer.wasm",
here + "/process_tracer.wasm",
here + "/../samples/process_tracer.wasm",
};
QString path;
for (const auto& p : candidates) {
if (QFile::exists(p)) { path = p; break; }
}
if (path.isEmpty()) {
path = QFileDialog::getOpenFileName(this, "Select process_tracer.wasm",
QString(), "WebAssembly (*.wasm)");
if (path.isEmpty()) return;
}
quint32 id = 0; QString err;
if (!drv->load_module(path, 0, id, err)) {
set_status(QString("load failed: %1").arg(err), "#e06c6c");
emit toast("load failed: " + err, 1);
return;
}
tracer_id = (int)id;
set_status(QString("tracer loaded as module %1").arg(id), "#7fb069");
startBtn->setEnabled(true);
reactiveBtn->setEnabled(true);
stopBtn->setEnabled(true);
loadBtn->setEnabled(false);
emit toast(QString("tracer loaded (module %1)").arg(id), 2);
}
void EventsPage::on_start(bool reactive)
{
if (tracer_id < 0) { emit toast("load tracer first", 1); return; }
const char* fn = reactive ? "start_reactive" : "start";
quint64 rv = 0; QString err;
if (drv->call_export((quint32)tracer_id, fn, 2000, {}, rv, err)) {
running = true;
set_status(QString("%1 running (module %2)").arg(reactive ? "reactive dispatch" : "callbacks").arg(tracer_id), "#7fb069");
emit toast(QString("%1 on").arg(reactive ? "reactive" : "polling"), 2);
} else {
set_status("start failed: " + err, "#e06c6c");
emit toast("start failed: " + err, 1);
}
}
void EventsPage::on_stop()
{
// stop dispatch worker + remove kernel callbacks first, THEN unload the
// module. otherwise callbacks keep firing after the module is gone.
drv->notify_stop();
if (tracer_id >= 0) drv->unload((quint32)tracer_id);
tracer_id = -1;
running = false;
set_status("stopped", "#8b8b8b");
loadBtn->setEnabled(true);
startBtn->setEnabled(false);
reactiveBtn->setEnabled(false);
stopBtn->setEnabled(false);
emit toast("callbacks removed, tracer unloaded", 0);
}
// LogPage
LogPage::LogPage(QWidget* p) : QWidget(p)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(14);
auto* bar = new QHBoxLayout;
auto* clearBtn = new QPushButton("Clear");
autoscroll = new QCheckBox("Auto-scroll"); autoscroll->setChecked(true);
statusLbl = new QLabel(""); statusLbl->setObjectName("dim");
bar->addWidget(clearBtn);
bar->addWidget(autoscroll);
bar->addStretch();
bar->addWidget(statusLbl);
root->addLayout(bar);
view = new QPlainTextEdit;
view->setReadOnly(true);
view->setFont(QFont("JetBrainsMono NF", 10));
view->setObjectName("logView");
view->setMaximumBlockCount(4000);
root->addWidget(view, 1);
connect(clearBtn, &QPushButton::clicked, this, [this]{ view->clear(); });
}
void LogPage::append(const QVector<LogEntry>& entries, quint64 seq, quint32 dropped)
{
for (const auto& e : entries) {
QString col = evt_col(e.kind).name();
QString html = QString("<span style='color:#6a6a6a'>%1</span> <span style='color:%2'>%3</span>")
.arg(e.ts, col, e.line.toHtmlEscaped());
view->appendHtml(html);
}
statusLbl->setText(QString("seq %1 | %2 dropped").arg(seq).arg(dropped));
if (autoscroll->isChecked()) {
auto* sb = view->verticalScrollBar();
sb->setValue(sb->maximum());
}
}
void LogPage::clear() { view->clear(); }
// ProfilerPage
ProfilerPage::ProfilerPage(QWidget* p) : QWidget(p)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(12);
auto* row = new QHBoxLayout;
auto* clr = new QPushButton("Reset counts");
total = new QLabel("no data yet");
total->setStyleSheet("color:#7b828e; font-size:12px;");
row->addWidget(clr); row->addWidget(total, 1);
root->addLayout(row);
table = new QTableWidget(0, 5);
table->setObjectName("dataTable");
table->setHorizontalHeaderLabels({"Function", "Count", "Total", "Max", "Avg"});
table->horizontalHeader()->setStretchLastSection(false);
table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
table->setColumnWidth(1, 100);
table->setColumnWidth(2, 140);
table->setColumnWidth(3, 140);
table->setColumnWidth(4, 140);
table->verticalHeader()->setVisible(false);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setShowGrid(false);
table->setAlternatingRowColors(true);
table->setSortingEnabled(true);
root->addWidget(table, 1);
connect(clr, &QPushButton::clicked, this, &ProfilerPage::clear);
}
static QString fmt_time_ns(quint64 ns)
{
if (ns < 1000) return QString::asprintf("%llu ns", (unsigned long long)ns);
if (ns < 1000 * 1000) return QString::asprintf("%.2f us", ns / 1000.0);
if (ns < 1000ULL*1000*1000) return QString::asprintf("%.2f ms", ns / 1e6);
return QString::asprintf("%.2f s", ns / 1e9);
}
void ProfilerPage::observe(const QVector<TraceEvent>& events)
{
for (const auto& e : events) {
auto& s = stats[e.name];
s.count++;
if (e.kind == TRK_CALL) {
s.last_call_ts = e.timestamp_100ns;
} else if (e.kind == TRK_RETURN && s.last_call_ts && e.timestamp_100ns >= s.last_call_ts) {
quint64 delta_ns = (e.timestamp_100ns - s.last_call_ts) * 100; // 100ns ticks -> ns
s.total_ns += delta_ns;
if (delta_ns > s.max_ns) s.max_ns = delta_ns;
s.last_call_ts = 0;
}
}
refresh();
}
void ProfilerPage::refresh()
{
table->setSortingEnabled(false);
table->setRowCount(stats.size());
QFont mono("JetBrainsMono NF", 10);
int r = 0; quint64 grand = 0;
for (auto it = stats.constBegin(); it != stats.constEnd(); ++it, r++) {
const auto& s = it.value();
grand += s.count;
auto* n = new QTableWidgetItem(it.key()); n->setFont(mono);
table->setItem(r, 0, n);
auto* c = new QTableWidgetItem;
c->setData(Qt::DisplayRole, (qulonglong)s.count);
c->setFont(mono);
table->setItem(r, 1, c);
auto* t = new QTableWidgetItem(s.total_ns ? fmt_time_ns(s.total_ns) : "");
t->setData(Qt::UserRole, (qulonglong)s.total_ns);
t->setFont(mono); t->setForeground(QColor("#ffffff"));
table->setItem(r, 2, t);
auto* mx = new QTableWidgetItem(s.max_ns ? fmt_time_ns(s.max_ns) : "");
mx->setFont(mono);
table->setItem(r, 3, mx);
quint64 avg = s.count ? s.total_ns / s.count : 0;
auto* av = new QTableWidgetItem(avg ? fmt_time_ns(avg) : "");
av->setFont(mono); av->setForeground(QColor("#8a919e"));
table->setItem(r, 4, av);
}
table->setSortingEnabled(true);
table->sortItems(1, Qt::DescendingOrder);
total->setText(QString("%1 unique functions | %2 events total").arg(stats.size()).arg(grand));
}
void ProfilerPage::clear() { stats.clear(); table->setRowCount(0); total->setText("no data yet"); }
// DeployPage
DeployPage::DeployPage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(14);
auto* stateCard = card();
auto* sl = new QVBoxLayout(stateCard);
sl->setContentsMargins(20, 16, 20, 16);
sl->setSpacing(10);
sl->addWidget(section_label("Status"));
auto* g = new QGridLayout;
g->setHorizontalSpacing(30);
g->setVerticalSpacing(10);
auto mklbl = [](const QString& k){ auto* l = new QLabel(k); l->setStyleSheet("color:#8a8a8a;"); return l; };
g->addWidget(mklbl("service"), 0, 0);
svcState = new QLabel("-"); g->addWidget(svcState, 0, 1);
g->addWidget(mklbl("state"), 1, 0);
runState = new QLabel("-"); g->addWidget(runState, 1, 1);
g->addWidget(mklbl("driver"), 2, 0);
devState = new QLabel("-"); g->addWidget(devState, 2, 1);
sl->addLayout(g);
root->addWidget(stateCard);
auto* actCard = card();
auto* al = new QVBoxLayout(actCard);
al->setContentsMargins(20, 16, 20, 16);
al->setSpacing(10);
al->addWidget(section_label("Actions"));
auto* btns = new QHBoxLayout;
auto* install = new QPushButton("Install");
auto* start = new QPushButton("Start"); start->setObjectName("btnPrimary");
auto* stop = new QPushButton("Stop");
auto* remove = new QPushButton("Remove"); remove->setObjectName("btnDanger");
auto* refresh = new QPushButton("Refresh");
btns->addWidget(install); btns->addWidget(start); btns->addWidget(stop);
btns->addWidget(remove); btns->addStretch(); btns->addWidget(refresh);
al->addLayout(btns);
root->addWidget(actCard);
root->addStretch();
connect(install, &QPushButton::clicked, this, [this]{
wchar_t path[MAX_PATH]; GetModuleFileNameW(nullptr, path, MAX_PATH);
QString exe = QString::fromWCharArray(path);
QString dir = QFileInfo(exe).absolutePath();
drv->run_sc(QString("create Goodmans type= kernel binPath= \"%1\\Goodmans.sys\"").arg(dir));
emit stateChanged();
});
connect(start, &QPushButton::clicked, this, [this]{ drv->run_sc("start Goodmans"); emit stateChanged(); });
connect(stop, &QPushButton::clicked, this, [this]{ drv->run_sc("stop Goodmans"); emit stateChanged(); });
connect(remove, &QPushButton::clicked, this, [this]{
drv->run_sc("stop Goodmans"); drv->run_sc("delete Goodmans"); emit stateChanged();
});
connect(refresh, &QPushButton::clicked, this, [this]{ emit stateChanged(); });
}
void DeployPage::refresh(bool devUp, bool present, bool running)
{
auto set = [](QLabel* l, const QString& t, const QString& color){
l->setText(t);
l->setStyleSheet(QString("color:%1; font-weight:600;").arg(color));
};
set(svcState, present ? "installed" : "not installed", present ? "#8fbf5c" : "#cd5c5c");
set(runState, running ? "running" : "stopped", running ? "#8fbf5c" : "#e5b055");
set(devState, devUp ? "loaded" : "not loaded", devUp ? "#8fbf5c" : "#cd5c5c");
}
// TracePage
static const char* trace_kind_str(quint32 k)
{
switch (k) {
case TRK_IMPORT: return "IMPORT";
case TRK_CALL: return "CALL";
case TRK_RETURN: return "RETURN";
case TRK_TRAP: return "TRAP";
default: return "?";
}
}
static QColor trace_kind_col(quint32 k)
{
switch (k) {
case TRK_IMPORT: return QColor("#7cb1f0");
case TRK_CALL: return QColor("#d4a259");
case TRK_RETURN: return QColor("#8fbf5c");
case TRK_TRAP: return QColor("#cd5c5c");
default: return QColor("#8a8a8a");
}
}
// argument name hints per host import so details pane can label them properly.
// values are decoded per column type: 'A' = kernel address hex, 'X' = hex u64,
// 'x' = hex u32, 'd' = decimal u32.
struct ArgSpec { const char* fn; const char* names[4]; const char types[4]; };
static const ArgSpec ARG_SPECS[] = {
{ "host_read_u8", {"kaddr", "", "", ""}, {'A','\0','\0','\0'} },
{ "host_read_u32", {"kaddr", "", "", ""}, {'A','\0','\0','\0'} },
{ "host_read_u64", {"kaddr", "", "", ""}, {'A','\0','\0','\0'} },
{ "host_write_u64", {"kaddr", "value", "", ""}, {'A','X','\0','\0'} },
{ "host_read_bytes", {"kaddr", "guest_off", "len", ""}, {'A','x','d','\0'} },
{ "host_write_bytes", {"kaddr", "guest_off", "len", ""}, {'A','x','d','\0'} },
{ "host_alloc", {"size", "", "", ""}, {'d','\0','\0','\0'} },
{ "host_free", {"kernel_va", "", "", ""}, {'A','\0','\0','\0'} },
{ "host_dbg_print", {"guest_off", "len", "", ""}, {'x','d','\0','\0'} },
{ "host_readmsr", {"msr", "", "", ""}, {'x','\0','\0','\0'} },
{ "host_writemsr", {"msr", "value", "", ""}, {'x','X','\0','\0'} },
{ "host_cpuid", {"leaf", "sub", "guest_off",""}, {'x','x','x','\0'} },
{ "host_phys_read", {"pa", "guest_off","len", ""}, {'X','x','d','\0'} },
{ "host_phys_write", {"pa", "guest_off","len", ""}, {'X','x','d','\0'} },
{ "host_rdtsc", {"","","",""}, {'\0','\0','\0','\0'}},
{ "host_current_irql", {"","","",""}, {'\0','\0','\0','\0'}},
{ "host_process_id", {"","","",""}, {'\0','\0','\0','\0'}},
{ "host_thread_id", {"","","",""}, {'\0','\0','\0','\0'}},
{ "host_current_process", {"","","",""}, {'\0','\0','\0','\0'}},
{ nullptr, {}, {} }
};
static const ArgSpec* find_argspec(const QString& fn)
{
for (int i = 0; ARG_SPECS[i].fn; i++)
if (fn == ARG_SPECS[i].fn) return &ARG_SPECS[i];
return nullptr;
}
static QString fmt_arg_val(char type, quint64 v)
{
switch (type) {
case 'A': // kernel address split for readability
return QString::asprintf("0x%08x`%08x", (quint32)(v >> 32), (quint32)v);
case 'X': return QString::asprintf("0x%016llx", (unsigned long long)v);
case 'x': return QString::asprintf("0x%x", (quint32)v);
case 'd': return QString::number((quint32)v);
default: return QString::asprintf("0x%llx", (unsigned long long)v);
}
}
static QString short_args_display(const TraceEvent& e)
{
const ArgSpec* spec = find_argspec(e.name);
if (!spec) {
QString s;
for (int i = 0; i < e.argv.size(); i++) {
if (i) s += ", ";
s += QString::asprintf("0x%llx", (unsigned long long)e.argv[i]);
}
return s;
}
QString s;
for (int i = 0; i < e.argv.size() && spec->types[i]; i++) {
if (i) s += ", ";
s += fmt_arg_val(spec->types[i], e.argv[i]);
}
return s;
}
TracePage::TracePage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(12);
// toolbar
auto* toolbar = new QFrame;
toolbar->setObjectName("toolbar");
auto* tb = new QHBoxLayout(toolbar);
tb->setContentsMargins(14, 8, 14, 8);
tb->setSpacing(8);
toggleBtn = new QPushButton("Start");
toggleBtn->setObjectName("btnPrimary");
toggleBtn->setMinimumWidth(80);
toggleBtn->setShortcut(QKeySequence("F5"));
toggleBtn->setToolTip("Toggle tracing (F5)");
tb->addWidget(toggleBtn);
pauseBtn = new QPushButton("Pause");
pauseBtn->setEnabled(false);
pauseBtn->setShortcut(QKeySequence("F6"));
pauseBtn->setToolTip("Pause UI capture (F6)");
tb->addWidget(pauseBtn);
clearBtn = new QPushButton("Clear");
clearBtn->setShortcut(QKeySequence("Ctrl+L"));
clearBtn->setToolTip("Clear view (Ctrl+L)");
tb->addWidget(clearBtn);
openBtn = new QPushButton("Open…");
openBtn->setToolTip("Load .gtrace file");
tb->addWidget(openBtn);
exportBtn = new QPushButton("Save…");
exportBtn->setShortcut(QKeySequence("Ctrl+S"));
exportBtn->setToolTip("Save trace to .gtrace or .csv (Ctrl+S)");
tb->addWidget(exportBtn);
auto* sep1 = new QFrame; sep1->setFrameShape(QFrame::VLine); sep1->setObjectName("tbsep");
sep1->setFixedHeight(24);
tb->addWidget(sep1);
onlyModule = new QCheckBox("only");
onlyModule->setToolTip("Limit tracing to a specific module id");
moduleFilter = new QSpinBox;
moduleFilter->setRange(0, 999);
moduleFilter->setValue(0);
moduleFilter->setFixedWidth(60);
tb->addWidget(onlyModule);
tb->addWidget(moduleFilter);
regexMode = new QCheckBox(".*");
regexMode->setToolTip("Treat filter as regex");
tb->addWidget(regexMode);
auto* sep2 = new QFrame; sep2->setFrameShape(QFrame::VLine); sep2->setObjectName("tbsep");
sep2->setFixedHeight(24);
tb->addWidget(sep2);
filter = new QLineEdit;
filter->setPlaceholderText("filter (Ctrl+F): substring, module id, kind, or regex");
filter->setClearButtonEnabled(true);
tb->addWidget(filter, 1);
root->addWidget(toolbar);
// splitter: table above, details below
auto* split = new QSplitter(Qt::Vertical);
split->setObjectName("wbSplit");
split->setChildrenCollapsible(false);
split->setHandleWidth(4);
table = new QTableWidget(0, 9);
table->setObjectName("dataTable");
table->setHorizontalHeaderLabels({"", "Time", "Δt", "TID", "IRQL", "Mod", "Kind", "Function", "Arguments"});
table->horizontalHeader()->setStretchLastSection(true);
table->setColumnWidth(0, 14);
table->setColumnWidth(1, 110);
table->setColumnWidth(2, 80);
table->setColumnWidth(3, 60);
table->setColumnWidth(4, 50);
table->setColumnWidth(5, 46);
table->setColumnWidth(6, 78);
table->setColumnWidth(7, 200);
table->verticalHeader()->setVisible(false);
table->verticalHeader()->setDefaultSectionSize(24);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setShowGrid(false);
table->setAlternatingRowColors(true);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
split->addWidget(table);
details = new QPlainTextEdit;
details->setReadOnly(true);
details->setObjectName("detailsPane");
details->setPlaceholderText("Select an event to see its full details.");
details->setMinimumHeight(120);
split->addWidget(details);
split->setStretchFactor(0, 70);
split->setStretchFactor(1, 30);
split->setSizes({520, 200});
root->addWidget(split, 1);
// status bar at bottom
auto* statusRow = new QHBoxLayout;
statusRow->setContentsMargins(4, 0, 4, 0);
modeLbl = new QLabel; modeLbl->setObjectName("dim");
statsLbl = new QLabel; statsLbl->setObjectName("dim");
statusRow->addWidget(modeLbl);
statusRow->addStretch();
statusRow->addWidget(statsLbl);
root->addLayout(statusRow);
refresh_stats();
connect(toggleBtn, &QPushButton::clicked, this, &TracePage::toggleTracing);
connect(pauseBtn, &QPushButton::clicked, this, [this]{
paused = !paused; pauseBtn->setText(paused ? "Resume" : "Pause");
});
connect(clearBtn, &QPushButton::clicked, this, &TracePage::clear);
connect(openBtn, &QPushButton::clicked, this, [this]{
QString path = QFileDialog::getOpenFileName(this, "Load trace", QString(), "Goodmans trace (*.gtrace);;All (*.*)");
if (!path.isEmpty()) loadTraceFile(path);
});
connect(exportBtn, &QPushButton::clicked, this, [this]{
QString path = QFileDialog::getSaveFileName(this, "Save trace", "trace.gtrace",
"Goodmans trace (*.gtrace);;CSV (*.csv)");
if (!path.isEmpty()) saveTraceFile(path);
});
connect(filter, &QLineEdit::textChanged, this, [this]{ rebuild_from_backing(); });
connect(regexMode, &QCheckBox::toggled, this, [this]{ rebuild_from_backing(); });
connect(table, &QTableWidget::itemSelectionChanged, this, [this]{
auto sel = table->selectedItems();
if (sel.isEmpty()) { details->clear(); return; }
int row = sel.first()->row();
int back_idx = table->item(row, 0)->data(Qt::UserRole).toInt();
if (back_idx >= 0 && back_idx < backing.size()) render_details(backing[back_idx]);
});
auto* bmSc = new QShortcut(QKeySequence("Ctrl+B"), this);
connect(bmSc, &QShortcut::activated, this, &TracePage::toggleBookmark);
}
void TracePage::toggleTracing()
{
if (!tracing_on) {
int mode = onlyModule->isChecked() ? TRACE_ON_MODULE : TRACE_ON_ALL;
quint32 mid = onlyModule->isChecked() ? (quint32)moduleFilter->value() : 0;
if (drv->trace_ctl(mode, mid)) {
tracing_on = true;
toggleBtn->setText("Stop");
pauseBtn->setEnabled(true);
modeLbl->setText(onlyModule->isChecked()
? QString("tracing module %1").arg(mid)
: "tracing all modules");
modeLbl->setStyleSheet("color: #8fbf5c;");
emit toast("tracing on", 2);
} else emit toast("failed to enable trace", 1);
} else {
if (drv->trace_ctl(TRACE_OFF)) {
tracing_on = false;
toggleBtn->setText("Start");
pauseBtn->setEnabled(false);
paused = false;
pauseBtn->setText("Pause");
modeLbl->setText("tracing off");
modeLbl->setStyleSheet("color: #6a7280;");
emit toast("tracing off", 0);
}
}
}
void TracePage::toggleBookmark()
{
auto sel = table->selectedItems();
if (sel.isEmpty()) return;
int row = sel.first()->row();
int back_idx = table->item(row, 0)->data(Qt::UserRole).toInt();
if (bookmarks.contains(back_idx)) { bookmarks.remove(back_idx); table->item(row, 0)->setText(""); }
else { bookmarks.insert(back_idx); table->item(row, 0)->setText(""); }
}
bool TracePage::filter_match(const TraceEvent& e) const
{
QString f = filter->text();
if (f.isEmpty()) return true;
if (regexMode->isChecked()) {
QRegularExpression re(f, QRegularExpression::CaseInsensitiveOption);
if (!re.isValid()) return true; // don't hide during typing
return re.match(e.name).hasMatch()
|| re.match(QString::number(e.module_id)).hasMatch()
|| re.match(trace_kind_str(e.kind)).hasMatch();
}
return e.name.contains(f, Qt::CaseInsensitive)
|| QString::number(e.module_id) == f
|| QString(trace_kind_str(e.kind)).contains(f, Qt::CaseInsensitive);
}
void TracePage::insert_row(int row, const TraceEvent& e, int back_idx)
{
QFont mono("JetBrainsMono NF", 10);
table->insertRow(row);
auto* bm = new QTableWidgetItem(bookmarks.contains(back_idx) ? "" : "");
bm->setForeground(QColor("#ffffff"));
bm->setTextAlignment(Qt::AlignCenter);
bm->setData(Qt::UserRole, back_idx);
table->setItem(row, 0, bm);
auto* ts = new QTableWidgetItem(e.ts); ts->setFont(mono); ts->setForeground(QColor("#8a919e"));
table->setItem(row, 1, ts);
// delta-time: microseconds since first event
quint64 delta_100ns = (first_ts && e.timestamp_100ns >= first_ts) ? e.timestamp_100ns - first_ts : 0;
double delta_ms = delta_100ns / 10000.0;
QString dt = delta_ms < 1.0
? QString::asprintf("+%.1f us", delta_100ns / 10.0)
: (delta_ms < 1000.0 ? QString::asprintf("+%.2f ms", delta_ms)
: QString::asprintf("+%.3f s", delta_ms / 1000.0));
auto* dtc = new QTableWidgetItem(dt); dtc->setFont(mono); dtc->setForeground(QColor("#7b828e"));
table->setItem(row, 2, dtc);
auto* tid = new QTableWidgetItem(QString::number(e.thread_id));
tid->setFont(mono); tid->setForeground(QColor("#8a919e"));
table->setItem(row, 3, tid);
QString irqlStr;
switch (e.irql) {
case 0: irqlStr = "PASSIVE"; break;
case 1: irqlStr = "APC"; break;
case 2: irqlStr = "DISPATCH"; break;
default: irqlStr = QString::number(e.irql);
}
auto* iq = new QTableWidgetItem(irqlStr); iq->setFont(mono);
iq->setForeground(e.irql >= 2 ? QColor("#e5b055") : QColor("#8a919e"));
table->setItem(row, 4, iq);
auto* mid = new QTableWidgetItem(QString::number(e.module_id)); mid->setFont(mono);
table->setItem(row, 5, mid);
auto* kd = new QTableWidgetItem(trace_kind_str(e.kind)); kd->setFont(mono);
kd->setForeground(trace_kind_col(e.kind));
table->setItem(row, 6, kd);
auto* nm = new QTableWidgetItem(e.name); nm->setFont(mono);
table->setItem(row, 7, nm);
QString argstr = short_args_display(e);
if (e.kind != TRK_TRAP && e.kind != TRK_CALL) argstr += QString::asprintf(" 0x%llx", (unsigned long long)e.rv);
auto* ar = new QTableWidgetItem(argstr); ar->setFont(mono);
ar->setForeground(e.kind == TRK_TRAP ? QColor("#cd5c5c") : QColor("#b0b6c0"));
table->setItem(row, 8, ar);
}
void TracePage::rebuild_from_backing()
{
table->setRowCount(0);
shown_events = 0;
// insert newest first (like live append)
for (int i = backing.size() - 1; i >= 0; i--) {
if (!filter_match(backing[i])) continue;
insert_row(table->rowCount(), backing[i], i);
shown_events++;
}
refresh_stats();
}
void TracePage::render_details(const TraceEvent& e)
{
QString out;
QString irqlName = e.irql == 0 ? "PASSIVE_LEVEL" : (e.irql == 1 ? "APC_LEVEL" : (e.irql == 2 ? "DISPATCH_LEVEL" : QString("IRQL %1").arg(e.irql)));
out += QString("<pre style='font-family:JetBrainsMono NF, Consolas; font-size:12px; color:#d4d4d4;'>");
out += QString("<b style='color:%1;'>%2</b> <span style='color:#8a919e;'>at %3 | module %4 | TID %5 | %6</span>\n")
.arg(trace_kind_col(e.kind).name(), trace_kind_str(e.kind), e.ts).arg(e.module_id).arg(e.thread_id).arg(irqlName);
out += QString("<b>function</b> %1\n").arg(e.name.toHtmlEscaped());
const ArgSpec* spec = find_argspec(e.name);
if (!e.argv.isEmpty()) {
out += "<b>arguments</b>\n";
for (int i = 0; i < e.argv.size(); i++) {
QString name = (spec && spec->names[i][0]) ? spec->names[i] : QString("arg%1").arg(i);
char type = (spec && spec->types[i]) ? spec->types[i] : 'X';
out += QString(" <span style='color:#9ca3af;'>%1</span> %2\n")
.arg(name.leftJustified(12, ' ')).arg(fmt_arg_val(type, e.argv[i]));
}
}
if (e.kind == TRK_TRAP) {
out += QString("<b style='color:#cd5c5c;'>trap reason</b> %1\n").arg(e.name.toHtmlEscaped());
} else if (e.kind != TRK_CALL) {
out += QString("<b>return</b> 0x%1 (%2)\n")
.arg((unsigned long long)e.rv, 0, 16).arg((unsigned long long)e.rv);
}
out += "</pre>";
details->setPlainText("");
details->appendHtml(out);
}
void TracePage::refresh_stats()
{
QString s;
s = QString("events: %1 total | %2 shown").arg(total_events).arg(shown_events);
if (!kind_counts.isEmpty()) {
s += " | ";
QStringList parts;
for (auto it = kind_counts.constBegin(); it != kind_counts.constEnd(); ++it)
parts << QString("%1=%2").arg(it.key()).arg(it.value());
s += parts.join(" ");
}
statsLbl->setText(s);
}
void TracePage::append(const QVector<TraceEvent>& events)
{
if (paused) return;
for (const auto& e : events) {
if (first_ts == 0) first_ts = e.timestamp_100ns;
int back_idx = backing.size();
backing.push_back(e);
total_events++;
kind_counts[trace_kind_str(e.kind)]++;
if (!filter_match(e)) continue;
insert_row(0, e, back_idx);
shown_events++;
if (table->rowCount() > 5000) table->removeRow(5000);
}
while (backing.size() > 20000) {
backing.pop_front();
bookmarks.clear(); // indices invalidated
}
refresh_stats();
}
void TracePage::saveTraceFile(const QString& path)
{
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) { emit toast("cannot open file", 1); return; }
if (path.endsWith(".csv", Qt::CaseInsensitive)) {
QTextStream out(&f);
out << "time,delta_us,tid,irql,module,kind,function,args,result\n";
for (const auto& e : backing) {
quint64 dt = (first_ts && e.timestamp_100ns >= first_ts) ? (e.timestamp_100ns - first_ts) / 10 : 0;
out << e.ts << "," << dt << "," << e.thread_id << "," << e.irql << ","
<< e.module_id << "," << trace_kind_str(e.kind) << "," << e.name << ",\""
<< short_args_display(e) << "\","
<< QString::asprintf("0x%llx", (unsigned long long)e.rv) << "\n";
}
} else {
// .gtrace binary: [magic 'GTR1'][count u32][first_ts u64][entries...]
QDataStream ds(&f);
ds.setByteOrder(QDataStream::LittleEndian);
ds << (quint32)0x31525447u << (quint32)backing.size() << (quint64)first_ts;
for (const auto& e : backing) {
ds << e.timestamp_100ns << e.module_id << e.kind << e.thread_id << e.irql
<< (quint32)e.argv.size() << e.rv;
ds << e.name;
for (int i = 0; i < 4; i++) ds << (quint64)(i < e.argv.size() ? e.argv[i] : 0);
}
}
f.close();
emit toast(QString("saved %1 events -> %2").arg(backing.size()).arg(QFileInfo(path).fileName()), 2);
}
void TracePage::loadTraceFile(const QString& path)
{
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) { emit toast("cannot open file", 1); return; }
QDataStream ds(&f);
ds.setByteOrder(QDataStream::LittleEndian);
quint32 magic = 0, count = 0;
quint64 fts = 0;
ds >> magic >> count >> fts;
if (magic != 0x31525447u) { emit toast("not a .gtrace file", 1); return; }
clear();
first_ts = fts;
QVector<TraceEvent> loaded;
for (quint32 i = 0; i < count; i++) {
TraceEvent e;
quint32 argc;
ds >> e.timestamp_100ns >> e.module_id >> e.kind >> e.thread_id >> e.irql >> argc >> e.rv;
ds >> e.name;
for (int a = 0; a < 4; a++) {
quint64 v; ds >> v; if ((quint32)a < argc) e.argv.push_back(v);
}
SYSTEMTIME sys; FILETIME ft, lft;
ft.dwLowDateTime = (DWORD) e.timestamp_100ns;
ft.dwHighDateTime = (DWORD)(e.timestamp_100ns >> 32);
FileTimeToLocalFileTime(&ft, &lft); FileTimeToSystemTime(&lft, &sys);
e.ts = QString::asprintf("%02u:%02u:%02u.%03u", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
loaded.push_back(e);
}
f.close();
append(loaded);
emit toast(QString("loaded %1 events from %2").arg(count).arg(QFileInfo(path).fileName()), 2);
}
void TracePage::clear()
{
table->setRowCount(0); backing.clear(); kind_counts.clear();
total_events = 0; shown_events = 0; details->clear(); refresh_stats();
}
// ExplorerPage
ExplorerPage::ExplorerPage(DriverClient* d, QWidget* p) : QWidget(p), drv(d)
{
auto* root = new QVBoxLayout(this);
root->setContentsMargins(10, 10, 10, 8);
root->setSpacing(12);
auto* topbar = new QFrame;
topbar->setObjectName("toolbar");
auto* tb = new QHBoxLayout(topbar);
tb->setContentsMargins(14, 10, 14, 10);
tb->setSpacing(10);
auto* explain = new QLabel("Explorer uses a helper guest (toolkit.wasm) to read kernel state.");
explain->setStyleSheet("color:#999999; font-size:12px;");
tb->addWidget(explain);
tb->addStretch();
toolkitLbl = new QLabel("toolkit: not loaded");
toolkitLbl->setStyleSheet("color:#cd5c5c; font-weight:600;");
tb->addWidget(toolkitLbl);
loadToolkitBtn = new QPushButton("Load toolkit");
loadToolkitBtn->setObjectName("btnPrimary");
loadToolkitBtn->setToolTip("Loads toolkit.wasm from the app directory (or pick another).");
tb->addWidget(loadToolkitBtn);
root->addWidget(topbar);
auto* tabs = new QTabWidget;
tabs->setObjectName("wbTabs");
// processes tab
{
auto* w = new QWidget;
auto* l = new QVBoxLayout(w);
l->setContentsMargins(0, 8, 0, 0);
auto* row = new QHBoxLayout;
auto* refreshBtn = new QPushButton("Refresh");
refreshBtn->setObjectName("btnPrimary");
row->addWidget(refreshBtn); row->addStretch();
l->addLayout(row);
procTable = new QTableWidget(0, 4);
procTable->setObjectName("dataTable");
procTable->setHorizontalHeaderLabels({"PID", "Name", "EPROCESS", "Threads"});
procTable->horizontalHeader()->setStretchLastSection(false);
procTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
procTable->setColumnWidth(0, 80);
procTable->setColumnWidth(2, 200);
procTable->setColumnWidth(3, 80);
procTable->verticalHeader()->setVisible(false);
procTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
procTable->setShowGrid(false);
procTable->setAlternatingRowColors(true);
l->addWidget(procTable, 1);
connect(refreshBtn, &QPushButton::clicked, this, &ExplorerPage::refresh_procs);
tabs->addTab(w, "Processes");
}
// memory tab
{
auto* w = new QWidget;
auto* l = new QVBoxLayout(w);
l->setContentsMargins(0, 8, 0, 0);
auto* row = new QHBoxLayout;
row->addWidget(new QLabel("Address:"));
memAddr = new QLineEdit("0xfffff800`00000000");
memAddr->setFont(QFont("JetBrainsMono NF", 10));
memAddr->setFixedWidth(280);
row->addWidget(memAddr);
row->addWidget(new QLabel("Bytes:"));
memLen = new QSpinBox; memLen->setRange(16, 4096); memLen->setValue(256);
memLen->setFixedWidth(80);
row->addWidget(memLen);
auto* readBtn = new QPushButton("Read");
readBtn->setObjectName("btnPrimary");
row->addWidget(readBtn);
row->addStretch();
l->addLayout(row);
memHex = new QPlainTextEdit;
memHex->setReadOnly(true);
memHex->setFont(QFont("JetBrainsMono NF", 10));
memHex->setObjectName("hexView");
l->addWidget(memHex, 1);
connect(readBtn, &QPushButton::clicked, this, &ExplorerPage::read_memory);
tabs->addTab(w, "Memory");
}
// MSR tab
{
auto* w = new QWidget;
auto* l = new QVBoxLayout(w);
l->setContentsMargins(0, 8, 0, 0);
auto* row = new QHBoxLayout;
auto* readBtn = new QPushButton("Read presets");
readBtn->setObjectName("btnPrimary");
row->addWidget(readBtn);
msrCustom = new QLineEdit;
msrCustom->setPlaceholderText("custom MSR index (hex or dec)");
msrCustom->setFont(QFont("JetBrainsMono NF", 10));
row->addWidget(msrCustom);
auto* customBtn = new QPushButton("Read");
row->addWidget(customBtn);
row->addStretch();
l->addLayout(row);
msrTable = new QTableWidget(0, 3);
msrTable->setObjectName("dataTable");
msrTable->setHorizontalHeaderLabels({"MSR", "Name", "Value"});
msrTable->horizontalHeader()->setStretchLastSection(false);
msrTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
msrTable->setColumnWidth(0, 100);
msrTable->setColumnWidth(2, 220);
msrTable->verticalHeader()->setVisible(false);
msrTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
msrTable->setShowGrid(false);
msrTable->setAlternatingRowColors(true);
l->addWidget(msrTable, 1);
connect(readBtn, &QPushButton::clicked, this, &ExplorerPage::read_msrs);
connect(customBtn, &QPushButton::clicked, this, [this]{
if (toolkit_id < 0) { emit toast("load toolkit first", 1); return; }
QString s = msrCustom->text().trimmed(); if (s.isEmpty()) return;
bool ok = false;
quint32 idx = s.startsWith("0x", Qt::CaseInsensitive) ? s.mid(2).toUInt(&ok, 16) : s.toUInt(&ok, 0);
if (!ok) { emit toast("bad MSR", 1); return; }
QVector<quint64> argv = { idx };
quint64 rv = 0; QString err;
if (drv->call_export(toolkit_id, "toolkit_readmsr", 2000, argv, rv, err)) {
int row = msrTable->rowCount();
msrTable->insertRow(row);
msrTable->setItem(row, 0, new QTableWidgetItem(QString::asprintf("0x%x", idx)));
msrTable->setItem(row, 1, new QTableWidgetItem("(custom)"));
msrTable->setItem(row, 2, new QTableWidgetItem(QString::asprintf("0x%016llx", (unsigned long long)rv)));
for (int c = 0; c < 3; c++) msrTable->item(row, c)->setFont(QFont("JetBrainsMono NF", 10));
} else emit toast(err, 1);
});
tabs->addTab(w, "MSRs");
}
// CPUID tab
{
auto* w = new QWidget;
auto* l = new QVBoxLayout(w);
l->setContentsMargins(0, 8, 0, 0);
auto* row = new QHBoxLayout;
row->addWidget(new QLabel("Max leaf:"));
cpuidMax = new QSpinBox; cpuidMax->setRange(0, 0x40); cpuidMax->setValue(0x18);
cpuidMax->setFixedWidth(80);
row->addWidget(cpuidMax);
auto* dumpBtn = new QPushButton("Dump");
dumpBtn->setObjectName("btnPrimary");
row->addWidget(dumpBtn);
row->addStretch();
l->addLayout(row);
cpuidTable = new QTableWidget(0, 6);
cpuidTable->setObjectName("dataTable");
cpuidTable->setHorizontalHeaderLabels({"Leaf", "Sub", "EAX", "EBX", "ECX", "EDX"});
cpuidTable->horizontalHeader()->setStretchLastSection(true);
cpuidTable->setColumnWidth(0, 70);
cpuidTable->setColumnWidth(1, 50);
for (int i = 2; i <= 5; i++) cpuidTable->setColumnWidth(i, 110);
cpuidTable->verticalHeader()->setVisible(false);
cpuidTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
cpuidTable->setShowGrid(false);
cpuidTable->setAlternatingRowColors(true);
l->addWidget(cpuidTable, 1);
connect(dumpBtn, &QPushButton::clicked, this, &ExplorerPage::read_cpuid);
tabs->addTab(w, "CPUID");
}
root->addWidget(tabs, 1);
connect(loadToolkitBtn, &QPushButton::clicked, this, [this]{
QString here = QCoreApplication::applicationDirPath();
QString def = here + "/toolkit.wasm";
QString path;
if (QFile::exists(def)) {
// one-click if the shipped toolkit is next to the exe
path = def;
} else {
path = QFileDialog::getOpenFileName(this, "Select toolkit.wasm",
QString(), "WebAssembly (*.wasm)");
if (path.isEmpty()) return;
}
quint32 id = 0; QString err;
if (drv->load_module(path, 0, id, err)) {
setToolkitId((int)id);
emit toast(QString("toolkit loaded as module %1").arg(id), 2);
} else emit toast("load failed: " + err, 1);
});
}
void ExplorerPage::setToolkitId(int id)
{
toolkit_id = id;
if (id < 0) {
toolkitLbl->setText("toolkit: not loaded");
toolkitLbl->setStyleSheet("color:#cd5c5c; font-weight:600;");
} else {
toolkitLbl->setText(QString("toolkit: module %1").arg(id));
toolkitLbl->setStyleSheet("color:#8fbf5c; font-weight:600;");
}
}
void ExplorerPage::refresh_procs()
{
if (toolkit_id < 0) { emit toast("load toolkit first", 1); return; }
// 1. get scratch offset
QVector<quint64> a0; quint64 scratch = 0; QString err;
if (!drv->call_export(toolkit_id, "toolkit_scratch_off", 2000, a0, scratch, err))
{ emit toast("toolkit_scratch_off: " + err, 1); return; }
// 2. enumerate procs into scratch
QVector<quint64> a1 = { scratch, 64 };
quint64 count = 0;
if (!drv->call_export(toolkit_id, "toolkit_enum_procs", 5000, a1, count, err))
{ emit toast("toolkit_enum_procs: " + err, 1); return; }
// 3. read scratch back
QByteArray data;
if (!drv->read_guest((quint32)toolkit_id, (quint32)scratch, (quint32)(count * 32), data, err))
{ emit toast("read_guest: " + err, 1); return; }
procTable->setRowCount((int)count);
QFont mono("JetBrainsMono NF", 10);
for (int i = 0; i < (int)count; i++) {
const char* p = data.constData() + i * 32;
quint64 pid = *(const quint64*)(p + 0);
quint64 eproc = *(const quint64*)(p + 8);
char name[16] = {0};
memcpy(name, p + 16, 15);
auto* c0 = new QTableWidgetItem(QString::number(pid)); c0->setFont(mono);
auto* c1 = new QTableWidgetItem(QString::fromLatin1(name));
auto* c2 = new QTableWidgetItem(QString::asprintf("0x%016llx", (unsigned long long)eproc));
c2->setFont(mono); c2->setForeground(QColor("#8a8a8a"));
auto* c3 = new QTableWidgetItem("-"); c3->setFont(mono);
procTable->setItem(i, 0, c0); procTable->setItem(i, 1, c1);
procTable->setItem(i, 2, c2); procTable->setItem(i, 3, c3);
}
}
void ExplorerPage::read_memory()
{
if (toolkit_id < 0) { emit toast("load toolkit first", 1); return; }
QString a = memAddr->text(); a.remove('`').remove(' ');
bool ok = false;
quint64 va = a.startsWith("0x", Qt::CaseInsensitive) ? a.mid(2).toULongLong(&ok, 16) : a.toULongLong(&ok, 0);
if (!ok) { emit toast("bad address", 1); return; }
QString err;
quint64 scratch = 0;
if (!drv->call_export(toolkit_id, "toolkit_scratch_off", 2000, {}, scratch, err))
{ emit toast(err, 1); return; }
quint32 len = (quint32)memLen->value();
QVector<quint64> argv = { va, scratch, len };
quint64 bytes_read = 0;
if (!drv->call_export(toolkit_id, "toolkit_read_kmem", 2000, argv, bytes_read, err))
{ emit toast(err, 1); return; }
if (bytes_read == 0) { memHex->setPlainText("(zero bytes read - probably invalid address)"); return; }
QByteArray data;
if (!drv->read_guest((quint32)toolkit_id, (quint32)scratch, (quint32)bytes_read, data, err))
{ emit toast("read_guest: " + err, 1); return; }
QString out;
for (int off = 0; off < data.size(); off += 16) {
out += QString::asprintf("%016llx ", (unsigned long long)(va + off));
QString hex, ascii;
for (int i = 0; i < 16; i++) {
if (off + i < data.size()) {
unsigned char b = (unsigned char)data[off + i];
hex += QString::asprintf("%02x ", b);
ascii += (b >= 0x20 && b < 0x7f) ? QChar(b) : '.';
} else { hex += " "; ascii += ' '; }
if (i == 7) hex += " ";
}
out += hex + " " + ascii + "\n";
}
memHex->setPlainText(out);
}
static const struct { quint32 idx; const char* name; } MSR_PRESETS[] = {
{ 0x001b, "IA32_APIC_BASE" },
{ 0x003a, "IA32_FEATURE_CONTROL" },
{ 0x00c0, "IA32_PMC0" },
{ 0x00c1, "IA32_PMC1" },
{ 0x0174, "IA32_SYSENTER_CS" },
{ 0x0175, "IA32_SYSENTER_ESP" },
{ 0x0176, "IA32_SYSENTER_EIP" },
{ 0x01d9, "IA32_DEBUGCTL" },
{ 0x0277, "IA32_PAT" },
{ 0x02ff, "IA32_MTRR_DEF_TYPE" },
{ 0xc0000080, "IA32_EFER" },
{ 0xc0000081, "IA32_STAR" },
{ 0xc0000082, "IA32_LSTAR" },
{ 0xc0000083, "IA32_CSTAR" },
{ 0xc0000084, "IA32_FMASK" },
{ 0xc0000100, "IA32_FS_BASE" },
{ 0xc0000101, "IA32_GS_BASE" },
{ 0xc0000102, "IA32_KERNEL_GS_BASE" },
{ 0xc0000103, "IA32_TSC_AUX" },
{ 0, nullptr }
};
void ExplorerPage::read_msrs()
{
if (toolkit_id < 0) { emit toast("load toolkit first", 1); return; }
msrTable->setRowCount(0);
QFont mono("JetBrainsMono NF", 10);
for (int i = 0; MSR_PRESETS[i].name; i++) {
QVector<quint64> argv = { MSR_PRESETS[i].idx };
quint64 rv = 0; QString err;
bool ok = drv->call_export(toolkit_id, "toolkit_readmsr", 2000, argv, rv, err);
int row = msrTable->rowCount();
msrTable->insertRow(row);
msrTable->setItem(row, 0, new QTableWidgetItem(QString::asprintf("0x%x", MSR_PRESETS[i].idx)));
msrTable->setItem(row, 1, new QTableWidgetItem(MSR_PRESETS[i].name));
msrTable->setItem(row, 2, new QTableWidgetItem(ok ? QString::asprintf("0x%016llx", (unsigned long long)rv) : QString("err: " + err)));
for (int c = 0; c < 3; c++) msrTable->item(row, c)->setFont(mono);
if (!ok) msrTable->item(row, 2)->setForeground(QColor("#cd5c5c"));
}
}
void ExplorerPage::read_cpuid()
{
if (toolkit_id < 0) { emit toast("load toolkit first", 1); return; }
cpuidTable->setRowCount(0);
QFont mono("JetBrainsMono NF", 10);
QString err;
quint64 scratch = 0;
if (!drv->call_export(toolkit_id, "toolkit_scratch_off", 2000, {}, scratch, err))
{ emit toast(err, 1); return; }
int maxL = cpuidMax->value();
for (int leaf = 0; leaf <= maxL; leaf++) {
QVector<quint64> argv = { (quint32)leaf, 0, (quint32)scratch };
quint64 rv = 0;
if (!drv->call_export(toolkit_id, "toolkit_cpuid", 2000, argv, rv, err)) continue;
QByteArray data;
if (!drv->read_guest((quint32)toolkit_id, (quint32)scratch, 16, data, err)) continue;
quint32 eax = *(const quint32*)(data.constData() + 0);
quint32 ebx = *(const quint32*)(data.constData() + 4);
quint32 ecx = *(const quint32*)(data.constData() + 8);
quint32 edx = *(const quint32*)(data.constData() + 12);
int row = cpuidTable->rowCount();
cpuidTable->insertRow(row);
cpuidTable->setItem(row, 0, new QTableWidgetItem(QString::asprintf("0x%02x", leaf)));
cpuidTable->setItem(row, 1, new QTableWidgetItem("0"));
cpuidTable->setItem(row, 2, new QTableWidgetItem(QString::asprintf("0x%08x", eax)));
cpuidTable->setItem(row, 3, new QTableWidgetItem(QString::asprintf("0x%08x", ebx)));
cpuidTable->setItem(row, 4, new QTableWidgetItem(QString::asprintf("0x%08x", ecx)));
cpuidTable->setItem(row, 5, new QTableWidgetItem(QString::asprintf("0x%08x", edx)));
for (int c = 0; c < 6; c++) cpuidTable->item(row, c)->setFont(mono);
}
}