1 /* Compiled and loaded by win64-exceptions.impure.lisp
4 /* This software is part of the SBCL system. See the README file for
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
11 * This software is in the public domain and is provided with
12 * absolutely no warranty. See the COPYING and CREDITS files for
21 unsafe_div(int dividend
, int divisor
)
23 return dividend
/ divisor
;
27 raise_exception(int code
)
29 RaiseException(code
, EXCEPTION_NONCONTINUABLE
, 0, NULL
);
39 thread_fn(LPVOID param
)
41 struct division
*x
= param
;
42 x
->result
= x
->dividend
/ x
->divisor
;
46 /* Only used in self-tests, since SBCL can't (and probably shouldn't) handle
47 * exceptions in foreign threads. */
49 unsafe_div_in_foreign_thread(int dividend
, int divisor
)
51 struct division x
= { dividend
, divisor
, -1 };
53 HANDLE thread
= CreateThread(NULL
, 0, thread_fn
, &x
, 0, &tid
);
54 WaitForMultipleObjects(1, &thread
, TRUE
, INFINITE
);
59 typedef struct _UNWIND_INFO
{
64 uint8_t FrameRegister
: 4;
65 uint8_t FrameOffset
: 4;
66 ULONG ExceptionHandler
;
67 ULONG ExceptionData
[1];
72 uint8_t eh_trampoline
[16];
73 UNWIND_INFO ui
; // needs to be DWORD-aligned
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
);
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
);
99 ContextRecord
->Rip
+= 7; // skip over 7-byte MOV instruction
100 return ExceptionContinueExecution
;
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]
116 tramp
[6] = 0x66; // 2-byte nop
118 *(void **)(tramp
+8) = handler
;
120 UNWIND_INFO
*ui
= &fn
->ui
;
122 ui
->Flags
= UNW_FLAG_EHANDLER
;
123 ui
->SizeOfProlog
= 0;
124 ui
->CountOfCodes
= 0;
125 ui
->FrameRegister
= 0;
127 ui
->ExceptionHandler
= (DWORD64
) tramp
- base
;
128 ui
->ExceptionData
[0] = 0;
130 RUNTIME_FUNCTION
*rt
= &fn
->rt
;
131 rt
->BeginAddress
= 0;
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. */
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
149 code
[2] = 0xF7; // div ecx
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
))
157 // will raise EXCEPTION_INT_DIVIDE_BY_ZERO
158 (*((void (*)()) code
))();
164 allocate_readonly_int()
166 return VirtualAlloc(NULL
, sizeof(int), MEM_COMMIT
, PAGE_READONLY
);
170 free_readonly_int(int *n
)
172 return VirtualFree(n
, sizeof(int), MEM_RELEASE
);
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
189 code
[7] = 0xC3; // ret
191 if (handle
&& !set_up_exception_handler(fn
, mov_exception_handler
))
194 int *n
= allocate_readonly_int();
196 // will raise EXCEPTION_ACCESS_VIOLATION
197 (*((void (*)(int *)) code
))(n
);
199 free_readonly_int(n
);
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).
211 * for n in {1..6}; do gcc -DTEST_CASE=$n win64-exceptions.c && ./a.exe; done
220 eh(EXCEPTION_POINTERS
*ep
)
222 printf("raised exception 0x%lx ", ep
->ExceptionRecord
->ExceptionCode
);
224 return EXCEPTION_EXECUTE_HANDLER
;
229 printf("\n>>> TEST_CASE=%d\n", TEST_CASE
); fflush(stdout
);
232 printf("unsafe_div(12, 4) = %d\n", unsafe_div(12, 4)); fflush(stdout
);
235 printf("unsafe_div(12, 0) = "); fflush(stdout
);
236 printf("%d (unexpectedly)\n", unsafe_div(12, 0)); fflush(stdout
);
238 printf("which was handled gracefully\n"); fflush(stdout
);
244 printf("raise_exception(42) ... "); fflush(stdout
);
247 printf("which was handled gracefully\n"); fflush(stdout
);
252 printf("unsafe_div_in_foreign_thread(12, 4) = %d\n",
253 unsafe_div_in_foreign_thread(12, 4));
256 printf("unsafe_div_in_foreign_thread(12, 0) ... should kill the process\n");
258 printf("unexpectedly, it returned %d\n",
259 unsafe_div_in_foreign_thread(12, 0));
264 printf("raise_int_divide_by_zero(1) = %d\n",
265 raise_int_divide_by_zero(1)); fflush(stdout
);
268 printf("raise_int_divide_by_zero(0) = "); fflush(stdout
);
269 printf("%d (unexpectedly)\n", raise_int_divide_by_zero(0));
272 printf("which was handled gracefully\n"); fflush(stdout
);
277 printf("raise_access_violation(1) = %d\n",
278 raise_access_violation(1)); fflush(stdout
);
281 printf("raise_access_violation(0) = "); fflush(stdout
);
282 printf("%d (unexpectedly)\n", raise_access_violation(0));
285 printf("which was handled gracefully\n"); fflush(stdout
);
290 int *n
= allocate_readonly_int();
292 printf("writing into read-only memory "); fflush(stdout
);
293 printf("yielded %d (unexpectedly)\n", *n
= 42); fflush(stdout
);
295 printf("which was handled gracefully\n"); fflush(stdout
);
296 free_readonly_int(n
);