win32でのgccのtryマクロの実装

Windows用のGCCアセンブリ(cygwin、mingw)には、プログラム(MyExcをスロー)とシステム(シグナル)の両方をインターセプトするための便利な__try {} __except {}マクロはありません。 自転車を発明してみましょう。



記事全体の3つのポイント:



  1. try catchブロックを作成します
  2. SEHブロックでラップする
  3. SEHが例外をキャッチすると、ソフトウェア例外をスローします


興味があれば、猫へようこそ。



理論のビット



例外


例外はイベントであり、プログラムの実行中に発生した例外的な状況です。 これは、たとえば、ゼロ除算、無効なアドレスへのアクセス、スタックオーバーフローなどです。 一般的に、例外処理とは、イベントに対するプログラムの反応です。 例外はプログラムで生成できることを考慮する価値があります。



Windows例外パス


無効なアクションが発生した場合

オペレーティングシステムが処理するプロセッサ割り込み 。 例外がユーザーのアプリケーションのコンテキストで発生した場合、必要なアクションを実行したWindowsカーネルは、その後の処理のために例外が発生したスレッドに制御を移します。 ただし、スレッドは例外の場所からではなく、特別な機能であるKiUserExceptionDispatcher(NTDLL.DLL)例外マネージャーから実行を継続します。 ディスパッチャには、除外場所とその性質に関する必要な情報がすべて提供されます。 これらはEXCEPTION_RECORDおよびCONTEXT構造です。



KiUserExceptionDispatcherは、例外ハンドラのチェーンをロードし(詳細は後述)、例外が処理されるまでそれらを1つずつ呼び出します。



WindowsのSEH


SEH (Structured Exception Handling)ウィンドウの例外処理メカニズム。 これは、ストリームスタックにあるEXCEPTION_REGISTRATION構造体のチェーンです。



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





Win32では、最後のEXCEPTION_REGISTRATIONへのポインターはTIB(スレッド情報ブロック)にあります。 構造とそれらへのアクセス方法の詳細は、Win32のみに関係します。



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





TIBの最初のDWORD-現在のストリームのEXCEPTION_REGISTRATIONを示します。 現在のストリームのTIBは、FSレジスタによって示されます。 したがって、アドレスFS:[0]で、構造EXCEPTION_REGISTRATIONへのポインターを見つけることができます。



それでは始めましょう!



たくさんの練習



完全なソースコードはbitbucketで表示できます

Netbeans 8.1で行われたプロジェクト

アセンブラーコードの場合、intel構文を使用します。 したがって、gccでは、ビルドするために-masm = intelスイッチが必要です。



実験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) :); }
      
      





実行し、結果を確認します。

EXCEPTION_RECORD(0028f994):

アドレス= 00401d1b例外が発生したEIP命令

コード= c0000005 STATUS_ACCESS_VIOLATION((DWORD)0xC0000005)







実験2


try {} catch {}でex_1内にコードをラップし、except_handlerから例外をスローするようにします。

EXCEPTION_RECORD(0028f994):

アドレス= 00401d7e

コード= c0000005

'test :: SEH_EXCEPT'のインスタンスをスローした後に呼び出された終了



論理的な結果。



私たちは、catchがgccに変わること、アセンブラーコードを調べること、マニュアルを吸うことを試みます。



  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"); } }
      
      









__cxa_allocate_exceptionと__cxa_throwが何であるか疑問に思う場合は、一連の記事「C ++の内部での例外処理、またはC ++での例外の仕組み」を読むことをお勧めします。



アイデア:except_handlerからではなく、エラーを引き起こした命令ではなく、呼び出しが「発生する」合成関数から例外をスローします。



最終オプション


コード
 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"); } }
      
      







except_handlerでは、CONTEXTの編集により、例外をスローする関数に移行します。



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





SEHを設定した後、tryブロック内に特別な関数__throw_magic_linkの呼び出しを追加します。これは、コンパイラーによって例外をスローする可能性があります。 これにより、コンパイラがtry ... catchブロックを未使用として切り捨てることができなくなります。 ネストされたブロックを使用する際の問題を回避するには、catchIndexを覚えて復元します。



マクロ


コード
 #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
      
      







だから、概念は機能します。今、便利な使用のためにマクロを書く必要があります、テンプレートはこれです:



 // __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"); }
      
      





このコードは、既成のビジネスソリューションというよりは心のウォームアップであり、アセンブラーコードをより深く掘り下げたいという欲求のために、数晩かけて書かれました。 ご清聴ありがとうございました。批判に感謝します。



ソースコード



関連記事:



Win32 seh内部

内部のC ++例外処理



All Articles