Ответ 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());
}