C语言 从程序内部调用 gdb 以打印其堆栈跟踪的最佳方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3151779/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to invoke gdb from inside program to print its stacktrace?
提问by Vi.
Using a function like this:
使用这样的函数:
#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);
}
}
I see the details of print_trace in the output.
我在输出中看到了 print_trace 的详细信息。
What are other ways to do it?
还有什么其他方法可以做到?
回答by karlphillip
You mentioned on my other answer (now deleted) that you also want to see line numbers. I'm not sure how to do that when invoking gdb from inside your application.
您在我的另一个答案(现已删除)中提到您还想查看行号。从应用程序内部调用 gdb 时,我不确定如何执行此操作。
But I'm going to share with you a couple of ways to print a simple stacktrace with function names and their respective line numbers without using gdb. Most of them came from a very nicearticle from Linux Journal:
但我将与您分享几种不使用 gdb打印带有函数名称及其各自行号的简单堆栈跟踪的方法。其中大部分来自Linux Journal 的一篇非常好的文章:
- Method #1:
- 方法#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:
第一种方法是通过打印和日志消息传播它,以便查明执行路径。在一个复杂的程序中,这个选项会变得繁琐和乏味,即使在一些 GCC 特定的宏的帮助下,它可以被简化一点。例如,考虑一个调试宏,例如:
#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.
您可以通过剪切和粘贴在整个程序中快速传播此宏。当您不再需要它时,只需将其定义为 no-op 即可将其关闭。
- Method #2:(It doesn't say anything about line numbers, but I do on method 4)
- 方法#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.
然而,获得堆栈回溯的更好方法是使用 glibc 提供的一些特定支持函数。关键是 backtrace(),它将堆栈帧从调用点导航到程序的开头,并提供返回地址数组。然后,通过使用 nm 命令查看目标文件,您可以将每个地址映射到代码中特定函数的主体。或者,您可以使用更简单的方法——使用 backtrace_symbols()。该函数将 backtrace() 返回的返回地址列表转换为字符串列表,每个字符串都包含函数内的函数名称偏移量和返回地址。字符串列表是从您的堆空间分配的(就像您调用 malloc() 一样),因此您应该在完成后立即 free() 它。
I encourage you to read it since the page has source codeexamples. In order to convert an address to a function name you must compile your application with the -rdynamicoption.
我鼓励您阅读它,因为该页面有源代码示例。为了将地址转换为函数名称,您必须使用-rdynamic选项编译您的应用程序。
- Method #3:(A better way of doing method 2)
- 方法#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
这种技术的一个更有用的应用是在信号处理程序中放置一个堆栈回溯,并让后者捕获您的程序可以接收的所有“坏”信号(SIGSEGV、SIGBUS、SIGILL、SIGFPE 等)。这样,如果您的程序不幸崩溃并且您没有使用调试器运行它,您可以获得堆栈跟踪并知道故障发生的位置。此技术还可用于了解程序停止响应时的循环位置
An implementation of this technique is available here.
此处提供了此技术的实现。
- Method #4:
- 方法#4:
A small improvement I've done on method #3 to print line numbers. This could be copied to work on method #2 also.
我在方法#3 上做了一个小的改进来打印行号。这也可以复制到方法 #2 上。
Basically, I followed a tipthat uses addr2lineto
基本上,我跟着一个尖端,它使用addr2line到
convert addresses into file names and line numbers.
将地址转换为文件名和行号。
The source code below prints line numbers for all local functions. If a function from another library is called, you might see a couple of ??:0instead of file names.
下面的源代码打印所有本地函数的行号。如果调用另一个库中的函数,您可能会看到几个??: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());
}
This code should be compiled as: gcc sighandler.c -o sighandler -rdynamic
此代码应编译为: gcc sighandler.c -o sighandler -rdynamic
The program outputs:
程序输出:
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
Update 2012/04/28for recent linux kernel versions, the above sigactionsignature is obsolete. Also I improved it a bit by grabbing the executable name from this answer. Here is an up to date version:
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);
}
and initialise like this:
并像这样初始化:
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());
}
回答by Jim Blandy
If you're using Linux, the standard C library includes a function called backtrace, which populates an array with frames' return addresses, and another function called backtrace_symbols, which will take the addresses from backtraceand look up the corresponding function names. These are documented in the GNU C Library manual.
如果您使用的是 Linux,则标准 C 库包含一个名为 的函数backtrace,该函数使用帧的返回地址填充数组,以及另一个名为 的函数backtrace_symbols,该函数将从中获取地址backtrace并查找相应的函数名称。这些都记录在GNU C 库手册中。
Those won't show argument values, source lines, and the like, and they only apply to the calling thread. However, they should be a lot faster (and perhaps less flaky) than running GDB that way, so they have their place.
这些不会显示参数值、源代码行等,它们只适用于调用线程。但是,它们应该比以这种方式运行 GDB 快得多(并且可能不那么不稳定),因此它们占有一席之地。
回答by karlphillip
nobarposted a fantastic answer. In short;
So you want a stand-alone function that prints a stack tracewith all of the features that gdbstack traces have and that doesn't terminate your application. The answer is to automate the launch of gdb in a non-interactive mode to perform just the tasks that you want.
This is done by executing gdb in a child process, using fork(), and scripting it to display a stack-trace while your application waits for it to complete. This can be performed without the use of a core-dump and without aborting the application.
因此,您需要一个独立的函数来打印具有gdb堆栈跟踪具有的所有功能的堆栈跟踪,并且不会终止您的应用程序。答案是在非交互模式下自动启动 gdb 以执行您想要的任务。
这是通过在子进程中执行 gdb,使用 fork() 并编写脚本以在您的应用程序等待它完成时显示堆栈跟踪来完成的。这可以在不使用核心转储和不中止应用程序的情况下执行。
I believe that this is what you are looking for, @Vi
我相信这就是你要找的,@Vi
回答by Motti
Isn't abort()simpler?
不是abort()更简单吗?
That way if it happens in the field the customer can send you the core file (I don't know many users who are involved enough in myapplication to want me to force them to debug it).
这样,如果它发生在现场,客户可以将核心文件发送给您(我不知道有多少用户参与到我的应用程序中以希望我强迫他们对其进行调试)。

