mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-30 17:35:09 +02:00
step-over, step-out and run-until plant a one-shot breakpoint at the address they want execution to return to. Keeping it in breakPoints_ alongside the user's own meant the two kept colliding: - Adding a log-only user breakpoint at the same address hijacked the temporary one. AddBreakPoint() didn't match across temp-ness so both existed, and then ChangeBreakPoint() looked up "the first enabled breakpoint at this address" - a log-only breakpoint isn't enabled, so the temporary one won and had its action overwritten to log-only. It lost PAUSE and the step never came back. - RemoveBreakPoint() erased up to two entries per address to catch an overlapping temporary one, so deleting either deleted both - including the interpreter's cleanup path in CheckExecBreakpoints() taking the user's breakpoint with it. - ExecBreakPoint() handled one breakpoint per address, so with both at the same address only one of them did anything: the step completed but the user's log line never printed. - Nothing dropped it when something *else* stopped us first, so an interrupted step left a breakpoint armed at an address nobody was waiting for anymore, which later fired as a phantom stop. It's a single TempBreakPoint member now, invisible to the breakpoint lists and untouched by user edits. One is enough: step over/out and cross-thread step into all require the CPU to already be stepping and resume it immediately, so only one can be in flight, and run-until now replaces rather than stacking (two pending run-untils had no coherent meaning, and the loser stayed armed). Behavior follows what other debuggers do. Both breakpoints at an address are evaluated independently and their actions combine, so a log-only breakpoint logs without stopping and still lets the step finish. Core_Break() drops the temporary breakpoint on any stop, whatever the reason - the same way gdb deletes its step-resume breakpoint and lldb discards the thread plan. Two things to be careful of, both covered by the new TempBreakpoints test: HasBreakPoints() has to account for it, or the interpreter's checked run loop and the JIT skip breakpoint checking entirely and a step with no user breakpoints set never returns; and IsAddressBreakPoint() (user-facing, for the lists and disassembly markers) is now separate from NeedsBreakCheckAt() (what the JIT frontends and interpreter ask), since only the latter should see it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
300 lines
8.6 KiB
C++
300 lines
8.6 KiB
C++
#include <cstdio>
|
|
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Math/expression_parser.h"
|
|
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
#include "BreakpointWindow.h"
|
|
#include "../resource.h"
|
|
|
|
INT_PTR CALLBACK BreakpointWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
|
|
BreakpointWindow *thiz;
|
|
if (iMsg == WM_INITDIALOG) {
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam);
|
|
thiz = (BreakpointWindow *)lParam;
|
|
}
|
|
else {
|
|
thiz = (BreakpointWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
}
|
|
|
|
if (!thiz)
|
|
return FALSE;
|
|
return thiz->DlgFunc(hWnd, iMsg, wParam, lParam);
|
|
}
|
|
|
|
INT_PTR BreakpointWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
char str[128];
|
|
|
|
switch (iMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_EXECUTE), BM_SETCHECK, memory ? BST_UNCHECKED : BST_CHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_MEMORY), BM_SETCHECK, memory ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_READ), BM_SETCHECK, read ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_WRITE), BM_SETCHECK, write ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_ONCHANGE), BM_SETCHECK, onChange ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_ENABLED), BM_SETCHECK, enabled ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
SendMessage(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG), BM_SETCHECK, log ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_READ), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_WRITE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_ONCHANGE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), log);
|
|
|
|
if (address != -1) {
|
|
snprintf(str, sizeof(str), "0x%08X", address);
|
|
SetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_ADDRESS), str);
|
|
}
|
|
|
|
snprintf(str, sizeof(str), "0x%08X", size);
|
|
SetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), str);
|
|
|
|
SetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_CONDITION), ConvertUTF8ToWString(condition).c_str());
|
|
SetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), ConvertUTF8ToWString(logFormat).c_str());
|
|
return TRUE;
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDC_BREAKPOINT_EXECUTE:
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case BN_CLICKED:
|
|
memory = false;
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_READ), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_WRITE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_ONCHANGE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), memory);
|
|
break;
|
|
}
|
|
break;
|
|
case IDC_BREAKPOINT_MEMORY:
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case BN_CLICKED:
|
|
memory = true;
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_READ), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_WRITE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_ONCHANGE), memory);
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), memory);
|
|
break;
|
|
}
|
|
break;
|
|
case IDC_BREAKPOINT_LOG:
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case BN_CLICKED:
|
|
EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), GetCheckState(hwnd, IDC_BREAKPOINT_LOG));
|
|
break;
|
|
}
|
|
break;
|
|
case IDC_BREAKPOINT_OK:
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case BN_CLICKED:
|
|
if (fetchDialogData(hwnd)) {
|
|
EndDialog(hwnd, true);
|
|
}
|
|
break;
|
|
};
|
|
break;
|
|
case IDC_BREAKPOINT_CANCEL:
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case BN_CLICKED:
|
|
EndDialog(hwnd, false);
|
|
break;
|
|
};
|
|
break;
|
|
case IDOK:
|
|
if (fetchDialogData(hwnd)) {
|
|
EndDialog(hwnd, true);
|
|
}
|
|
break;
|
|
case IDCANCEL:
|
|
EndDialog(hwnd, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool BreakpointWindow::fetchDialogData(HWND hwnd)
|
|
{
|
|
char str[256], errorMessage[512];
|
|
PostfixExpression exp;
|
|
|
|
memory = GetCheckState(hwnd, IDC_BREAKPOINT_MEMORY);
|
|
read = GetCheckState(hwnd, IDC_BREAKPOINT_READ);
|
|
write = GetCheckState(hwnd, IDC_BREAKPOINT_WRITE);
|
|
enabled = GetCheckState(hwnd, IDC_BREAKPOINT_ENABLED);
|
|
log = GetCheckState(hwnd, IDC_BREAKPOINT_LOG);
|
|
onChange = GetCheckState(hwnd, IDC_BREAKPOINT_ONCHANGE);
|
|
|
|
// parse address
|
|
GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_ADDRESS), str, 256);
|
|
if (initExpression(cpu, str, exp) == false)
|
|
{
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError());
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
|
|
if (parseExpression(cpu, exp, address) == false)
|
|
{
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError());
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
|
|
if (memory)
|
|
{
|
|
// parse size
|
|
GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), str, 256);
|
|
if (initExpression(cpu, str, exp) == false)
|
|
{
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError());
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
|
|
if (parseExpression(cpu, exp, size) == false)
|
|
{
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError());
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// condition
|
|
wchar_t tempCond[512];
|
|
GetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_CONDITION), tempCond, 512);
|
|
condition = ConvertWStringToUTF8(tempCond);
|
|
compiledCondition.clear();
|
|
if (!condition.empty())
|
|
{
|
|
if (initExpression(cpu, condition.c_str(), compiledCondition) == false)
|
|
{
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", condition.c_str(), getExpressionError());
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
wchar_t tempLogFormat[512];
|
|
GetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), tempLogFormat, 512);
|
|
logFormat = ConvertWStringToUTF8(tempLogFormat);
|
|
if (!g_breakpoints.ValidateLogFormat(cpu, logFormat)) {
|
|
snprintf(errorMessage, sizeof(errorMessage), "Invalid log format (example: \"{a1}\").");
|
|
MessageBoxA(hwnd, errorMessage, "Error", MB_OK);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BreakpointWindow::GetCheckState(HWND hwnd, int dlgItem) {
|
|
return SendMessage(GetDlgItem(hwnd, dlgItem), BM_GETCHECK, 0, 0) != 0;
|
|
}
|
|
|
|
bool BreakpointWindow::exec() {
|
|
return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_BREAKPOINT), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0;
|
|
}
|
|
|
|
void BreakpointWindow::addBreakpoint() {
|
|
BreakAction result = BREAK_ACTION_NONE;
|
|
if (log)
|
|
result |= BREAK_ACTION_LOG;
|
|
if (enabled)
|
|
result |= BREAK_ACTION_PAUSE;
|
|
|
|
if (memory) {
|
|
// add memcheck
|
|
int cond = 0;
|
|
if (read)
|
|
cond |= MEMCHECK_READ;
|
|
if (write)
|
|
cond |= MEMCHECK_WRITE;
|
|
if (onChange)
|
|
cond |= MEMCHECK_WRITE_ONCHANGE;
|
|
|
|
g_breakpoints.AddMemCheck(address, address + size, (MemCheckCondition)cond, result);
|
|
|
|
if (!condition.empty()) {
|
|
BreakPointCond cond;
|
|
cond.debug = cpu;
|
|
cond.expressionString = condition;
|
|
cond.expression = compiledCondition;
|
|
g_breakpoints.ChangeMemCheckAddCond(address, address + size, cond);
|
|
}
|
|
|
|
g_breakpoints.ChangeMemCheckLogFormat(address, address + size, logFormat);
|
|
}
|
|
else {
|
|
// add breakpoint
|
|
g_breakpoints.AddBreakPoint(address);
|
|
|
|
if (!condition.empty()) {
|
|
BreakPointCond cond;
|
|
cond.debug = cpu;
|
|
cond.expressionString = condition;
|
|
cond.expression = compiledCondition;
|
|
g_breakpoints.ChangeBreakPointAddCond(address, cond);
|
|
}
|
|
|
|
g_breakpoints.ChangeBreakPoint(address, result);
|
|
g_breakpoints.ChangeBreakPointLogFormat(address, logFormat);
|
|
}
|
|
}
|
|
|
|
void BreakpointWindow::loadFromMemcheck(const MemCheck &memcheck) {
|
|
memory = true;
|
|
|
|
read = (memcheck.cond & MEMCHECK_READ) != 0;
|
|
write = (memcheck.cond & MEMCHECK_WRITE) != 0;
|
|
onChange = (memcheck.cond & MEMCHECK_WRITE_ONCHANGE) != 0;
|
|
|
|
log = (memcheck.action & BREAK_ACTION_LOG) != 0;
|
|
enabled = (memcheck.action & BREAK_ACTION_PAUSE) != 0;
|
|
|
|
address = memcheck.start;
|
|
size = memcheck.end - address;
|
|
|
|
if (memcheck.hasCondition) {
|
|
condition = memcheck.condition.expressionString;
|
|
}
|
|
else {
|
|
condition.clear();
|
|
}
|
|
|
|
logFormat = memcheck.logFormat;
|
|
}
|
|
|
|
void BreakpointWindow::loadFromBreakpoint(const BreakPoint& breakpoint) {
|
|
memory = false;
|
|
|
|
log = (breakpoint.action & BREAK_ACTION_LOG) != 0;
|
|
enabled = (breakpoint.action & BREAK_ACTION_PAUSE) != 0;
|
|
address = breakpoint.addr;
|
|
size = 1;
|
|
|
|
if (breakpoint.hasCond) {
|
|
condition = breakpoint.cond.expressionString;
|
|
}
|
|
else {
|
|
condition.clear();
|
|
}
|
|
|
|
logFormat = breakpoint.logFormat;
|
|
}
|
|
|
|
void BreakpointWindow::initBreakpoint(u32 _address) {
|
|
memory = false;
|
|
enabled = true;
|
|
address = _address;
|
|
size = 1;
|
|
condition.clear();
|
|
}
|