Как лучше вызывать gdb из программы для печати его stacktrace?

Теперь я использую такую ​​функцию:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "--pid=%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

И я вижу детали print_trace в выводе.

Каковы другие способы сделать это?

Ответ 1

Вы упомянули мой другой ответ (теперь он удален), что вы также хотите увидеть номера строк. Я не уверен, как это сделать при вызове gdb из вашего приложения.

Но я собираюсь поделиться с вами несколькими способами печати простой stacktrace с именами функций и их номерами строк без использования gdb. Большинство из них пришли из очень хорошей статьи из Linux Journal:

  • Метод # 1:

Первый метод заключается в его распространении с сообщениями печати и журналов в порядке чтобы определить путь выполнения. В сложной программы, эта опция может становятся громоздкими и утомительными, даже если, с помощью некоторых специфичных для GCC макросов, его можно немного упростить. Рассмотрим, например, макрос отладки например:

 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                          "() [%s:%d] here I am\n", \
                          __FILE__, __LINE__)

Вы можете быстро распространять этот макрос во всей вашей программе путем резки и вставляя его. Когда вам это не нужно больше, отключите его просто определяя его без-op.

  • Метод # 2: (он ничего не говорит о номерах строк, но я делаю по методу 4)

Более удобный способ получить обратную трассировку стека, однако, заключается в использовании некоторых из конкретные вспомогательные функции, обеспечиваемые Glibc. Ключевым является backtrace(), который перемещает кадры стека из вызывающий момент к началу программы и предоставляет массив обратные адреса. Затем вы можете отобразить каждый адрес в тело особая функция в вашем коде взглянув на объектный файл с команда nm. Или вы можете сделать это более простой способ - использовать backtrace_symbols(). Эта функция преобразует список обратные адреса, возвращаемые backtrace(), в список строк, каждый из которых содержит имя функции смещение внутри функции и обратный адрес. Список строк выделено из вашего места кучи (как будто вы вызвали malloc()), так что вы должны free(), как только вы закончите с он.

Я рекомендую вам прочитать его, поскольку на странице есть исходный код. Чтобы преобразовать адрес в имя функции, вы должны скомпилировать свое приложение с помощью параметра -rdynamic.

  • Метод № 3: (лучший способ сделать метод 2)

Еще более полезное приложение для этот метод ставит стек backtrace внутри обработчика сигнала и с последним поймать все "плохие" сигналы, которые ваша программа может получать (SIGSEGV, SIGBUS, SIGILL, SIGFPE и  как). Таким образом, если ваша программа к сожалению, сбой, и вы не запуская его с помощью отладчика, вы можете получить трассировку стека и знать, где произошла ошибка. Этот метод также могут быть использованы для понимания того, где программа зацикливается, если она останавливается отвечая

Реализация этого метода доступна здесь.

  • Метод # 4:

Небольшое улучшение, которое я сделал по методу № 3 для печати номеров строк. Это можно скопировать и для работы с методом # 2.

В принципе, я следовал за советом, который использует addr2line для

конвертировать адреса в имена файлов и номера строк.

Исходный код ниже печатает номера строк для всех локальных функций. Если вы вызываете функцию из другой библиотеки, вы можете увидеть пару ??:0 вместо имен файлов.

#include <stdio.h>
#include <signal.h>
#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]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
        //last parameter is the file name of the symbol
    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

Обновление 2012/04/28 для последних версий ядра Linux, вышеуказанная подпись sigaction устарела. Также я немного улучшил его, захватив исполняемое имя из этого ответа. Вот обновленная версия:

char* exe = 0;

int initialiseExecutableName() 
{
    char link[1024];
    exe = new char[1024];
    snprintf(link,sizeof link,"/proc/%d/exe",getpid());
    if(readlink(link,exe,sizeof link)==-1) {
        fprintf(stderr,"ERRORRRRR\n");
        exit(1);
    }
    printf("Executable name initialised: %s\n",exe);
}

const char* getExecutableName()
{
    if (exe == 0)
        initialiseExecutableName();
    return exe;
}

/* get REG_EIP from ucontext.h */
#define __USE_GNU
#include <ucontext.h>

void bt_sighandler(int sig, siginfo_t *info,
                   void *secret) {

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

  /* Do something useful with siginfo_t */
  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, info->si_addr, 
           uc->uc_mcontext.gregs[REG_EIP]);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller address */
  trace[1] = (void *) uc->uc_mcontext.gregs[REG_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] %s\n", messages[i]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i] , p, messages[i] );
           //last parameter is the filename of the symbol
    system(syscom);

  }
  exit(0);
}

и инициализируйте следующим образом:

int main() {

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

  sa.sa_sigaction = (void *)bt_sighandler;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

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

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

}

Ответ 2

Если вы используете Linux, стандартная библиотека C включает в себя функцию с именем backtrace, которая заполняет массив обратными адресами фреймов, а другая функция называется backtrace_symbols, которая будет принимать адреса от backtrace и найдите соответствующие имена функций. Они описаны в Руководство по библиотеке GNU C.

Те не будут отображать значения аргументов, строки источника и т.п., и они применяются только к вызывающему потоку. Тем не менее, они должны быть намного быстрее (и, возможно, менее flaky), чем запустить GDB таким образом, поэтому они имеют свое место.

Ответ 3

Я уже уведомил nobar, что он должен опубликовать свой фантастический ответ здесь, но так как он Не двигай пальцем, я сделаю это для тебя, ребята:

Итак, вам нужна автономная функция , которая печатает трассировку стека со всеми функциями, которые имеют трассировки стека gdb, и это не останавливает ваше приложение. Ответ заключается в том, чтобы автоматизировать запуск gdb в неинтерактивном режиме для выполнения только тех задач, которые вы хотите.

Это выполняется, выполняя gdb в дочернем процессе, используя fork() и создавая сценарий для отображения трассировки стека, пока ваше приложение ждет его завершения. Это может быть выполнено без использования ядро-дампа и без прерывания работы приложения.

Я считаю, что это то, что вы ищете, @Vi

Ответ 4

Не проще abort()?

Таким образом, если это происходит в поле, клиент может отправить вам основной файл (я не знаю многих пользователей, которые достаточно вовлечены в мое приложение, чтобы заставить меня отлаживать его).