📜 ⬆️ ⬇️

Implement a try macro for gcc under win32

In the GCC builds for windows (cygwin, mingw) out of the box there is no convenient macro __try {} __except {} for intercepting both program (throw MyExc) and system (signals). Let's try to invent your bike.

The whole article in 3 points:

  1. Create try catch block
  2. Wrapping it in a seh block
  3. When SEH catches an exception, we throw a program exception.

If interested, welcome under cat.

Some theory


An exception

An exception is a certain event, an exceptional situation that occurred during the execution of a program. This can be, for example, dividing by zero, referring to an invalid address, or stack overflow. In general, exception handling is the program's reaction to an event that has occurred. It should be borne in mind that exceptions can be generated programmatically.
')
Windows exception path

Inappropriate actions occur
processor interrupt that the operating system handles. If the exception occurred in the context of the user's application, then the Windows kernel, having carried out the necessary actions, transfers control to the thread in which the exception occurred for its further processing. However, the thread continues its execution not from the place of an exception, but from a special function - the exception manager KiUserExceptionDispatcher (NTDLL.DLL). The dispatcher is transferred all the necessary information about the place of exclusion and its nature. These are the EXCEPTION_RECORD and CONTEXT structures.

KiUserExceptionDispatcher loads a chain of exception handlers (more on this later) and calls them in turn until the exception is handled.

Seh in windows

The SEH (Structured Exception Handling) windows exception handling mechanism. Represents a chain of EXCEPTION_REGISTRATION structures located on the thread stack.

typedef struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION *prev; //   EXCEPTION_REGISTRATION     PEXCEPTION_ROUTINE handler; //   - } EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION; 

In Win32, the pointer to the last EXCEPTION_REGISTRATION is in the TIB (Thread Information Block) . Further description of the structures and ways to access them will apply only to Win32.

 typedef struct _NT_TIB32 { DWORD ExceptionList; DWORD StackBase; DWORD StackLimit; DWORD SubSystemTib; DWORD FiberData; DWORD ArbitraryUserPointer; DWORD Self; } NT_TIB32,*PNT_TIB32; 

The first DWORD in TIB'e - points to the EXCEPTION_REGISTRATION of the current stream. The tib of the current thread is indicated by the FS register. Thus, at FS: [0], you can find a pointer to the EXCEPTION_REGISTRATION structure.

So, let's begin!

Lot of practice


The full version of the source can be viewed on bitbucket .
Project made in Netbeans 8.1 .
For assembly code, I use intel syntax, since he is accustomed to me. For this in gcc for building you need the -masm = intel key .

Experiment 1

 EXCEPTION_DISPOSITION __cdecl except_handler( PEXCEPTION_RECORD pException, PEXCEPTION_REGISTRATION pEstablisherFrame, PCONTEXT pContext, PEXCEPTION_REGISTRATION *pDispatcherContext) { printf("EXCEPTION_RECORD(%p):\n" " Address=%p\n" " Code=%lx\n" pException, pException->ExceptionAddress, pException->ExceptionCode); } void ex_1() { //    EXCEPTION_REGISTRATION EXCEPTION_REGISTRATION seh_ex_reg = EXCEPTION_REGISTRATION(); //  fs:[0]     int seh_prev_addr; asm ("mov %0,fs:[0];" : "=r" (seh_prev_addr) :); seh_ex_reg.prev = (_EXCEPTION_REGISTRATION_RECORD*) seh_prev_addr; seh_ex_reg.handler = (PEXCEPTION_ROUTINE) & except_handler; //  fs:[0]    asm volatile("mov fs:[0], %0;"::"r"(&seh_ex_reg) :); *(char *) 0 = 0; //   //   asm volatile("mov fs:[0], %0;"::"r"(seh_ex_reg.prev) :); } 

Perform, see the result:
EXCEPTION_RECORD (0028f994):
Address = 00401d1b EIP instructions where the exception occurred
Code = c0000005 STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)



Experiment 2

Wrap the code inside ex_1 in try {} catch {} and try to just throw an exception from except_handler:
EXCEPTION_RECORD (0028f994):
Address = 00401d7e
Code = c0000005
terminate called after throwing an instance of 'test :: SEH_EXCEPT'

Natural result.

We look into what turns into try ... catch in gcc, we look into assembly code, we smoke manuals.

  void throw_seh() { throw SEH_EXCEPT(); } void ex_2() { NOP; try { printf("try1\n"); throw SEH_EXCEPT(); } catch (...) { printf("catch1\n"); } NOP; try { printf("try2\n"); throw_seh(); } catch (...) { printf("catch2\n"); } } 



If you are interested in what __cxa_allocate_exception and __cxa_throw are and recommend reading the cycle of articles “C ++ exception handling under the hood or how exceptions work in C ++”.

The idea: we will throw exceptions not from except_handler but from a synthetic function, into which the call will “occur” instead of the instruction that caused the error.

Final option

Code
 struct SEH_EXCEPTION { PVOID address; DWORD code; }; void __stdcall landing_throw_unwinder(PVOID exceptionAddress, DWORD exceptionCode) { SEH_EXCEPTION ex = SEH_EXCEPTION(); ex.address = exceptionAddress; ex.code = exceptionCode; throw ex; } EXCEPTION_DISPOSITION __cdecl except_handler( PEXCEPTION_RECORD pException, PEXCEPTION_REGISTRATION pEstablisherFrame, PCONTEXT pContext, PEXCEPTION_REGISTRATION *pDispatcherContext) { DWORD pLanding = (DWORD) & landing_throw_unwinder; // call // push  DWORD exceptionCode pContext->Esp = pContext->Esp - 4; *(DWORD *) (pContext->Esp) = pException->ExceptionCode; // push  exceptionAddress pContext->Esp = pContext->Esp - 4; *(PVOID *) (pContext->Esp) = pException->ExceptionAddress; // push   pContext->Esp = pContext->Esp - 4; *(int *) (pContext->Esp) = pContext->Eip; pContext->Eip = pLanding; //   return ExceptionContinueExecution; } /** *     try{..}catch{...}       *          catchIndex *      * mov[esp+20],index * call __throw_magic_link *(push eip; jmp __throw_magic_link) */ __attribute__((noinline, stdcall)) void __throw_magic_link() { int test; asm volatile ("mov %0,1;" : "=r" (test)); // gcc     throw if (test > 0) { return; } throw SEH_EXCEPTION(); } void ex_4() { EXCEPTION_REGISTRATION __seh_ex_reg = EXCEPTION_REGISTRATION(); try { //  EXCEPTION_REGISTRATION,    fs:[0] int __seh_prev_addr; asm ( "mov %0,fs:[0];" : "=r" (__seh_prev_addr) :); __seh_ex_reg . prev = (_EXCEPTION_REGISTRATION_RECORD *) __seh_prev_addr; __seh_ex_reg . handler = (PEXCEPTION_ROUTINE) & seh::except_handler; asm volatile ( "mov fs:[0], %0;" ::"r" (& __seh_ex_reg) :); //       catch  int catchIndex; asm volatile ( "mov %0,[esp+0x20];" : "=r" (catchIndex) :); //""   ""   //    try{..}catch{...}       //   catchIndex seh::__throw_magic_link(); { *(char *) 0 = 0; } //  ,  catchIndex,       asm volatile ( "mov [esp+0x20],%0;" ::"r" (catchIndex) :); //    asm volatile ( "mov fs:[0], %0;" ::"r" (__seh_ex_reg . prev) :); } catch (SEH_EXCEPTION) { //    asm volatile ( "mov fs:[0], %0;" ::"r" (__seh_ex_reg . prev) :); printf("except1!\n"); } } 


In except_handler, we make the transition to the function that throws an exception, by editing the CONTEXT:

 DWORD pLanding = (DWORD) & landing_throw_unwinder; // push   pContext->Esp = pContext->Esp - 4; *(int *) (pContext->Esp) = pContext->Eip; pContext->Eip = pLanding; //   return ExceptionContinueExecution; 

After installing SEH, inside the try block we add a call to a special function __throw_magic_link, which, according to the compiler, can throw an exception. This will prevent the compiler from cutting out our try ... catch block as not used. To avoid problems when working nested blocks, remember and restore catchIndex.

Macro

Code
 #undef __try #define __try \ if (bool _try = true) {\ EXCEPTION_REGISTRATION __seh_ex_reg = EXCEPTION_REGISTRATION();/*    EXCEPTION_REGISTRATION*/\ try {\ int __seh_prev_addr;\ asm ("mov %0,fs:[0];" : "=r" (__seh_prev_addr) :);\ __seh_ex_reg.prev = (_EXCEPTION_REGISTRATION_RECORD*) __seh_prev_addr;\ __seh_ex_reg.handler = (PEXCEPTION_ROUTINE) & seh::except_handler;\ asm volatile("mov fs:[0], %0;"::"r"(&__seh_ex_reg) :);\ int catchIndex; asm volatile ("mov %0,[esp+0x20];" : "=r" (catchIndex) :);/* catch */\ seh::__throw_magic_link();\ /*begin try bloc*/ #define __except_line(filter, line )\ asm volatile ("mov [esp+0x20],%0;" ::"r" (catchIndex) :);;\ asm volatile("mov fs:[0], %0;"::"r"(__seh_ex_reg.prev) :);\ } catch (filter) {\ asm volatile("mov fs:[0], %0;"::"r"(__seh_ex_reg.prev) :);\ _try = false;\ goto __seh_catch_ ## line;\ }\ } else\ __seh_catch_ ## line:\ if (!_try)\ /*begin catch bloc*/ #define __except_line__wrap(filter, line ) __except_line(filter,line) #undef __except #define __except(filter) __except_line__wrap(filter,__LINE__) #define __exceptSEH __except_line__wrap(SEH_EXCEPTION,__LINE__) #endif 


So, the concept works, now you need to write a macro for convenient use, a template like this:

 // __try: if (bool _try = true) { //       else EXCEPTION_REGISTRATION __seh_ex_reg; // try     catch try { // seh   //  __try: { //  } // __except: // seh }catch (filter) { // seh _try = false; goto seh_label; }\ } else seh_label: if (!_try) //  __except: { //  } // : __try{ throw_test(); } __except{ printf("except1!\n"); } 

This code is more a warm-up for the mind, non-residential ready-made business decision, and was written for several evenings because of the desire to dig deeper into the assembler code. Thank you for your attention, I will be grateful for the criticism.

Sources

Related articles:

Win32 SEH inside
C ++ exception handling under the hood

Source: https://habr.com/ru/post/280304/


All Articles