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/DisasmSubscriber.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 <algorithm>
19
#include <cctype>
20
21
#include "Common/Data/Encoding/Utf8.h"
22
23
#include "Common/StringUtils.h"
24
#include "Core/Debugger/Breakpoints.h"
25
#include "Core/Debugger/DisassemblyManager.h"
26
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
27
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
28
#include "Core/HLE/sceKernelThread.h"
29
#include "Core/MemMap.h"
30
#include "Core/MIPS/MIPSAsm.h"
31
#include "Core/MIPS/MIPSDebugInterface.h"
32
#include "Core/Reporting.h"
33
34
class WebSocketDisasmState : public DebuggerSubscriber {
35
public:
36
WebSocketDisasmState() {
37
disasm_.setCpu(currentDebugMIPS);
38
}
39
~WebSocketDisasmState() {
40
disasm_.clear();
41
}
42
43
void Base(DebuggerRequest &req);
44
void Disasm(DebuggerRequest &req);
45
void SearchDisasm(DebuggerRequest &req);
46
void Assemble(DebuggerRequest &req);
47
48
protected:
49
void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l);
50
void WriteBranchGuide(JsonWriter &json, const BranchLine &l);
51
52
DisassemblyManager disasm_;
53
};
54
55
DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) {
56
auto p = new WebSocketDisasmState();
57
map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1);
58
map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1);
59
map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1);
60
map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1);
61
62
return p;
63
}
64
65
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
66
if (!req.HasParam("thread"))
67
return currentDebugMIPS;
68
69
u32 uid;
70
if (!req.ParamU32("thread", &uid))
71
return nullptr;
72
73
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
74
if (!cpuDebug)
75
req.Fail("Thread could not be found");
76
return cpuDebug;
77
}
78
79
void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) {
80
u32 addr = l.info.opcodeAddress;
81
json.pushDict();
82
if (l.type == DISTYPE_OPCODE)
83
json.writeString("type", "opcode");
84
else if (l.type == DISTYPE_MACRO)
85
json.writeString("type", "macro");
86
else if (l.type == DISTYPE_DATA)
87
json.writeString("type", "data");
88
else if (l.type == DISTYPE_OTHER)
89
json.writeString("type", "other");
90
91
json.writeUint("address", addr);
92
json.writeInt("addressSize", l.totalSize);
93
json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0);
94
if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) {
95
json.pushArray("macroEncoding");
96
for (u32 off = 0; off < l.totalSize; off += 4) {
97
json.writeUint(Memory::Read_Instruction(addr + off).encoding);
98
}
99
json.pop();
100
} else {
101
json.writeNull("macroEncoding");
102
}
103
int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF;
104
json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16));
105
json.writeString("name", l.name);
106
json.writeString("params", l.params);
107
108
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
109
if (addressSymbol.empty())
110
json.writeNull("symbol");
111
else
112
json.writeString("symbol", addressSymbol);
113
114
const u32 funcAddress = g_symbolMap->GetFunctionStart(addr);
115
const std::string funcName = g_symbolMap->GetLabelString(funcAddress);
116
if (funcName.empty())
117
json.writeNull("function");
118
else
119
json.writeString("function", funcName);
120
121
if (l.type == DISTYPE_DATA) {
122
u32 dataStart = g_symbolMap->GetDataStart(addr);
123
if (dataStart == -1)
124
dataStart = addr;
125
const std::string dataLabel = g_symbolMap->GetLabelString(dataStart);
126
127
json.pushDict("dataSymbol");
128
json.writeUint("start", dataStart);
129
if (dataLabel.empty())
130
json.writeNull("label");
131
else
132
json.writeString("label", dataLabel);
133
json.pop();
134
} else {
135
json.writeNull("dataSymbol");
136
}
137
138
bool enabled = false;
139
int breakpointOffset = -1;
140
for (u32 i = 0; i < l.totalSize; i += 4) {
141
if (CBreakPoints::IsAddressBreakPoint(addr + i, &enabled))
142
breakpointOffset = i;
143
if (breakpointOffset != -1 && enabled)
144
break;
145
}
146
// TODO: Account for bp inside macro?
147
if (breakpointOffset != -1) {
148
json.pushDict("breakpoint");
149
json.writeBool("enabled", enabled);
150
json.writeUint("address", addr + breakpointOffset);
151
auto cond = CBreakPoints::GetBreakPointCondition(addr + breakpointOffset);
152
if (cond)
153
json.writeString("condition", cond->expressionString);
154
else
155
json.writeNull("condition");
156
json.pop();
157
} else {
158
json.writeNull("breakpoint");
159
}
160
161
// This is always the current execution's PC.
162
json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr);
163
if (l.info.isBranch) {
164
json.pushDict("branch");
165
std::string targetSymbol;
166
if (!l.info.isBranchToRegister) {
167
targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget);
168
json.writeUint("targetAddress", l.info.branchTarget);
169
json.writeNull("register");
170
} else {
171
json.writeNull("targetAddress");
172
json.writeInt("register", l.info.branchRegisterNum);
173
}
174
json.writeBool("isLinked", l.info.isLinkedBranch);
175
json.writeBool("isLikely", l.info.isLikelyBranch);
176
if (targetSymbol.empty())
177
json.writeNull("symbol");
178
else
179
json.writeString("symbol", targetSymbol);
180
json.pop();
181
} else {
182
json.writeNull("branch");
183
}
184
185
if (l.info.hasRelevantAddress) {
186
json.pushDict("relevantData");
187
json.writeUint("address", l.info.relevantAddress);
188
if (Memory::IsValidRange(l.info.relevantAddress, 4))
189
json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress));
190
else
191
json.writeNull("uintValue");
192
if (IsLikelyStringAt(l.info.relevantAddress))
193
json.writeString("stringValue", Memory::GetCharPointer(l.info.relevantAddress));
194
else
195
json.writeNull("stringValue");
196
json.pop();
197
} else {
198
json.writeNull("relevantData");
199
}
200
201
if (l.info.isConditional)
202
json.writeBool("conditionMet", l.info.conditionMet);
203
else
204
json.writeNull("conditionMet");
205
206
if (l.info.isDataAccess) {
207
json.pushDict("dataAccess");
208
json.writeUint("address", l.info.dataAddress);
209
json.writeInt("size", l.info.dataSize);
210
211
std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress);
212
std::string valueSymbol;
213
if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize))
214
json.writeNull("uintValue");
215
else if (l.info.dataSize == 1)
216
json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress));
217
else if (l.info.dataSize == 2)
218
json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress));
219
else if (l.info.dataSize >= 4) {
220
u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress);
221
valueSymbol = g_symbolMap->GetLabelString(data);
222
json.writeUint("uintValue", data);
223
}
224
225
if (!dataSymbol.empty())
226
json.writeString("symbol", dataSymbol);
227
else
228
json.writeNull("symbol");
229
if (!valueSymbol.empty())
230
json.writeString("valueSymbol", valueSymbol);
231
else
232
json.writeNull("valueSymbol");
233
json.pop();
234
} else {
235
json.writeNull("dataAccess");
236
}
237
238
json.pop();
239
}
240
241
void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) {
242
json.pushDict();
243
json.writeUint("top", l.first);
244
json.writeUint("bottom", l.second);
245
if (l.type == LINE_UP)
246
json.writeString("direction", "up");
247
else if (l.type == LINE_DOWN)
248
json.writeString("direction", "down");
249
else if (l.type == LINE_RIGHT)
250
json.writeString("direction", "right");
251
json.writeInt("lane", l.laneIndex);
252
json.pop();
253
}
254
255
// Request the current PSP memory base address (memory.base)
256
//
257
// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space.
258
//
259
// No parameters.
260
//
261
// Response (same event name):
262
// - addressHex: string indicating base address in hexadecimal (may be 64 bit.)
263
void WebSocketDisasmState::Base(DebuggerRequest &req) {
264
JsonWriter &json = req.Respond();
265
Reporting::NotifyDebugger();
266
json.writeString("addressHex", StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));
267
}
268
269
// Disassemble a range of memory as CPU instructions (memory.disasm)
270
//
271
// Parameters (by count):
272
// - thread: optional number indicating the thread id for branch info.
273
// - address: number specifying the start address.
274
// - count: number of lines to return (may be clamped to an internal limit.)
275
// - displaySymbols: boolean true to show symbol names in instruction params.
276
//
277
// Parameters (by end address):
278
// - thread: optional number indicating the thread id for branch info.
279
// - address: number specifying the start address.
280
// - end: number which must be after the start address (may be clamped to an internal limit.)
281
// - displaySymbols: boolean true to show symbol names in instruction params.
282
//
283
// Response (same event name):
284
// - range: object with result "start" and "end" properties, the addresses actually used.
285
// (disassembly may have snapped to a nearby instruction.)
286
// - branchGuides: array of objects:
287
// - top: the earlier address as a number.
288
// - bottom: the later address as a number.
289
// - direction: "up", "down", or "right" depending on the flow of the branch.
290
// - lane: number index to avoid overlapping guides.
291
// - lines: array of objects:
292
// - type: "opcode", "macro", "data", or "other".
293
// - address: address of first actual instruction.
294
// - addressSize: bytes used by this line (might be more than 4.)
295
// - encoding: uint value of actual instruction (may differ from memory read when using jit.)
296
// - macroEncoding: null, or an array of encodings if this line represents multiple instructions.
297
// - name: string name of the instruction.
298
// - params: formatted parameters for the instruction.
299
// - (other info about the disassembled line.)
300
void WebSocketDisasmState::Disasm(DebuggerRequest &req) {
301
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
302
return req.Fail("CPU not started");
303
auto cpuDebug = CPUFromRequest(req);
304
if (!cpuDebug)
305
return;
306
307
// In case of client errors, we limit the range to something that won't make us crash.
308
static const uint32_t MAX_RANGE = 10000;
309
310
uint32_t start, end;
311
if (!req.ParamU32("address", &start))
312
return;
313
uint32_t count = 0;
314
if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL))
315
return;
316
if (count != 0) {
317
count = std::min(count, MAX_RANGE);
318
// Let's assume everything is two instructions.
319
disasm_.analyze(start - 4, count * 8 + 8);
320
start = disasm_.getStartAddress(start);
321
if (start == -1)
322
req.ParamU32("address", &start);
323
end = disasm_.getNthNextAddress(start, count);
324
} else if (req.ParamU32("end", &end)) {
325
end = std::max(start, end);
326
if (end - start > MAX_RANGE * 4)
327
end = start + MAX_RANGE * 4;
328
// Let's assume everything is two instructions at most.
329
disasm_.analyze(start - 4, end - start + 8);
330
start = disasm_.getStartAddress(start);
331
if (start == -1)
332
req.ParamU32("address", &start);
333
// Correct end and calculate count based on it.
334
// This accounts for macros as one line, although two instructions.
335
u32 stop = end;
336
u32 next = start;
337
count = 0;
338
if (stop < start) {
339
for (next = start; next > stop; next = disasm_.getNthNextAddress(next, 1)) {
340
count++;
341
}
342
}
343
for (end = next; end < stop && end >= next; end = disasm_.getNthNextAddress(end, 1)) {
344
count++;
345
}
346
} else {
347
// Error message already sent.
348
return;
349
}
350
351
bool displaySymbols = true;
352
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
353
return;
354
355
JsonWriter &json = req.Respond();
356
json.pushDict("range");
357
json.writeUint("start", start);
358
json.writeUint("end", end);
359
json.pop();
360
361
json.pushArray("lines");
362
DisassemblyLineInfo line;
363
uint32_t addr = start;
364
for (uint32_t i = 0; i < count; ++i) {
365
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
366
WriteDisasmLine(json, line);
367
addr += line.totalSize;
368
369
// These are pretty long, so let's grease the wheels a bit.
370
if (i % 50 == 0)
371
req.Flush();
372
}
373
json.pop();
374
375
json.pushArray("branchGuides");
376
auto branchGuides = disasm_.getBranchLines(start, end - start);
377
for (auto bl : branchGuides)
378
WriteBranchGuide(json, bl);
379
json.pop();
380
}
381
382
// Search disassembly for some text (memory.searchDisasm)
383
//
384
// Parameters:
385
// - thread: optional number indicating the thread id (may not affect search much.)
386
// - address: starting address as a number.
387
// - end: optional end address as a number (otherwise uses start address.)
388
// - match: string to search for.
389
// - displaySymbols: optional, specify false to hide symbols in the searched parameters.
390
//
391
// Response (same event name):
392
// - address: number address of match or null if none was found.
393
void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {
394
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
395
return req.Fail("CPU not started");
396
auto cpuDebug = CPUFromRequest(req);
397
if (!cpuDebug)
398
return;
399
400
uint32_t start;
401
if (!req.ParamU32("address", &start))
402
return;
403
uint32_t end = start;
404
if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))
405
return;
406
std::string match;
407
if (!req.ParamString("match", &match))
408
return;
409
bool displaySymbols = true;
410
if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL))
411
return;
412
413
bool loopSearch = end <= start;
414
start = RoundMemAddressUp(start);
415
if ((end <= start) != loopSearch) {
416
// We must've passed end by rounding up.
417
JsonWriter &json = req.Respond();
418
json.writeNull("address");
419
return;
420
}
421
422
// We do this after the check in case both were in unused memory.
423
end = RoundMemAddressUp(end);
424
425
std::transform(match.begin(), match.end(), match.begin(), ::tolower);
426
427
DisassemblyLineInfo line;
428
bool found = false;
429
uint32_t addr = start;
430
do {
431
disasm_.getLine(addr, displaySymbols, line, cpuDebug);
432
const std::string addressSymbol = g_symbolMap->GetLabelString(addr);
433
434
std::string mergeForSearch;
435
// Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case.
436
mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size());
437
438
sprintf(&mergeForSearch[0], "%08x ", addr);
439
auto inserter = mergeForSearch.begin() + 9;
440
if (!addressSymbol.empty()) {
441
inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower);
442
*inserter++ = ':';
443
*inserter++ = ' ';
444
}
445
inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower);
446
*inserter++ = ' ';
447
inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower);
448
449
if (mergeForSearch.find(match) != mergeForSearch.npos) {
450
found = true;
451
break;
452
}
453
454
addr = RoundMemAddressUp(addr + line.totalSize);
455
} while (addr != end);
456
457
JsonWriter &json = req.Respond();
458
if (found)
459
json.writeUint("address", addr);
460
else
461
json.writeNull("address");
462
}
463
464
// Assemble an instruction (memory.assemble)
465
//
466
// Parameters:
467
// - address: number indicating the address to write to.
468
// - code: string containing the instruction to assemble.
469
//
470
// Response (same event name):
471
// - encoding: resulting encoding at this address. Always returns one value, even for macros.
472
void WebSocketDisasmState::Assemble(DebuggerRequest &req) {
473
if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) {
474
return req.Fail("CPU not started");
475
}
476
477
uint32_t address;
478
if (!req.ParamU32("address", &address))
479
return;
480
std::string code;
481
if (!req.ParamString("code", &code))
482
return;
483
484
if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))
485
return req.Fail(StringFromFormat("Could not assemble: %s", MIPSAsm::GetAssembleError().c_str()));
486
487
JsonWriter &json = req.Respond();
488
Reporting::NotifyDebugger();
489
json.writeUint("encoding", Memory::Read_Instruction(address).encoding);
490
}
491
492