例外を投げた関数をマップファイルから特定するにはどうしたらいいでしょう?

[戻る]
しん 2005/02/25(金) 17:20:51
例外を投げた関数をマップファイルから特定するにはどうしたらいいでしょう?

例えば下記コード
void f(){ throw 0; }
int APIENTRY WinMain( HINSTANCE, HINSTANCE, LPTSTR, int ){
   f();
   return 0;
}
をデバッグ実行すると、f()を通過時に出力ウィンドウに
「test_main2.exe の 0x7c81eb33 で初回の例外が発生しました : Microsoft C++ 
exception: int @ 0x0012fcc8」
と出ます。この0x7c81eb33と0x0012fcc8とマップファイルを使い、f()が例外を投げた事
を特定したいのです。

無論、自分でもやりましたが、0x7c81eb33と0x0012fcc8とマップファイルのf()のアドレ
スの相関が分かりませんでした。

これら2値は何者なんでしょうか?どうぞ宜しくお願いします。

シャノン 2005/02/27(日) 03:05:26
0x7c81eb33 には何がありました?
そんなアドレスには、大抵システム DLL がロードされています。
例外を投げている(VC++ の例外機構の実装に使われている)のは、Win32 API の 
RaiseException です。
アセンブリ語なんて読めないので、確かなところはわかりませんが、RaiseException の
中で、例外を投げるような何らかの CPU 命令が発行された地点とか、そんな感じかと。
ちなみに、そのアドレスは俺の環境でも同じになりました。
RaiseException は KERNEL32.DLL に実装されており、KERNEL32.DLL は必ず固定アドレ
スにロードされることからも、おそらくそうではないかと思われます。

後者の 0x0012fcc8 の方は、毎回変わりますね。
こちらはどうやら、例外として投げられたオブジェクトのアドレスのようです。

なので、これら2つのアドレスと、関数 f のアドレスは、特に相関関係はないと思いま
す。

で、本題である例外発生箇所の特定法ですが…
マップファイルってのを普段使わないので、何に使えるのかわかりません。
f で例外が発生したことは、スタックを逆にたどれば、突き止められるのではないでし
ょうか(VC++ の「呼び出し履歴」だと思ってください)。
これには、StackWalk という API が使えます。
使い方は、ぐぐってみてください。サンプルが出てきます。

シャノン 2005/02/27(日) 03:13:46
#デバッガ作りたい…面白そうだなぁ

Ban 2005/02/27(日) 10:04:09
> マップファイルってのを普段使わないので、何に使えるのかわかりません。

関数やらの(デフォルト)配置アドレスが入るので、アセンブラでデバッグしてる
ときとか、呼び先や現在位置の特定に使ったりするのですが......
まともなデバッガのある環境だと普通は使わないですねぇ。

> f で例外が発生したことは、スタックを逆にたどれば、突き止められるのではない
> でしょうか(VC++ の「呼び出し履歴」だと思ってください)。
> これには、StackWalk という API が使えます。

これ、どのタイミングで呼ぶかにもよりますが、多分難しいと思います。
一度例外を投げてしまうと、例外処理は「スタックを巻き戻しながら」動作するので、
catch した時点ではそこまでの呼び出し履歴はなくなってたはずです。(少なくとも VC 
の場合)
例外として投入されるオブジェクトのコンストラクタでブレイクすれば巻き戻せたと
思いますので、何かするならそちらでしょうか。

しん 2005/02/27(日) 20:24:05
>0x7c81eb33 には何がありました?
残念乍、分かりません。自アプリケーションのアドレス領域から大きく外れてるなあ、く
らいしか見切れませんでした。

>例外を投げているのは、RaiseException()
知りませんでした。情報有難う御座います。とても得した気分です。

>そのアドレスは俺の環境でも同じになりました
RaiseException()のアドレスである可能性がとても高いですね。
だとすると、私にとってこの値には価値が無いですね。なんか他の使い道があるのかもし
れません。

>どうやら、例外として投げられたオブジェクトのアドレスのよう
そうだったのですか!?再確認してみます。

>マップファイルってのを何に使えるのかわかりません。
Banさんの仰る通りです。ただWin32での使い道もあります。
開発完了後、飛ぶような致命的なバグを含んでリリースしてしまったとします。
で、飛んだ時のプログラムカウンタをユーザに報告してもらいます。
その値とリリースビルド時のマップファイルから、飛んだ関数を割り出すといった感じで
す。

>StackWalk()
これも初耳です。調べてみます!

>例外として投入されるオブジェクトのコンストラクタでブレイクすれば巻き戻せた
なるほど〜。がこの方法は私の環境では使えないんです。何故なら、例外をthrowする側
がリリースDLLとして他社から提供されているからです。で、不完全ながらも原因を極力
絞り込みたかった為、今回のような質問をさせて頂きました。

でも、全てを自力でやる時はこの情報を有効活用させて頂きます。どうも有難うございま
す。

なかなか難しい問題ですが、もうちょっと調べてみます。お二人とも有難うございまし
た。

Ban 2005/02/27(日) 22:41:09
> 開発完了後、飛ぶような致命的なバグを含んでリリースしてしまったとします。
> で、飛んだ時のプログラムカウンタをユーザに報告してもらいます。
> その値とリリースビルド時のマップファイルから、飛んだ関数を割り出すといった
> 感じです。

Windows ならこのくらいしか使い道はないと思いますが、あくまで「最後の手段」かと。
(リリースビルドでも PDB を保存しておくことは可能で、提供せずに取っておけばいい。
 但し VC環境依存。map の方はテキストで環境非依存なので両方とっておくと吉)


> 何故なら、例外をthrowする側がリリースDLLとして他社から提供されているからです。

そうですか....(あ痛っ)。それは外から catch はできないのでしょうか。
直接使ったことないですが、例えば SetUnhandledExceptionFilter API とか。
<MSDN>
SetUnhandledExceptionFilter 関数を実行すると、呼び出し元プロセスの既存の
スレッドと今後作成されるスレッドの、すべての既存のトップレベル例外フィルタが
置き換わります。
</MSDN>


# 「Windowsプログラマのためのデバッグテクニック徹底解析」(Microsoft Press)
# (http://www.amazon.co.jp/exec/obidos/ASIN/4891001860/250-2452259-3793037)
# もし読まれていなければお勧めです。
# StackWalk や、SetUnhandledExceptionFilter などの解説も触れてますし、
# 一応MS公認で著者は元NuMegaのBoundCheckerやSoftICE等の開発チームメンバという。

Ban 2005/02/27(日) 22:44:39
訂正。
> # 「Windowsプログラマのためのデバッグテクニック徹底解析」(Microsoft Press)
# 「Windowsプログラマのためのデバッグテクニック徹底解説」(Microsoft Press)

しん 2005/02/28(月) 08:36:39
>あくまで「最後の手段」
まったくその通りですね!ICEを使わない時の組み込み環境では、mapファイルさまさま、
という感じです。

>外から catch はできないのでしょうか。
残念乍、出来ませんでした。何故なんだろう?調査が不十分かもしれません。
この辺は、時間が許す限り調査を続けようと思います。

>SetUnhandledExceptionFilter()
初耳です。早速実験してみます。貴重な情報、有難うございます。

>Windowsプログラマのためのデバッグテクニック徹底解説
初耳です。リンク先を参照したら、とても興味深い概要が書かれていました。
今度、紀伊国屋に行って中身を確認してみます。良かったら買いますね。

Banさん、どうも有難うございました。

しん 2005/02/28(月) 15:37:36
[[解決]]
う〜ん、結局分からずじまいです。解決には時間がかかりそうなので、ひとまず解決マー
クを付けます。
が 今回このやり取りで勉強になった事が沢山ありました。どうも有難うございました。
あと、下記に追記させて頂きます。

>>どうやら、例外として投げられたオブジェクトのアドレスのよう
>そうだったのですか!?再確認してみます。
どうやら違うようです。下記のようなコードを試しました。
void f()
{
   int i;
   TRACE( "%p", &i );
   throw i;
}
が、TRACE()出力値は0x12F534で、throw時の出力値は0x12f468でした。
両者の差は0xCC(=204)でした。何か見落としたかな?

Ban 2005/02/28(月) 15:51:14
# 確認したわけでないので確証もないのですが、
# 例外で投入されるオブジェクトは常にコピーされるはずで、
# その違いだったりして。いや、分からないですけど。

シャノン 2005/02/28(月) 23:13:58
> # 例外で投入されるオブジェクトは常にコピーされるはずで、

はい。コピー先のオブジェクトのアドレスです。
ただし、

catch( int i )

とかすると、コピーされたものがさらにコピーされる(例外ハンドラに値渡しされる)た
め、期待する結果は取れません。

catch( const int & i )

とかすると、この様子を観察できます。

サンプル:


#include <iostream>
#include <exception>

using namespace std;

class MyException : exception
{
public:
    MyException()
    {
        cout << "MyException default constructor( this : " << this 
<< " )" << endl;
    }

    MyException( const MyException & ref )
    {
        cout << "MyException copy constructor( this : " << this 
<< ", source : " << &ref << " )" << endl;
    }

    ~MyException()
    {
        cout << "MyException destructor( this : " << this << " )" << 
endl;
    }
};

void f() throw( MyException )
{
    MyException e;
    cout << "Throwing exception( " << &e << " )" << endl;
    throw e;
}

int main()
{
    try
    {
        f();
    }
    catch( MyException e )
    {
        cout << "Caught exception( " << &e << " )" << endl;
    }

    cout << endl;

    try
    {
        f();
    }
    catch( const MyException & e )
    {
        cout << "Caught exception( " << &e << " )" << endl;
    }

    return 0;
}

英語はテキトー。

しん 2005/03/02(水) 10:28:44
>Banさん"例外で投入されるオブジェクトは常にコピーされるはず"
>シャノンさん"コピー先のオブジェクトのアドレス"
全くその通りでした。下記コードを試して確認しました。
void f()
{
  try{
    throw 0;
  }catch( const int& i ){
    TRACE( "%p", &i );
  }
}
------------------------- 出力窓の内容 -------------------------
・・・前半は略・・・int @ 0x0012f51c。( →デバッガによって表示 )
0012F51C                              ( →TRACE() によって表示 )

謎だった2値の内、後者は判明しました。お二人とも、どうも有難うございました!

しん 2005/03/02(水) 10:33:40
環境を記しておりませんでした。失礼致しました。
今更ですが、規則の為、記しておきます。
・WinXP Pro. SP2
・VC++7.1 DLL版MFC ダイアログApp

シャノン 2005/03/03(木) 01:37:33
他のスレにポストした話題ですが、面白そうなのでこっちにも。
深いところに興味がおありなら、調べてみてはいかがでしょう?

0xe06d7363 でぐぐってみてください。
この奇妙な数字は何かと言うと、C++ 例外処理(try catch)ではなく、構造化例外処理
(__try __except)で C++ 例外(throw で投げたもの)を捕まえたときの例外コードで
す。
ぐぐると、例外オブジェクトの内部構造とかがわかります。

#自分で言語処理系を実装するときにヒントになったりして

ただし、可搬性はこれっぽちもありません。

しん 2005/03/03(木) 11:57:58
>0xe06d7363 でぐぐってみてください
という事で、早速見つけました。↓
http://support.microsoft.com/default.aspx?scid=kb;en-us;185294

この値は単なる情報なんですね。"MS-C++コンパイラが生成した例外だよ"という。
RaiseException()のアドレスではない事が分かりました。

これで謎の値に関しては、完全に解が得られました。
が、throwの発行元を検出する方法は相変わらず分からないままです。
なので、作成元に問い合わせる事にしました。

あと、このURL情報にある「問題となるコードをtry〜catchブロックで包めば、正しくエ
ラー(=多分、例外の意)を処理できるよ」の通りにやりました。
が、catchできなかったなあ。何が問題なんだろう?

ともあれ、お世話様でした。シャノンさん、どうも有難うございます!

シャノン 2005/03/03(木) 14:18:10
#勘違いされてるかも

0xe06d7363 の追記は、当初の質問に対する回答ではありません。
例外について興味があるなら、(純粋に興味本位で)調べてみてはいかがでしょうか、と
いうことであり、例外アドレスを云々するものではありません。
なので

> これで謎の値に関しては、完全に解が得られました。

追記からどんな解が得られたのか、大変謎です。

> 「問題となるコードをtry〜catchブロックで包めば、正しくエ
> ラー(=多分、例外の意)を処理できるよ」の通りにやりました。
> が、catchできなかったなあ。何が問題なんだろう?

try - catch ではなく、__try - __except で囲めば、ひょっとしたら例外を捕まえられ
るかもしれません。
捕まえて無かったことにしてしまうことは出来ますが、あまり望ましい動作とは思えませ
ん。

しん 2005/03/03(木) 14:49:13
失礼しました。勘違いです。シャノンさん、ごめんなさい。
0x7c81eb33はやはりRaiseException()のアドレスのようですね!

# 0xe06d7363と全然違うじゃん!参りました。

>追記からどんな解が得られたのか、大変謎です。
そんな、皮肉を仰らずに〜。間違いですよぉ!前者の値は、予想値という状況に戻りまし
た。

>__try - __except で囲めば、ひょっとしたら例外を捕まえられるかも
ちょっと実験してみますね。

シャノン 2005/03/04(金) 10:12:36
> 0x7c81eb33はやはりRaiseException()のアドレスのようですね!

はい。RaiseException のエントリポイントのアドレスではありませんが、
RaiseException のコード中の値であることは間違いありません。

> そんな、皮肉を仰らずに〜。間違いですよぉ!

や、皮肉のつもりはありませんでした。
お気を害されましたら、申し訳ありません。

古いスレッドには追加発言できません。