Cでスタックトレースを取得するにはどうすればよいですか?


83

これを行うための標準のC関数がないことを私は知っています。私はWindowsと* nixでこれを行うためのテクニックは何だろうと思っていましたか?(Windows XPは、今これを行うための私の最も重要なOSです。)


回答:


81

glibcはbacktrace()関数を提供します。

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html


6
glibc FTW ...再び。(これが、Cプログラミング(およびそれに付随するコンパイラー)に関して、glibcが絶対的なゴールドスタンダードであると考えるもう1つの理由です。)
Trevor Boyd Smith

4
しかし、もっと待ってください!backtrace()関数は、コールスタック関数を表すvoid *ポインターの配列のみを提供します。「それはあまり役に立ちません。arg。」恐れるな!glibcは、すべてのvoid *アドレス(コールスタック関数アドレス)を人間が読める文字列シンボルに変換する関数を提供します。char ** backtrace_symbols (void *const *buffer, int size)
トレバーボイドスミス

1
警告:C関数でのみ機能すると思います。@Trevor:ELFテーブルのアドレスでsymを検索しているだけです。
コンラッドマイヤー

2
void backtrace_symbols_fd(void *const *buffer, int size, int fd)たとえば、出力をstdout / errに直接送信できるものもあります。
wkz 2012

2
backtrace_symbols()吸う。すべてのシンボルをエクスポートする必要があり、DWARF(デバッグ)シンボルをサポートしていません。libbacktraceは、多くの(ほとんどの)場合にはるかに優れたオプションです。
Erwan Legrand 2018

29

backtrace()とbacktrace_symbols()があります:

マニュアルページから:

#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
    printf("%s\n", strs[i]);
}
free(strs);
...

これをより便利な/ OOPの方法で使用する1つの方法は、backtrace_symbols()の結果を例外クラスコンストラクターに保存することです。したがって、そのタイプの例外をスローするたびに、スタックトレースがあります。次に、それを印刷するための機能を提供するだけです。例えば:

class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i < frames; ++i) {
            printf("%s\n", strs[i]);
        }
        free(strs);
    }
};

..。

try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

タダ!

注:最適化フラグを有効にすると、結果のスタックトレースが不正確になる可能性があります。理想的には、デバッグフラグをオンにし、最適化フラグをオフにして、この機能を使用します。


@shuckcは、アドレスをシンボル文字列に変換する場合にのみ使用します。これは、必要に応じて他のツールを使用して外部で実行できます。
Woodrow Barlow 2016

22

Windowsの場合は、StackWalk64()APIを確認してください(32ビットWindowsでも)。UNIXの場合は、OSのネイティブな方法を使用するか、使用可能な場合はglibcのbacktrace()にフォールバックする必要があります。

ただし、ネイティブコードでスタックトレースを使用することはめったに良い考えではないことに注意してください。それが不可能なためではなく、通常は間違ったことを達成しようとしているためです。

ほとんどの場合、人々は、例外がキャッチされたとき、アサーションが失敗したとき、または-最悪で最も間違っている-致命的な「例外」または次のような信号を受け取ったときなど、例外的な状況でスタックトレースを取得しようとしますセグメンテーション違反。

最後の問題を考慮すると、ほとんどのAPIでは、メモリを明示的に割り当てる必要があるか、内部で割り当てる場合があります。プログラムが現在置かれている可能性のある脆弱な状態でこれを行うと、事態がさら​​に悪化する可能性があります。たとえば、クラッシュレポート(またはコアダンプ)は問題の実際の原因を反映していませんが、それを処理しようとして失敗しました)。

スタックトレースを取得することになると、ほとんどの人がそれを試みているように見えるので、あなたはその致命的なエラー処理を達成しようとしていると思います。もしそうなら、私はデバッガー(開発中)に依存し、プロセスを本番環境でコアダンプ(またはWindowsでミニダンプ)させます。適切なシンボル管理と一緒に、あなたは死後に原因となる指示を理解するのに問題がないはずです。


2
シグナルハンドラまたは例外ハンドラでメモリ割り当てを試みるのは脆弱であるというのは正しいことです。考えられる解決策の1つは、プログラムの開始時に一定量の「緊急」スペースを割り当てるか、静的バッファーを使用することです。
j_random_hacker 2009

もう1つの方法は、独立して実行されるコアダンプサービスを作成することです
Kobor42 2012

5

アンワインドライブラリを使用する必要があります

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

共有ライブラリから電話をかけない限り、あなたのアプローチもうまくいきます。

addr2lineLinuxでコマンドを使用して、対応するPCのソース関数/行番号を取得できます。


「ソース関数/行番号」?リンクがコードサイズの縮小に最適化されている場合はどうなりますか?とはいえ、これは便利なプロジェクトのように思えます。レジスターを取得する方法がないのは残念です。私は間違いなくこれを調べます。それが完全にプロセッサに依存しないことを知っていますか?Cコンパイラを備えたもので動作しますか?
Mawgは、2010

1
役立つaddr2lineコマンドについて言及しているという理由だけで、このコメントは価値がありました。
鬼詩篇33 2010

addr2lineは、ASLRを備えたシステム(つまり、過去10年間に人々が使用してきたもののほとんど)で再配置可能なコードに対して失敗します。
Erwan Legrand 2018

4

プラットフォームに依存しない方法はありません。

最も近い方法は、最適化せずにコードを実行することです。このようにして、(ビジュアルc ++デバッガーまたはGDBを使用して)プロセスにアタッチし、使用可能なスタックトレースを取得できます。


現場の組み込みコンピュータでクラッシュが発生した場合、それは役に立ちません。:(
ケビン

@Kevin:組み込みマシンでも、通常、リモートデバッガスタブまたは少なくともコアダンプを取得する方法があります。たぶん、それがフィールドに展開された後ではないかもしれません...
ephemient 2010年

選択したプラットフォームwindows / linux / mac ...でgcc-glibcを使用して実行すると、backtrace()とbacktrace_symbols()は3つのプラットフォームすべてで機能します。その声明を考えると、私は「それを行う[ポータブル]方法はありません」という言葉を使用します。
トレバーボイドスミス

4

Windowsの場合CaptureStackBackTrace()もオプションであり、ユーザー側で必要な準備コードが少なくて済みますStackWalk64()。(また、私が持っていた同様のシナリオでは、CaptureStackBackTrace()より良く(より確実に)動作することになりましたStackWalk64()。)


2

Solarisにはpstackコマンドがあり、これもLinuxにコピーされています。


1
便利ですが、実際にはCではありません(これは外部ユーティリティです)。
ephemient 2010年

1
また、説明(セクション:制限)から「:pstackは現在Linuxでのみ機能し、32ビットELFバイナリ(64ビットはサポートされていません)を実行しているx86マシンでのみ機能します」
Ciro Costa

0

スタックを後方に歩くことでそれを行うことができます。ただし、実際には、各関数の最初にある呼び出しスタックに識別子を追加し、最後にそれをポップしてから、内容を印刷する方が簡単な場合がよくあります。これは少しPITAですが、うまく機能し、最終的には時間を節約できます。


1
「スタックを後方に歩く」ことをもっと詳しく説明していただけますか?
スパイダーマン2012

@Spidey組み込みシステムでは、これだけで済む場合もあります。プラットフォームがWinXPであるため、これは却下されたと思います。しかし、スタックウォーキングをサポートするlibcがなければ、基本的にスタックを「ウォーク」する必要があります。現在のベースポインタから始めます(x86では、これはRBP regの内容です)。これにより、1。保存された以前のRBP(スタックウォークを続行する方法)、および2.関数が何であったかを示す呼び出し/ブランチリターンアドレス(関数の保存されたRIP regを呼び出す)でスタックをポイントすることができます。次に、シンボルテーブルにアクセスできる場合は、関数アドレスを検索できます。
テッドミドルトン

0

過去数年間、私はIan LanceTaylorのlibbacktraceを使用してきました。これは、すべてのシンボルをエクスポートする必要があるGNUCライブラリの関数よりもはるかにクリーンです。libunwindよりもバックトレースの生成に多くのユーティリティを提供します。そして最後に重要なことですが、などの外部ツールを必要とするアプローチのように、ASLRによって打ち負かされることはありませんaddr2line

Libbacktraceは当初GCCディストリビューションの一部でしたが、現在はBSDライセンスの下でスタンドアロンライブラリとして作成者によって利用可能になっています。

https://github.com/ianlancetaylor/libbacktrace

これを書いている時点では、libbacktraceでサポートされていないプラットフォームでバックトレースを生成する必要がない限り、他には何も使用しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.