問題

私は全体の質問を小さなものに分けました:

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

  ベストアンサー

Pseudocodeと言えば、すべてのスタックフレームが可変サイズのデータ構造であるスタック "パックされたスタックフレームの配列"を呼び出すことができます。

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

問題は、すべての関数が異なる<N> - フレームサイズが異なることです。

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

このメソッドを使用している場合は、フレームリンケージを必要とせず、-fomit-frame-pointerを使用していてもバックトレースは正常に動作します。一方、フレームリンケージがある場合、スタックの反復はリンクされたリストに従っています。新しいスタックフレーム内のすべてのフラメポインタは、前のスタックを指す関数プロローグコードによって初期化されるためです。

フレームサイズ情報もフラメポインタもありませんが、まだシンボルテーブルを持っている場合は、実際のバイナリからフレームサイズを計算するために、リバースエンジニアリングのビットでバックトレースを実行することもできます。プログラムカウンタから始め、シンボルテーブルに属する関数を調べ、その関数を最初から逆アセンブルします。関数の先頭と実際にスタックポインタを変更するプログラムカウンタ(スタックに何かを書き込んだり、スタックスペースを割り当てたりする)の間のすべての操作を分離します。これは、現在の関数のフレームサイズを計算

最後に、スタックの内容のヒューリスティック解析を行うことができます。実行可能なスタック内のすべての単語を分離し、プロセスアドレス空間のマップされたセグメント(それによって関数オフセットとして戻りアドレスになる可能性があります)を実行し、メモリの長さを調べて、そこに命令を組み立て、実際にそれが「次」と呼ばれているかどうかを確認します。バイナリが完全に取り除かれていても、これは程度に機能します(ただし、すべての場合はリストアドレスを取得することはできますが、これはX86の命令の長さを使用することができません。

これらのアルゴリズムのシンプルで複雑で網羅的な実装を作る穴がいくつかあります。たとえば、tail-recursive関数、インラインコードなどです。 gdbソースコードはあなたにいくつかのアイデアを与えるかもしれません:

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

gdb は様々な技術を採用している。

  同じタグがついた質問を見る

gdbg++stack-trace