prehash-for-perfect-hash: add truly-thes.
[sbcl.git] / tests / win64-exceptions.c
blob9438d0d152dd5aac4feacfcb72b9dce66ba02e2b
1 /* Compiled and loaded by win64-exceptions.impure.lisp
2 */
4 /* This software is part of the SBCL system. See the README file for
5 * more information.
7 * While most of SBCL is derived from the CMU CL system, the test
8 * files (like this one) were written from scratch after the fork
9 * from CMU CL.
11 * This software is in the public domain and is provided with
12 * absolutely no warranty. See the COPYING and CREDITS files for
13 * more information.
16 #include <windows.h>
17 #include <stdio.h>
18 #include <stdint.h>
20 extern int
21 unsafe_div(int dividend, int divisor)
23 return dividend / divisor;
26 extern void
27 raise_exception(int code)
29 RaiseException(code, EXCEPTION_NONCONTINUABLE, 0, NULL);
32 struct division {
33 int dividend;
34 int divisor;
35 int result;
38 static DWORD WINAPI
39 thread_fn(LPVOID param)
41 struct division *x = param;
42 x->result = x->dividend / x->divisor;
43 return 0;
46 /* Only used in self-tests, since SBCL can't (and probably shouldn't) handle
47 * exceptions in foreign threads. */
48 extern int
49 unsafe_div_in_foreign_thread(int dividend, int divisor)
51 struct division x = { dividend, divisor, -1 };
52 DWORD tid;
53 HANDLE thread = CreateThread(NULL, 0, thread_fn, &x, 0, &tid);
54 WaitForMultipleObjects(1, &thread, TRUE, INFINITE);
55 CloseHandle(thread);
56 return x.result;
59 typedef struct _UNWIND_INFO {
60 uint8_t Version : 3;
61 uint8_t Flags : 5;
62 uint8_t SizeOfProlog;
63 uint8_t CountOfCodes;
64 uint8_t FrameRegister : 4;
65 uint8_t FrameOffset : 4;
66 ULONG ExceptionHandler;
67 ULONG ExceptionData[1];
68 } UNWIND_INFO;
70 struct fn {
71 uint8_t code[8];
72 uint8_t eh_trampoline[16];
73 UNWIND_INFO ui; // needs to be DWORD-aligned
74 RUNTIME_FUNCTION rt;
77 static EXCEPTION_DISPOSITION
78 div_exception_handler(PEXCEPTION_RECORD ExceptionRecord,
79 ULONG64 EstablisherFrame,
80 PCONTEXT ContextRecord,
81 PDISPATCHER_CONTEXT DispatcherContext)
83 printf("div_exception_handler() got exception 0x%lx; continuing\n",
84 ExceptionRecord->ExceptionCode);
85 fflush(stdout);
86 ContextRecord->Rip += 2; // skip over DIV instruction
87 return ExceptionContinueExecution;
90 static EXCEPTION_DISPOSITION
91 mov_exception_handler(PEXCEPTION_RECORD ExceptionRecord,
92 ULONG64 EstablisherFrame,
93 PCONTEXT ContextRecord,
94 PDISPATCHER_CONTEXT DispatcherContext)
96 printf("mov_exception_handler() got exception 0x%lx; continuing\n",
97 ExceptionRecord->ExceptionCode);
98 fflush(stdout);
99 ContextRecord->Rip += 7; // skip over 7-byte MOV instruction
100 return ExceptionContinueExecution;
104 static BOOLEAN
105 set_up_exception_handler(struct fn *fn, void *handler)
107 DWORD64 base = (DWORD64) fn;
109 uint8_t *tramp = fn->eh_trampoline;
110 tramp[0] = 0xFF; // jmp qword ptr [rip+2]
111 tramp[1] = 0x25;
112 tramp[2] = 0x02;
113 tramp[3] = 0x00;
114 tramp[4] = 0x00;
115 tramp[5] = 0x00;
116 tramp[6] = 0x66; // 2-byte nop
117 tramp[7] = 0x90;
118 *(void **)(tramp+8) = handler;
120 UNWIND_INFO *ui = &fn->ui;
121 ui->Version = 1;
122 ui->Flags = UNW_FLAG_EHANDLER;
123 ui->SizeOfProlog = 0;
124 ui->CountOfCodes = 0;
125 ui->FrameRegister = 0;
126 ui->FrameOffset = 0;
127 ui->ExceptionHandler = (DWORD64) tramp - base;
128 ui->ExceptionData[0] = 0;
130 RUNTIME_FUNCTION *rt = &fn->rt;
131 rt->BeginAddress = 0;
132 rt->EndAddress = 8;
133 rt->UnwindData = (DWORD64) ui - base;
135 return RtlAddFunctionTable(rt, 1, base);
138 /* Sadly, MINGW's __try1/__except don't compile with -shared, so we have to do
139 * exception handling the hard way, again. */
140 extern int
141 raise_int_divide_by_zero(int handle)
143 struct fn *fn = VirtualAlloc(NULL, sizeof(struct fn), MEM_COMMIT,
144 PAGE_EXECUTE_READWRITE);
146 uint8_t *code = fn->code;
147 code[0] = 0x31; // xor ecx, ecx
148 code[1] = 0xC9;
149 code[2] = 0xF7; // div ecx
150 code[3] = 0xF1;
151 code[4] = 0xC3; // ret
152 code[5] = code[6] = code[7] = 0x90; // nop
154 if (handle && !set_up_exception_handler(fn, div_exception_handler))
155 return 0;
157 // will raise EXCEPTION_INT_DIVIDE_BY_ZERO
158 (*((void (*)()) code))();
160 return 1;
163 extern int *
164 allocate_readonly_int()
166 return VirtualAlloc(NULL, sizeof(int), MEM_COMMIT, PAGE_READONLY);
169 extern int
170 free_readonly_int(int *n)
172 return VirtualFree(n, sizeof(int), MEM_RELEASE);
175 extern int
176 raise_access_violation(int handle)
178 struct fn *fn = VirtualAlloc(NULL, sizeof(struct fn), MEM_COMMIT,
179 PAGE_EXECUTE_READWRITE);
181 uint8_t *code = fn->code;
182 code[0] = 0x48; // mov qword ptr [rcx], 42
183 code[1] = 0xC7;
184 code[2] = 0x01;
185 code[3] = 0x2A;
186 code[4] = 0x00;
187 code[5] = 0x00;
188 code[6] = 0x00;
189 code[7] = 0xC3; // ret
191 if (handle && !set_up_exception_handler(fn, mov_exception_handler))
192 return 0;
194 int *n = allocate_readonly_int();
196 // will raise EXCEPTION_ACCESS_VIOLATION
197 (*((void (*)(int *)) code))(n);
199 free_readonly_int(n);
201 return 1;
205 * Self-tests. As of GCC 9.2.0, __try1/__except1 is very finicky. -O1 or higher
206 * fails to compile, they can only appear once per file (therefore we need to
207 * enable each test case individually), and there are some restrictions on what
208 * can happen within the __try1 block (e.g., no returns).
210 * Run with:
211 * for n in {1..6}; do gcc -DTEST_CASE=$n win64-exceptions.c && ./a.exe; done
214 #ifndef TEST_CASE
215 #define TEST_CASE 0
216 #endif
218 #if TEST_CASE > 0
219 static long CALLBACK
220 eh(EXCEPTION_POINTERS *ep)
222 printf("raised exception 0x%lx ", ep->ExceptionRecord->ExceptionCode);
223 fflush(stdout);
224 return EXCEPTION_EXECUTE_HANDLER;
227 int main(void)
229 printf("\n>>> TEST_CASE=%d\n", TEST_CASE); fflush(stdout);
231 # if TEST_CASE == 1
232 printf("unsafe_div(12, 4) = %d\n", unsafe_div(12, 4)); fflush(stdout);
234 __try1(eh) {
235 printf("unsafe_div(12, 0) = "); fflush(stdout);
236 printf("%d (unexpectedly)\n", unsafe_div(12, 0)); fflush(stdout);
237 } __except1 {
238 printf("which was handled gracefully\n"); fflush(stdout);
240 # endif
242 # if TEST_CASE == 2
243 __try1(eh) {
244 printf("raise_exception(42) ... "); fflush(stdout);
245 raise_exception(42);
246 } __except1 {
247 printf("which was handled gracefully\n"); fflush(stdout);
249 # endif
251 # if TEST_CASE == 3
252 printf("unsafe_div_in_foreign_thread(12, 4) = %d\n",
253 unsafe_div_in_foreign_thread(12, 4));
254 fflush(stdout);
256 printf("unsafe_div_in_foreign_thread(12, 0) ... should kill the process\n");
257 fflush(stdout);
258 printf("unexpectedly, it returned %d\n",
259 unsafe_div_in_foreign_thread(12, 0));
260 fflush(stdout);
261 # endif
263 # if TEST_CASE == 4
264 printf("raise_int_divide_by_zero(1) = %d\n",
265 raise_int_divide_by_zero(1)); fflush(stdout);
267 __try1(eh) {
268 printf("raise_int_divide_by_zero(0) = "); fflush(stdout);
269 printf("%d (unexpectedly)\n", raise_int_divide_by_zero(0));
270 fflush(stdout);
271 } __except1 {
272 printf("which was handled gracefully\n"); fflush(stdout);
274 # endif
276 # if TEST_CASE == 5
277 printf("raise_access_violation(1) = %d\n",
278 raise_access_violation(1)); fflush(stdout);
280 __try1(eh) {
281 printf("raise_access_violation(0) = "); fflush(stdout);
282 printf("%d (unexpectedly)\n", raise_access_violation(0));
283 fflush(stdout);
284 } __except1 {
285 printf("which was handled gracefully\n"); fflush(stdout);
287 # endif
289 # if TEST_CASE == 6
290 int *n = allocate_readonly_int();
291 __try1(eh) {
292 printf("writing into read-only memory "); fflush(stdout);
293 printf("yielded %d (unexpectedly)\n", *n = 42); fflush(stdout);
294 } __except1 {
295 printf("which was handled gracefully\n"); fflush(stdout);
296 free_readonly_int(n);
298 # endif
299 return EXIT_SUCCESS;
301 #endif