From 9dc52577ea22510c9a043a76558c4d6562ce583e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 7 Jul 2025 02:11:57 +0300 Subject: [PATCH 1/3] Qt: Initial CPU debugger widget implementation Co-Authored-By: liuk707 <62625900+liuk7071@users.noreply.github.com> --- CMakeLists.txt | 4 +- include/capstone.hpp | 2 +- include/emulator.hpp | 8 +- include/panda_qt/cpu_debugger.hpp | 38 +++ include/panda_qt/disabled_widget_overlay.hpp | 28 +++ include/panda_qt/main_window.hpp | 2 + src/panda_qt/cpu_debugger.cpp | 232 +++++++++++++++++++ src/panda_qt/main_window.cpp | 3 + 8 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 include/panda_qt/cpu_debugger.hpp create mode 100644 include/panda_qt/disabled_widget_overlay.hpp create mode 100644 src/panda_qt/cpu_debugger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e50af1b8..bae21128 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -733,12 +733,12 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp - src/panda_qt/thread_debugger.cpp + src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp ) set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp - include/panda_qt/thread_debugger.hpp + include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/disabled_widget_overlay.hpp ) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) diff --git a/include/capstone.hpp b/include/capstone.hpp index 32ca404f..14dd44b5 100644 --- a/include/capstone.hpp +++ b/include/capstone.hpp @@ -23,7 +23,7 @@ namespace Common { // pc: program counter of the instruction to disassemble // bytes: Byte representation of instruction // buffer: text buffer to output the disassembly too - usize disassemble(std::string& buffer, u32 pc, std::span bytes, u64 offset = 0) { + usize disassemble(std::string& buffer, u32 pc, std::span bytes, u64 offset = 0) { if (!initialized) { buffer = "Capstone was not properly initialized"; return 0; diff --git a/include/emulator.hpp b/include/emulator.hpp index 6b63a211..51242494 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -122,14 +122,16 @@ class Emulator { // Reloads some settings that require special handling, such as audio enable void reloadSettings(); + CPU& getCPU() { return cpu; } + Memory& getMemory() { return memory; } + Kernel& getKernel() { return kernel; } + Scheduler& getScheduler() { return scheduler; } + EmulatorConfig& getConfig() { return config; } Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } LuaManager& getLua() { return lua; } - Scheduler& getScheduler() { return scheduler; } - Memory& getMemory() { return memory; } AudioDeviceInterface& getAudioDevice() { return audioDevice; } - Kernel& getKernel() { return kernel; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/panda_qt/cpu_debugger.hpp b/include/panda_qt/cpu_debugger.hpp new file mode 100644 index 00000000..f6a6aa95 --- /dev/null +++ b/include/panda_qt/cpu_debugger.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include + +#include "emulator.hpp" +#include "panda_qt/disabled_widget_overlay.hpp" + +class CPUDebugger : public QWidget { + Q_OBJECT + Emulator* emu; + + QListWidget* disasmListWidget; + QScrollBar* verticalScrollBar; + QPlainTextEdit* registerTextEdit; + + DisabledWidgetOverlay* disabledOverlay; + + bool enabled = false; + + public: + CPUDebugger(Emulator* emulator, QWidget* parent = nullptr); + void enable(); + void disable(); + + private: + // Update the state of the disassembler. Qt events should always call update, not updateDisasm/updateRegister + // As update properly handles thread safety + void update(); + void updateDisasm(); + void updateRegisters(); + + bool eventFilter(QObject* obj, QEvent* event) override; + void showEvent(QShowEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void keyPressEvent(QKeyEvent* event); +}; diff --git a/include/panda_qt/disabled_widget_overlay.hpp b/include/panda_qt/disabled_widget_overlay.hpp new file mode 100644 index 00000000..12e7c26e --- /dev/null +++ b/include/panda_qt/disabled_widget_overlay.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +class DisabledWidgetOverlay : public QWidget { + Q_OBJECT + + public: + DisabledWidgetOverlay(QWidget *parent = nullptr, QString overlayText = tr("This widget is disabled")) : text(overlayText), QWidget(parent) { + setVisible(false); + } + + private: + QString text; + + void paintEvent(QPaintEvent *) override { + QPainter painter = QPainter(this); + painter.fillRect(rect(), QColor(60, 60, 60, 128)); + painter.setPen(Qt::gray); + + QFont font = painter.font(); + font.setBold(true); + font.setPointSize(18); + + painter.setFont(font); + painter.drawText(rect(), Qt::AlignCenter, text); + } +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 715f73fb..c5032a87 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -17,6 +17,7 @@ #include "panda_qt/about_window.hpp" #include "panda_qt/cheats_window.hpp" #include "panda_qt/config_window.hpp" +#include "panda_qt/cpu_debugger.hpp" #include "panda_qt/patch_window.hpp" #include "panda_qt/screen.hpp" #include "panda_qt/shader_editor.hpp" @@ -110,6 +111,7 @@ class MainWindow : public QMainWindow { TextEditorWindow* luaEditor; PatchWindow* patchWindow; ShaderEditorWindow* shaderEditor; + CPUDebugger* cpuDebugger; ThreadDebugger* threadDebugger; // We use SDL's game controller API since it's the sanest API that supports as many controllers as possible diff --git a/src/panda_qt/cpu_debugger.cpp b/src/panda_qt/cpu_debugger.cpp new file mode 100644 index 00000000..37c2693c --- /dev/null +++ b/src/panda_qt/cpu_debugger.cpp @@ -0,0 +1,232 @@ +#include "panda_qt/cpu_debugger.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "capstone.hpp" + +static int getLinesInViewport(QListWidget* listWidget) { + auto viewportHeight = listWidget->viewport()->height(); + QFontMetrics fm = QFontMetrics(listWidget->font()); + auto lineHeight = fm.height(); + + return int(viewportHeight / lineHeight); +} + +static std::pair getVisibleLineRange(QListWidget* listWidget, QScrollBar* scrollBar) { + int firstLine = scrollBar->value(); + int lineCount = getLinesInViewport(listWidget); + + return {firstLine, lineCount}; +} + +CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), QWidget(parent, Qt::Window) { + setWindowTitle(tr("CPU debugger")); + resize(1000, 600); + + // Main grid layout + QGridLayout* gridLayout = new QGridLayout(this); + + // Top row: buttons in a horizontal layout + QHBoxLayout* horizontalLayout = new QHBoxLayout(); + QPushButton* stepButton = new QPushButton(tr("Step"), this); + QPushButton* goToAddressButton = new QPushButton(tr("Go to address"), this); + QPushButton* goToPCButton = new QPushButton(tr("Go to PC"), this); + + horizontalLayout->addWidget(stepButton); + horizontalLayout->addWidget(goToAddressButton); + horizontalLayout->addWidget(goToPCButton); + gridLayout->addLayout(horizontalLayout, 0, 0); + + // Disassembly list on the left + disasmListWidget = new QListWidget(this); + gridLayout->addWidget(disasmListWidget, 1, 0); + + // Vertical scroll bar in the middle + verticalScrollBar = new QScrollBar(Qt::Vertical, this); + gridLayout->addWidget(verticalScrollBar, 1, 1); + + // Register view on the right + registerTextEdit = new QPlainTextEdit(this); + registerTextEdit->setEnabled(true); + registerTextEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + registerTextEdit->setMaximumWidth(800); + gridLayout->addWidget(registerTextEdit, 1, 2); + + // Setup disabled widget overlay + disabledOverlay = new DisabledWidgetOverlay(this, tr("Pause the emulator to use the CPU Debugger")); + disabledOverlay->resize(size()); // Fill the whole screen + disabledOverlay->raise(); + disabledOverlay->hide(); + + // Monospace font + QFont mono_font = QFont("Courier New"); + mono_font.setStyleHint(QFont::Monospace); + disasmListWidget->setFont(mono_font); + registerTextEdit->setFont(mono_font); + + // To forward scrolling from the list widget to the external scrollbar + disasmListWidget->installEventFilter(this); + + // Setup scroll bar + verticalScrollBar->setRange(0, INT32_MAX); + verticalScrollBar->setSingleStep(8); + verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget)); + verticalScrollBar->show(); + connect(verticalScrollBar, &QScrollBar::valueChanged, this, &CPUDebugger::updateDisasm); + registerTextEdit->setReadOnly(true); + + connect(goToPCButton, &QPushButton::clicked, this, [&]() { + u32 pc = emu->getCPU().getReg(15); + verticalScrollBar->setValue(pc); + }); + + disable(); + hide(); +} + +void CPUDebugger::enable() { + enabled = true; + auto pc = emu->getCPU().getReg(15); + + disabledOverlay->hide(); + verticalScrollBar->setValue(pc); + + update(); +} + +void CPUDebugger::disable() { + enabled = false; + + disabledOverlay->show(); +} + +void CPUDebugger::update() { + if (enabled) { + updateDisasm(); + updateRegisters(); + } +} + +void CPUDebugger::updateDisasm() { + int currentRow = disasmListWidget->currentRow(); + disasmListWidget->clear(); + + auto [firstLine, lineCount] = getVisibleLineRange(disasmListWidget, verticalScrollBar); + const u32 startPC = (firstLine + 3) & ~3; // Align PC to 4 bytes + const u32 endPC = startPC + lineCount * sizeof(u32); + + auto& cpu = emu->getCPU(); + auto& mem = emu->getMemory(); + u32 pc = cpu.getReg(15); + + Common::CapstoneDisassembler disassembler(CS_ARCH_ARM, CS_MODE_ARM); + std::string disassembly; + + for (u32 addr = startPC; addr < endPC; addr += sizeof(u32)) { + if (auto pointer = (u32*)mem.getReadPointer(addr)) { + const u32 instruction = *pointer; + + // Convert instruction to byte array to pass to Capstone + const std::array bytes = { + u8(instruction & 0xff), + u8((instruction >> 8) & 0xff), + u8((instruction >> 16) & 0xff), + u8((instruction >> 24) & 0xff), + }; + + disassembler.disassemble(disassembly, pc, std::span(bytes)); + disassembly = fmt::format("{:08X} | {}", addr, disassembly); + + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(disassembly)); + if (addr == pc) { + item->setBackground(Qt::darkGreen); + } + disasmListWidget->addItem(item); + } else + disasmListWidget->addItem(QString::fromStdString(fmt::format("{:08X} | ???", addr))); + } + + disasmListWidget->setCurrentRow(currentRow); +} + +void CPUDebugger::updateRegisters() { + auto& cpu = emu->getCPU(); + const std::span gprs = cpu.regs(); + const std::span fprs = cpu.fprs(); + const u32 pc = gprs[15]; + const u32 cpsr = cpu.getCPSR(); + const u32 fpscr = cpu.getFPSCR(); + + std::string text = ""; + text.reserve(2048); + + text += fmt::format("PC: {:08X}\nCPSR: {:08X}\nFPSCR: {:08X}\n", pc, cpsr, fpscr); + + text += "\nGeneral Purpose Registers\n"; + for (int i = 0; i < 10; i++) { + text += fmt::format("r{:01d}: 0x{:08X}\n", i, gprs[i]); + } + for (int i = 10; i < 16; i++) { + text += fmt::format("r{:02d}: 0x{:08X}\n", i, gprs[i]); + } + + text += "\nFloating Point Registers\n"; + for (int i = 0; i < 10; i++) { + text += fmt::format("f{:01d}: {:f}\n", i, Helpers::bit_cast(fprs[i])); + } + for (int i = 10; i < 32; i++) { + text += fmt::format("f{:01d}: {:f}\n", i, Helpers::bit_cast(fprs[i])); + } + + registerTextEdit->setPlainText(QString::fromStdString(text)); +} + +bool CPUDebugger::eventFilter(QObject* obj, QEvent* event) { + // Forward scroll events from the list widget to the scrollbar + if (obj == disasmListWidget && event->type() == QEvent::Wheel) { + QWheelEvent* wheelEvent = (QWheelEvent*)event; + + int wheelSteps = wheelEvent->angleDelta().y() / 60; + int newScrollValue = verticalScrollBar->value() - wheelSteps; + newScrollValue = qBound(verticalScrollBar->minimum(), newScrollValue, verticalScrollBar->maximum()); + verticalScrollBar->setValue(newScrollValue); + + return true; + } + + return QWidget::eventFilter(obj, event); +} + +void CPUDebugger::showEvent(QShowEvent* event) { + QWidget::showEvent(event); + + enable(); +} + +void CPUDebugger::keyPressEvent(QKeyEvent* event) { + constexpr usize instructionSize = sizeof(u32); + + if (event->key() == Qt::Key_Up) { + verticalScrollBar->setValue(verticalScrollBar->value() - instructionSize); + } else if (event->key() == Qt::Key_Down) { + verticalScrollBar->setValue(verticalScrollBar->value() + instructionSize); + } else { + QWidget::keyPressEvent(event); + } +} + +void CPUDebugger::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); + disabledOverlay->resize(event->size()); + verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget)); + + update(); +} \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index d29b9875..e5a2795d 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -66,6 +66,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window")); auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor")); + auto cpuDebuggerAction = toolsMenu->addAction(tr("Open CPU Debugger")); auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger")); auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware")); @@ -74,6 +75,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); }); connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); }); connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); }); + connect(cpuDebuggerAction, &QAction::triggered, this, [this]() { cpuDebugger->show(); }); connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); }); connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware); @@ -94,6 +96,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) luaEditor = new TextEditorWindow(this, "script.lua", ""); shaderEditor = new ShaderEditorWindow(this, "shader.glsl", ""); threadDebugger = new ThreadDebugger(emu, this); + cpuDebugger = new CPUDebugger(emu, this); shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload()); if (shaderEditor->supported) { From 37f38509dbab47370a02bc39ba8ee8e8de8f2e1b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 7 Jul 2025 04:30:03 +0300 Subject: [PATCH 2/3] More CPU debugger work Co-Authored-By: liuk707 <62625900+liuk7071@users.noreply.github.com> --- include/panda_qt/cpu_debugger.hpp | 5 ++ src/panda_qt/cpu_debugger.cpp | 76 +++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/include/panda_qt/cpu_debugger.hpp b/include/panda_qt/cpu_debugger.hpp index f6a6aa95..a0999436 100644 --- a/include/panda_qt/cpu_debugger.hpp +++ b/include/panda_qt/cpu_debugger.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -14,10 +15,13 @@ class CPUDebugger : public QWidget { QListWidget* disasmListWidget; QScrollBar* verticalScrollBar; QPlainTextEdit* registerTextEdit; + QTimer* updateTimer; + QLineEdit* addressInput; DisabledWidgetOverlay* disabledOverlay; bool enabled = false; + bool followPC = false; public: CPUDebugger(Emulator* emulator, QWidget* parent = nullptr); @@ -30,6 +34,7 @@ class CPUDebugger : public QWidget { void update(); void updateDisasm(); void updateRegisters(); + void scrollToPC(); bool eventFilter(QObject* obj, QEvent* event) override; void showEvent(QShowEvent* event) override; diff --git a/src/panda_qt/cpu_debugger.cpp b/src/panda_qt/cpu_debugger.cpp index 37c2693c..552acc80 100644 --- a/src/panda_qt/cpu_debugger.cpp +++ b/src/panda_qt/cpu_debugger.cpp @@ -2,16 +2,19 @@ #include +#include #include #include #include #include #include +#include #include #include #include "capstone.hpp" +// TODO: Make this actually thread-safe by having it only work when paused static int getLinesInViewport(QListWidget* listWidget) { auto viewportHeight = listWidget->viewport()->height(); QFontMetrics fm = QFontMetrics(listWidget->font()); @@ -31,62 +34,83 @@ CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), Q setWindowTitle(tr("CPU debugger")); resize(1000, 600); - // Main grid layout QGridLayout* gridLayout = new QGridLayout(this); - - // Top row: buttons in a horizontal layout QHBoxLayout* horizontalLayout = new QHBoxLayout(); - QPushButton* stepButton = new QPushButton(tr("Step"), this); + + // Set up the top line widgets QPushButton* goToAddressButton = new QPushButton(tr("Go to address"), this); QPushButton* goToPCButton = new QPushButton(tr("Go to PC"), this); + QCheckBox* followPCCheckBox = new QCheckBox(tr("Follow PC"), this); + addressInput = new QLineEdit(this); - horizontalLayout->addWidget(stepButton); horizontalLayout->addWidget(goToAddressButton); horizontalLayout->addWidget(goToPCButton); + horizontalLayout->addWidget(followPCCheckBox); + horizontalLayout->addWidget(addressInput); + + followPCCheckBox->setChecked(followPC); + connect(followPCCheckBox, &QCheckBox::toggled, this, [&](bool checked) { followPC = checked; }); + + addressInput->setPlaceholderText(tr("Address to jump to")); + addressInput->setMaximumWidth(100); + gridLayout->addLayout(horizontalLayout, 0, 0); - // Disassembly list on the left + // Disassembly list on the left, scrollbar in the middle, register view on the right disasmListWidget = new QListWidget(this); gridLayout->addWidget(disasmListWidget, 1, 0); - // Vertical scroll bar in the middle verticalScrollBar = new QScrollBar(Qt::Vertical, this); gridLayout->addWidget(verticalScrollBar, 1, 1); - // Register view on the right registerTextEdit = new QPlainTextEdit(this); registerTextEdit->setEnabled(true); registerTextEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); registerTextEdit->setMaximumWidth(800); gridLayout->addWidget(registerTextEdit, 1, 2); - // Setup disabled widget overlay + // Setup overlay for when the widget is disabled disabledOverlay = new DisabledWidgetOverlay(this, tr("Pause the emulator to use the CPU Debugger")); disabledOverlay->resize(size()); // Fill the whole screen disabledOverlay->raise(); disabledOverlay->hide(); - // Monospace font + // Use a monospace font for the disassembly to align it QFont mono_font = QFont("Courier New"); mono_font.setStyleHint(QFont::Monospace); disasmListWidget->setFont(mono_font); registerTextEdit->setFont(mono_font); - // To forward scrolling from the list widget to the external scrollbar + // Forward scrolling from the list widget to our scrollbar disasmListWidget->installEventFilter(this); - // Setup scroll bar - verticalScrollBar->setRange(0, INT32_MAX); + // Annoyingly, due to a Qt limitation we can't set it to U32_MAX + verticalScrollBar->setRange(0, std::numeric_limits::max()); verticalScrollBar->setSingleStep(8); verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget)); verticalScrollBar->show(); connect(verticalScrollBar, &QScrollBar::valueChanged, this, &CPUDebugger::updateDisasm); registerTextEdit->setReadOnly(true); - connect(goToPCButton, &QPushButton::clicked, this, [&]() { - u32 pc = emu->getCPU().getReg(15); - verticalScrollBar->setValue(pc); + connect(goToPCButton, &QPushButton::clicked, this, [&]() { scrollToPC(); }); + + // We have a QTimer that triggers every 500ms to update our widget when it's active + updateTimer = new QTimer(this); + connect(updateTimer, &QTimer::timeout, this, &CPUDebugger::update); + + // Go to address when the "Go to address" button is pressed, or when we press enter inside the address input box + connect(goToAddressButton, &QPushButton::clicked, this, [&]() { + QString text = addressInput->text().trimmed(); + + bool validAddr = false; + u32 addr = text.toUInt(&validAddr, 16); // Parse address as hex + if (validAddr) { + verticalScrollBar->setValue(addr); + } else { + addressInput->setText(tr("Invalid hexadecimal address")); + } }); + connect(addressInput, &QLineEdit::returnPressed, goToAddressButton, &QPushButton::click); disable(); hide(); @@ -94,22 +118,28 @@ CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), Q void CPUDebugger::enable() { enabled = true; - auto pc = emu->getCPU().getReg(15); disabledOverlay->hide(); - verticalScrollBar->setValue(pc); + scrollToPC(); + // Update the widget every 500ms + updateTimer->start(500); update(); } void CPUDebugger::disable() { enabled = false; + updateTimer->stop(); disabledOverlay->show(); } void CPUDebugger::update() { if (enabled) { + if (followPC) { + scrollToPC(); + } + updateDisasm(); updateRegisters(); } @@ -154,7 +184,12 @@ void CPUDebugger::updateDisasm() { disasmListWidget->addItem(QString::fromStdString(fmt::format("{:08X} | ???", addr))); } - disasmListWidget->setCurrentRow(currentRow); + disasmListWidget->setCurrentRow(currentRow); +} + +void CPUDebugger::scrollToPC() { + u32 pc = emu->getCPU().getReg(15); + verticalScrollBar->setValue(pc); } void CPUDebugger::updateRegisters() { @@ -211,8 +246,9 @@ void CPUDebugger::showEvent(QShowEvent* event) { enable(); } +// Scroll 1 instruction up or down when the arrow keys are pressed and we're at the edge of the disassembly list void CPUDebugger::keyPressEvent(QKeyEvent* event) { - constexpr usize instructionSize = sizeof(u32); + constexpr usize instructionSize = sizeof(u32); if (event->key() == Qt::Key_Up) { verticalScrollBar->setValue(verticalScrollBar->value() - instructionSize); From 2909e671aaae3d1900e83652fff2a799e069ce59 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 7 Jul 2025 04:37:12 +0300 Subject: [PATCH 3/3] Make address input wider Co-Authored-By: liuk707 <62625900+liuk7071@users.noreply.github.com> --- include/panda_qt/cpu_debugger.hpp | 2 ++ src/panda_qt/cpu_debugger.cpp | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/panda_qt/cpu_debugger.hpp b/include/panda_qt/cpu_debugger.hpp index a0999436..7e3803cd 100644 --- a/include/panda_qt/cpu_debugger.hpp +++ b/include/panda_qt/cpu_debugger.hpp @@ -5,6 +5,7 @@ #include #include +#include "capstone.hpp" #include "emulator.hpp" #include "panda_qt/disabled_widget_overlay.hpp" @@ -22,6 +23,7 @@ class CPUDebugger : public QWidget { bool enabled = false; bool followPC = false; + Common::CapstoneDisassembler disassembler; public: CPUDebugger(Emulator* emulator, QWidget* parent = nullptr); diff --git a/src/panda_qt/cpu_debugger.cpp b/src/panda_qt/cpu_debugger.cpp index 552acc80..b5bcf81e 100644 --- a/src/panda_qt/cpu_debugger.cpp +++ b/src/panda_qt/cpu_debugger.cpp @@ -12,8 +12,6 @@ #include #include -#include "capstone.hpp" - // TODO: Make this actually thread-safe by having it only work when paused static int getLinesInViewport(QListWidget* listWidget) { auto viewportHeight = listWidget->viewport()->height(); @@ -30,7 +28,7 @@ static std::pair getVisibleLineRange(QListWidget* listWidget, QScrollB return {firstLine, lineCount}; } -CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), QWidget(parent, Qt::Window) { +CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), disassembler(CS_ARCH_ARM, CS_MODE_ARM), QWidget(parent, Qt::Window) { setWindowTitle(tr("CPU debugger")); resize(1000, 600); @@ -52,7 +50,7 @@ CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), Q connect(followPCCheckBox, &QCheckBox::toggled, this, [&](bool checked) { followPC = checked; }); addressInput->setPlaceholderText(tr("Address to jump to")); - addressInput->setMaximumWidth(100); + addressInput->setMaximumWidth(150); gridLayout->addLayout(horizontalLayout, 0, 0); @@ -84,7 +82,7 @@ CPUDebugger::CPUDebugger(Emulator* emulator, QWidget* parent) : emu(emulator), Q // Forward scrolling from the list widget to our scrollbar disasmListWidget->installEventFilter(this); - // Annoyingly, due to a Qt limitation we can't set it to U32_MAX + // Annoyingly, due to a Qt limitation we can't set it to U32_MAX verticalScrollBar->setRange(0, std::numeric_limits::max()); verticalScrollBar->setSingleStep(8); verticalScrollBar->setPageStep(getLinesInViewport(disasmListWidget)); @@ -157,7 +155,6 @@ void CPUDebugger::updateDisasm() { auto& mem = emu->getMemory(); u32 pc = cpu.getReg(15); - Common::CapstoneDisassembler disassembler(CS_ARCH_ARM, CS_MODE_ARM); std::string disassembly; for (u32 addr = startPC; addr < endPC; addr += sizeof(u32)) {