Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/CPUCoreSubscriber.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/Core.h"
20
#include "Core/System.h"
21
#include "Core/CoreTiming.h"
22
#include "Core/Debugger/Breakpoints.h"
23
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
24
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
25
#include "Core/HLE/sceKernelThread.h"
26
#include "Core/MIPS/MIPS.h"
27
#include "Core/MIPS/MIPSDebugInterface.h"
28
#include "Core/Reporting.h"
29
30
DebuggerSubscriber *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) {
31
// No need to bind or alloc state, these are all global.
32
map["cpu.stepping"] = &WebSocketCPUStepping;
33
map["cpu.resume"] = &WebSocketCPUResume;
34
map["cpu.status"] = &WebSocketCPUStatus;
35
map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs;
36
map["cpu.getReg"] = &WebSocketCPUGetReg;
37
map["cpu.setReg"] = &WebSocketCPUSetReg;
38
map["cpu.evaluate"] = &WebSocketCPUEvaluate;
39
40
return nullptr;
41
}
42
43
static std::string RegValueAsFloat(uint32_t u) {
44
union {
45
uint32_t u;
46
float f;
47
} bits = { u };
48
return StringFromFormat("%f", bits.f);
49
}
50
51
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
52
if (!req.HasParam("thread"))
53
return currentDebugMIPS;
54
55
u32 uid;
56
if (!req.ParamU32("thread", &uid))
57
return nullptr;
58
59
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
60
if (!cpuDebug)
61
req.Fail("Thread could not be found");
62
return cpuDebug;
63
}
64
65
// Begin stepping and pause the CPU (cpu.stepping)
66
//
67
// No parameters.
68
//
69
// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent.
70
void WebSocketCPUStepping(DebuggerRequest &req) {
71
if (!currentDebugMIPS->isAlive()) {
72
return req.Fail("CPU not started");
73
}
74
if (!Core_IsStepping() && Core_IsActive()) {
75
Core_Break(BreakReason::DebugStep, 0);
76
}
77
}
78
79
// Stop stepping and resume the CPU (cpu.resume)
80
//
81
// No parameters.
82
//
83
// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent.
84
void WebSocketCPUResume(DebuggerRequest &req) {
85
if (!currentDebugMIPS->isAlive()) {
86
return req.Fail("CPU not started");
87
}
88
if (!Core_IsStepping() || coreState == CORE_POWERDOWN) {
89
return req.Fail("CPU not stepping");
90
}
91
92
g_breakpoints.SetSkipFirst(currentMIPS->pc);
93
if (currentMIPS->inDelaySlot) {
94
Core_RequestCPUStep(CPUStepType::Into, 1);
95
}
96
Core_Resume();
97
}
98
99
// Request the current CPU status (cpu.status)
100
//
101
// No parameters.
102
//
103
// Response (same event name):
104
// - stepping: boolean, CPU currently stepping.
105
// - paused: boolean, CPU paused or not started yet.
106
// - pc: number value of PC register (inaccurate unless stepping.)
107
// - ticks: number of CPU cycles into emulation.
108
void WebSocketCPUStatus(DebuggerRequest &req) {
109
JsonWriter &json = req.Respond();
110
111
const bool pspInited = PSP_GetBootState() == BootState::Complete;
112
113
json.writeBool("stepping", pspInited && Core_IsStepping() && coreState != CORE_POWERDOWN);
114
json.writeBool("paused", GetUIState() != UISTATE_INGAME);
115
// Avoid NULL deference.
116
json.writeUint("pc", pspInited ? currentMIPS->pc : 0);
117
// A double ought to be good enough for a 156 day debug session.
118
json.writeFloat("ticks", pspInited ? CoreTiming::GetTicks() : 0);
119
}
120
121
// Retrieve all regs and their values (cpu.getAllRegs)
122
//
123
// Parameters:
124
// - thread: optional number indicating the thread id to get regs for.
125
//
126
// Response (same event name):
127
// - categories: array of objects:
128
// - id: "category" property to use for other events.
129
// - name: a category name, such as "GPR".
130
// - registerNames: array of string names of the registers (size varies per category.)
131
// - uintValues: array of unsigned integer values for the registers.
132
// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf".
133
void WebSocketCPUGetAllRegs(DebuggerRequest &req) {
134
auto cpuDebug = CPUFromRequest(req);
135
if (!cpuDebug)
136
return;
137
138
JsonWriter &json = req.Respond();
139
140
json.pushArray("categories");
141
for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) {
142
json.pushDict();
143
json.writeInt("id", c);
144
json.writeString("name", MIPSDebugInterface::GetCategoryName(c));
145
146
int total = MIPSDebugInterface::GetNumRegsInCategory(c);
147
148
json.pushArray("registerNames");
149
for (int r = 0; r < total; ++r)
150
json.writeString(MIPSDebugInterface::GetRegName(c, r));
151
if (c == 0) {
152
json.writeString("pc");
153
json.writeString("hi");
154
json.writeString("lo");
155
}
156
json.pop();
157
158
json.pushArray("uintValues");
159
// Writing as floating point to avoid negatives. Actually double, so safe.
160
for (int r = 0; r < total; ++r)
161
json.writeUint(cpuDebug->GetRegValue(c, r));
162
if (c == 0) {
163
json.writeUint(cpuDebug->GetPC());
164
json.writeUint(cpuDebug->GetHi());
165
json.writeUint(cpuDebug->GetLo());
166
}
167
json.pop();
168
169
json.pushArray("floatValues");
170
// Note: String so it can have Infinity and NaN.
171
for (int r = 0; r < total; ++r)
172
json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r)));
173
if (c == 0) {
174
json.writeString(RegValueAsFloat(cpuDebug->GetPC()));
175
json.writeString(RegValueAsFloat(cpuDebug->GetHi()));
176
json.writeString(RegValueAsFloat(cpuDebug->GetLo()));
177
}
178
json.pop();
179
180
json.pop();
181
}
182
json.pop();
183
}
184
185
enum class DebuggerRegType {
186
INVALID,
187
NORMAL,
188
PC,
189
HI,
190
LO,
191
};
192
193
static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {
194
if (name == "pc") {
195
*cat = 0;
196
*reg = 32;
197
return DebuggerRegType::PC;
198
}
199
if (name == "hi") {
200
*cat = 0;
201
*reg = 33;
202
return DebuggerRegType::HI;
203
}
204
if (name == "lo") {
205
*cat = 0;
206
*reg = 34;
207
return DebuggerRegType::LO;
208
}
209
210
for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {
211
int total = currentDebugMIPS->GetNumRegsInCategory(c);
212
for (int r = 0; r < total; ++r) {
213
if (name == currentDebugMIPS->GetRegName(c, r)) {
214
*cat = c;
215
*reg = r;
216
return DebuggerRegType::NORMAL;
217
}
218
}
219
}
220
221
req.Fail("Invalid 'name' parameter");
222
return DebuggerRegType::INVALID;
223
}
224
225
static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {
226
const char *name = req.data.getStringOr("name", nullptr);
227
if (name)
228
return ValidateRegName(req, name, cat, reg);
229
230
*cat = req.data.getInt("category", -1);
231
*reg = req.data.getInt("register", -1);
232
233
if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {
234
req.Fail("Invalid 'category' parameter");
235
return DebuggerRegType::INVALID;
236
}
237
238
// TODO: We fake it for GPR... not sure yet if this is a good thing.
239
if (*cat == 0) {
240
// Intentionally retains the reg value.
241
if (*reg == 32)
242
return DebuggerRegType::PC;
243
if (*reg == 33)
244
return DebuggerRegType::HI;
245
if (*reg == 34)
246
return DebuggerRegType::LO;
247
}
248
249
if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {
250
req.Fail("Invalid 'register' parameter");
251
return DebuggerRegType::INVALID;
252
}
253
254
return DebuggerRegType::NORMAL;
255
}
256
257
// Retrieve the value of a single register (cpu.getReg)
258
//
259
// Parameters (by name):
260
// - thread: optional number indicating the thread id to get from.
261
// - name: string name of register to lookup.
262
//
263
// Parameters (by category id and index, ignored if name specified):
264
// - thread: optional number indicating the thread id to get from.
265
// - category: id of category for the register.
266
// - register: index into array of registers.
267
//
268
// Response (same event name):
269
// - category: id of category for the register.
270
// - register: index into array of registers.
271
// - uintValue: value in register.
272
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
273
void WebSocketCPUGetReg(DebuggerRequest &req) {
274
auto cpuDebug = CPUFromRequest(req);
275
if (!cpuDebug)
276
return;
277
278
int cat, reg;
279
uint32_t val;
280
switch (ValidateCatReg(req, &cat, &reg)) {
281
case DebuggerRegType::NORMAL:
282
val = cpuDebug->GetRegValue(cat, reg);
283
break;
284
285
case DebuggerRegType::PC:
286
val = cpuDebug->GetPC();
287
break;
288
case DebuggerRegType::HI:
289
val = cpuDebug->GetHi();
290
break;
291
case DebuggerRegType::LO:
292
val = cpuDebug->GetLo();
293
break;
294
295
case DebuggerRegType::INVALID:
296
// Error response already sent.
297
return;
298
}
299
300
JsonWriter &json = req.Respond();
301
json.writeInt("category", cat);
302
json.writeInt("register", reg);
303
json.writeUint("uintValue", val);
304
json.writeString("floatValue", RegValueAsFloat(val));
305
}
306
307
// Update the value of a single register (cpu.setReg)
308
//
309
// Parameters (by name):
310
// - thread: optional number indicating the thread id to update.
311
// - name: string name of register to lookup.
312
// - value: number (uint values only) or string to set to. Values may include
313
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
314
//
315
// Parameters (by category id and index, ignored if name specified):
316
// - thread: optional number indicating the thread id to update.
317
// - category: id of category for the register.
318
// - register: index into array of registers.
319
// - value: number (uint values only) or string to set to. Values may include
320
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
321
//
322
// Response (same event name):
323
// - category: id of category for the register.
324
// - register: index into array of registers.
325
// - uintValue: new value in register.
326
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
327
//
328
// NOTE: Cannot be called unless the CPU is currently stepping.
329
void WebSocketCPUSetReg(DebuggerRequest &req) {
330
if (!currentDebugMIPS->isAlive()) {
331
return req.Fail("CPU not started");
332
}
333
if (!Core_IsStepping()) {
334
return req.Fail("CPU currently running (cpu.stepping first)");
335
}
336
337
auto cpuDebug = CPUFromRequest(req);
338
if (!cpuDebug)
339
return;
340
341
uint32_t val;
342
if (!req.ParamU32("value", &val, true)) {
343
// Already sent error.
344
return;
345
}
346
347
int cat, reg;
348
switch (ValidateCatReg(req, &cat, &reg)) {
349
case DebuggerRegType::NORMAL:
350
if (cat == 0 && reg == 0 && val != 0) {
351
return req.Fail("Cannot change reg zero");
352
}
353
cpuDebug->SetRegValue(cat, reg, val);
354
// In case part of it was ignored (e.g. flags reg.)
355
val = cpuDebug->GetRegValue(cat, reg);
356
break;
357
358
case DebuggerRegType::PC:
359
cpuDebug->SetPC(val);
360
break;
361
case DebuggerRegType::HI:
362
cpuDebug->SetHi(val);
363
break;
364
case DebuggerRegType::LO:
365
cpuDebug->SetLo(val);
366
break;
367
368
case DebuggerRegType::INVALID:
369
// Error response already sent.
370
return;
371
}
372
373
Reporting::NotifyDebugger();
374
375
JsonWriter &json = req.Respond();
376
// Repeat it back just to avoid confusion on how it parsed.
377
json.writeInt("category", cat);
378
json.writeInt("register", reg);
379
json.writeUint("uintValue", val);
380
json.writeString("floatValue", RegValueAsFloat(val));
381
}
382
383
// Evaluate an expression (cpu.evaluate)
384
//
385
// Parameters:
386
// - thread: optional number indicating the thread id to update.
387
// - expression: string containing labels, operators, regs, etc.
388
//
389
// Response (same event name):
390
// - uintValue: value in register.
391
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
392
void WebSocketCPUEvaluate(DebuggerRequest &req) {
393
if (!currentDebugMIPS->isAlive()) {
394
return req.Fail("CPU not started");
395
}
396
397
auto cpuDebug = CPUFromRequest(req);
398
if (!cpuDebug)
399
return;
400
401
std::string exp;
402
if (!req.ParamString("expression", &exp)) {
403
// Already sent error.
404
return;
405
}
406
407
u32 val;
408
PostfixExpression postfix;
409
if (!initExpression(cpuDebug, exp.c_str(), postfix)) {
410
return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
411
}
412
if (!parseExpression(cpuDebug, postfix, val)) {
413
return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));
414
}
415
416
JsonWriter &json = req.Respond();
417
json.writeUint("uintValue", val);
418
json.writeString("floatValue", RegValueAsFloat(val));
419
}
420
421