Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/SteppingSubscriber.cpp
5667 views
1
// Copyright (c) 2018- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "Common/StringUtils.h"
19
#include "Core/Debugger/Breakpoints.h"
20
#include "Core/Debugger/DisassemblyManager.h"
21
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
22
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
23
#include "Core/Core.h"
24
#include "Core/HLE/HLE.h"
25
#include "Core/HLE/sceKernelThread.h"
26
#include "Core/MIPS/MIPSDebugInterface.h"
27
#include "Core/MIPS/MIPSStackWalk.h"
28
29
using namespace MIPSAnalyst;
30
31
struct WebSocketSteppingState : public DebuggerSubscriber {
32
WebSocketSteppingState() {
33
g_disassemblyManager.setCpu(currentDebugMIPS);
34
}
35
~WebSocketSteppingState() {
36
g_disassemblyManager.clear();
37
}
38
39
void Into(DebuggerRequest &req);
40
void Over(DebuggerRequest &req);
41
void Out(DebuggerRequest &req);
42
void RunUntil(DebuggerRequest &req);
43
void HLE(DebuggerRequest &req);
44
45
protected:
46
uint32_t GetNextAddress(DebugInterface *cpuDebug);
47
int GetNextInstructionCount(DebugInterface *cpuDebug);
48
void PrepareResume();
49
void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID);
50
};
51
52
DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) {
53
auto p = new WebSocketSteppingState();
54
map["cpu.stepInto"] = [p](DebuggerRequest &req) { p->Into(req); };
55
map["cpu.stepOver"] = [p](DebuggerRequest &req) { p->Over(req); };
56
map["cpu.stepOut"] = [p](DebuggerRequest &req) { p->Out(req); };
57
map["cpu.runUntil"] = [p](DebuggerRequest &req) { p->RunUntil(req); };
58
map["cpu.nextHLE"] = [p](DebuggerRequest &req) { p->HLE(req); };
59
return p;
60
}
61
62
static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {
63
if (!req.HasParam("thread")) {
64
if (threadID)
65
*threadID = -1;
66
return currentDebugMIPS;
67
}
68
69
uint32_t uid;
70
if (!req.ParamU32("thread", &uid))
71
return nullptr;
72
73
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
74
if (!cpuDebug)
75
req.Fail("Thread could not be found");
76
if (threadID)
77
*threadID = uid;
78
return cpuDebug;
79
}
80
81
// Single step into the next instruction (cpu.stepInto)
82
//
83
// Parameters:
84
// - thread: optional number indicating the thread id to plan stepping on.
85
//
86
// No immediate response. A cpu.stepping event will be sent once complete.
87
//
88
// Note: any thread can wake the cpu when it hits the next instruction currently.
89
void WebSocketSteppingState::Into(DebuggerRequest &req) {
90
if (!currentDebugMIPS->isAlive())
91
return req.Fail("CPU not started");
92
if (!Core_IsStepping()) {
93
Core_Break(BreakReason::DebugStepInto, 0);
94
return;
95
}
96
97
uint32_t threadID;
98
auto cpuDebug = CPUFromRequest(req, &threadID);
99
if (!cpuDebug)
100
return;
101
102
if (cpuDebug == currentDebugMIPS) {
103
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
104
g_breakpoints.SetSkipFirst(currentMIPS->pc);
105
106
int c = GetNextInstructionCount(cpuDebug);
107
Core_RequestCPUStep(CPUStepType::Into, c);
108
} else {
109
uint32_t breakpointAddress = cpuDebug->GetPC();
110
PrepareResume();
111
// Could have advanced to the breakpoint already in PrepareResume().
112
// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)
113
cpuDebug = CPUFromRequest(req);
114
if (cpuDebug != currentDebugMIPS) {
115
g_breakpoints.AddBreakPoint(breakpointAddress, true);
116
AddThreadCondition(breakpointAddress, threadID);
117
Core_Resume();
118
}
119
}
120
}
121
122
// Step over the next instruction (cpu.stepOver)
123
//
124
// Note: this jumps over function calls, but also delay slots.
125
//
126
// Parameters:
127
// - thread: optional number indicating the thread id to plan stepping on.
128
//
129
// No immediate response. A cpu.stepping event will be sent once complete.
130
//
131
// Note: any thread can wake the cpu when it hits the next instruction currently.
132
void WebSocketSteppingState::Over(DebuggerRequest &req) {
133
if (!currentDebugMIPS->isAlive())
134
return req.Fail("CPU not started");
135
if (!Core_IsStepping())
136
return req.Fail("CPU currently running (cpu.stepping first)");
137
138
uint32_t threadID;
139
auto cpuDebug = CPUFromRequest(req, &threadID);
140
if (!cpuDebug)
141
return;
142
143
MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());
144
uint32_t breakpointAddress = GetNextAddress(cpuDebug);
145
if (info.isBranch) {
146
if (info.isConditional && !info.isLinkedBranch) {
147
if (info.conditionMet) {
148
breakpointAddress = info.branchTarget;
149
} else {
150
// Skip over the delay slot.
151
breakpointAddress += 4;
152
}
153
} else {
154
if (info.isLinkedBranch) {
155
// jal or jalr - a function call. Skip the delay slot.
156
breakpointAddress += 4;
157
} else {
158
// j - for absolute branches, set the breakpoint at the branch target.
159
breakpointAddress = info.branchTarget;
160
}
161
}
162
}
163
164
PrepareResume();
165
// Could have advanced to the breakpoint already in PrepareResume().
166
cpuDebug = CPUFromRequest(req);
167
if (cpuDebug->GetPC() != breakpointAddress) {
168
g_breakpoints.AddBreakPoint(breakpointAddress, true);
169
if (cpuDebug != currentDebugMIPS)
170
AddThreadCondition(breakpointAddress, threadID);
171
Core_Resume();
172
}
173
}
174
175
// Step out of a function based on a stack walk (cpu.stepOut)
176
//
177
// Parameters:
178
// - thread: optional number indicating the thread id to plan stepping on.
179
//
180
// No immediate response. A cpu.stepping event will be sent once complete.
181
//
182
// Note: any thread can wake the cpu when it hits the next instruction currently.
183
void WebSocketSteppingState::Out(DebuggerRequest &req) {
184
if (!currentDebugMIPS->isAlive())
185
return req.Fail("CPU not started");
186
if (!Core_IsStepping())
187
return req.Fail("CPU currently running (cpu.stepping first)");
188
189
uint32_t threadID;
190
auto cpuDebug = CPUFromRequest(req, &threadID);
191
if (!cpuDebug)
192
return;
193
194
auto threads = GetThreadsInfo();
195
uint32_t entry = cpuDebug->GetPC();
196
uint32_t stackTop = 0;
197
for (const DebugThreadInfo &th : threads) {
198
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
199
entry = th.entrypoint;
200
stackTop = th.initialStack;
201
break;
202
}
203
}
204
205
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
206
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
207
auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
208
if (frames.size() < 2) {
209
return req.Fail("Could not find function call to step out into");
210
}
211
212
uint32_t breakpointAddress = frames[1].pc;
213
PrepareResume();
214
// Could have advanced to the breakpoint already in PrepareResume().
215
cpuDebug = CPUFromRequest(req);
216
if (cpuDebug->GetPC() != breakpointAddress) {
217
g_breakpoints.AddBreakPoint(breakpointAddress, true);
218
if (cpuDebug != currentDebugMIPS)
219
AddThreadCondition(breakpointAddress, threadID);
220
Core_Resume();
221
}
222
}
223
224
// Run until a certain address (cpu.runUntil)
225
//
226
// Parameters:
227
// - address: number parameter for destination.
228
//
229
// No immediate response. A cpu.stepping event will be sent once complete.
230
void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {
231
if (!currentDebugMIPS->isAlive()) {
232
return req.Fail("CPU not started");
233
}
234
235
uint32_t address = 0;
236
if (!req.ParamU32("address", &address)) {
237
// Error already sent.
238
return;
239
}
240
241
bool wasAtAddress = currentMIPS->pc == address;
242
PrepareResume();
243
// We may have arrived already if PauseResume() stepped out of a delay slot.
244
if (currentMIPS->pc != address || wasAtAddress) {
245
g_breakpoints.AddBreakPoint(address, true);
246
Core_Resume();
247
}
248
}
249
250
// Jump after the next HLE call (cpu.nextHLE)
251
//
252
// No parameters.
253
//
254
// No immediate response. A cpu.stepping event will be sent once complete.
255
void WebSocketSteppingState::HLE(DebuggerRequest &req) {
256
if (!currentDebugMIPS->isAlive()) {
257
return req.Fail("CPU not started");
258
}
259
260
PrepareResume();
261
hleDebugBreak();
262
Core_Resume();
263
}
264
265
uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {
266
uint32_t current = g_disassemblyManager.getStartAddress(cpuDebug->GetPC());
267
return g_disassemblyManager.getNthNextAddress(current, 1);
268
}
269
270
int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {
271
return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;
272
}
273
274
void WebSocketSteppingState::PrepareResume() {
275
if (currentMIPS->inDelaySlot) {
276
// Delay slot instructions are never joined, so we pass 1.
277
Core_RequestCPUStep(CPUStepType::Into, 1);
278
} else {
279
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
280
g_breakpoints.SetSkipFirst(currentMIPS->pc);
281
}
282
}
283
284
void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {
285
BreakPointCond cond;
286
cond.debug = currentDebugMIPS;
287
cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);
288
if (initExpression(currentDebugMIPS, cond.expressionString.c_str(), cond.expression))
289
g_breakpoints.ChangeBreakPointAddCond(breakpointAddress, cond);
290
}
291
292