从内部程序调用gdb打印其堆栈跟踪的最佳方法是什么?
作者:互联网
使用这样的函数:
#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的细节.
有什么其他方法可以做到这一点?
解决方法:
您在我的其他答案(现已删除)中提到您还想查看行号.从应用程序内部调用gdb时,我不确定如何做到这一点.
但是我将与您分享几种方法来打印带有函数名称的简单堆栈跟踪及其各自的行号而不使用gdb.他们中的大多数来自Linux Journal的一篇非常好的文章:
>方法#1:
The first method is to disseminate it
with print and log messages in order
to pinpoint the execution path. In a
complex program, this option can
become cumbersome and tedious even if,
with the help of some GCC-specific
macros, it can be simplified a bit.
Consider, for example, a debug macro
such as:
#define TRACE_MSG fprintf(stderr, __FUNCTION__ \
"() [%s:%d] here I am\n", \
__FILE__, __LINE__)
You can propagate this macro quickly
throughout your program by cutting and
pasting it. When you do not need it
anymore, switch it off simply by
defining it to no-op.
>方法#2 :(它没有说明行号,但是我对方法4做了什么)
A nicer way to get a stack backtrace,
however, is to use some of the
specific support functions provided by
glibc. The key one is backtrace(),
which navigates the stack frames from
the calling point to the beginning of
the program and provides an array of
return addresses. You then can map
each address to the body of a
particular function in your code by
having a look at the object file with
the nm command. Or, you can do it a
simpler way–use backtrace_symbols().
This function transforms a list of
return addresses, as returned by
backtrace(), into a list of strings,
each containing the function name
offset within the function and the
return address. The list of strings is
allocated from your heap space (as if
you called malloc()), so you should
free() it as soon as you are done with
it.
我鼓励你阅读它,因为该页面有source code个例子.要将地址转换为函数名,必须使用-rdynamic选项编译应用程序.
>方法#3 :(做方法2的更好方法)
An even more useful application for
this technique is putting a stack
backtrace inside a signal handler and
having the latter catch all the “bad”
signals your program can receive
(SIGSEGV, SIGBUS, SIGILL, SIGFPE and
the like). This way, if your program
unfortunately crashes and you were not
running it with a debugger, you can
get a stack trace and know where the
fault happened. This technique also
can be used to understand where your
program is looping in case it stops
responding
该技术的实现可用于here.
>方法#4:
我在方法#3上做了一个很小的改进来打印行号.这也可以复制到方法#2上.
基本上,我使用addr2line来followed a tip
convert addresses into file names and
line numbers.
下面的源代码打印所有本地函数的行号.如果调用另一个库中的函数,您可能会看到几个??: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
对于最近的Linux内核版本更新2012/04/28,上述sigaction签名已过时.此外,我通过从this answer获取可执行文件名称来改进它.这是一个up to date version:
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());
}
标签:c-3,linux,gdb,stack-trace 来源: https://codeday.me/bug/20190916/1807518.html