Windows用のGCCアセンブリ(cygwin、mingw)には、プログラム(MyExcをスロー)とシステム(シグナル)の両方をインターセプトするための便利な__try {} __except {}マクロはありません。 自転車を発明してみましょう。
記事全体の3つのポイント:
- try catchブロックを作成します
- SEHブロックでラップする
- 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;
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_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;
except_handlerでは、CONTEXTの編集により、例外をスローする関数に移行します。
DWORD pLanding = (DWORD) & landing_throw_unwinder;
SEHを設定した後、tryブロック内に特別な関数__throw_magic_linkの呼び出しを追加します。これは、コンパイラーによって例外をスローする可能性があります。 これにより、コンパイラがtry ... catchブロックを未使用として切り捨てることができなくなります。 ネストされたブロックを使用する際の問題を回避するには、catchIndexを覚えて復元します。
マクロ
コード #undef __try #define __try \ if (bool _try = true) {\ EXCEPTION_REGISTRATION __seh_ex_reg = 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) :);\ seh::__throw_magic_link();\ #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)\ #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
だから、概念は機能します。今、便利な使用のためにマクロを書く必要があります、テンプレートはこれです:
このコードは、既成のビジネスソリューションというよりは心のウォームアップであり、アセンブラーコードをより深く掘り下げたいという欲求のために、数晩かけて書かれました。 ご清聴ありがとうございました。批判に感謝します。
ソースコード関連記事:
Win32 seh内部内部のC ++例外処理