Last active
April 16, 2021 15:42
-
-
Save zefklop/a2fa222a405527778b7b0449e654c80d to your computer and use it in GitHub Desktop.
GCC x64 _SEH2_EXCEPT implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <stdio.h> | |
#include <intrin.h> | |
#include <stdint.h> | |
#define TOKENIZE(x) #x | |
#define TOKTOKENIZE(x) TOKENIZE(x) | |
#define LINE_TOKEN TOKTOKENIZE(__LINE__) | |
/* Declare our global trampoline function for filter and unwinder */ | |
__asm__( | |
"\t.seh_proc __seh2_global_filter_func\n" | |
"__seh2_global_filter_func:\n" | |
"\tpush %rbp\n" | |
"\t.seh_pushreg %rbp\n" | |
"\tsub $32, %rsp\n" | |
"\t.seh_stackalloc 32\n" | |
"\t.seh_endprologue\n" | |
/* Restore frame pointer. */ | |
"\tmov %rdx, %rbp\n" | |
/* Tell our finally funclet that this is abnormal termination */ | |
"\tmov $1, %edx\n" | |
/* Actually execute the filter funclet */ | |
"\tjmp *%rax\n" | |
"__seh2_global_filter_func_exit:\n" | |
"\t.p2align 4\n" | |
"\tadd $32, %rsp\n" | |
"\tpop %rbp\n" | |
"\tret\n" | |
"\t.seh_endproc"); | |
#if 0 | |
#define _SEH2_TRY \ | |
{ \ | |
__label__ __seh2$$real_handler__; \ | |
auto void __seh2$$nested_try_func(void); \ | |
/* Actually call it */ \ | |
__seh2$$nested_try_func(); \ | |
void \ | |
__attribute__((__optimize__("-fno-omit-frame-pointer"))) \ | |
__seh2$$nested_try_func(void) \ | |
{ \ | |
__begin_try__: | |
#define _SEH2_EXCEPT(...) \ | |
__done_try__: \ | |
\ | |
/* Declare our handlers & filters */ \ | |
__asm__ __volatile__ goto ( \ | |
".seh_handler __C_specific_handler, @except\n" \ | |
".seh_handlerdata\n" \ | |
"\t.long 1\n" \ | |
"\t.rva %l0, %l1\n" /* Range of tried code */ \ | |
"\t.rva __seh2_filter_" LINE_TOKEN "\n" /* filter */ \ | |
"\t.rva %l2\n" /* handler */ \ | |
"\t.text" \ | |
: : : : \ | |
__begin_try__, \ | |
__done_try__, \ | |
__handler__); \ | |
\ | |
if (0) \ | |
{ \ | |
/* Get out of here */ \ | |
__handler__: \ | |
goto __seh2$$real_handler__; \ | |
} \ | |
} \ | |
/* This allows us to force using frame pointer (even with -fomit-frame-pointer) \ | |
* and having a non-trivial-always-false condition forces GCC to actually \ | |
* compile all this below */ \ | |
if (__builtin_frame_address(0) == 0) \ | |
{ \ | |
/* Jump to the global filter. Tell it where the filter funclet lies */ \ | |
__asm__( \ | |
"__seh2_filter_" LINE_TOKEN ":\n" \ | |
"\tleaq __seh2_filter_funclet_" LINE_TOKEN "(%rip), %rax\n" \ | |
"\tjmp __seh2_global_filter_func\n"); \ | |
/* Actually declare our filter funclet */ \ | |
__asm__("__seh2_filter_funclet_" LINE_TOKEN ":\n"); \ | |
/* At this point, the compiler can't count on any register being valid */ \ | |
__asm__ __volatile__("nop" \ | |
: /* No output */ \ | |
: /* No input */ \ | |
: /* Everything */ \ | |
"%rax", "%rbx", "%rcx", "%rdx", "%rdi", "%rsi", \ | |
"%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15"); \ | |
/* Actually evaluate our filter */ \ | |
register long __filter_funclet_ret asm("eax") = ((__VA_ARGS__)); \ | |
/* Go back to the global filter function */ \ | |
__asm__("jmp __seh2_global_filter_func_exit"); \ | |
\ | |
/* Now we can declare our handler */ \ | |
__seh2$$real_handler__: | |
#define _SEH2_FINALLY \ | |
__done_try__: \ | |
\ | |
/* Declare our handlers & filters */ \ | |
__asm__ __volatile__ goto ( \ | |
".seh_handler __C_specific_handler, @unwind\n" \ | |
".seh_handlerdata\n" \ | |
"\t.long 1\n" \ | |
"\t.rva %l0, %l1\n" /* Range of tried code */ \ | |
"\t.rva __seh2_finally_" LINE_TOKEN "\n" /* unwinder */ \ | |
"\t.long 0\n" /* nothing */ \ | |
"\t.text" \ | |
: : : : \ | |
__begin_try__, \ | |
__done_try__); \ | |
} \ | |
/* This allows us to force using frame pointer (even with -fomit-frame-pointer) \ | |
* and having a non-trivial-always-false condition forces GCC to actually \ | |
* compile all this below */ \ | |
if (__builtin_frame_address(0) == 0) \ | |
{ \ | |
/* There is actually little difference \ | |
* between the filter and the finally funclet. \ | |
* Except that the finally part must be executed \ | |
* even if there is no exception raised. */ \ | |
else | |
#define _SEH2_END \ | |
} /* End of except or finally code */ \ | |
} | |
#else | |
#define _SEH2_TRY \ | |
{ \ | |
__label__ __seh2$$begin_try__; \ | |
__label__ __seh2$$end_try__; \ | |
__label__ __seh2$$handler__; \ | |
/* GAS won't allow us to add data to the current function, \ | |
* but it allows to close it and open a new one. So do that. */ \ | |
__asm__( \ | |
".seh_endproc\n" \ | |
".seh_proc __seh2_try_block_" LINE_TOKEN); \ | |
__seh2$$begin_try__: | |
#define _SEH2_EXCEPT(...) \ | |
__seh2$$end_try__: \ | |
\ | |
__asm__ __volatile__ goto ( \ | |
".seh_handler __C_specific_handler, @except\n" \ | |
".seh_handlerdata\n" \ | |
"\t.long 1\n" \ | |
"\t.rva %l0, %l1\n" /* Range of tried code */ \ | |
"\t.rva __seh2_filter_" LINE_TOKEN "\n" /* filter */ \ | |
"\t.rva %l2\n" /* handler */ \ | |
"\t.text\n" \ | |
".seh_endproc\n" \ | |
".seh_proc __seh2_try_block_" LINE_TOKEN "_byebye" \ | |
: : : : \ | |
__seh2$$begin_try__, \ | |
__seh2$$end_try__, \ | |
__seh2$$handler__); \ | |
/* This allows us to force using frame pointer (even with -fomit-frame-pointer) \ | |
* and having a non-trivial-always-false condition forces GCC to actually \ | |
* compile all this below */ \ | |
if (__builtin_frame_address(0) == 0) \ | |
{ \ | |
/* Jump to the global filter. Tell it where the filter funclet lies */ \ | |
__asm__( \ | |
"__seh2_filter_" LINE_TOKEN ":\n" \ | |
"\tleaq __seh2_filter_funclet_" LINE_TOKEN "(%rip), %rax\n" \ | |
"\tjmp __seh2_global_filter_func\n"); \ | |
/* Actually declare our filter funclet */ \ | |
__asm__("__seh2_filter_funclet_" LINE_TOKEN ":\n"); \ | |
/* At this point, the compiler can't count on any register being valid */ \ | |
__asm__ __volatile__("nop" \ | |
: /* No output */ \ | |
: /* No input */ \ | |
: /* Everything */ \ | |
"%rax", "%rbx", "%rcx", "%rdx", "%rdi", "%rsi", \ | |
"%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15"); \ | |
/* Actually evaluate our filter */ \ | |
register long __filter_funclet_ret asm("eax") = ((__VA_ARGS__)); \ | |
/* Go back to the global filter function */ \ | |
__asm__("jmp __seh2_global_filter_func_exit"); \ | |
static const int __seh2$$abnormal_termination__ = 0; \ | |
__seh2$$handler__: | |
#define _SEH2_FINALLY \ | |
__seh2$$end_try__: \ | |
\ | |
/* Declare our handlers & filters */ \ | |
__asm__ __volatile__ goto ( \ | |
".seh_handler __C_specific_handler, @unwind\n" \ | |
".seh_handlerdata\n" \ | |
"\t.long 1\n" \ | |
"\t.rva %l0, %l1\n" /* Range of tried code */ \ | |
"\t.rva __seh2_unwind_" LINE_TOKEN "\n" /* filter */ \ | |
"\t.long 0\n" /* nothing */ \ | |
"\t.text\n" \ | |
".seh_endproc\n" \ | |
".seh_proc __seh2_try_block_" LINE_TOKEN "_byebye\n" \ | |
: : : : \ | |
__seh2$$begin_try__, \ | |
__seh2$$end_try__); \ | |
/* This allows us to force using frame pointer (even with -fomit-frame-pointer) \ | |
* and having a non-trivial-always-false condition forces GCC to actually \ | |
* compile all this below */ \ | |
int __seh2$$abnormal_termination__; \ | |
if (__builtin_frame_address(0) == 0) \ | |
{ \ | |
/* Jump to the global filter. Tell it where the filter funclet lies */ \ | |
__asm__( \ | |
"__seh2_unwind_" LINE_TOKEN ":\n" \ | |
"\tleaq __seh2_unwind_funclet_" LINE_TOKEN "(%rip), %rax\n" \ | |
"\tjmp __seh2_global_filter_func\n"); \ | |
} \ | |
/* At this point, the compiler can't count on any register being valid */ \ | |
__asm__ __volatile__("nop" \ | |
: /* No output */ \ | |
: /* No input */ \ | |
: /* Everything */ \ | |
"%rax", "%rbx", "%rcx", "%rdx", "%rdi", "%rsi", \ | |
"%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15"); \ | |
/* Actually declare our finally funclet */ \ | |
__asm__ __volatile__( \ | |
"xor %%edx, %%edx\n" /* Normal termination, unless said so */ \ | |
"__seh2_unwind_funclet_" LINE_TOKEN ":\n" \ | |
"mov %%edx, %0" \ | |
: "=r" (__seh2$$abnormal_termination__)); \ | |
{ | |
#define _SEH2_END \ | |
if (__seh2$$abnormal_termination__) \ | |
{ \ | |
__asm__("jmp __seh2_global_filter_func_exit"); \ | |
} \ | |
} \ | |
} | |
#define _SEH2_GetExceptionInformation() __seh2$$exception_ptr | |
#define _SEH2_LEAVE goto __seh2$$leave_scope | |
#endif | |
int* get_prout(void) | |
{ | |
return (int*) 5; | |
} | |
//__attribute__((optimize("-fomit-frame-pointer"))) | |
void call_seh(void) | |
{ | |
int* prout = get_prout(); | |
int filter = 1; | |
printf("Address of filter 1, %p - %u\n", &filter, filter); | |
printf("Frame in func: %p\n", __builtin_frame_address(0)); | |
_SEH2_TRY | |
{ | |
printf("Address of filter 1, %p - %u\n", &filter, filter); | |
printf("Frame in try: %p\n", __builtin_frame_address(0)); | |
printf("Parent frame in try: %p\n", __builtin_frame_address(1)); | |
printf("About to crash 1 - %p\n", prout); | |
*prout = 0; | |
} | |
_SEH2_EXCEPT(printf("Frame in filter: %p\n", __builtin_frame_address(0)), 1) | |
{ | |
printf("Frame in handler: %p\n", __builtin_frame_address(0)); | |
printf("Caught 1\n"); | |
} | |
_SEH2_END | |
} | |
void call_nested_seh(void) | |
{ | |
_SEH2_TRY | |
{ | |
_SEH2_TRY | |
{ | |
printf("Frame in try: %p\n", __builtin_frame_address(0)); | |
printf("About to crash - nested\n"); | |
*(int*)5 = 0; | |
} | |
_SEH2_EXCEPT(printf("In nested filter - OK\n"), 1) | |
{ | |
printf("Caught in nested handler - OK\n"); | |
} | |
_SEH2_END | |
} | |
_SEH2_EXCEPT(printf("In outer filter - NOK\n"), 1) | |
{ | |
printf("Caught in outer handler - NOK\n"); | |
} | |
_SEH2_END | |
} | |
void call_nested_finally(void) | |
{ | |
_SEH2_TRY | |
{ | |
_SEH2_TRY | |
{ | |
printf("About to crash - nested\n"); | |
*(int*)5 = 0; | |
} | |
_SEH2_FINALLY | |
{ | |
printf("UNWINDER CALLED - OK\n"); | |
} | |
_SEH2_END | |
} | |
_SEH2_EXCEPT(printf("In outer filter - MUST BE BEFORE UNWINDER\n"), 1) | |
{ | |
printf("MUST BE AFTER UNWINDER\n"); | |
} | |
_SEH2_END | |
} | |
void outer_finally() | |
{ | |
_SEH2_TRY | |
{ | |
printf("About to crash - outer\n"); | |
*(int*)5 = 0; | |
} | |
_SEH2_FINALLY | |
{ | |
printf("UNWINDER CALLED - OK\n"); | |
} | |
_SEH2_END | |
} | |
void call_outer_finally(void) | |
{ | |
_SEH2_TRY | |
{ | |
outer_finally(); | |
} | |
_SEH2_EXCEPT(printf("In outer filter - MUST BE BEFORE UNWINDER\n"), 1) | |
{ | |
printf("MUST BE AFTER UNWINDER\n"); | |
} | |
_SEH2_END | |
} | |
int | |
main() | |
{ | |
call_seh(); | |
call_nested_seh(); | |
call_nested_finally(); | |
call_outer_finally(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment