SEH on mingw32
-
Upload
kikairoya -
Category
Engineering
-
view
188 -
download
0
Transcript of SEH on mingw32
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 を使う場合
● __finally を使う場合
__try { /* try block */} __except (filter-expr) { /* except block */}
__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.prevfn2のer.handlerfn1のフレームポインタfn2からの戻りアドレスfn2の引数fn1のローカル変数fn1のer.prevfn1のer.handlerfn0のフレームポインタfn1からの戻りアドレスfn1の引数
[fs:0]
esp
ebp
巻き戻し先の探索● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階
● まずはスタックをどこまで巻き戻すか探索 __except のフィルタ式を呼び出して判定
● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行
巻き戻し先の探索例外発生
kernel呼出し
ハンドラ探索
ハンドラC
ハンドラA
ハンドラB
コンテキストを保存してjmp
callcall
return 1
call
return 1
call
filterA
filterB
filterC
call
return 0
call
return 0
call
return 1
探索終了
} __except (filter-expr) {
これ
SEHの実現方法● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階
● まずはスタックをどこまで巻き戻すか探索 __except のフィルタ式を呼び出して判定
● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行
巻き戻し実行
ハンドラA
ハンドラB
RtlUnwind
ハンドラC
call
return 1
call
return 1
call
finally blockの実行call
return
call
return
jmp
正常処理に復帰
finaly blockの実行
except blockの実行ハンドラCの続き
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の呼出し
● TargetFrame に巻き戻し先のEXCEPTION_REGISTRATION を指定
● TargetIp に巻き戻し完了後の復帰点を指定● 残りは nullptr
void WINAPI RtlUnwind( PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue);
RtlUnwindの呼出し● ライブラリ関数のクセに callee-save レジスタを破壊する曲者
● 呼んだ後に esi/edi がクリアされている● call で呼ばれる前提のクセに戻りアドレスを引数
で指定する必要がある● GCC の拡張インラインアセンブラで破壊レジスタ
を指定して対処
RtlUnwindの呼出しasm volatile (
"pushl $0\n\t" // ReturnValue"pushl $0\n\t" // ExceptionRecord"pushl $1f\n\t" // TargetIp"pushl %0\n\t" // TargetFrame"call _RtlUnwind@16\n\t""1: nop\n\t":: "a"(reg): "ecx", "edx", "ebx", "esi", "edi", "esp", "cc", "memory"
);
黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し
スタックの巻き戻し● 実装のキモ● finally block の実行とデストラクタの呼出しを行
う必要がある● __finally だけなら __try で setjmp した地点に
longjmp するだけ● デストラクタを呼ぶには C++ 例外を投げる必要
がある
巻き戻し実行(/EHsc相当)
ハンドラA
ハンドラB
RtlUnwind
ハンドラC
call
return 1
call
return 1
call
finally blockの実行call
return
call
return
jmp
正常処理に復帰
finaly blockの実行
except blockの実行ハンドラCの続き
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, %%ebp\n\t" // フレームポインタを切り替え "pushl %1\n\t" // 戻りアドレスの偽装 "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
RtlUnwind
ハンドラC
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
ハンドラCの続き
jmp
巻き戻し実行 (/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++ は黒魔術のない素敵な言語です
おしまい