Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Debugger/WebSocket/HLESubscriber.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 "Common/StringUtils.h"
19
#include "Core/Config.h"
20
#include "Core/Core.h"
21
#include "Core/System.h"
22
#include "Core/Debugger/DisassemblyManager.h"
23
#include "Core/Debugger/SymbolMap.h"
24
#include "Core/Debugger/WebSocket/HLESubscriber.h"
25
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
26
#include "Core/MemMap.h"
27
#include "Core/MIPS/MIPSAnalyst.h"
28
#include "Core/MIPS/MIPSDebugInterface.h"
29
#include "Core/MIPS/MIPSStackWalk.h"
30
#include "Core/HLE/sceKernelThread.h"
31
#include "Core/Reporting.h"
32
33
DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
34
map["hle.thread.list"] = &WebSocketHLEThreadList;
35
map["hle.thread.wake"] = &WebSocketHLEThreadWake;
36
map["hle.thread.stop"] = &WebSocketHLEThreadStop;
37
map["hle.func.list"] = &WebSocketHLEFuncList;
38
map["hle.func.add"] = &WebSocketHLEFuncAdd;
39
map["hle.func.remove"] = &WebSocketHLEFuncRemove;
40
map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;
41
map["hle.func.rename"] = &WebSocketHLEFuncRename;
42
map["hle.func.scan"] = &WebSocketHLEFuncScan;
43
map["hle.module.list"] = &WebSocketHLEModuleList;
44
map["hle.backtrace"] = &WebSocketHLEBacktrace;
45
46
return nullptr;
47
}
48
49
// List all current HLE threads (hle.thread.list)
50
//
51
// No parameters.
52
//
53
// Response (same event name):
54
// - threads: array of objects, each with properties:
55
// - id: unsigned integer unique id of thread.
56
// - name: name given to thread when created.
57
// - status: numeric status flags of thread.
58
// - statuses: array of string status names, e.g. 'running'. Typically only one set.
59
// - pc: unsigned integer address of next instruction on thread.
60
// - entry: unsigned integer address thread execution started at.
61
// - initialStackSize: unsigned integer, size of initial stack.
62
// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)
63
// - priority: numeric priority level, lower values are better priority.
64
// - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting.
65
// - isCurrent: boolean, true for the currently executing thread.
66
void WebSocketHLEThreadList(DebuggerRequest &req) {
67
// Will just return none of the CPU isn't ready yet.
68
auto threads = GetThreadsInfo();
69
70
JsonWriter &json = req.Respond();
71
json.pushArray("threads");
72
for (const auto &th : threads) {
73
json.pushDict();
74
json.writeUint("id", th.id);
75
json.writeString("name", th.name);
76
json.writeInt("status", th.status);
77
json.pushArray("statuses");
78
if (th.status & THREADSTATUS_RUNNING)
79
json.writeString("running");
80
if (th.status & THREADSTATUS_READY)
81
json.writeString("ready");
82
if (th.status & THREADSTATUS_WAIT)
83
json.writeString("wait");
84
if (th.status & THREADSTATUS_SUSPEND)
85
json.writeString("suspend");
86
if (th.status & THREADSTATUS_DORMANT)
87
json.writeString("dormant");
88
if (th.status & THREADSTATUS_DEAD)
89
json.writeString("dead");
90
json.pop();
91
json.writeUint("pc", th.curPC);
92
json.writeUint("entry", th.entrypoint);
93
json.writeUint("initialStackSize", th.initialStack);
94
json.writeUint("currentStackSize", th.stackSize);
95
json.writeInt("priority", th.priority);
96
json.writeInt("waitType", (int)th.waitType);
97
json.writeBool("isCurrent", th.isCurrent);
98
json.pop();
99
}
100
json.pop();
101
}
102
103
static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) {
104
if (PSP_GetBootState() != BootState::Complete) {
105
req.Fail("CPU not active");
106
return false;
107
}
108
if (!Core_IsStepping()) {
109
req.Fail("CPU currently running (cpu.stepping first)");
110
return false;
111
}
112
113
uint32_t threadID;
114
if (!req.ParamU32("thread", &threadID))
115
return false;
116
117
auto threads = GetThreadsInfo();
118
for (const auto &t : threads) {
119
if (t.id == threadID) {
120
*result = t;
121
return true;
122
}
123
}
124
125
req.Fail("Thread could not be found");
126
return false;
127
}
128
129
// Force resume a thread (hle.thread.wake)
130
//
131
// Parameters:
132
// - thread: number indicating the thread id to resume.
133
//
134
// Response (same event name):
135
// - thread: id repeated back.
136
// - status: string 'ready'.
137
void WebSocketHLEThreadWake(DebuggerRequest &req) {
138
DebugThreadInfo threadInfo{ -1 };
139
if (!ThreadInfoForStatus(req, &threadInfo))
140
return;
141
142
switch (threadInfo.status) {
143
case THREADSTATUS_SUSPEND:
144
case THREADSTATUS_WAIT:
145
case THREADSTATUS_WAITSUSPEND:
146
if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0)
147
return req.Fail("Failed to resume thread");
148
break;
149
150
default:
151
return req.Fail("Cannot force run thread based on current status");
152
}
153
154
Reporting::NotifyDebugger();
155
156
JsonWriter &json = req.Respond();
157
json.writeUint("thread", threadInfo.id);
158
json.writeString("status", "ready");
159
}
160
161
// Force stop a thread (hle.thread.stop)
162
//
163
// Parameters:
164
// - thread: number indicating the thread id to stop.
165
//
166
// Response (same event name):
167
// - thread: id repeated back.
168
// - status: string 'dormant'.
169
void WebSocketHLEThreadStop(DebuggerRequest &req) {
170
DebugThreadInfo threadInfo{ -1 };
171
if (!ThreadInfoForStatus(req, &threadInfo))
172
return;
173
174
switch (threadInfo.status) {
175
case THREADSTATUS_SUSPEND:
176
case THREADSTATUS_WAIT:
177
case THREADSTATUS_WAITSUSPEND:
178
case THREADSTATUS_READY:
179
__KernelStopThread(threadInfo.id, 0, "stopped from debugger");
180
break;
181
182
default:
183
return req.Fail("Cannot force run thread based on current status");
184
}
185
186
// Get it again to verify.
187
if (!ThreadInfoForStatus(req, &threadInfo))
188
return;
189
if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)
190
return req.Fail("Failed to stop thread");
191
192
Reporting::NotifyDebugger();
193
194
JsonWriter &json = req.Respond();
195
json.writeUint("thread", threadInfo.id);
196
json.writeString("status", "dormant");
197
}
198
199
// List all current known function symbols (hle.func.list)
200
//
201
// No parameters.
202
//
203
// Response (same event name):
204
// - functions: array of objects, each with properties:
205
// - name: current name of function.
206
// - address: unsigned integer start address of function.
207
// - size: unsigned integer size in bytes.
208
void WebSocketHLEFuncList(DebuggerRequest &req) {
209
if (!g_symbolMap)
210
return req.Fail("CPU not active");
211
212
auto functions = g_symbolMap->GetAllActiveSymbols(ST_FUNCTION);
213
214
JsonWriter &json = req.Respond();
215
json.pushArray("functions");
216
for (auto f : functions) {
217
json.pushDict();
218
json.writeString("name", f.name);
219
json.writeUint("address", f.address);
220
json.writeUint("size", f.size);
221
json.pop();
222
}
223
json.pop();
224
}
225
226
// Add a new function symbols (hle.func.add)
227
//
228
// Parameters:
229
// - address: unsigned integer address for the start of the function.
230
// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,
231
// defaults to that function's end, otherwise 4 bytes.
232
// - name: string to name the function, optional and defaults to an auto-generated name.
233
//
234
// Response (same event name):
235
// - address: the start address, repeated back.
236
// - size: the size of the function, whether autodetected or not.
237
// - name: name of the new function.
238
//
239
// Note: will fail if a function starts at that location already, or if size spans multiple
240
// existing functions. Remove those functions first if necessary.
241
void WebSocketHLEFuncAdd(DebuggerRequest &req) {
242
if (!g_symbolMap)
243
return req.Fail("CPU not active");
244
if (!Core_IsStepping())
245
return req.Fail("CPU currently running (cpu.stepping first)");
246
247
u32 addr;
248
if (!req.ParamU32("address", &addr))
249
return;
250
u32 size = -1;
251
if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))
252
return;
253
if (size == 0)
254
size = -1;
255
256
std::string name;
257
if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))
258
return;
259
if (name.empty())
260
name = StringFromFormat("z_un_%08x", addr);
261
262
u32 prevBegin = g_symbolMap->GetFunctionStart(addr);
263
u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 1);
264
if (prevBegin == addr) {
265
return req.Fail("Function already exists at 'address'");
266
} else if (endBegin != prevBegin) {
267
return req.Fail("Function already exists between 'address' and 'address' + 'size'");
268
} else if (prevBegin != -1) {
269
std::string prevName = g_symbolMap->GetLabelString(prevBegin);
270
u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);
271
u32 newPrevSize = addr - prevBegin;
272
273
// The new function will be the remainder, unless otherwise specified.
274
if (size == -1)
275
size = prevSize - newPrevSize;
276
277
// Make sure we register the new length for replacements too.
278
MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize - 1);
279
g_symbolMap->SetFunctionSize(prevBegin, newPrevSize);
280
MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str());
281
} else {
282
// There was no function there, so hopefully they specified a size.
283
if (size == -1)
284
size = 4;
285
}
286
287
// To ensure we restore replacements.
288
MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);
289
g_symbolMap->AddFunction(name.c_str(), addr, size);
290
g_symbolMap->SortSymbols();
291
MIPSAnalyst::RegisterFunction(addr, size, name.c_str());
292
293
MIPSAnalyst::UpdateHashMap();
294
MIPSAnalyst::ApplyHashMap();
295
296
if (g_Config.bFuncReplacements) {
297
MIPSAnalyst::ReplaceFunctions();
298
}
299
300
// Clear cache for branch lines and such.
301
g_disassemblyManager.clear();
302
303
JsonWriter &json = req.Respond();
304
json.writeUint("address", addr);
305
json.writeUint("size", size);
306
json.writeString("name", name);
307
}
308
309
// Remove a function symbol (hle.func.remove)
310
//
311
// Parameters:
312
// - address: unsigned integer address within function to remove.
313
//
314
// Response (same event name):
315
// - address: the start address of the removed function.
316
// - size: the size in bytes of the removed function.
317
//
318
// Note: will expand any previous function automatically.
319
void WebSocketHLEFuncRemove(DebuggerRequest &req) {
320
if (!g_symbolMap)
321
return req.Fail("CPU not active");
322
if (!Core_IsStepping())
323
return req.Fail("CPU currently running (cpu.stepping first)");
324
325
u32 addr;
326
if (!req.ParamU32("address", &addr))
327
return;
328
329
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
330
if (funcBegin == -1)
331
return req.Fail("No function found at 'address'");
332
u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);
333
334
// Expand the previous function.
335
u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1);
336
if (prevBegin != -1) {
337
std::string prevName = g_symbolMap->GetLabelString(prevBegin);
338
u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize;
339
g_symbolMap->SetFunctionSize(prevBegin, expandedSize);
340
MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize - 1);
341
MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str());
342
} else {
343
MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);
344
}
345
346
g_symbolMap->RemoveFunction(funcBegin, true);
347
g_symbolMap->SortSymbols();
348
349
MIPSAnalyst::UpdateHashMap();
350
MIPSAnalyst::ApplyHashMap();
351
352
if (g_Config.bFuncReplacements) {
353
MIPSAnalyst::ReplaceFunctions();
354
}
355
356
// Clear cache for branch lines and such.
357
g_disassemblyManager.clear();
358
359
JsonWriter &json = req.Respond();
360
json.writeUint("address", funcBegin);
361
json.writeUint("size", funcSize);
362
}
363
364
// This function removes function symbols that intersect or lie inside the range
365
// (Note: this makes no checks whether the range is valid)
366
// Returns the number of removed functions
367
static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {
368
u32 func_address = g_symbolMap->GetFunctionStart(addr);
369
if (func_address == SymbolMap::INVALID_ADDRESS) {
370
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
371
}
372
373
u32 counter = 0;
374
while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {
375
g_symbolMap->RemoveFunction(func_address, true);
376
++counter;
377
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
378
}
379
380
if (counter) {
381
MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);
382
383
// The following was copied from hle.func.remove:
384
g_symbolMap->SortSymbols();
385
386
MIPSAnalyst::UpdateHashMap();
387
MIPSAnalyst::ApplyHashMap();
388
389
if (g_Config.bFuncReplacements) {
390
MIPSAnalyst::ReplaceFunctions();
391
}
392
393
// Clear cache for branch lines and such.
394
g_disassemblyManager.clear();
395
}
396
return counter;
397
}
398
399
// Remove function symbols in range (hle.func.removeRange)
400
//
401
// Parameters:
402
// - address: unsigned integer address for the start of the range.
403
// - size: unsigned integer size in bytes for removal
404
//
405
// Response (same event name):
406
// - count: number of removed functions
407
void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {
408
if (!g_symbolMap)
409
return req.Fail("CPU not active");
410
if (!Core_IsStepping())
411
return req.Fail("CPU currently running (cpu.stepping first)");
412
413
u32 addr;
414
if (!req.ParamU32("address", &addr))
415
return;
416
u32 size;
417
if (!req.ParamU32("size", &size))
418
return;
419
420
if (!Memory::IsValidRange(addr, size))
421
return req.Fail("Address or size outside valid memory");
422
423
u32 count = RemoveFuncSymbolsInRange(addr, size);
424
425
JsonWriter &json = req.Respond();
426
json.writeUint("count", count);
427
}
428
429
// Rename a function symbol (hle.func.rename)
430
//
431
// Parameters:
432
// - address: unsigned integer address within function to rename.
433
// - name: string, new name for the function.
434
//
435
// Response (same event name):
436
// - address: the start address of the renamed function.
437
// - size: the size in bytes of the renamed function.
438
// - name: string, new name repeated back.
439
void WebSocketHLEFuncRename(DebuggerRequest &req) {
440
if (!g_symbolMap)
441
return req.Fail("CPU not active");
442
if (!Core_IsStepping())
443
return req.Fail("CPU currently running (cpu.stepping first)");
444
445
u32 addr;
446
if (!req.ParamU32("address", &addr))
447
return;
448
std::string name;
449
if (!req.ParamString("name", &name))
450
return;
451
452
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
453
if (funcBegin == -1)
454
return req.Fail("No function found at 'address'");
455
u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);
456
457
g_symbolMap->SetLabelName(name.c_str(), funcBegin);
458
// To ensure we reapply replacements (in case we check name there.)
459
MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);
460
MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str());
461
MIPSAnalyst::UpdateHashMap();
462
MIPSAnalyst::ApplyHashMap();
463
if (g_Config.bFuncReplacements) {
464
MIPSAnalyst::ReplaceFunctions();
465
}
466
467
JsonWriter &json = req.Respond();
468
json.writeUint("address", funcBegin);
469
json.writeUint("size", funcSize);
470
json.writeString("name", name);
471
}
472
473
// Auto-detect functions in a memory range (hle.func.scan)
474
//
475
// Parameters:
476
// - address: unsigned integer address for the start of the range.
477
// - size: unsigned integer size in bytes for scan.
478
// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning
479
//
480
// Response (same event name) with no extra data.
481
void WebSocketHLEFuncScan(DebuggerRequest &req) {
482
if (!g_symbolMap)
483
return req.Fail("CPU not active");
484
if (!Core_IsStepping())
485
return req.Fail("CPU currently running (cpu.stepping first)");
486
487
u32 addr;
488
if (!req.ParamU32("address", &addr))
489
return;
490
u32 size;
491
if (!req.ParamU32("size", &size))
492
return;
493
494
bool remove = false;
495
if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL))
496
return;
497
498
if (!Memory::IsValidRange(addr, size))
499
return req.Fail("Address or size outside valid memory");
500
501
if (remove) {
502
RemoveFuncSymbolsInRange(addr, size);
503
}
504
505
bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size - 1, true);
506
MIPSAnalyst::FinalizeScan(insertSymbols);
507
508
req.Respond();
509
}
510
511
// List all known user modules (hle.module.list)
512
//
513
// No parameters.
514
//
515
// Response (same event name):
516
// - modules: array of objects, each with properties:
517
// - name: name of module when loaded.
518
// - address: unsigned integer start address.
519
// - size: unsigned integer size in bytes.
520
// - isActive: boolean, true if this module is active.
521
void WebSocketHLEModuleList(DebuggerRequest &req) {
522
if (!g_symbolMap)
523
return req.Fail("CPU not active");
524
525
auto modules = g_symbolMap->getAllModules();
526
527
JsonWriter &json = req.Respond();
528
json.pushArray("modules");
529
for (auto m : modules) {
530
json.pushDict();
531
json.writeString("name", m.name);
532
json.writeUint("address", m.address);
533
json.writeUint("size", m.size);
534
json.writeBool("isActive", m.active);
535
json.pop();
536
}
537
json.pop();
538
}
539
540
// Walk the stack and list stack frames (hle.backtrace)
541
//
542
// Parameters:
543
// - thread: optional number indicating the thread id to backtrace, default current.
544
//
545
// Response (same event name):
546
// - frames: array of objects, each with properties:
547
// - entry: unsigned integer address of function start (may be estimated.)
548
// - pc: unsigned integer next execution address.
549
// - sp: unsigned integer stack address in this func (beware of alloca().)
550
// - stackSize: integer size of stack frame.
551
// - code: string disassembly of pc.
552
void WebSocketHLEBacktrace(DebuggerRequest &req) {
553
if (!g_symbolMap)
554
return req.Fail("CPU not active");
555
if (!Core_IsStepping())
556
return req.Fail("CPU currently running (cpu.stepping first)");
557
558
uint32_t threadID = -1;
559
DebugInterface *cpuDebug = currentDebugMIPS;
560
if (req.HasParam("thread")) {
561
if (!req.ParamU32("thread", &threadID))
562
return;
563
564
cpuDebug = KernelDebugThread((SceUID)threadID);
565
if (!cpuDebug)
566
return req.Fail("Thread could not be found");
567
}
568
569
auto threads = GetThreadsInfo();
570
uint32_t entry = cpuDebug->GetPC();
571
uint32_t stackTop = 0;
572
for (const DebugThreadInfo &th : threads) {
573
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
574
entry = th.entrypoint;
575
stackTop = th.initialStack;
576
break;
577
}
578
}
579
580
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
581
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
582
auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
583
584
JsonWriter &json = req.Respond();
585
json.pushArray("frames");
586
for (auto f : frames) {
587
json.pushDict();
588
json.writeUint("entry", f.entry);
589
json.writeUint("pc", f.pc);
590
json.writeUint("sp", f.sp);
591
json.writeUint("stackSize", f.stackSize);
592
593
DisassemblyLineInfo line;
594
g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug);
595
json.writeString("code", line.name + " " + line.params);
596
597
json.pop();
598
}
599
json.pop();
600
}
601
602