Skip to content

Instantly share code, notes, and snippets.

@hackpascal
Forked from kikairoya/seh.cc
Created July 10, 2017 12:40
Show Gist options
  • Save hackpascal/c22a4859d5ebb9090cd8b73eb9277501 to your computer and use it in GitHub Desktop.
Save hackpascal/c22a4859d5ebb9090cd8b73eb9277501 to your computer and use it in GitHub Desktop.
SEH for gcc (working)
#include <stdio.h>
#include <windows.h>
#include <excpt.h>
#include <functional>
#pragma GCC optimize ("no-omit-frame-pointer")
//#define SEH_NO_CALL_DESTRUCTORS
#define SEH_ENABLE_TRACE
#ifdef SEH_ENABLE_TRACE
#define SEH_TRACE_2(line, func, ...) (printf("trace %d (%s): ", line, func), printf(__VA_ARGS__))
#else
#define SEH_TRACE_2(...) ((void)0, (int)0)
#endif
#define SEH_TRACE_1(line, func, ...) SEH_TRACE_2(line, func, __VA_ARGS__)
#define SEH_TRACE(...) SEH_TRACE_1(__LINE__, __func__, __VA_ARGS__)
volatile unsigned long *start_esp;
void dump_stack(const char *name) {
volatile unsigned long *end_esp;
asm volatile ("mov %%esp, %0": "=g"(end_esp));
FILE *fp = fopen(name, "w");
while (start_esp > end_esp) {
fprintf(fp, "%p: %08lX %08lX %08lX %08lX\n", end_esp, end_esp[0], end_esp[1], end_esp[2], end_esp[3]);
end_esp += 4;
}
fclose(fp);
}
namespace seh {
struct simple_safe_bool_t_base;
typedef simple_safe_bool_t_base *simple_safe_bool_t;
struct seh_jmp_context;
int __attribute__((__returns_twice__)) seh_setjmp(volatile seh_jmp_context &);
int __attribute__((__noreturn__)) seh_longjmp(const volatile seh_jmp_context &, int);
struct seh_unwinder { };
struct at_scope_exit {
template <typename F>
at_scope_exit(const F &f): f(f) { SEH_TRACE("register at_scope_exit %p\n", &this->f); }
at_scope_exit() = default;
~at_scope_exit() throw (seh_unwinder) {
if (f) {
SEH_TRACE("execute at_scope_exit %p\n", &f);
const auto g = f;
f = []{};
g();
}
}
std::function<void ()> f;
operator simple_safe_bool_t() const volatile { return 0; }
};
enum class unwind_state {
normal_exit,
unwinding,
unwind_to_here,
};
__attribute__((noreturn)) void throw_seh_unwinder() {
throw seh_unwinder();
}
template <typename T>
struct value_wrapper {
T &operator ()() { return v; }
volatile T &operator ()() volatile { return v; }
const T &operator ()() const { return v; }
const volatile T &operator ()() const volatile { return v; }
value_wrapper(const T &v): v(v) { }
T v;
operator simple_safe_bool_t() const volatile { return 0; }
};
template <typename T, typename U>
inline bool operator ==(const value_wrapper<T> &x, const value_wrapper<U> &y) { return x.v == y.v; }
template <typename T, typename U>
inline bool operator ==(const value_wrapper<T> &x, const U &y) { return x.v == y; }
template <typename T, typename U>
inline bool operator ==(const T &x, const value_wrapper<U> &y) { return x == y.v; }
struct stack_tracer {
__attribute__((noinline)) static uintptr_t get_ebp() {
uintptr_t r;
asm volatile ("mov (%%ebp), %0" : "=r"(r) : : "cc");
return r;
}
typedef uintptr_t reg_t;
reg_t ebp;
stack_tracer(reg_t r = get_ebp()): ebp(r) { }
static uintptr_t dereference(uintptr_t r) { return *reinterpret_cast<uintptr_t *>(r); }
stack_tracer &operator ++() {
ebp = dereference(ebp);
return *this;
}
stack_tracer operator ++(int) {
stack_tracer o(*this);
++*this;
return o;
}
uintptr_t get_callee() const { return dereference(ebp+sizeof(ebp)); }
};
}
#ifdef __x86_64__
#error x86_64 target is not supported.
#undef __try
#define __try try
#undef __except
#define __except(filter) catch (::seh::seh_unwinder &)
#undef __finally
#define __finally catch (...)
#else
namespace seh {
struct seh_jmp_context {
uintptr_t ebp;
uintptr_t ebx;
uintptr_t edi;
uintptr_t esi;
uintptr_t esp;
uintptr_t eip;
};
void print_jb(const seh_jmp_context &jb) {
printf("jmp_buf(%p):\n"
" ebp=%x\n"
" ebx=%x\n"
" edi=%x\n"
" esi=%x\n"
" esp=%x\n"
" eip=%x\n",
&jb, jb.ebp, jb.ebx, jb.edi, jb.esi, jb.esp, jb.eip);
}
void print_ctx(const CONTEXT *pctx) {
printf("CONTEXT(%p):\n"
" ebp=%lx\n"
" ebx=%lx\n"
" edi=%lx\n"
" esi=%lx\n"
" esp=%lx\n"
" eip=%lx\n",
pctx, pctx->Ebp, pctx->Ebx, pctx->Edi, pctx->Esi, pctx->Esp, pctx->Eip);
}
struct exception_registration {
exception_registration *prev;
int (*handler)(PEXCEPTION_RECORD rec, exception_registration *reg, PCONTEXT ctx, void *);
unsigned magic;
seh_jmp_context jb_try;
EXCEPTION_POINTERS ptrs;
exception_registration *chain;
std::function<int ()> filter;
unwind_state state;
operator simple_safe_bool_t() const volatile { return 0; }
};
__attribute__((noreturn)) void throw_seh_unwinder(const seh_jmp_context &b) {
asm volatile (
"movl %[bp], %%ebp\n\t"
"pushl %[ip]\n\t"
"jmp __ZN3seh18throw_seh_unwinderEv"
:
: [bp]"g"(b.ebp), [ip]"r"(b.eip), "b"(b.ebx), "S"(b.esi), "D"(b.edi)
: "memory");
__builtin_unreachable();
}
__attribute__((noreturn)) void throw_seh_unwinder(PCONTEXT pctx) {
seh_jmp_context b;
b.ebx = pctx->Ebx;
b.esi = pctx->Esi;
b.edi = pctx->Edi;
b.ebp = pctx->Ebp;
b.esp = pctx->Esp;
b.eip = pctx->Eip;
throw_seh_unwinder(b);
__builtin_unreachable();
}
exception_registration *search_except_block(exception_registration *reg, int &code) {
if (reg->chain) {
reg->chain->ptrs = reg->ptrs;
exception_registration *p = search_except_block(reg->chain, code);
if (p) return p;
}
code = reg->filter();
return code ? reg : 0;
}
int exception_handler(PEXCEPTION_RECORD prec, exception_registration *reg, PCONTEXT pctx, void *xxx) {
static __thread seh_jmp_context leaf_jb;
static __thread unsigned char *volatile callee_stack_top;
static __thread unsigned char *volatile save_stack_ptr;
SEH_TRACE("args: (%p, %p, %p, %p), exception: %x, flags: %x, eip: %p, ebp: %p\n",
prec, reg, pctx, xxx,
(unsigned)prec->ExceptionCode, (unsigned)prec->ExceptionFlags, (void*)pctx->Eip, (void*)pctx->Ebp);
if (prec->ExceptionFlags & EXCEPTION_UNWINDING) {
if (reg->chain) exception_handler(prec, reg->chain, pctx, xxx);
SEH_TRACE("unwind step %p\n", reg);
reg->state = unwind_state::unwinding;
#ifndef SEH_NO_CALL_DESTRUCTORS
seh_jmp_context r = leaf_jb;
#endif
leaf_jb = reg->jb_try;
if (seh_setjmp(reg->jb_try)) {
asm ("": : : "memory");
SEH_TRACE("restore %d bytes from %p to %p\n", callee_stack_top - (unsigned char *)__builtin_frame_address(0), save_stack_ptr, (unsigned char *)__builtin_frame_address(0));
memcpy(__builtin_frame_address(0), save_stack_ptr, callee_stack_top - (unsigned char *)__builtin_frame_address(0));
asm ("": : : "memory");
free(save_stack_ptr);
save_stack_ptr = 0;
SEH_TRACE("finish unwind %p\n", reg);
return 1;
} else {
save_stack_ptr = (unsigned char *)malloc(callee_stack_top - (unsigned char *)__builtin_frame_address(0));
SEH_TRACE("save %d bytes from %p to %p\n", callee_stack_top - (unsigned char *)__builtin_frame_address(0), (unsigned char *)__builtin_frame_address(0), save_stack_ptr);
memcpy(save_stack_ptr, __builtin_frame_address(0), callee_stack_top - (unsigned char *)__builtin_frame_address(0));
#ifdef SEH_NO_CALL_DESTRUCTORS
seh_longjmp(leaf_jb, 1);
#else
throw_seh_unwinder(r);
#endif
}
__builtin_unreachable();
}
if (prec->ExceptionFlags & EXCEPTION_NONCONTINUABLE) {
return 1;
}
reg->ptrs.ExceptionRecord = prec;
reg->ptrs.ContextRecord = pctx;
int code = 0;
exception_registration *p = search_except_block(reg, code);
if (!p || p->magic != 0xDEADBEEF) return 1;
if (code < 0) return 0;
callee_stack_top = (unsigned char *)__builtin_frame_address(1);
save_stack_ptr = 0;
#ifndef SEH_NO_CALL_DESTRUCTORS
leaf_jb.ebx = pctx->Ebx;
leaf_jb.edi = pctx->Edi;
leaf_jb.esi = pctx->Esi;
leaf_jb.ebp = pctx->Ebp;
leaf_jb.esp = pctx->Esp;
leaf_jb.eip = pctx->Eip;
#endif
SEH_TRACE("start unwind\n");
asm volatile (
"pushl $0\n\t"
"pushl $0\n\t"
"pushl $1f\n\t"
"pushl %0\n\t"
"call _RtlUnwind@16\n\t"
"1: nop\n\t"
:
: "a"(reg)
: "ecx", "edx", "ebx", "esi", "edi", "esp", "cc", "memory"
);
SEH_TRACE("finish unwind\n");
p->state = unwind_state::unwind_to_here;
#ifdef SEH_NO_CALL_DESTRUCTORS
seh_longjmp(p->jb_try, 1);
#else
throw_seh_unwinder(leaf_jb);
#endif
__builtin_unreachable();
}
#define _exception_info() ((EXCEPTION_POINTERS *)&seh_exc_reg.ptrs)
#define _exception_code() (seh_exc_reg.ptrs.ExceptionRecord->ExceptionCode)
#define _abnormal_termination() (seh_exc_reg.state != ::seh::unwind_state::normal_exit)
struct seh_register_helper {
seh_register_helper(exception_registration &seh_exc_reg, exception_registration &prev): seh_exc_reg(seh_exc_reg), prev(prev) {
if (!prev.filter) {
asm volatile ("movl %%fs:0, %0\n\t"
"movl %1, %%fs:0"
: "=&r"(seh_exc_reg.prev)
: "r"(&seh_exc_reg)
: "cc");
} else {
prev.chain = &seh_exc_reg;
}
SEH_TRACE("register %p\n", &seh_exc_reg);
}
seh_register_helper(const seh_register_helper &) = delete;
~seh_register_helper() {
if (!prev.filter) asm volatile ("movl %0, %%fs:0" : : "r" (seh_exc_reg.prev): "cc"); \
else prev.chain = 0; \
SEH_TRACE("unregister %p\n", &seh_exc_reg);
}
exception_registration &seh_exc_reg;
exception_registration &prev;
operator simple_safe_bool_t() const volatile { return 0; }
};
}
#undef __try
#ifdef SEH_NO_CALL_DESTRUCTORS
#define __try \
if (::seh::value_wrapper< ::seh::exception_registration &> seh_prev_reg = seh_exc_reg) ; \
else if (::seh::exception_registration seh_exc_reg = { 0, &::seh::exception_handler, 0xDEADBEEF, {0}, {0}}); \
else if (::seh::seh_register_helper seh_reg_help = {seh_exc_reg, seh_prev_reg()}); \
else if (::seh::at_scope_exit seh_finally_hopper = []{}) ; \
else if (::seh::value_wrapper<int> seh_state = ::seh::seh_setjmp(seh_exc_reg.jb_try)) ;\
else if (seh_state == 3)
#else
#define __try \
if (::seh::value_wrapper< ::seh::exception_registration &> seh_prev_reg = seh_exc_reg) ; \
else if (::seh::exception_registration seh_exc_reg = { 0, &::seh::exception_handler, 0xDEADBEEF, {0}, {0}}); \
else if (::seh::seh_register_helper seh_reg_help = {seh_exc_reg, seh_prev_reg()}); \
else if (::seh::at_scope_exit seh_finally_hopper = []{}) ; \
else if (::seh::value_wrapper<int> seh_state = ::seh::seh_setjmp(seh_exc_reg.jb_try)) ;\
else if (seh_state == 3) try
#endif
#undef __except
#ifdef SEH_NO_CALL_DESTRUCTORS
#define __except_1(filter_expr, line) \
else if (seh_state == 0) { \
seh_exc_reg.filter = [&] { return filter_expr; }; \
SEH_TRACE("start __try\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 3); \
} else if (seh_state == 1) { \
seh_state = 2; \
if (seh_exc_reg.state != ::seh::unwind_state::unwind_to_here) ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); \
else goto seh_label ## line; \
} else seh_label ## line: if (seh_state == 2)
#else
#define __except_1(filter_expr, line) \
catch (::seh::seh_unwinder &) { \
SEH_TRACE("catch(except)\n"); \
seh_state = 2; \
if (seh_exc_reg.state == ::seh::unwind_state::unwind_to_here) { SEH_TRACE("start __except\n"); goto seh_label ## line; } \
else seh_finally_hopper.f = [&seh_exc_reg] { ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); }; \
} else if (seh_state == 0) { \
seh_exc_reg.filter = [&] { return filter_expr; }; \
SEH_TRACE("start __try\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 3); \
} else if (seh_state == 1) { \
if (seh_exc_reg.state != ::seh::unwind_state::unwind_to_here) ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); \
} else seh_label ## line: if (seh_state == 2)
#endif
#define __except_2(filter_expr, line) __except_1(filter_expr, line)
#define __except(filter_expr) __except_2(filter_expr, __LINE__)
#undef __finally
#ifdef SEH_NO_CALL_DESTRUCTORS
#define __finally_1(line) \
else if (seh_state == 0) { \
seh_exc_reg.filter = [] { return 0; }; \
seh_finally_hopper.f = [&seh_exc_reg] { \
SEH_TRACE("start __finally\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 4); \
}; \
SEH_TRACE("start __try\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 3); \
} else if (seh_state == 1) { \
seh_finally_hopper.f = [&seh_exc_reg] { ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); }; \
seh_state = 4; \
goto seh_label ## line; \
} else seh_label ## line: if (seh_state == 4)
#else
#define __finally_1(line) \
catch (::seh::seh_unwinder &) { \
SEH_TRACE("catch(finally)\n"); \
seh_finally_hopper.f = [&seh_exc_reg] { ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); }; \
seh_state = 4; \
goto seh_label ## line; \
} else if (seh_state == 0) { \
seh_exc_reg.filter = [] { return 0; }; \
seh_finally_hopper.f = [&seh_exc_reg] { \
SEH_TRACE("start __finally\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 4); \
}; \
SEH_TRACE("start __try\n");\
::seh::seh_longjmp(seh_exc_reg.jb_try, 3); \
} else if (seh_state == 1) { \
seh_finally_hopper.f = [&seh_exc_reg] { ::seh::seh_longjmp(seh_exc_reg.jb_try, 1); }; \
} else seh_label ## line: if (seh_state == 4)
#endif
#define __finally_2(line) __finally_1(line)
#define __finally __finally_2(__LINE__)
#undef __leave
#define __leave (throw (SEH_TRACE("leave %p\n", &seh_exc_reg), ::seh::seh_unwinder()))
extern ::seh::exception_registration seh_exc_reg;
#endif
int filter(unsigned int code, volatile _EXCEPTION_POINTERS *ep) {
printf("in filter. code: %x\n", code);
if (code == EXCEPTION_ACCESS_VIOLATION) {
puts("caught AV as expected.");
return EXCEPTION_EXECUTE_HANDLER;
} else {
puts("didn't catch AV, unexpected.");
return EXCEPTION_CONTINUE_SEARCH;
};
}
void fn_5(volatile int *p) {
seh::at_scope_exit f = [] { puts(" this line should appear between throw and 3:c++ catch"); };
// needs -fnon-call-exceptions
*p = 0;
}
void fn_3(volatile int *p) {
__try {
puts("3:in try");
__try {
puts("4:in try");
__try {
puts("5: in try");
fn_5(p);
//__leave;
*p = 0;
RaiseException(0xC0000005, 0, 0, 0);
} __except(0) {
puts("5: in except");
}
} __finally {
puts("4:in finally");
}
*p = 0;
} __except(0) {
puts("3:in except");
}
}
void fn_4() {
seh::at_scope_exit f = [] { puts(" this line should appear between 3:unreg and 2:c++ catch"); };
fn_3(0);
}
void fn_2() {
int p = 0;
// test: force grow stack
volatile int large[1024];
for (int n = 0; n < 1024; ++n) large[n] = n;
__try {
puts("2:in try");
fn_4();
} __finally {
puts("2:in finally");
for (int n = 0; n < 1024; ++n) p += large[n];
printf("%d\n", p);
}
}
int main() {
asm volatile ("mov %%esp, %0": "=g"(start_esp));
printf("%d\n", sizeof(seh::exception_registration::filter));
puts("hello");
__try {
puts("1:in try");
fn_2();
} __except (filter(GetExceptionCode(), GetExceptionInformation())) {
puts("1:in except");
}
puts("leave try");
puts("world");
return 0;
}
#ifdef __x86_64__
#else
::seh::exception_registration seh_exc_reg;
asm(".section .text");
asm(".global __ZN3seh10seh_setjmpERVNS_15seh_jmp_contextE");
asm("__ZN3seh10seh_setjmpERVNS_15seh_jmp_contextE:");
asm("movl 4(%esp), %ecx");
asm("movl %ebp, 0(%ecx)");
asm("movl %ebx, 4(%ecx)");
asm("movl %edi, 8(%ecx)");
asm("movl %esi, 12(%ecx)");
asm("popl %edx");
asm("movl %esp, 16(%ecx)");
asm("movl %edx, 20(%ecx)");
asm("xorl %eax, %eax");
asm("jmp *%edx");
asm(".global __ZN3seh11seh_longjmpERVKNS_15seh_jmp_contextEi");
asm("__ZN3seh11seh_longjmpERVKNS_15seh_jmp_contextEi:");
asm("movl 8(%esp), %eax");
asm("movl 4(%esp), %ecx");
asm("movl 0(%ecx), %ebp");
asm("movl 4(%ecx), %ebx");
asm("movl 8(%ecx), %edi");
asm("movl 12(%ecx), %esi");
asm("movl 16(%ecx), %esp");
asm("jmp *20(%ecx)");
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment