SEH on mingw32

37
SEH on Mingw32 Boost. 勉強会 #8 大阪 @kikairoya (Tomohiro Kashiwada)

Transcript of SEH on mingw32

Page 1: SEH on mingw32

SEH on Mingw32

Boost. 勉強会 #8 大阪

@kikairoya (Tomohiro Kashiwada)

Page 2: SEH on mingw32

発表者について● 名前: @kikairoya● 南九州から来ました● もうすぐ大阪人になります● 触手の下僕として働くことになりました● C++ とか出来ます

Page 3: SEH on mingw32

喋ること

● SEH( 構造化例外 ) とは● SEH ライブラリ実装の解説

Page 4: SEH on mingw32

喋ること

● SEH( 構造化例外 ) とは● SEH ライブラリ実装の解説● Boost はいつも通り出てきません

Page 5: SEH on mingw32

…その前に● https://gist.github.com/1710310 を開いて

おいてください。● スライドにコードを表示するのはちょっと無理があ

りました

Page 6: SEH on mingw32

SEH(構造化例外)とは● Windows システムが使用する例外処理の方法● 主に CPU のハードウェア例外を扱う

● ぬるぽとか● divide by zero とか

● 一部システム API がソフトウェア例外を投げる● HeapAlloc とか

● UNIX クローンの非同期シグナルに相当

Page 7: SEH on mingw32

SEHのシンタックス● __except を使う場合

● __finally を使う場合

__try { /* try block */} __except (filter-expr) { /* except block */}

__try { /* try block */} __finally { /* finally block */}

Page 8: SEH on mingw32

SEHの処理手順● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階

● まずはスタックをどこまで巻き戻すか探索 __except のフィルタ式を呼び出して判定

● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行

Page 9: SEH on mingw32

例外ハンドラの登録● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階

● まずはスタックをどこまで巻き戻すか探索 __excpet のフィルタ式を呼び出して判定

● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行

Page 10: SEH on mingw32

例外ハンドラの登録● EXCEPTION_REGISTRATION 構造体をスタッ

ク上に構築● prev メンバに [fs:0] の値をコピー● [fs:0] に構造体のアドレスをコピー

struct EXCEPTION_REGISTRATION { EXCEPTION_REGISTRATION *prev; PEXCEPTION_HANDLER handler; unsigned char user_data[VAR_LENGTH];};

Page 11: SEH on mingw32

スタックの構造fn2のローカル変数fn2のer.prevfn2のer.handlerfn1のフレームポインタfn2からの戻りアドレスfn2の引数fn1のローカル変数fn1のer.prevfn1のer.handlerfn0のフレームポインタfn1からの戻りアドレスfn1の引数

[fs:0]

esp

ebp

Page 12: SEH on mingw32

巻き戻し先の探索● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階

● まずはスタックをどこまで巻き戻すか探索 __except のフィルタ式を呼び出して判定

● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行

Page 13: SEH on mingw32

巻き戻し先の探索例外発生

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) {

これ

Page 14: SEH on mingw32

SEHの実現方法● 関数呼び出しごとに例外ハンドラを登録● 例外処理 ( ハンドラの呼び出し ) は二段階

● まずはスタックをどこまで巻き戻すか探索 __except のフィルタ式を呼び出して判定

● 巻き戻し先が決まってから実際に巻き戻す finally block をスタックの先端から順に実行

Page 15: SEH on mingw32

巻き戻し実行

ハンドラA

ハンドラB

RtlUnwind

ハンドラC

call

return 1

call

return 1

call

finally blockの実行call

return

call

return

jmp

正常処理に復帰

finaly blockの実行

except blockの実行ハンドラCの続き

jmp

Page 16: SEH on mingw32

構造化例外のライブラリ実装● GCC でも SEH 使いたい● VC++ と互換性のある構文を目指して

→ __try, __except, __finally のキーワードを魔クロで乗っ取る

● もちろんデストラクタも呼びたい→ 黒魔術の山

Page 17: SEH on mingw32

黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し

Page 18: SEH on mingw32

黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し

Page 19: SEH on mingw32

setjmp/longjmp● setjmp/longjmp を使ってスタックを縦横無尽に駆

け回る● msvcrt の setjmp/longjmp は内部で SEH を使う● __builtin_longjmp は 1 しか返せない

→ 自前実装

Page 20: SEH on mingw32

黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し

Page 21: SEH on mingw32

filterの呼出し● EXCEPTION_REGISTRATION に filter を wrap した

lambda-expression を保存● lambda-expr がローカル変数をキャプチャしている

ため、例外ハンドラから直接呼び出せる● C++98 で実現するには setjmp/longjmp でスタック

を行き来する必要がある→ コールスタックをどこまで壊すか予測不可能なため非現実的

Page 22: SEH on mingw32

黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し

Page 23: SEH on mingw32

RtlUnwindの呼出し

● TargetFrame に巻き戻し先のEXCEPTION_REGISTRATION を指定

● TargetIp に巻き戻し完了後の復帰点を指定● 残りは nullptr

void WINAPI RtlUnwind( PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue);

Page 24: SEH on mingw32

RtlUnwindの呼出し● ライブラリ関数のクセに callee-save レジスタを破壊する曲者

● 呼んだ後に esi/edi がクリアされている● call で呼ばれる前提のクセに戻りアドレスを引数

で指定する必要がある● GCC の拡張インラインアセンブラで破壊レジスタ

を指定して対処

Page 25: SEH on mingw32

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"

);

Page 26: SEH on mingw32

黒魔術っぽいところだけ解説● setjmp/longjmp● filter の呼出し● RtlUnwind の呼出し● スタックの巻き戻し

Page 27: SEH on mingw32

スタックの巻き戻し● 実装のキモ● finally block の実行とデストラクタの呼出しを行

う必要がある● __finally だけなら __try で setjmp した地点に

longjmp するだけ● デストラクタを呼ぶには C++ 例外を投げる必要

がある

Page 28: SEH on mingw32

巻き戻し実行(/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

Page 29: SEH on mingw32

スタックの巻き戻し(C++例外)● ebp を操作して例外発生地点から throw したかのように振舞う

● 2 段目以降のフレームでは、下位フレームのtry block 終了地点で例外が発生したかのように振る舞う

● Leaf-function のデストラクタを正しく呼び出すために -fnon-call-exceptions が必須

Page 30: SEH on mingw32

スタックの巻き戻し(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();}

Page 31: SEH on mingw32

巻き戻し実行(/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

Page 32: SEH on mingw32

巻き戻し実行 (/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

Page 33: SEH on mingw32

スタックの巻き戻し(スタック破壊)● C++ 例外で巻き戻している間も普通にスタックは

使われる● 巻き戻すごとにスタックは短くなる ( スタックの末

端にある領域は潰される )● 例外ハンドラのコールスタックは例外発生場所よ

り先端側にある● つまり、スタック破壊が避けられない● 巻き戻し中はコールスタックをヒープに一時退避

Page 34: SEH on mingw32

制限事項● -fnon-call-exceptions をつけないと leaf-function

上にあるオブジェクトのデストラクタが呼ばれない● VC++の/EHaとは互換性が無い● try block に出入りするたびに setjmp/longjmp をす

るのでパフォーマンスペナルティがある● 某ランドの特許に引っかかるかどうか不明

Page 35: SEH on mingw32

まとめ● Mingw で動く SEH 構文をライブラリで実装した● longjmp とかインラインアセンブリとか使ってるけ

ど C++ です

Page 36: SEH on mingw32

まとめ● Mingw で動く SEH 構文をライブラリで実装した● longjmp とかインラインアセンブリとか使ってるけ

ど C++ です● C++ は黒魔術のない素敵な言語です

Page 37: SEH on mingw32

おしまい