SEH on Mingw32 
Boost.勉強会 #8 大阪 
@kikairoya (Tomohiro Kashiwada)
発表者について 
● 名前: @kikairoya 
● 南九州から来ました 
● もうすぐ大阪人になります 
● 触手の下僕として働くことになりました 
● C++ とか出来ます
喋ること 
● SEH(構造化例外) とは 
● SEH ライブラリ実装の解説
喋ること 
● SEH(構造化例外) とは 
● SEH ライブラリ実装の解説 
● Boostはいつも通り出てきません
…その前に 
● https://gist.github.com/1710310 を開いて 
おいてください。 
● スライドにコードを表示するのはちょっと無理があ 
りました
SEH(構造化例外)とは 
● Windowsシステムが使用する例外処理の方法 
● 主にCPUのハードウェア例外を扱う 
● ぬるぽとか 
● divide by zero とか 
● 一部システムAPIがソフトウェア例外を投げる 
● HeapAllocとか 
● UNIX クローンの非同期シグナルに相当
SEHのシンタックス 
● __except を使う場合 
__try { 
/* try block */ 
} __except (filter-expr) { 
/* except block */ 
} 
● __finally を使う場合 
__try { 
/* try block */ 
} __finally { 
/* finally block */ 
}
SEHの処理手順 
● 関数呼び出しごとに例外ハンドラを登録 
● 例外処理(ハンドラの呼び出し)は二段階 
● まずはスタックをどこまで巻き戻すか探索 
__exceptのフィルタ式を呼び出して判定 
● 巻き戻し先が決まってから実際に巻き戻す 
finally blockをスタックの先端から順に実行
例外ハンドラの登録 
● 関数呼び出しごとに例外ハンドラを登録 
● 例外処理(ハンドラの呼び出し)は二段階 
● まずはスタックをどこまで巻き戻すか探索 
__excpetのフィルタ式を呼び出して判定 
● 巻き戻し先が決まってから実際に巻き戻す 
finally blockをスタックの先端から順に実行
例外ハンドラの登録 
● EXCEPTION_REGISTRATION構造体をスタッ 
ク上に構築 
● prev メンバに[fs:0]の値をコピー 
● [fs:0]に構造体のアドレスをコピー 
struct EXCEPTION_REGISTRATION { 
EXCEPTION_REGISTRATION *prev; 
PEXCEPTION_HANDLER handler; 
unsigned char user_data[VAR_LENGTH]; 
};
スタックの構造 
fn2のローカル変数 
fn2のer.prev 
fn2のer.handler 
fn1のフレームポインタ 
fn2からの戻りアドレス 
fn2の引数 
fn1のローカル変数 
fn1のer.prev 
fn1のer.handler 
fn0のフレームポインタ 
fn1からの戻りアドレス 
fn1の引数 
esp 
[fs:0] 
ebp
巻き戻し先の探索 
● 関数呼び出しごとに例外ハンドラを登録 
● 例外処理(ハンドラの呼び出し)は二段階 
● まずはスタックをどこまで巻き戻すか探索 
__exceptのフィルタ式を呼び出して判定 
● 巻き戻し先が決まってから実際に巻き戻す 
finally blockをスタックの先端から順に実行
巻き戻し先の探索 
例外発生 
コンテキストを保存してjmp 
kernel呼出し 
ハンドラ探索 
} __except (filter-expr) { 
ハンドラA 
ハンドラB 
ハンドラC 
call 
call 
return 1 
call 
return 1 
call 
filterA 
filterB 
filterC 
call 
return 0 
call 
return 0 
call 
return 1 
探索終了 
これ
SEHの実現方法 
● 関数呼び出しごとに例外ハンドラを登録 
● 例外処理(ハンドラの呼び出し)は二段階 
● まずはスタックをどこまで巻き戻すか探索 
__exceptのフィルタ式を呼び出して判定 
● 巻き戻し先が決まってから実際に巻き戻す 
finally blockをスタックの先端から順に実行
巻き戻し実行 
ハンドラA 
ハンドラB 
ハンドラC 
RtlUnwind 
call 
return 1 
call 
return 1 
call 
finally blockの実行 
call 
return 
call 
return 
jmp 
finaly blockの実行 
ハンドラCの続きexcept blockの実行 
正常処理に復帰 
jmp
構造化例外のライブラリ実装 
● GCCでもSEH使いたい 
● VC++と互換性のある構文を目指して 
→ __try, __except, __finallyのキーワードを 
魔クロで乗っ取る 
● もちろんデストラクタも呼びたい 
→ 黒魔術の山
黒魔術っぽいところだけ解説 
● setjmp/longjmp 
● filterの呼出し 
● RtlUnwindの呼出し 
● スタックの巻き戻し
黒魔術っぽいところだけ解説 
● setjmp/longjmp 
● filterの呼出し 
● RtlUnwindの呼出し 
● スタックの巻き戻し
setjmp/longjmp 
● setjmp/longjmpを使ってスタックを縦横無尽に駆 
け回る 
● msvcrtのsetjmp/longjmpは内部でSEHを使う 
● __builtin_longjmpは1しか返せない 
→ 自前実装
黒魔術っぽいところだけ解説 
● setjmp/longjmp 
● filterの呼出し 
● RtlUnwindの呼出し 
● スタックの巻き戻し
filterの呼出し 
● EXCEPTION_REGISTRATIONにfilterをwrapした 
lambda-expressionを保存 
● lambda-exprがローカル変数をキャプチャしている 
ため、例外ハンドラから直接呼び出せる 
● C++98で実現するにはsetjmp/longjmpでスタック 
を行き来する必要がある 
→ コールスタックをどこまで壊すか予測不可能な 
ため非現実的
黒魔術っぽいところだけ解説 
● setjmp/longjmp 
● filterの呼出し 
● RtlUnwindの呼出し 
● スタックの巻き戻し
RtlUnwindの呼出し 
void WINAPI RtlUnwind( 
PVOID TargetFrame, 
PVOID TargetIp, 
PEXCEPTION_RECORD ExceptionRecord, 
PVOID ReturnValue 
); 
● TargetFrameに巻き戻し先の 
EXCEPTION_REGISTRATION を指定 
● TargetIpに巻き戻し完了後の復帰点を指定 
● 残りはnullptr
RtlUnwindの呼出し 
● ライブラリ関数のクセにcallee-saveレジスタを破 
壊する曲者 
● 呼んだ後にesi/ediがクリアされている 
● callで呼ばれる前提のクセに戻りアドレスを引数 
で指定する必要がある 
● GCCの拡張インラインアセンブラで破壊レジスタ 
を指定して対処
RtlUnwindの呼出し 
asm volatile ( 
"pushl $0nt" // ReturnValue 
"pushl $0nt" // ExceptionRecord 
"pushl $1fnt" // TargetIp 
"pushl %0nt" // TargetFrame 
"call _RtlUnwind@16nt" 
"1: nopnt" 
: 
: "a"(reg) 
: "ecx", "edx", "ebx", "esi", "edi", 
"esp", "cc", "memory" 
);
黒魔術っぽいところだけ解説 
● setjmp/longjmp 
● filterの呼出し 
● RtlUnwindの呼出し 
● スタックの巻き戻し
スタックの巻き戻し 
● 実装のキモ 
● finally blockの実行とデストラクタの呼出しを行 
う必要がある 
● __finallyだけなら__tryでsetjmp した地点に 
longjmp するだけ 
● デストラクタを呼ぶにはC++例外を投げる必要 
がある
巻き戻し実行(/EHsc相当) 
ハンドラA 
ハンドラB 
ハンドラC 
RtlUnwind 
call 
return 1 
call 
return 1 
call 
finally blockの実行 
call 
return 
call 
return 
jmp 
finaly blockの実行 
ハンドラCの続きexcept blockの実行 
正常処理に復帰 
jmp
スタックの巻き戻し(C++例外) 
● ebpを操作して例外発生地点からthrowしたかの 
ように振舞う 
● 2段目以降のフレームでは、下位フレームの 
try block終了地点で例外が発生したかのように 
振る舞う 
● Leaf-functionのデストラクタを正しく呼び出すた 
めに-fnon-call-exceptionsが必須
スタックの巻き戻し(throw) 
void throw_seh_unwinder(const seh_jmp_buf &b) { 
asm volatile ( 
"movl %0, %%ebpnt" // フレームポインタを切り替え 
"pushl %1nt" // 戻りアドレスの偽装 
"jmp _throw_seh_unwinder" // call 
: 
: "r"(b.ebp), "a"(b.eip), 
"b"(b.ebx), "S"(b.esi), "D"(b.edi) 
: "memory"); 
__builtin_unreachable(); 
} 
extern "C" void throw_seh_unwinder() { 
// 例外発生地点から呼ばれているように見える 
throw seh_unwinder(); 
}
巻き戻し実行(/EHa相当) 
ハンドラA 
ハンドラB 
ハンドラC 
RtlUnwind 
call 
return 1 
call 
return 1 
call 
例外発生地点から例外を投 
げてtry blockでcatch 
jmp 
jmp 
jmp 
jmp 
jmp 
直前のtry blockから例外を 
投げてtry blockでcatch 
直前のtry blockから例外を 
投げてtry blockでcatch 
正常処理に復帰 
jmp 
ハンドラCの続き
巻き戻し実行 (/EHa相当) 
3段目のハンドラ: 
*1からthrow して*2でcatch 
そのままfinally block を実行 
*3からハンドラにlongjmp 
2段目のハンドラ: 
*3からthrow して*4でcatch 
そのままfinally block を実行 
*5からハンドラにlongjmp 
1段目のハンドラ: 
*5からthrow して*6でcatch 
そのままexcept block を実行 
*7から正常ルートに復帰 
__try { 
__try { 
__try { 
// *1 Access Violation 
*(int *)0 = 0; 
} __finally { 
// *2 
} // *3 
} __finally { 
// *4 
} // *5 
} __except(1) { 
// *6 
} // *7 
throw 
throw 
throw 
longjmp 
longjmp
スタックの巻き戻し(スタック破壊) 
● C++例外で巻き戻している間も普通にスタックは 
使われる 
● 巻き戻すごとにスタックは短くなる(スタックの末 
端にある領域は潰される) 
● 例外ハンドラのコールスタックは例外発生場所よ 
り先端側にある 
● つまり、スタック破壊が避けられない 
● 巻き戻し中はコールスタックをヒープに一時退避
制限事項 
● -fnon-call-exceptionsをつけないとleaf-function 
上にあるオブジェクトのデストラクタが呼ばれない 
● VC++の/EHaとは互換性が無い 
● try blockに出入りするたびにsetjmp/longjmpをす 
るのでパフォーマンスペナルティがある 
● 某ランドの特許に引っかかるかどうか不明
まとめ 
● Mingwで動くSEH構文をライブラリで実装した 
● longjmp とかインラインアセンブリとか使ってるけ 
どC++です
まとめ 
● Mingwで動くSEH構文をライブラリで実装した 
● longjmp とかインラインアセンブリとか使ってるけ 
どC++です 
● C++は黒魔術のない素敵な言語です
おしまい

SEH on mingw32