linux下追踪函数调用堆栈

Linux下追踪函数调用堆栈


一般察看函数运行时堆栈的方法是使用 GDB之类的外部调试器,有些时候为了分析程序的 BUG,主要针对长时间运行程序的分析,在程序出错时打印出函数的调用堆栈是非常有用的。

在头文件*“execinfo.h”*中声明了三个函数用于获取当前线程的函数调用堆栈

 #include <execinfo.h>

int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

0x01 backtrace函数

Function: int backtrace(void**buffer, int size)
该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。
参数buffer:是一个指针列表,保存获取的信息
参数size: 用来指buffer中可以保存多少个void* 元素。
函数返回值是实际获取的指针个数,最大不超过size大小
在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。
注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰, 另外内联函数没有堆栈框架; 删除框架指针也会使无法正确解析堆栈内容
backtrace() returns the number of addresses returned in buffer, which is not greater than size.  If the return value is less than size, then the full backtrace was stored;  if  it
is equal to size, then it may have been truncated, in which case the addresses of the oldest stack frames are not returned.

0x02 backtrace_symbols函数

Function: char ** backtrace_symbols (void *const *buffer, int size)
backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组
参数buffer:是从backtrace函数获取的数组指针,
size是该数组中的元素个数(backtrace的返回值)    
函数返回值是一个指向字符串数组的指针,它的大小同buffer相同
On success, backtrace_symbols() returns a pointer to the array malloc(3)ed by the call; on error, NULL is returned.

每个字符串包含了一个相对于buffer中对应元素的可打印信息。它包括函数名,函数的偏移地址,和实际的返回地址现在,只有使用ELF二进制格式的程序和库中才能获取函数名称和偏移地址
在其他系统,只有十六进制的返回地址能被获取。另外,你可能需要传递相应的标志给链接器,以能支持函数名功能(比如,在使用GNU ld的系统中,你需要传递(-rdynamic))。该函数的返回值是通过malloc函数申请的空间,因此调用这必须使用free函数来释放指针
注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL

备注:gcc -rdynamic参数

选项 -rdynamic 用来通知链接器将所有符号添加到动态符号表中
(目的是能够通过使用 dlopen 来实现向后跟踪)
-rdynamic
Pass the flag ‘-export-dynamic’ to the ELF linker, on targets that support
it. This instructs the linker to add all symbols, not only used ones, to the
dynamic symbol table. This option is needed for some uses of dlopen or to
allow obtaining backtraces from within a program.

0x03 backtrace_symbols_fd函数

Function:void backtrace_symbols_fd (void *const *buffer, int size, int fd) 
    
backtrace_symbols_fd() takes the same buffer and size arguments as backtrace_symbols(), but instead of returning an array of strings to the caller, it writes the strings, one per line, to the file descriptor fd. 
backtrace_symbols_fd() does not call malloc(3), and so can be employed in situations where the latter function might fail.

backtrace_symbols_fd与backtrace_symbols函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行。它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。

0x04 使用案例

下面是glibc中的实例:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void myfunc3(void)
{
    int j, nptrs;
#define SIZE 100
    void *buffer[100];
    char **strings;

    nptrs = backtrace(buffer, SIZE);
    printf("backtrace() returned %d addresses\n", nptrs);

    /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
        would produce similar output to the following: */
    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL) {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (j = 0; j < nptrs; j++)
        printf("%s\n", strings[j]);

    free(strings);
}

/* "static" means don't export the symbol... */
static void myfunc2(void)
{
    myfunc3();
}

void myfunc(int ncalls)
{
    if (ncalls > 1)
        myfunc(ncalls - 1);
    else
        myfunc2();
}

int main(int argc, char *argv[])
{
    if (argc != 2) 
    {
        fprintf(stderr, "%s num-calls\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    myfunc(atoi(argv[1]));
    exit(EXIT_SUCCESS);
}

运行结果:

[root@python zbb]# gcc trace.c -rdynamic -o trace
[root@python zbb]# ./trace 3
backtrace() returned 8 addresses
./trace(myfunc3+0x1f) [0x4009cc]
./trace() [0x400a61]
./trace(myfunc+0x25) [0x400a88]
./trace(myfunc+0x1e) [0x400a81]
./trace(myfunc+0x1e) [0x400a81]
./trace(main+0x59) [0x400ae3]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7fde34fe63d5]
./trace() [0x4008e9]
[root@python zbb]# ./trace 2
backtrace() returned 7 addresses
./trace(myfunc3+0x1f) [0x4009cc]
./trace() [0x400a61]
./trace(myfunc+0x25) [0x400a88]
./trace(myfunc+0x1e) [0x400a81]
./trace(main+0x59) [0x400ae3]

我们还可以利用backtrace来定位程序段错误位置。

通常情况下,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function); 函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <execinfo.h>
#include <signal.h>
 
void Handler(int signo)
{
	void *buffer[1024] = {0};
	size_t size;
	char **strings = NULL;
	size_t i = 0;
 
	size = backtrace(buffer, 1024);
	fprintf(stdout, "backtrace() returned %d addresses\n", size);
	strings = backtrace_symbols(buffer, size);
	if (strings == NULL)
	{
		perror("backtrace_symbols.");
		exit(EXIT_FAILURE);
	}
	
	for (i = 0; i < size; i++)
	{
		fprintf(stdout, "%s\n", strings[i]);
	}
	free(strings);
	strings = NULL;
	exit(0);
}
 
void myfunc3()
{
	*((volatile char *)0x0) = 0x20;
}
 
static void myfunc2(void)
{
    myfunc3();
}

void myfunc(int ncalls)
{
    if (ncalls > 1)
        myfunc(ncalls - 1);
    else
        myfunc2();
}

int main(int argc, char *argv[])
{
    if (argc != 2) 
    {
        fprintf(stderr, "%s num-calls\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if (signal(SIGSEGV, Handler) == SIG_ERR)
		perror("can't catch SIGSEGV");
    myfunc(atoi(argv[1]));
    exit(EXIT_SUCCESS);
}
[root@python zbb]# gcc -g testTrace.c -rdynamic -o testTrace
[root@python zbb]# ./testTrace 2
backtrace() returned 9 addresses
./testTrace(Handler+0x4f) [0x400a5c]
/lib64/libc.so.6(+0x36280) [0x7fbe462f4280]
./testTrace(myfunc3+0x9) [0x400b24]
./testTrace() [0x400b37]
./testTrace(myfunc+0x25) [0x400b5e]
./testTrace(myfunc+0x1e) [0x400b57]
./testTrace(main+0x78) [0x400bd8]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7fbe462e03d5]
./testTrace() [0x400949]
[root@python zbb]# addr2line 0x400a5c -e testTrace -f
Handler
/home/zbb/testTrace.c:14
[root@python zbb]# addr2line 0x400b24 -e testTrace -f
myfunc3
/home/zbb/testTrace.c:34

Reference:
参考链接1
参考链接2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Erice_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值