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