2010-12-03 16 views
5

は、私は小さなものに全体の質問に分かれています:gdbはC++のスタックトレースをどのように再構築しますか?

    GDBは、スタックトレースを再構築するために使用することができ、異なるアルゴリズムのどのような
  1. それぞれのスタックトレース再構築アルゴリズムはどのように高レベルで動作しますか?長所と短所?
  2. 各スタックトレース再構築アルゴリズムが動作するためには、どのような種類のメタ情報コンパイラがプログラム内で提供する必要がありますか?
  3. 特定のアルゴリズムを有効/無効にする対応するg ++コンパイラスイッチもありますか?
+1

http://en.wikipedia.org/wiki/Call_stack、コンパイラはデバッグ情報マッピングコードのオフセットをソース行に追加します。 –

+1

そのウィキペディアの記事では、他のアルゴリズムの詳細については、「別の関数を呼び出すときにスタックにRBPを格納する」以外の方法はありません。たとえば、コンパイル時に-fomit-frame-pointerが設定されていると、そのアルゴリズムはもう動作しません。 スタックアンワインド記述子はどうですか?スタックトレースをどのように再構築するのに使われますか?他のアルゴリズムはありますか? –

+1

'-fomit-frame-pointer'を指定すると、GDBが起動します。 –

答えて

7

のような擬似コードをいえば、あなたは、スタックのすべてのスタックフレームは、あなたが表現できる可変サイズのデータ​​構造であり、「パックスタックフレームの配列を」呼び出すことができます。

template struct stackframe<N> { 
    uintptr_t contents[N]; 
#ifndef OMIT_FRAME_POINTER 
    struct stackframe<> *nextfp; 
#endif 
    void *retaddr; 
}; 

問題がおきていることです機能が異なる<N> - フレームサイズが異なります。

コンパイラはフレームサイズを知っていて、デバッグ情報を作成すると、通常、これらをフレームの一部として出力します。デバッガが最後に行う必要があるのは、最後のプログラムカウンタを見つけて、シンボルテーブルの関数を調べ、その名前を使ってデバッグ情報のフレームサイズを調べることです。これをスタックポインタに追加すると、次のフレームの先頭に移動します。

この方法を使用する場合、フレームリンケージは不要で、を使用してもバックトレースは正常に機能します。一方、フレームリンケージがある場合、新しいスタックフレーム内のすべてのフレームポインタは、関数プロローグコードによって前のものを指すように初期化されるため、スタックを反復するのはリンクされたリストの直後です。

フレームサイズ情報もフレームポインタもありませんが、シンボルテーブルがない場合は、リバースエンジニアリングによってバックトレースを実行して、実際のバイナリからフレームサイズを計算することもできます。プログラムカウンタから始め、シンボルテーブルにある関数を調べてから、関数を最初から逆アセンブルします。関数の先頭と実際にスタックポインタを変更するプログラムカウンタ(スタックに何かを書き込む、スタックスタックを割り当てる)の間で、すべての演算を分離します。これは現在の関数のフレームサイズを計算してスタックポインタから減算し、関数が入力される前にスタックに書き込まれた最後の単語を(ほとんどのアーキテクチャで)見つける必要があります。通常は呼び出し元への戻りアドレスです。必要に応じて繰り返します。

最後に、あなたがスタックの内容のヒューリスティック分析を行うことができます - すべてのプロセスのアドレス空間の実行可能にマッピングされたセグメント内にあるスタック内の単語(と、それによって、戻りアドレス別名機能オフセットすることができる)に分離し、メモリを検索して命令を逆アセンブルして実際にソートの呼び出し命令であるかどうかを確認し、そうであればそれが本当に「次」と呼ばれているかどうか、そして中断のない呼び出しシーケンスを構築できるかどうかを確認します。バイナリが完全に削除されたとしても、これはある程度は機能します(ただし、その場合はすべてリターンアドレスのリストです)。私はGDBがこの手法を採用しているとは思っていませんが、埋め込まれた低レベルのデバッガの中にはいくつかあります。 x86では、命令の長さが異なるため、命令ストリームを介して簡単に「ステップバック」できず、命令の長さが固定されているRISCでは非常に困難です。 ARMでは、これははるかに簡単です。

テール再帰関数、インラインコードなどのように、これらのアルゴリズムの単純な実装または複雑な/徹底的な実装が時々失敗する穴があります。GDBのソースコードは、あなたにいくつかのより多くのアイデアを与えるかもしれない:

http://sourceware.org/cgi-bin/cvsweb.cgi/src/gdb/frame.c?rev=1.287&content-type=text/x-cvsweb-markup&cvsroot=src

GDBは、このような様々な技術を採用しています。

+0

これは特にC++固有のものではないと言います。 C++の仕様は例外スタックを巻き戻すときに出ますが、それはデバッガではありません( 'throw()'でクラッシュしても例外スタックを解除していない場合、 'catch {}'でクラッシュした場合あなたはそうしています;巻き戻しのプロセスでクラッシュすると、コンパイラのバグがあります。デバッグすると、真の神に任せられます...)。 –

+0

私はちょうど.eh_frame(C++特有)がスタックを巻き戻すためにGDBで使用されるかもしれないことを示唆する別のソースを見つけました:http://sourceware.org/gdb/papers/unwind.html –

関連する問題