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