Skip to content

Instantly share code, notes, and snippets.

@zefklop
Last active April 16, 2021 15:42
Show Gist options
  • Save zefklop/a2fa222a405527778b7b0449e654c80d to your computer and use it in GitHub Desktop.
Save zefklop/a2fa222a405527778b7b0449e654c80d to your computer and use it in GitHub Desktop.
GCC x64 _SEH2_EXCEPT implementation
#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