Вопрос: Лучший способ вызвать 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 в выводе.

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


52


источник


Ответы:


Вы упомянули в моем другом ответе (теперь удаленном), что вы также хотите видеть номера строк. Я не уверен, как это сделать при вызове 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'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]);

    /* 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's 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());

}

88



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

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


8



nobar  отправил фантастический ответ , Вкратце;

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

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

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


6



не abort() проще?

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


1