CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/SteppingSubscriber.cpp
Views: 1401
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
disasm_.setCpu(currentDebugMIPS);
34
}
35
~WebSocketSteppingState() {
36
disasm_.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
DisassemblyManager disasm_;
52
};
53
54
DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) {
55
auto p = new WebSocketSteppingState();
56
map["cpu.stepInto"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1);
57
map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1);
58
map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1);
59
map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1);
60
map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1);
61
62
return p;
63
}
64
65
static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {
66
if (!req.HasParam("thread")) {
67
if (threadID)
68
*threadID = -1;
69
return currentDebugMIPS;
70
}
71
72
uint32_t uid;
73
if (!req.ParamU32("thread", &uid))
74
return nullptr;
75
76
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
77
if (!cpuDebug)
78
req.Fail("Thread could not be found");
79
if (threadID)
80
*threadID = uid;
81
return cpuDebug;
82
}
83
84
// Single step into the next instruction (cpu.stepInto)
85
//
86
// Parameters:
87
// - thread: optional number indicating the thread id to plan stepping on.
88
//
89
// No immediate response. A cpu.stepping event will be sent once complete.
90
//
91
// Note: any thread can wake the cpu when it hits the next instruction currently.
92
void WebSocketSteppingState::Into(DebuggerRequest &req) {
93
if (!currentDebugMIPS->isAlive())
94
return req.Fail("CPU not started");
95
if (!Core_IsStepping()) {
96
Core_EnableStepping(true, "cpu.stepInto", 0);
97
return;
98
}
99
100
uint32_t threadID;
101
auto cpuDebug = CPUFromRequest(req, &threadID);
102
if (!cpuDebug)
103
return;
104
105
if (cpuDebug == currentDebugMIPS) {
106
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
107
CBreakPoints::SetSkipFirst(currentMIPS->pc);
108
109
int c = GetNextInstructionCount(cpuDebug);
110
for (int i = 0; i < c; ++i) {
111
Core_DoSingleStep();
112
}
113
} else {
114
uint32_t breakpointAddress = cpuDebug->GetPC();
115
PrepareResume();
116
// Could have advanced to the breakpoint already in PrepareResume().
117
// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)
118
cpuDebug = CPUFromRequest(req);
119
if (cpuDebug != currentDebugMIPS) {
120
CBreakPoints::AddBreakPoint(breakpointAddress, true);
121
AddThreadCondition(breakpointAddress, threadID);
122
Core_EnableStepping(false);
123
}
124
}
125
}
126
127
// Step over the next instruction (cpu.stepOver)
128
//
129
// Note: this jumps over function calls, but also delay slots.
130
//
131
// Parameters:
132
// - thread: optional number indicating the thread id to plan stepping on.
133
//
134
// No immediate response. A cpu.stepping event will be sent once complete.
135
//
136
// Note: any thread can wake the cpu when it hits the next instruction currently.
137
void WebSocketSteppingState::Over(DebuggerRequest &req) {
138
if (!currentDebugMIPS->isAlive())
139
return req.Fail("CPU not started");
140
if (!Core_IsStepping())
141
return req.Fail("CPU currently running (cpu.stepping first)");
142
143
uint32_t threadID;
144
auto cpuDebug = CPUFromRequest(req, &threadID);
145
if (!cpuDebug)
146
return;
147
148
MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());
149
uint32_t breakpointAddress = GetNextAddress(cpuDebug);
150
if (info.isBranch) {
151
if (info.isConditional && !info.isLinkedBranch) {
152
if (info.conditionMet) {
153
breakpointAddress = info.branchTarget;
154
} else {
155
// Skip over the delay slot.
156
breakpointAddress += 4;
157
}
158
} else {
159
if (info.isLinkedBranch) {
160
// jal or jalr - a function call. Skip the delay slot.
161
breakpointAddress += 4;
162
} else {
163
// j - for absolute branches, set the breakpoint at the branch target.
164
breakpointAddress = info.branchTarget;
165
}
166
}
167
}
168
169
PrepareResume();
170
// Could have advanced to the breakpoint already in PrepareResume().
171
cpuDebug = CPUFromRequest(req);
172
if (cpuDebug->GetPC() != breakpointAddress) {
173
CBreakPoints::AddBreakPoint(breakpointAddress, true);
174
if (cpuDebug != currentDebugMIPS)
175
AddThreadCondition(breakpointAddress, threadID);
176
Core_EnableStepping(false);
177
}
178
}
179
180
// Step out of a function based on a stack walk (cpu.stepOut)
181
//
182
// Parameters:
183
// - thread: optional number indicating the thread id to plan stepping on.
184
//
185
// No immediate response. A cpu.stepping event will be sent once complete.
186
//
187
// Note: any thread can wake the cpu when it hits the next instruction currently.
188
void WebSocketSteppingState::Out(DebuggerRequest &req) {
189
if (!currentDebugMIPS->isAlive())
190
return req.Fail("CPU not started");
191
if (!Core_IsStepping())
192
return req.Fail("CPU currently running (cpu.stepping first)");
193
194
uint32_t threadID;
195
auto cpuDebug = CPUFromRequest(req, &threadID);
196
if (!cpuDebug)
197
return;
198
199
auto threads = GetThreadsInfo();
200
uint32_t entry = cpuDebug->GetPC();
201
uint32_t stackTop = 0;
202
for (const DebugThreadInfo &th : threads) {
203
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
204
entry = th.entrypoint;
205
stackTop = th.initialStack;
206
break;
207
}
208
}
209
210
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
211
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
212
auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
213
if (frames.size() < 2) {
214
return req.Fail("Could not find function call to step out into");
215
}
216
217
uint32_t breakpointAddress = frames[1].pc;
218
PrepareResume();
219
// Could have advanced to the breakpoint already in PrepareResume().
220
cpuDebug = CPUFromRequest(req);
221
if (cpuDebug->GetPC() != breakpointAddress) {
222
CBreakPoints::AddBreakPoint(breakpointAddress, true);
223
if (cpuDebug != currentDebugMIPS)
224
AddThreadCondition(breakpointAddress, threadID);
225
Core_EnableStepping(false);
226
}
227
}
228
229
// Run until a certain address (cpu.runUntil)
230
//
231
// Parameters:
232
// - address: number parameter for destination.
233
//
234
// No immediate response. A cpu.stepping event will be sent once complete.
235
void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {
236
if (!currentDebugMIPS->isAlive()) {
237
return req.Fail("CPU not started");
238
}
239
240
uint32_t address = 0;
241
if (!req.ParamU32("address", &address)) {
242
// Error already sent.
243
return;
244
}
245
246
bool wasAtAddress = currentMIPS->pc == address;
247
PrepareResume();
248
// We may have arrived already if PauseResume() stepped out of a delay slot.
249
if (currentMIPS->pc != address || wasAtAddress) {
250
CBreakPoints::AddBreakPoint(address, true);
251
Core_EnableStepping(false);
252
}
253
}
254
255
// Jump after the next HLE call (cpu.nextHLE)
256
//
257
// No parameters.
258
//
259
// No immediate response. A cpu.stepping event will be sent once complete.
260
void WebSocketSteppingState::HLE(DebuggerRequest &req) {
261
if (!currentDebugMIPS->isAlive()) {
262
return req.Fail("CPU not started");
263
}
264
265
PrepareResume();
266
hleDebugBreak();
267
Core_EnableStepping(false);
268
}
269
270
uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {
271
uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC());
272
return disasm_.getNthNextAddress(current, 1);
273
}
274
275
int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {
276
return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;
277
}
278
279
void WebSocketSteppingState::PrepareResume() {
280
if (currentMIPS->inDelaySlot) {
281
Core_DoSingleStep();
282
} else {
283
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
284
CBreakPoints::SetSkipFirst(currentMIPS->pc);
285
}
286
}
287
288
void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {
289
BreakPointCond cond;
290
cond.debug = currentDebugMIPS;
291
cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);
292
if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression))
293
CBreakPoints::ChangeBreakPointAddCond(breakpointAddress, cond);
294
}
295
296