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