Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/MemFault.cpp
3185 views
1
// Copyright (C) 2020 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 "ppsspp_config.h"
19
20
#include <cstdint>
21
#include <unordered_set>
22
#include <mutex>
23
#include <sstream>
24
25
#include "Common/StringUtils.h"
26
#include "Common/MachineContext.h"
27
28
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
29
#include "Common/x64Analyzer.h"
30
31
#elif PPSSPP_ARCH(ARM64)
32
#include "Core/Util/DisArm64.h"
33
#elif PPSSPP_ARCH(ARM)
34
#include "ext/disarm.h"
35
#endif
36
37
#include "Common/Log.h"
38
#include "Core/Config.h"
39
#include "Core/Core.h"
40
#include "Core/MemFault.h"
41
#include "Core/MemMap.h"
42
#include "Core/MIPS/JitCommon/JitCommon.h"
43
#include "Core/Debugger/SymbolMap.h"
44
45
// Stack walking stuff
46
#include "Core/MIPS/MIPSStackWalk.h"
47
#include "Core/MIPS/MIPSDebugInterface.h"
48
#include "Core/HLE/sceKernelThread.h"
49
50
namespace Memory {
51
52
static int64_t g_numReportedBadAccesses = 0;
53
const uint8_t *g_lastCrashAddress;
54
MemoryExceptionType g_lastMemoryExceptionType;
55
static bool inCrashHandler = false;
56
57
std::unordered_set<const uint8_t *> g_ignoredAddresses;
58
59
void MemFault_Init() {
60
g_numReportedBadAccesses = 0;
61
g_lastCrashAddress = nullptr;
62
g_lastMemoryExceptionType = MemoryExceptionType::NONE;
63
g_ignoredAddresses.clear();
64
}
65
66
bool MemFault_MayBeResumable() {
67
return g_lastCrashAddress != nullptr;
68
}
69
70
void MemFault_IgnoreLastCrash() {
71
g_ignoredAddresses.insert(g_lastCrashAddress);
72
}
73
74
#ifdef MACHINE_CONTEXT_SUPPORTED
75
76
static bool DisassembleNativeAt(const uint8_t *codePtr, int instructionSize, std::string *dest) {
77
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
78
auto lines = DisassembleX86(codePtr, instructionSize);
79
if (!lines.empty()) {
80
*dest = lines[0];
81
return true;
82
}
83
#elif PPSSPP_ARCH(ARM64)
84
auto lines = DisassembleArm64(codePtr, instructionSize);
85
if (!lines.empty()) {
86
*dest = lines[0];
87
return true;
88
}
89
#elif PPSSPP_ARCH(ARM)
90
auto lines = DisassembleArm2(codePtr, instructionSize);
91
if (!lines.empty()) {
92
*dest = lines[0];
93
return true;
94
}
95
#elif PPSSPP_ARCH(RISCV64)
96
auto lines = DisassembleRV64(codePtr, instructionSize);
97
if (!lines.empty()) {
98
*dest = lines[0];
99
return true;
100
}
101
#elif PPSSPP_ARCH(LOONGARCH64)
102
auto lines = DisassembleLA64(codePtr, instructionSize);
103
if (!lines.empty()) {
104
*dest = lines[0];
105
return true;
106
}
107
#endif
108
return false;
109
}
110
111
bool HandleFault(uintptr_t hostAddress, void *ctx) {
112
if (inCrashHandler)
113
return false;
114
inCrashHandler = true;
115
116
SContext *context = (SContext *)ctx;
117
const uint8_t *codePtr = (uint8_t *)(context->CTX_PC);
118
119
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
120
121
// We set this later if we think it can be resumed from.
122
g_lastCrashAddress = nullptr;
123
124
// TODO: Check that codePtr is within the current JIT space.
125
bool inJitSpace = MIPSComp::jit && MIPSComp::jit->CodeInRange(codePtr);
126
if (!inJitSpace) {
127
// This is a crash in non-jitted code. Not something we want to handle here, ignore.
128
// Actually, we could handle crashes from the IR interpreter here, although recovering the call stack
129
// might be tricky...
130
inCrashHandler = false;
131
return false;
132
}
133
134
uintptr_t baseAddress = (uintptr_t)base;
135
#ifdef MASKED_PSP_MEMORY
136
const uintptr_t addressSpaceSize = 0x40000000ULL;
137
#else
138
const uintptr_t addressSpaceSize = 0x100000000ULL;
139
#endif
140
141
// Check whether hostAddress is within the PSP memory space, which (likely) means it was a guest executable that did the bad access.
142
bool invalidHostAddress = hostAddress == (uintptr_t)0xFFFFFFFFFFFFFFFFULL;
143
if (hostAddress < baseAddress || hostAddress >= baseAddress + addressSpaceSize) {
144
// Host address outside - this was a different kind of crash.
145
if (!invalidHostAddress) {
146
inCrashHandler = false;
147
return false;
148
}
149
}
150
151
// OK, a guest executable did a bad access. Let's handle it.
152
153
uint32_t guestAddress = invalidHostAddress ? 0xFFFFFFFFUL : (uint32_t)(hostAddress - baseAddress);
154
155
// TODO: Share the struct between the various analyzers, that will allow us to share most of
156
// the implementations here.
157
bool success = false;
158
159
MemoryExceptionType type = MemoryExceptionType::NONE;
160
161
int instructionSize = 4;
162
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
163
// X86, X86-64. Variable instruction size so need to analyze the mov instruction in detail.
164
instructionSize = 15;
165
166
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
167
LSInstructionInfo info{};
168
success = X86AnalyzeMOV(codePtr, info);
169
if (success)
170
instructionSize = info.instructionSize;
171
#elif PPSSPP_ARCH(ARM64)
172
uint32_t word;
173
memcpy(&word, codePtr, 4);
174
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
175
Arm64LSInstructionInfo info{};
176
success = Arm64AnalyzeLoadStore((uint64_t)codePtr, word, &info);
177
#elif PPSSPP_ARCH(ARM)
178
uint32_t word;
179
memcpy(&word, codePtr, 4);
180
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
181
ArmLSInstructionInfo info{};
182
success = ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info);
183
#elif PPSSPP_ARCH(RISCV64)
184
// TODO: Put in a disassembler.
185
struct RiscVLSInstructionInfo {
186
int instructionSize;
187
bool isIntegerLoadStore;
188
bool isFPLoadStore;
189
int size;
190
bool isMemoryWrite;
191
};
192
193
uint32_t word;
194
memcpy(&word, codePtr, 4);
195
196
RiscVLSInstructionInfo info{};
197
// Compressed instructions have low bits 00, 01, or 10.
198
info.instructionSize = (word & 3) == 3 ? 4 : 2;
199
instructionSize = info.instructionSize;
200
201
success = true;
202
switch (word & 0x7F) {
203
case 3:
204
info.isIntegerLoadStore = true;
205
info.size = 1 << ((word >> 12) & 3);
206
break;
207
case 7:
208
info.isFPLoadStore = true;
209
info.size = 1 << ((word >> 12) & 3);
210
break;
211
case 35:
212
info.isIntegerLoadStore = true;
213
info.isMemoryWrite = true;
214
info.size = 1 << ((word >> 12) & 3);
215
break;
216
case 39:
217
info.isFPLoadStore = true;
218
info.isMemoryWrite = true;
219
info.size = 1 << ((word >> 12) & 3);
220
break;
221
default:
222
// Compressed instruction.
223
switch (word & 0x6003) {
224
case 0x4000:
225
case 0x4002:
226
case 0x6000:
227
case 0x6002:
228
info.isIntegerLoadStore = true;
229
info.size = (word & 0x2000) != 0 ? 8 : 4;
230
info.isMemoryWrite = (word & 0x8000) != 0;
231
break;
232
case 0x2000:
233
case 0x2002:
234
info.isFPLoadStore = true;
235
info.size = 8;
236
info.isMemoryWrite = (word & 0x8000) != 0;
237
break;
238
default:
239
// Not a read or a write.
240
success = false;
241
break;
242
}
243
break;
244
}
245
#endif
246
247
if (MIPSComp::jit && MIPSComp::jit->IsAtDispatchFetch(codePtr)) {
248
u32 targetAddr = currentMIPS->pc; // bad approximation
249
// TODO: Do the other archs and platforms.
250
#if PPSSPP_ARCH(AMD64) && PPSSPP_PLATFORM(WINDOWS)
251
// We know which register the address is in, look in Asm.cpp.
252
targetAddr = (uint32_t)context->Rax;
253
#endif
254
Core_ExecException(targetAddr, currentMIPS->pc, ExecExceptionType::JUMP);
255
// Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately.
256
uintptr_t crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
257
if (crashHandler != 0) {
258
context->CTX_PC = crashHandler;
259
ERROR_LOG(Log::MemMap, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress);
260
inCrashHandler = false;
261
return true;
262
}
263
264
type = MemoryExceptionType::UNKNOWN;
265
} else if (success) {
266
if (info.isMemoryWrite) {
267
type = MemoryExceptionType::WRITE_WORD;
268
} else {
269
type = MemoryExceptionType::READ_WORD;
270
}
271
} else {
272
type = MemoryExceptionType::UNKNOWN;
273
}
274
275
g_lastMemoryExceptionType = type;
276
277
bool handled = true;
278
if (success && (g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) {
279
if (!info.isMemoryWrite) {
280
// It was a read. Fill the destination register with 0.
281
// TODO
282
}
283
// Move on to the next instruction. Note that handling bad accesses like this is pretty slow.
284
context->CTX_PC += info.instructionSize;
285
g_numReportedBadAccesses++;
286
if (g_numReportedBadAccesses < 100) {
287
ERROR_LOG(Log::MemMap, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress);
288
}
289
} else {
290
std::string infoString = "";
291
std::string temp;
292
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(codePtr, temp)) {
293
infoString += temp + "\n";
294
}
295
temp.clear();
296
if (DisassembleNativeAt(codePtr, instructionSize, &temp)) {
297
infoString += temp + "\n";
298
}
299
300
// Either bIgnoreBadMemAccess is off, or we failed recovery analysis.
301
// We can't ignore this memory access.
302
uint32_t approximatePC = currentMIPS->pc;
303
// TODO: Determine access size from the disassembled native instruction. We have some partial info already,
304
// just need to clean it up.
305
Core_MemoryExceptionInfo(guestAddress, 0, approximatePC, type, infoString, true);
306
307
// There's a small chance we can resume from this type of crash.
308
g_lastCrashAddress = codePtr;
309
310
// Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately.
311
uintptr_t crashHandler = 0;
312
if (MIPSComp::jit)
313
crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
314
if (crashHandler != 0)
315
context->CTX_PC = crashHandler;
316
else
317
handled = false;
318
ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str());
319
}
320
321
inCrashHandler = false;
322
return handled;
323
}
324
325
#else
326
327
bool HandleFault(uintptr_t hostAddress, void *ctx) {
328
ERROR_LOG(Log::MemMap, "Exception handling not supported");
329
return false;
330
}
331
332
#endif
333
334
} // namespace Memory
335
336
std::vector<MIPSStackWalk::StackFrame> WalkCurrentStack(int threadID) {
337
DebugInterface *cpuDebug = currentDebugMIPS;
338
339
auto threads = GetThreadsInfo();
340
uint32_t entry = cpuDebug->GetPC();
341
uint32_t stackTop = 0;
342
for (const DebugThreadInfo &th : threads) {
343
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
344
entry = th.entrypoint;
345
stackTop = th.initialStack;
346
break;
347
}
348
}
349
350
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
351
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
352
return MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
353
}
354
355
std::string FormatStackTrace(const std::vector<MIPSStackWalk::StackFrame> &frames) {
356
std::stringstream str;
357
for (const auto &frame : frames) {
358
std::string desc = g_symbolMap->GetDescription(frame.entry);
359
str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frame.entry, frame.pc - frame.entry, frame.pc, frame.sp);
360
}
361
return str.str();
362
}
363
364