Есть ли способ сбросить трассировку стека с номером строки из бинарной версии Linux?

У меня есть требование сбрасывать следы стека при сбое моего приложения c++ Linux. Мне удалось сделать это с помощью backtrace() и backtrace_symbols(). Теперь, кроме того, я хотел бы получить номера строк сбоя. Как это сделать?

Ответ 1

Я получил помощь от

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 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

Ответ 2

Это возможно только в том случае, если программа была скомпилирована с отладочной информацией (то есть с помощью gcc -Wall -g или с g++ -Wall -g). Без -g исполняемый файл не содержит никакой информации об исходной строке. И если вы используете gcc вы можете скомпилировать как информацию об оптимизации, так и информацию об отладке (например, g++ -Wall -g -O2), но иногда расположение линии будет "удивительным".

Флаг -Wall просит GCC показать все предупреждения.Это очень полезно (поэтому я рекомендую его использовать), но не связано с -g или информацией отладки.

Что касается того, как извлечь номер строки, самый простой способ - это обработать процесс gdb. В качестве альтернативы, вы можете получить отладочную информацию (в формате DWARF) и проанализировать ее, возможно, используя libdwarf из цепочки инструментов ELF. Я не уверен, что это стоит того...

Чтобы просто получить обратную трассировку, вы можете просто запустить свою программу через gdb возможно, как gdb --args yourprogram itsarguments...


добавлений

Вы также можете использовать libbacktrace изнутри недавнего GCC (на самом деле это libbacktrace Иана Тейлора), который предназначен для решения вашей проблемы (это "интерпретация" формата DWARF текущего исполняемого файла, который вы скомпилируете с g++ -O -g).

Ответ 3

Как отметил Саклайн, addr2line может быть использован для получения номера строки.

Если библиотека предпочтительнее, взгляните на комплект LPT. Инструкции по установке находятся здесь. LPT опирается на библиотеку bfd.

Ответ 4

system() требуется #include <stdlib.h>

Немногие другие вещи, кажется, отсутствуют.

$ g++-8 -g -o dump dump.cpp
dump.cpp: In function ‘void bt_sighandler(int, sigcontext):
dump.cpp:15:43: error: ‘struct sigcontext has no member named
‘eip; did > you mean ‘rip?
         "from %p\n", sig, ctx.cr2, ctx.eip);
                                        ^~~
                                        rip
dump.cpp:21:26: error: ‘struct sigcontext has no member
named ‘eip;
did you mean ‘rip?
  trace[1] = (void *)ctx.eip;
                         ^~~
                         rip
dump.cpp: In function ‘int main():
dump.cpp:64:19: error: invalid conversion from ‘void* to
   ‘__sighandler_t > {aka ‘void (*)(int)} [-fpermissive]
   sa.sa_handler = (void *)bt_sighandler;
                   ^~~~~~~~~~~~~~~~~~~~~