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/BreakpointSubscriber.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/SymbolMap.h"
22
#include "Core/Debugger/WebSocket/BreakpointSubscriber.h"
23
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
24
#include "Core/MIPS/MIPSDebugInterface.h"
25
26
DebuggerSubscriber *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) {
27
// No need to bind or alloc state, these are all global.
28
map["cpu.breakpoint.add"] = &WebSocketCPUBreakpointAdd;
29
map["cpu.breakpoint.update"] = &WebSocketCPUBreakpointUpdate;
30
map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove;
31
map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList;
32
33
map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd;
34
map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate;
35
map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove;
36
map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList;
37
38
return nullptr;
39
}
40
41
struct WebSocketCPUBreakpointParams {
42
uint32_t address = 0;
43
bool hasEnabled = false;
44
bool hasLog = false;
45
bool hasCondition = false;
46
bool hasLogFormat = false;
47
48
bool enabled;
49
bool log;
50
std::string condition;
51
PostfixExpression compiledCondition;
52
std::string logFormat;
53
54
bool Parse(DebuggerRequest &req) {
55
if (!currentDebugMIPS->isAlive()) {
56
req.Fail("CPU not started");
57
return false;
58
}
59
60
if (!req.ParamU32("address", &address))
61
return false;
62
63
hasEnabled = req.HasParam("enabled");
64
if (hasEnabled) {
65
if (!req.ParamBool("enabled", &enabled))
66
return false;
67
}
68
hasLog = req.HasParam("log");
69
if (hasLog) {
70
if (!req.ParamBool("log", &log))
71
return false;
72
}
73
hasCondition = req.HasParam("condition");
74
if (hasCondition) {
75
if (!req.ParamString("condition", &condition))
76
return false;
77
if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) {
78
req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
79
return false;
80
}
81
}
82
hasLogFormat = req.HasParam("logFormat");
83
if (hasLogFormat) {
84
if (!req.ParamString("logFormat", &logFormat))
85
return false;
86
}
87
88
return true;
89
}
90
91
void Apply() {
92
if (hasCondition && !condition.empty()) {
93
BreakPointCond cond;
94
cond.debug = currentDebugMIPS;
95
cond.expressionString = condition;
96
cond.expression = compiledCondition;
97
CBreakPoints::ChangeBreakPointAddCond(address, cond);
98
} else if (hasCondition && condition.empty()) {
99
CBreakPoints::ChangeBreakPointRemoveCond(address);
100
}
101
102
if (hasLogFormat) {
103
CBreakPoints::ChangeBreakPointLogFormat(address, logFormat);
104
}
105
106
// TODO: Fix this interface.
107
if (hasLog && !hasEnabled) {
108
CBreakPoints::IsAddressBreakPoint(address, &enabled);
109
hasEnabled = true;
110
}
111
if (hasLog && hasEnabled) {
112
BreakAction result = BREAK_ACTION_IGNORE;
113
if (log)
114
result |= BREAK_ACTION_LOG;
115
if (enabled)
116
result |= BREAK_ACTION_PAUSE;
117
CBreakPoints::ChangeBreakPoint(address, result);
118
} else if (hasEnabled) {
119
CBreakPoints::ChangeBreakPoint(address, enabled);
120
}
121
}
122
};
123
124
// Add a new CPU instruction breakpoint (cpu.breakpoint.add)
125
//
126
// Parameters:
127
// - address: unsigned integer address of instruction to break at.
128
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
129
// - log: optional boolean, whether to log when this breakpoint trips.
130
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
131
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
132
//
133
// Response (same event name) with no extra data.
134
//
135
// Note: will replace any breakpoint at the same address.
136
void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {
137
WebSocketCPUBreakpointParams params;
138
if (!params.Parse(req))
139
return;
140
141
CBreakPoints::AddBreakPoint(params.address);
142
params.Apply();
143
req.Respond();
144
}
145
146
// Update a CPU instruction breakpoint (cpu.breakpoint.update)
147
//
148
// Parameters:
149
// - address: unsigned integer address of instruction to break at.
150
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
151
// - log: optional boolean, whether to log when this breakpoint trips.
152
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
153
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
154
//
155
// Response (same event name) with no extra data.
156
void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {
157
WebSocketCPUBreakpointParams params;
158
if (!params.Parse(req))
159
return;
160
bool enabled;
161
if (!CBreakPoints::IsAddressBreakPoint(params.address, &enabled))
162
return req.Fail("Breakpoint not found");
163
164
params.Apply();
165
req.Respond();
166
}
167
168
// Remove a CPU instruction breakpoint (cpu.breakpoint.remove)
169
//
170
// Parameters:
171
// - address: unsigned integer address of instruction to break at.
172
//
173
// Response (same event name) with no extra data.
174
void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {
175
if (!currentDebugMIPS->isAlive()) {
176
return req.Fail("CPU not started");
177
}
178
179
uint32_t address;
180
if (!req.ParamU32("address", &address))
181
return;
182
183
CBreakPoints::RemoveBreakPoint(address);
184
req.Respond();
185
}
186
187
// List all CPU instruction breakpoints (cpu.breakpoint.list)
188
//
189
// No parameters.
190
//
191
// Response (same event name):
192
// - breakpoints: array of objects, each with properties:
193
// - address: unsigned integer address of instruction to break at.
194
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
195
// - log: optional boolean, whether to log when this breakpoint trips.
196
// - condition: null, or string expression to evaluate - breakpoint does not trip if false.
197
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
198
// - symbol: null, or string label or symbol at breakpoint address.
199
// - code: string disassembly of breakpoint address.
200
void WebSocketCPUBreakpointList(DebuggerRequest &req) {
201
if (!currentDebugMIPS->isAlive()) {
202
return req.Fail("CPU not started");
203
}
204
205
JsonWriter &json = req.Respond();
206
json.pushArray("breakpoints");
207
auto bps = CBreakPoints::GetBreakpoints();
208
for (const auto &bp : bps) {
209
if (bp.temporary)
210
continue;
211
212
json.pushDict();
213
json.writeUint("address", bp.addr);
214
json.writeBool("enabled", bp.IsEnabled());
215
json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);
216
if (bp.hasCond)
217
json.writeString("condition", bp.cond.expressionString);
218
else
219
json.writeNull("condition");
220
if (!bp.logFormat.empty())
221
json.writeString("logFormat", bp.logFormat);
222
else
223
json.writeNull("logFormat");
224
std::string symbol = g_symbolMap->GetLabelString(bp.addr);
225
if (symbol.empty())
226
json.writeNull("symbol");
227
else
228
json.writeString("symbol", symbol);
229
230
DisassemblyManager manager;
231
DisassemblyLineInfo line;
232
manager.getLine(manager.getStartAddress(bp.addr), true, line);
233
json.writeString("code", line.name + " " + line.params);
234
235
json.pop();
236
}
237
json.pop();
238
}
239
240
struct WebSocketMemoryBreakpointParams {
241
uint32_t address = 0;
242
uint32_t end = 0;
243
bool hasEnabled = false;
244
bool hasLog = false;
245
bool hasCond = false;
246
bool hasCondition = false;
247
bool hasLogFormat = false;
248
249
bool enabled = true;
250
bool log = true;
251
MemCheckCondition cond = MEMCHECK_READWRITE;
252
std::string condition;
253
PostfixExpression compiledCondition;
254
std::string logFormat;
255
256
bool Parse(DebuggerRequest &req) {
257
if (!currentDebugMIPS->isAlive()) {
258
req.Fail("CPU not started");
259
return false;
260
}
261
262
if (!req.ParamU32("address", &address))
263
return false;
264
uint32_t size;
265
if (!req.ParamU32("size", &size))
266
return false;
267
if (address + size < address) {
268
req.Fail("Size is too large");
269
return false;
270
}
271
end = size == 0 ? 0 : address + size;
272
273
hasEnabled = req.HasParam("enabled");
274
if (hasEnabled) {
275
if (!req.ParamBool("enabled", &enabled))
276
return false;
277
}
278
hasLog = req.HasParam("log");
279
if (hasLog) {
280
if (!req.ParamBool("log", &log))
281
return false;
282
}
283
hasCond = req.HasParam("read") || req.HasParam("write") || req.HasParam("change");
284
if (hasCond) {
285
bool read = false, write = false, change = false;
286
if (!req.ParamBool("read", &read, DebuggerParamType::OPTIONAL) || !req.ParamBool("write", &write, DebuggerParamType::OPTIONAL) || !req.ParamBool("change", &change, DebuggerParamType::OPTIONAL))
287
return false;
288
int bits = (read ? MEMCHECK_READ : 0) | (write ? MEMCHECK_WRITE : 0) | (change ? MEMCHECK_WRITE_ONCHANGE : 0);
289
cond = MemCheckCondition(bits);
290
}
291
hasCondition = req.HasParam("condition");
292
if (hasCondition) {
293
if (!req.ParamString("condition", &condition))
294
return false;
295
if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) {
296
req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
297
return false;
298
}
299
}
300
hasLogFormat = req.HasParam("logFormat");
301
if (hasLogFormat) {
302
if (!req.ParamString("logFormat", &logFormat))
303
return false;
304
}
305
306
return true;
307
}
308
309
BreakAction Result(bool adding) {
310
int bits = MEMCHECK_READWRITE;
311
if (adding || (hasLog && hasEnabled)) {
312
bits = (enabled ? BREAK_ACTION_PAUSE : 0) | (log ? BREAK_ACTION_LOG : 0);
313
} else {
314
MemCheck prev;
315
if (CBreakPoints::GetMemCheck(address, end, &prev))
316
bits = prev.result;
317
318
if (hasEnabled)
319
bits = (bits & ~BREAK_ACTION_PAUSE) | (enabled ? BREAK_ACTION_PAUSE : 0);
320
if (hasLog)
321
bits = (bits & ~BREAK_ACTION_LOG) | (log ? BREAK_ACTION_LOG : 0);
322
}
323
324
return BreakAction(bits);
325
}
326
327
void Apply() {
328
if (hasCondition && !condition.empty()) {
329
BreakPointCond cond;
330
cond.debug = currentDebugMIPS;
331
cond.expressionString = condition;
332
cond.expression = compiledCondition;
333
CBreakPoints::ChangeMemCheckAddCond(address, end, cond);
334
} else if (hasCondition && condition.empty()) {
335
CBreakPoints::ChangeMemCheckRemoveCond(address, end);
336
}
337
if (hasLogFormat) {
338
CBreakPoints::ChangeMemCheckLogFormat(address, end, logFormat);
339
}
340
}
341
};
342
343
// Add a new memory breakpoint (memory.breakpoint.add)
344
//
345
// Parameters:
346
// - address: unsigned integer address for the start of the memory range.
347
// - size: unsigned integer specifying size of memory range.
348
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
349
// - log: optional boolean, whether to log when this breakpoint trips.
350
// - read: optional boolean, whether to trip on any read to this address.
351
// - write: optional boolean, whether to trip on any write to this address.
352
// - change: optional boolean, whether to trip on a write to this address which modifies data
353
// (or any write that may modify data.)
354
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
355
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
356
//
357
// Response (same event name) with no extra data.
358
//
359
// Note: will replace any breakpoint that has the same start address and size.
360
void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {
361
WebSocketMemoryBreakpointParams params;
362
if (!params.Parse(req))
363
return;
364
365
CBreakPoints::AddMemCheck(params.address, params.end, params.cond, params.Result(true));
366
params.Apply();
367
req.Respond();
368
}
369
370
// Update a memory breakpoint (memory.breakpoint.update)
371
//
372
// Parameters:
373
// - address: unsigned integer address for the start of the memory range.
374
// - size: unsigned integer specifying size of memory range.
375
// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.
376
// - log: optional boolean, whether to log when this breakpoint trips.
377
// - read: optional boolean, whether to trip on any read to this address.
378
// - write: optional boolean, whether to trip on any write to this address.
379
// - change: optional boolean, whether to trip on a write to this address which modifies data
380
// (or any write that may modify data.)
381
// - condition: optional string expression to evaluate - breakpoint does not trip if false.
382
// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.
383
//
384
// Response (same event name) with no extra data.
385
void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {
386
WebSocketMemoryBreakpointParams params;
387
if (!params.Parse(req))
388
return;
389
390
MemCheck mc;
391
if (!CBreakPoints::GetMemCheck(params.address, params.end, &mc))
392
return req.Fail("Breakpoint not found");
393
394
CBreakPoints::ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));
395
params.Apply();
396
req.Respond();
397
}
398
399
// Remove a memory breakpoint (memory.breakpoint.remove)
400
//
401
// Parameters:
402
// - address: unsigned integer address for the start of the memory range.
403
// - size: unsigned integer specifying size of memory range.
404
//
405
// Response (same event name) with no extra data.
406
void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {
407
if (!currentDebugMIPS->isAlive()) {
408
return req.Fail("CPU not started");
409
}
410
411
uint32_t address;
412
if (!req.ParamU32("address", &address))
413
return;
414
uint32_t size;
415
if (!req.ParamU32("size", &size))
416
return;
417
418
CBreakPoints::RemoveMemCheck(address, size == 0 ? 0 : address + size);
419
req.Respond();
420
}
421
422
// List all memory breakpoints (memory.breakpoint.list)
423
//
424
// No parameters.
425
//
426
// Response (same event name):
427
// - breakpoints: array of objects, each with properties:
428
// - address: unsigned integer address for the start of the memory range.
429
// - size: unsigned integer specifying size of memory range.
430
// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.
431
// - log: optional boolean, whether to log when this breakpoint trips.
432
// - read: optional boolean, whether to trip on any read to this address.
433
// - write: optional boolean, whether to trip on any write to this address.
434
// - change: optional boolean, whether to trip on a write to this address which modifies data
435
// (or any write that may modify data.)
436
// - condition: null, or string expression to evaluate - breakpoint does not trip if false.
437
// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.
438
// - symbol: null, or string label or symbol at breakpoint address.
439
void WebSocketMemoryBreakpointList(DebuggerRequest &req) {
440
if (!currentDebugMIPS->isAlive()) {
441
return req.Fail("CPU not started");
442
}
443
444
JsonWriter &json = req.Respond();
445
json.pushArray("breakpoints");
446
auto mcs = CBreakPoints::GetMemChecks();
447
for (const auto &mc : mcs) {
448
json.pushDict();
449
json.writeUint("address", mc.start);
450
json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);
451
json.writeBool("enabled", mc.IsEnabled());
452
json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);
453
json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);
454
json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);
455
json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);
456
json.writeUint("hits", mc.numHits);
457
if (mc.hasCondition)
458
json.writeString("condition", mc.condition.expressionString);
459
else
460
json.writeNull("condition");
461
if (!mc.logFormat.empty())
462
json.writeString("logFormat", mc.logFormat);
463
else
464
json.writeNull("logFormat");
465
std::string symbol = g_symbolMap->GetLabelString(mc.start);
466
if (symbol.empty())
467
json.writeNull("symbol");
468
else
469
json.writeString("symbol", symbol);
470
471
json.pop();
472
}
473
json.pop();
474
}
475
476