20

基本的に glibc の backtrace() ライブラリ関数と同じように、例外ハンドラとデバッグ関数でコール スタック バックトレースを出力できるようにしたいと考えています。残念ながら、私の C ライブラリ (Newlib) はそのような呼び出しを提供していません。

私はこのようなものを持っています:

#include <unwind.h> // GCC's internal unwinder, part of libgcc
_Unwind_Reason_Code trace_fcn(_Unwind_Context *ctx, void *d)
{
    int *depth = (int*)d;
    printf("\t#%d: program counter at %08x\n", *depth, _Unwind_GetIP(ctx));
    (*depth)++;
    return _URC_NO_REASON;
}

void print_backtrace_here()
{
    int depth = 0;
    _Unwind_Backtrace(&trace_fcn, &depth);
}

これは基本的に機能しますが、結果として得られるトレースは必ずしも完全ではありません。たとえば、

int func3() { print_backtrace_here(); return 0; }
int func2() { return func3(); }
int func1() { return func2(); }
int main()  { return func1(); }

バックトレースには func3() と main() のみが表示されます。(これは明らかなおもちゃの例ですが、逆アセンブルを確認し、これらの関数がすべて完全にここにあり、最適化またはインライン化されていないことを確認しました。)

更新:古い ARM7 システムでこのバックトレース コードを試しましたが、同じ (または少なくとも、可能な限り同等の) コンパイラ オプションとリンカー スクリプトを使用して、正しい完全なバックトレースを出力しました (つまり、func1 と func2 が欠落していません)。実際、メインを過ぎてブート初期化コードにまで遡ることさえできます。したがって、おそらく問題はリンカー スクリプトやコンパイラ オプションにあるわけではありません。(また、このARM7テストでもフレームポインタが使用されていないことを逆アセンブルから確認しました)。

コードは -fomit-frame-pointer でコンパイルされていますが、私のプラットフォーム (ベア メタル ARM Cortex M3) はフレーム ポインターを使用しない ABI を定義しています。(このシステムの以前のバージョンでは、ARM7 で古い APCS ABI を使用し、スタック フレームとフレーム ポインターを強制し、こちらのようなバックトレースを使用していましたが、完全に機能していました)。

システム全体が -fexception でコンパイルされ、_Unwind が使用する必要なメタデータが ELF ファイルに含まれるようになります。(_Unwind は例外処理用に設計されていると思います)。

だから、私の質問は次のとおり です。GCCを使用して組み込みシステムで信頼できるバックトレースを取得する「標準」の受け入れられた方法はありますか?

必要に応じてリンカー スクリプトと crt0 コードをいじる必要はありませんが、ツールチェーン自体にチャンスを与える必要はありません。

ありがとう!

4

6 に答える 6

10

これには-funwind-tables、または一部のターゲットでは、正しく機能-fasynchronous-unwind-tables するために必要です!_Unwind_Backtrace

于 2011-08-04T19:07:26.797 に答える
8

ARM プラットフォームはフレーム ポインターを使用しないため、スタックフレームの大きさを完全に把握することはできず、R14 の単一の戻り値を超えてスタックをロールアウトすることはできません。

デバッグ シンボルがないクラッシュを調査するときは、スタック全体をダンプし、命令範囲内の各項目に最も近いシンボルを検索します。大量の誤検知が発生しますが、それでもクラッシュの調査には非常に役立ちます。

純粋な ELF 実行可能ファイルを実行している場合は、リリース実行可能ファイルからデバッグ シンボルを分離できます。gdb は、標準の UNIX コア ダンプから何が起こっているかを調べるのに役立ちます。

于 2010-08-03T16:57:58.073 に答える
7

gcc は最適化を返します。func1() と func2() では、func2()/func3() を呼び出しません。代わりに、func2()/func3() にジャンプするため、func3() はすぐに main() に戻ることができます。

あなたの場合、 func1() と func2() はスタックフレームをセットアップする必要はありませんが、そうする場合(ローカル変数の場合など)、関数呼び出しが最後の命令である場合、gcc は最適化を実行できます-その後、クリーンアップしますfunc3() にジャンプする前にスタックをアップします。

生成されたアセンブラ コードを見て確認してください。


編集/更新:

これが理由であることを確認するには、関数呼び出しの後に、コンパイラが並べ替えることができない何かを行います (たとえば、戻り値を使用します)。または、-O0 を付けてコンパイルしてみてください。

于 2010-08-03T16:40:25.310 に答える
3

例で述べたように、GCC最適化関数呼び出しなどの一部のコンパイラ。コード フラグメントの操作では、呼び出しチェーンに中間リターン ポインターを格納する必要はありません。中間関数は別の関数を呼び出す以外に何もしないので、 from からfunc3()toに戻ってもまったく問題ありません。main()

これはコードの削除とは異なり (実際には、中間関数を完全に最適化することができます)、別のコンパイラ パラメーターがこの種の最適化を制御する場合があります。

GCCを使用している場合は、試してください-fno-optimize-sibling-calls

もう 1 つの便利な GCC オプションは-mno-sched-prolog、関数プロローグでの命令の並べ替えを防止する です。これは、コードをバイト単位で解析する場合に重要です 。 checkstack-pl.txt

于 2014-02-25T14:25:34.410 に答える
0

実行可能ファイルには、オプションを使用してコンパイルしたデバッグ情報が含まれてい-gますか? これは、フレーム ポインターなしで完全なスタック トレースを取得するために必要だと思います。

-gdwarf-2アンワインド情報を含む形式を使用していることを確認する必要がある場合があります。

于 2010-08-04T11:54:42.160 に答える