13

C++ Linux アプリケーションがクラッシュしたときに、スタック トレースをダンプする必要があります。backtrace()と を使用してこれを正常に行うことができましたbacktrace_symbols()。さらに、クラッシュの行番号を取得したいと思います。それはどのように行うのですか?

4

5 に答える 5

18

から助けてもらいました

http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/063/6391/6391l2.html およびhttp://www.linuxjournal.com/article/6391?page=0,0が表示されますこれを実現する方法を示すサンプル コードを示します。

基本的には、スタック バックトレースをシグナル ハンドラー内に配置し、プログラムが受信できるすべての「悪い」シグナル (SIGSEGV、SIGBUS、SIGILL、SIGFPE など) をシグナル ハンドラーにキャッチさせることです。このようにして、プログラムが残念ながらクラッシュし、デバッガーで実行していなかった場合、スタック トレースを取得して、障害が発生した場所を知ることができます。この手法は、プログラムが応答を停止した場合にループしている場所を理解するためにも使用できます...

以下のコードは、トレース内のすべてのアドレスに対して外部プログラムaddr2lineを実行して、ファイル名と行番号に変換します。

以下のソース コードは、すべてのローカル関数の行番号を出力します。別のライブラリの関数が呼び出された場合、ファイル名の代わりに ??:0 がいくつか表示されることがあります。

#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;

  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *)ctx.eip;
  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s\n", i, messages[i]);

    char syscom[256];
    sprintf(syscom,"addr2line %p -e sighandler", trace[i]); //last parameter is the name of this app
    system(syscom);
  }

  exit(0);
}


int func_a(int a, char b) {

  char *p = (char *)0xdeadbeef;

  a = a + b;
  *p = 10;  /* CRASH here!! */

  return 2*a;
}


int func_b() {

  int res, a = 5;

  res = 5 + func_a(a, 't');

  return res;
}


int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_handler = (void *)bt_sighandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());
}

このコードは次のようにコンパイルする必要があります: gcc sighandler.c -o sighandler -rdynamic

プログラムの出力:

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0
于 2013-02-28T07:39:51.793 に答える
3

これは、プログラムがデバッグ情報を使用してコンパイルされている場合にのみ可能です (つまり、gcc -Wall -gまたは を使用g++ -Wall -g)。実行可能ファイルがない-gと、ソース行情報は含まれません。また、使用gccすると、最適化とデバッグ情報の両方を使用してコンパイルできますが (例: g++ -Wall -g -O2)、行の場所が「驚くべき」ものになる場合があります。

この-Wallフラグは、GCC にすべての警告を表示するように要求します。これは非常に便利です (したがって、使用することをお勧めします) が-g、デバッグ情報とは関係ありません。

行番号を抽出する方法については、プロセスを fork するのが最も簡単な方法gdbです。または、デバッグ情報 ( DWARF形式) を取得し、おそらくELF ツール チェーンlibdwarfを使用して解析することもできます。苦労する価値があるかどうかはわかりません...

gdbバックトレースを取得するには、おそらく次のようにプログラムを実行するだけgdb --args yourprogram itsargumentsです...


補遺

最近の GCC 内からlibbacktraceを使用することもできます(実際には、Ian Taylor の libbacktrace です)。これは、問題を解決するように設計されています (現在の実行可能ファイルの DWARF 形式を「解釈」しており、これを使用してコンパイルしますg++ -O -g)。

于 2013-02-28T06:43:16.483 に答える