您在我的另一个答案(现已删除)中提到您还想查看行号。从应用程序内部调用 gdb 时,我不确定如何执行此操作。
但我将与您分享几种打印简单堆栈跟踪的方法,其中包含函数名称及其各自的行号不使用 gdb。其中大部分来自Linux Journal的一篇非常好的文章:
第一种方法是传播它
按顺序打印和记录消息
确定执行路径。在一个
复杂的程序,这个选项可以
变得繁琐乏味,即使,
在一些 GCC 特定的帮助下
宏,它可以简化一点。
例如,考虑一个调试宏
如:
#define TRACE_MSG fprintf(stderr, __FUNCTION__ \
"() [%s:%d] here I am\n", \
__FILE__, __LINE__)
您可以快速传播此宏
在整个程序中通过剪切和
粘贴它。当你不需要它时
不再,只需通过以下方式将其关闭
将其定义为无操作。
获取堆栈回溯的更好方法,
然而,是使用一些
提供的特定支持功能
glibc。关键是 backtrace(),
从中导航堆栈帧
调用点到开头
该程序并提供了一个数组
返回地址。然后你可以映射
每个地址到一个的主体
代码中的特定功能
查看目标文件
nm 命令。或者,你可以这样做
更简单的方法——使用 backtrace_symbols()。
这个函数转换一个列表
返回地址,由
backtrace(),进入字符串列表,
每个都包含函数名
函数内的偏移量和
退货地址。字符串列表是
从你的堆空间分配(好像
你调用了 malloc()),所以你应该
完成后立即释放()
它。
我鼓励您阅读它,因为该页面有 source code 示例。为了将地址转换为函数名,您必须使用 -rdynamic 选项编译您的应用程序。
一个更有用的应用程序
这种技术正在堆放
回溯信号处理程序和
让后者抓住所有的“坏”
您的程序可以接收的信号
(SIGSEGV、SIGBUS、SIGILL、SIGFPE 和
类似)。这样,如果你的程序
不幸的是崩溃了,你没有
用调试器运行它,你可以
获取堆栈跟踪并知道在哪里
发生故障。这种技术也
可用于了解您的
程序正在循环,以防它停止
回应
here 提供了此技术的实现。
我对方法 #3 进行了一个小改进,以打印行号。这也可以复制到方法 #2 上。
基本上,我followed a tip 使用 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 签名已过时。我还通过从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());
}