記事全体の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; // 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 ++例外処理