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