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/Common/ExceptionHandlerSetup.cpp
Views: 1401
1
// Copyright 2008 Dolphin Emulator Project
2
// Licensed under GPLv2+
3
// Refer to the license.txt file included.
4
5
// The corresponding file is called MemTools in the Dolphin project.
6
7
#include "ppsspp_config.h"
8
9
#include <cstdio>
10
#include <cstdlib>
11
#include <cstring>
12
#include <vector>
13
#include <thread>
14
15
#include "Common/CommonFuncs.h"
16
#include "Common/CommonTypes.h"
17
#include "Common/Log.h"
18
#include "Common/Thread/ThreadUtil.h"
19
#include "Common/MachineContext.h"
20
#include "Common/ExceptionHandlerSetup.h"
21
22
static BadAccessHandler g_badAccessHandler;
23
static void *altStack = nullptr;
24
25
#ifdef MACHINE_CONTEXT_SUPPORTED
26
27
// We cannot handle exceptions in UWP builds. Bleh.
28
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
29
30
static PVOID g_vectoredExceptionHandle;
31
32
static LONG NTAPI GlobalExceptionHandler(PEXCEPTION_POINTERS pPtrs) {
33
switch (pPtrs->ExceptionRecord->ExceptionCode) {
34
case EXCEPTION_ACCESS_VIOLATION:
35
{
36
int accessType = (int)pPtrs->ExceptionRecord->ExceptionInformation[0];
37
if (accessType == 8) { // Rule out DEP
38
return (DWORD)EXCEPTION_CONTINUE_SEARCH;
39
}
40
41
// virtual address of the inaccessible data
42
uintptr_t badAddress = (uintptr_t)pPtrs->ExceptionRecord->ExceptionInformation[1];
43
CONTEXT* ctx = pPtrs->ContextRecord;
44
45
if (g_badAccessHandler(badAddress, ctx)) {
46
return (DWORD)EXCEPTION_CONTINUE_EXECUTION;
47
} else {
48
// Let's not prevent debugging.
49
return (DWORD)EXCEPTION_CONTINUE_SEARCH;
50
}
51
}
52
53
case EXCEPTION_STACK_OVERFLOW:
54
// Dolphin has some handling of this for the RET optimization emulation.
55
return EXCEPTION_CONTINUE_SEARCH;
56
57
case EXCEPTION_ILLEGAL_INSTRUCTION:
58
// No SSE support? Or simply bad codegen?
59
return EXCEPTION_CONTINUE_SEARCH;
60
61
case EXCEPTION_PRIV_INSTRUCTION:
62
// okay, dynarec codegen is obviously broken.
63
return EXCEPTION_CONTINUE_SEARCH;
64
65
case EXCEPTION_IN_PAGE_ERROR:
66
// okay, something went seriously wrong, out of memory?
67
return EXCEPTION_CONTINUE_SEARCH;
68
69
case EXCEPTION_BREAKPOINT:
70
// might want to do something fun with this one day?
71
return EXCEPTION_CONTINUE_SEARCH;
72
73
default:
74
return EXCEPTION_CONTINUE_SEARCH;
75
}
76
}
77
78
void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
79
if (g_vectoredExceptionHandle) {
80
g_badAccessHandler = badAccessHandler;
81
return;
82
}
83
84
INFO_LOG(Log::System, "Installing exception handler");
85
g_badAccessHandler = badAccessHandler;
86
g_vectoredExceptionHandle = AddVectoredExceptionHandler(TRUE, GlobalExceptionHandler);
87
}
88
89
void UninstallExceptionHandler() {
90
if (g_vectoredExceptionHandle) {
91
RemoveVectoredExceptionHandler(g_vectoredExceptionHandle);
92
INFO_LOG(Log::System, "Removed exception handler");
93
g_vectoredExceptionHandle = nullptr;
94
}
95
g_badAccessHandler = nullptr;
96
}
97
98
#elif defined(__APPLE__)
99
100
static void CheckKR(const char* name, kern_return_t kr) {
101
_assert_msg_(kr == 0, "%s failed: kr=%x", name, kr);
102
}
103
104
static void ExceptionThread(mach_port_t port) {
105
SetCurrentThreadName("Mach exception thread");
106
#pragma pack(4)
107
struct {
108
mach_msg_header_t Head;
109
NDR_record_t NDR;
110
exception_type_t exception;
111
mach_msg_type_number_t codeCnt;
112
int64_t code[2];
113
int flavor;
114
mach_msg_type_number_t old_stateCnt;
115
natural_t old_state[x86_THREAD_STATE64_COUNT];
116
mach_msg_trailer_t trailer;
117
} msg_in;
118
119
struct {
120
mach_msg_header_t Head;
121
NDR_record_t NDR;
122
kern_return_t RetCode;
123
int flavor;
124
mach_msg_type_number_t new_stateCnt;
125
natural_t new_state[x86_THREAD_STATE64_COUNT];
126
} msg_out;
127
#pragma pack()
128
memset(&msg_in, 0xee, sizeof(msg_in));
129
memset(&msg_out, 0xee, sizeof(msg_out));
130
mach_msg_header_t* send_msg = &msg_out.Head;
131
mach_msg_size_t send_size = 0;
132
mach_msg_option_t option = MACH_RCV_MSG;
133
while (true) {
134
// If this isn't the first run, send the reply message. Then, receive
135
// a message: either a mach_exception_raise_state RPC due to
136
// thread_set_exception_ports, or MACH_NOTIFY_NO_SENDERS due to
137
// mach_port_request_notification.
138
CheckKR("mach_msg_overwrite",
139
mach_msg_overwrite(send_msg, option, send_size, sizeof(msg_in), port,
140
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg_in.Head, 0));
141
142
if (msg_in.Head.msgh_id == MACH_NOTIFY_NO_SENDERS) {
143
// the other thread exited
144
mach_port_destroy(mach_task_self(), port);
145
return;
146
}
147
148
_assert_msg_(msg_in.Head.msgh_id == 2406, "unknown message received");
149
_assert_msg_(msg_in.flavor == x86_THREAD_STATE64, "unknown flavor %d (expected %d)", msg_in.flavor, x86_THREAD_STATE64);
150
151
x86_thread_state64_t* state = (x86_thread_state64_t*)msg_in.old_state;
152
153
bool ok = g_badAccessHandler((uintptr_t)msg_in.code[1], state);
154
155
// Set up the reply.
156
msg_out.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg_in.Head.msgh_bits), 0);
157
msg_out.Head.msgh_remote_port = msg_in.Head.msgh_remote_port;
158
msg_out.Head.msgh_local_port = MACH_PORT_NULL;
159
msg_out.Head.msgh_id = msg_in.Head.msgh_id + 100;
160
msg_out.NDR = msg_in.NDR;
161
if (ok) {
162
msg_out.RetCode = KERN_SUCCESS;
163
msg_out.flavor = x86_THREAD_STATE64;
164
msg_out.new_stateCnt = x86_THREAD_STATE64_COUNT;
165
memcpy(msg_out.new_state, msg_in.old_state, x86_THREAD_STATE64_COUNT * sizeof(natural_t));
166
} else {
167
// Pass the exception to the next handler (debugger or crash).
168
msg_out.RetCode = KERN_FAILURE;
169
msg_out.flavor = 0;
170
msg_out.new_stateCnt = 0;
171
}
172
msg_out.Head.msgh_size =
173
offsetof(__typeof__(msg_out), new_state) + msg_out.new_stateCnt * sizeof(natural_t);
174
175
send_msg = &msg_out.Head;
176
send_size = msg_out.Head.msgh_size;
177
option |= MACH_SEND_MSG;
178
}
179
}
180
181
void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
182
if (g_badAccessHandler) {
183
// The rest of the setup we don't need to do again.
184
g_badAccessHandler = badAccessHandler;
185
return;
186
}
187
g_badAccessHandler = badAccessHandler;
188
189
INFO_LOG(Log::System, "Installing exception handler");
190
mach_port_t port;
191
CheckKR("mach_port_allocate",
192
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port));
193
std::thread exc_thread(ExceptionThread, port);
194
exc_thread.detach();
195
// Obtain a send right for thread_set_exception_ports to copy...
196
CheckKR("mach_port_insert_right",
197
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND));
198
// Mach tries the following exception ports in order: thread, task, host.
199
// Debuggers set the task port, so we grab the thread port.
200
CheckKR("thread_set_exception_ports",
201
thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port,
202
EXCEPTION_STATE | MACH_EXCEPTION_CODES, x86_THREAD_STATE64));
203
// ...and get rid of our copy so that MACH_NOTIFY_NO_SENDERS works.
204
CheckKR("mach_port_mod_refs",
205
mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1));
206
mach_port_t previous;
207
CheckKR("mach_port_request_notification",
208
mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS, 0, port,
209
MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous));
210
}
211
212
void UninstallExceptionHandler() {
213
}
214
215
#else
216
217
#include <signal.h>
218
219
static struct sigaction old_sa_segv;
220
static struct sigaction old_sa_bus;
221
222
static void sigsegv_handler(int sig, siginfo_t* info, void* raw_context) {
223
if (sig != SIGSEGV && sig != SIGBUS) {
224
// We are not interested in other signals - handle it as usual.
225
return;
226
}
227
ucontext_t* context = (ucontext_t*)raw_context;
228
int sicode = info->si_code;
229
if (sicode != SEGV_MAPERR && sicode != SEGV_ACCERR) {
230
// Huh? Return.
231
return;
232
}
233
uintptr_t bad_address = (uintptr_t)info->si_addr;
234
235
// Get all the information we can out of the context.
236
#ifdef __OpenBSD__
237
ucontext_t* ctx = context;
238
#else
239
mcontext_t* ctx = &context->uc_mcontext;
240
#endif
241
// assume it's not a write
242
if (!g_badAccessHandler(bad_address,
243
#ifdef __APPLE__
244
*ctx
245
#else
246
ctx
247
#endif
248
)) {
249
// retry and crash
250
// According to the sigaction man page, if sa_flags "SA_SIGINFO" is set to the sigaction
251
// function pointer, otherwise sa_handler contains one of:
252
// SIG_DEF: The 'default' action is performed
253
// SIG_IGN: The signal is ignored
254
// Any other value is a function pointer to a signal handler
255
256
struct sigaction* old_sa;
257
if (sig == SIGSEGV) {
258
old_sa = &old_sa_segv;
259
} else {
260
old_sa = &old_sa_bus;
261
}
262
263
if (old_sa->sa_flags & SA_SIGINFO) {
264
old_sa->sa_sigaction(sig, info, raw_context);
265
return;
266
}
267
if (old_sa->sa_handler == SIG_DFL) {
268
signal(sig, SIG_DFL);
269
return;
270
}
271
if (old_sa->sa_handler == SIG_IGN) {
272
// Ignore signal
273
return;
274
}
275
old_sa->sa_handler(sig);
276
}
277
}
278
279
void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
280
if (!badAccessHandler) {
281
return;
282
}
283
if (g_badAccessHandler) {
284
g_badAccessHandler = badAccessHandler;
285
return;
286
}
287
288
size_t altStackSize = SIGSTKSZ;
289
290
// Add some extra room.
291
altStackSize += 65536;
292
293
INFO_LOG(Log::System, "Installed exception handler. stack size: %d", (int)altStackSize);
294
g_badAccessHandler = badAccessHandler;
295
296
stack_t signal_stack{};
297
altStack = malloc(altStackSize);
298
#ifdef __FreeBSD__
299
signal_stack.ss_sp = (char*)altStack;
300
#else
301
signal_stack.ss_sp = altStack;
302
#endif
303
signal_stack.ss_size = altStackSize;
304
signal_stack.ss_flags = 0;
305
if (sigaltstack(&signal_stack, nullptr)) {
306
_assert_msg_(false, "sigaltstack failed");
307
}
308
struct sigaction sa{};
309
sa.sa_handler = nullptr;
310
sa.sa_sigaction = &sigsegv_handler;
311
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
312
sigemptyset(&sa.sa_mask);
313
sigaction(SIGSEGV, &sa, &old_sa_segv);
314
#ifdef __APPLE__
315
sigaction(SIGBUS, &sa, &old_sa_bus);
316
#endif
317
}
318
319
void UninstallExceptionHandler() {
320
if (!g_badAccessHandler) {
321
return;
322
}
323
stack_t signal_stack{};
324
signal_stack.ss_flags = SS_DISABLE;
325
if (0 != sigaltstack(&signal_stack, nullptr)) {
326
ERROR_LOG(Log::System, "Could not remove signal altstack");
327
}
328
if (altStack) {
329
free(altStack);
330
altStack = nullptr;
331
}
332
sigaction(SIGSEGV, &old_sa_segv, nullptr);
333
#ifdef __APPLE__
334
sigaction(SIGBUS, &old_sa_bus, nullptr);
335
#endif
336
INFO_LOG(Log::System, "Uninstalled exception handler");
337
g_badAccessHandler = nullptr;
338
}
339
340
#endif
341
342
#else // !MACHINE_CONTEXT_SUPPORTED
343
344
void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
345
ERROR_LOG(Log::System, "Exception handler not implemented on this platform, can't install");
346
}
347
void UninstallExceptionHandler() { }
348
349
#endif // MACHINE_CONTEXT_SUPPORTED
350
351