Skip to content

Commit

Permalink
Debugger: Untangle the breakpoints data flow to resolve races
Browse files Browse the repository at this point in the history
Tightens the data flow between the CPU and UI threads
to resolve multiple race conditions, such as:
1. Unbinding a debug interface update CB while it's in use,
    causing a possible use-after-free.
2. Binding breakpoints via the disassembly widget that would read
    a stale local variable, and bind the breakpoint to a bogus address

+ probably more subtle races that are now resolved
  • Loading branch information
CookiePLMonster committed Apr 7, 2024
1 parent 708c25a commit 895bd34
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 87 deletions.
32 changes: 23 additions & 9 deletions pcsx2-qt/Debugger/CpuWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
connect(m_ui.registerWidget, &RegisterWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);

connect(m_ui.disassemblyWidget, &DisassemblyWidget::breakpointsChanged, this, &CpuWidget::updateBreakpoints);

connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &CpuWidget::onBPListContextMenu);
connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &CpuWidget::onBPListDoubleClicked);

Expand All @@ -74,6 +76,8 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
i++;
}

connect(&m_bpModel, &BreakpointModel::dataChanged, this, &CpuWidget::updateBreakpoints);

connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu);
connect(m_ui.threadList, &QTableView::doubleClicked, this, &CpuWidget::onThreadListDoubleClick);

Expand Down Expand Up @@ -160,6 +164,16 @@ void CpuWidget::refreshDebugger()
}
}

void CpuWidget::reloadCPUWidgets()
{
updateThreads();
updateStackFrames();

m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
}

void CpuWidget::paintEvent(QPaintEvent* event)
{
m_ui.registerWidget->update();
Expand Down Expand Up @@ -205,9 +219,9 @@ void CpuWidget::onStepInto()
if (info.isSyscall)
bpAddr = info.branchTarget; // Syscalls are always taken

Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), bpAddr, true);
m_cpu.resumeCpu();
Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});

this->repaint();
Expand All @@ -224,9 +238,9 @@ void CpuWidget::onStepOut()
if (m_stackModel.rowCount() < 2)
return;

Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), m_stackModel.data(m_stackModel.index(1, StackModel::PC), Qt::UserRole).toUInt(), true);
m_cpu.resumeCpu();
Host::RunOnCPUThread([cpu = &m_cpu, stackModel = &m_stackModel] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), stackModel->data(stackModel->index(1, StackModel::PC), Qt::UserRole).toUInt(), true);
cpu->resumeCpu();
});

this->repaint();
Expand Down Expand Up @@ -270,9 +284,9 @@ void CpuWidget::onStepOver()
}
}

Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), bpAddr, true);
m_cpu.resumeCpu();
Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});

this->repaint();
Expand Down
24 changes: 1 addition & 23 deletions pcsx2-qt/Debugger/CpuWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
#include "ui_CpuWidget.h"

#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/BiosDebugData.h"
#include "DebugTools/MipsStackWalk.h"

#include "Models/BreakpointModel.h"
#include "Models/ThreadModel.h"
Expand Down Expand Up @@ -72,26 +69,7 @@ public slots:
void onModuleTreeContextMenu(QPoint pos);
void onModuleTreeDoubleClick(QTreeWidgetItem* item);
void refreshDebugger();
void reloadCPUWidgets()
{
if (!QtHost::IsOnUIThread())
{
const auto& updateHandler = CBreakPoints::GetUpdateHandler();
if (updateHandler)
{
QtHost::RunOnUIThread(updateHandler);
}
return;
}

updateBreakpoints();
updateThreads();
updateStackFrames();

m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
};
void reloadCPUWidgets();

void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings();
Expand Down
13 changes: 1 addition & 12 deletions pcsx2-qt/Debugger/DebuggerWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,9 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)

m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900");
m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000");

CBreakPoints::SetUpdateHandler(std::bind(&DebuggerWindow::onBreakpointsChanged, this));
}

DebuggerWindow::~DebuggerWindow()
{
CBreakPoints::SetUpdateHandler(nullptr);
}
DebuggerWindow::~DebuggerWindow() = default;

// There is no straightforward way to set the tab text to bold in Qt
// Sorry colour blind people, but this is the best we can do for now
Expand Down Expand Up @@ -132,9 +127,3 @@ void DebuggerWindow::onStepOut()
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
currentCpu->onStepOut();
}

void DebuggerWindow::onBreakpointsChanged()
{
m_cpuWidget_r5900->reloadCPUWidgets();
m_cpuWidget_r3000->reloadCPUWidgets();
}
1 change: 0 additions & 1 deletion pcsx2-qt/Debugger/DebuggerWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public slots:
void onStepInto();
void onStepOver();
void onStepOut();
void onBreakpointsChanged();

private:
Ui::DebuggerWindow m_ui;
Expand Down
36 changes: 20 additions & 16 deletions pcsx2-qt/Debugger/DisassemblyWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void DisassemblyWidget::contextAssembleInstruction()
this->m_nopedInstructions.insert({i, cpu->read32(i)});
cpu->write32(i, val);
}
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}
}
Expand All @@ -91,7 +91,7 @@ void DisassemblyWidget::contextNoopInstruction()
this->m_nopedInstructions.insert({i, cpu->read32(i)});
cpu->write32(i, 0x00);
}
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}

Expand All @@ -106,15 +106,16 @@ void DisassemblyWidget::contextRestoreInstruction()
this->m_nopedInstructions.erase(i);
}
}
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}

void DisassemblyWidget::contextRunToCursor()
{
Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart, true);
m_cpu->resumeCpu();
const u32 selectedAddressStart = m_selectedAddressStart;
Host::RunOnCPUThread([cpu = m_cpu, selectedAddressStart] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), selectedAddressStart, true);
cpu->resumeCpu();
});
}

Expand All @@ -129,13 +130,15 @@ void DisassemblyWidget::contextToggleBreakpoint()
if (!m_cpu->isAlive())
return;

if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart))
const u32 selectedAddressStart = m_selectedAddressStart;
const BreakPointCpu cpuType = m_cpu->getCpuType();
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddressStart))
{
Host::RunOnCPUThread([&] { CBreakPoints::RemoveBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart); });
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddressStart); });
}
else
{
Host::RunOnCPUThread([&] { CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart); });
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::AddBreakPoint(cpuType, selectedAddressStart); });
}

breakpointsChanged();
Expand Down Expand Up @@ -280,7 +283,7 @@ void DisassemblyWidget::contextStubFunction()
this->m_stubbedFunctions.insert({curFuncAddress, {cpu->read32(curFuncAddress), cpu->read32(curFuncAddress + 4)}});
cpu->write32(curFuncAddress, 0x03E00008); // jr $ra
cpu->write32(curFuncAddress + 4, 0x00000000); // nop
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}
else // Stub the current opcode instead
Expand All @@ -289,7 +292,7 @@ void DisassemblyWidget::contextStubFunction()
this->m_stubbedFunctions.insert({m_selectedAddressStart, {cpu->read32(m_selectedAddressStart), cpu->read32(m_selectedAddressStart + 4)}});
cpu->write32(m_selectedAddressStart, 0x03E00008); // jr $ra
cpu->write32(m_selectedAddressStart + 4, 0x00000000); // nop
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}
}
Expand All @@ -303,7 +306,7 @@ void DisassemblyWidget::contextRestoreFunction()
cpu->write32(curFuncAddress, std::get<0>(this->m_stubbedFunctions[curFuncAddress]));
cpu->write32(curFuncAddress + 4, std::get<1>(this->m_stubbedFunctions[curFuncAddress]));
this->m_stubbedFunctions.erase(curFuncAddress);
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}
else if (m_stubbedFunctions.find(m_selectedAddressStart) != m_stubbedFunctions.end())
Expand All @@ -312,7 +315,7 @@ void DisassemblyWidget::contextRestoreFunction()
cpu->write32(m_selectedAddressStart, std::get<0>(this->m_stubbedFunctions[m_selectedAddressStart]));
cpu->write32(m_selectedAddressStart + 4, std::get<1>(this->m_stubbedFunctions[m_selectedAddressStart]));
this->m_stubbedFunctions.erase(m_selectedAddressStart);
QtHost::RunOnUIThread([this] { VMUpdate(); });
emit VMUpdate();
});
}
else
Expand Down Expand Up @@ -551,13 +554,14 @@ void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event)
return;

const u32 selectedAddress = (static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart;
if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), selectedAddress))
const BreakPointCpu cpuType = m_cpu->getCpuType();
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddress))
{
Host::RunOnCPUThread([&] { CBreakPoints::RemoveBreakPoint(m_cpu->getCpuType(), selectedAddress); });
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddress); });
}
else
{
Host::RunOnCPUThread([&] { CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), selectedAddress); });
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::AddBreakPoint(cpuType, selectedAddress); });
}
breakpointsChanged();
this->repaint();
Expand Down
32 changes: 17 additions & 15 deletions pcsx2-qt/Debugger/Models/BreakpointModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
MemCheckResult(mc.result ^ MEMCHECK_BREAK));
});
}
emit dataChanged(index, index);
return true;
}
else if (role == Qt::EditRole && index.column() == BreakpointColumns::CONDITION)
Expand Down Expand Up @@ -336,8 +337,9 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp, cond] {
CBreakPoints::ChangeBreakPointAddCond(cpu, bp.addr, cond);
});
return true;
}
emit dataChanged(index, index);
return true;
}

return false;
Expand All @@ -364,6 +366,9 @@ bool BreakpointModel::removeRows(int row, int count, const QModelIndex& index)
});
}
}
const auto begin = m_breakpoints.begin() + row;
const auto end = begin + count;
m_breakpoints.erase(begin, end);

endRemoveRows();
return true;
Expand Down Expand Up @@ -407,23 +412,20 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break

void BreakpointModel::refreshData()
{
beginResetModel();
Host::RunOnCPUThread([this]() mutable {

m_breakpoints.clear();
std::vector<BreakpointMemcheck> all_breakpoints;
std::ranges::move(CBreakPoints::GetBreakpoints(m_cpu.getCpuType(), false), std::back_inserter(all_breakpoints));
std::ranges::move(CBreakPoints::GetMemChecks(m_cpu.getCpuType()), std::back_inserter(all_breakpoints));

auto breakpoints = CBreakPoints::GetBreakpoints(m_cpu.getCpuType(), false);
for (const auto& bp : breakpoints)
{
m_breakpoints.push_back(bp);
}
QtHost::RunOnUIThread([this, breakpoints = std::move(all_breakpoints)]() mutable {

beginResetModel();
m_breakpoints = std::move(breakpoints);
endResetModel();
});

auto memchecks = CBreakPoints::GetMemChecks(m_cpu.getCpuType());
for (const auto& mc : memchecks)
{
m_breakpoints.push_back(mc);
}

endResetModel();
});
}

void BreakpointModel::loadBreakpointFromFieldList(QStringList fields)
Expand Down
4 changes: 0 additions & 4 deletions pcsx2/DebugTools/Breakpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ std::vector<MemCheck*> CBreakPoints::cleanupMemChecks_;
bool CBreakPoints::breakpointTriggered_ = false;
BreakPointCpu CBreakPoints::breakpointTriggeredCpu_;
bool CBreakPoints::corePaused = false;
std::function<void()> CBreakPoints::cb_bpUpdated_;

// called from the dynarec
u32 standardizeBreakpointAddress(u32 addr)
Expand Down Expand Up @@ -440,7 +439,4 @@ void CBreakPoints::Update(BreakPointCpu cpu, u32 addr)

if (resume)
r5900Debug.resumeCpu();

if (cb_bpUpdated_)
cb_bpUpdated_();
}
7 changes: 0 additions & 7 deletions pcsx2/DebugTools/Breakpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#pragma once

#include <algorithm>
#include <functional>
#include <iterator>
#include <vector>

Expand Down Expand Up @@ -150,10 +149,6 @@ class CBreakPoints
static bool GetCorePaused() { return corePaused; };
static void SetCorePaused(bool b) { corePaused = b; };

// This will have to do until a full fledged debugger host interface is made
static void SetUpdateHandler(std::function<void()> f) {cb_bpUpdated_ = std::move(f); };
static const std::function<void()>& GetUpdateHandler() { return cb_bpUpdated_; };

private:
static size_t FindBreakpoint(BreakPointCpu cpu, u32 addr, bool matchTemp = false, bool temp = false);
// Finds exactly, not using a range check.
Expand All @@ -169,8 +164,6 @@ class CBreakPoints
static BreakPointCpu breakpointTriggeredCpu_;
static bool corePaused;

static std::function<void()> cb_bpUpdated_;

static std::vector<MemCheck> memChecks_;
static std::vector<MemCheck *> cleanupMemChecks_;
};
Expand Down

0 comments on commit 895bd34

Please sign in to comment.