【debug】程序调用栈记录profile-backtrace和backtrace|分析瓶颈|分析bug所在

目录

简介

backtrace

profile-backtrace

区别

示例

profile-backtrace

backtrace


简介

backtrace

backtrace是一个用于生成函数调用栈的工具,在程序崩溃或者出现异常时,可以通过backtrace来获取函数调用栈信息,这些信息可以帮助我们了解到程序的执行流程,定位问题发生的位置。

backtrace的使用一般需要以下步骤:

扫描二维码关注公众号,回复: 15452533 查看本文章

1.在需要使用backtrace的代码中引入`#include <execinfo.h>`头文件。

2.使用backtrace函数获取函数调用栈信息:`void backtrace(void **buffer, int size)`

- **buffer**: 用于存储函数调用栈信息的buffer,类型为void*的数组。
- **size**: 表示buffer中可存储的指针个数。

3.使用backtrace_symbols函数将函数指针转换为函数名称:`char **backtrace_symbols(void *const *buffer, int size)`

- **buffer**: backtrace函数获取到的函数指针数组。
- **size**: backtrace函数获取到的函数指针数组的长度。

这样,我们就可以利用backtrace函数获取函数调用栈信息,然后使用backtrace_symbols函数将指针转化为函数名称,从而获取到函数调用栈。在获取函数调用栈信息的同时,我们还可以将函数调用栈信息输出到日志中,方便我们查找和定位程序崩溃或者出现异常的位置。

profile-backtrace

`profile-backtrace`是Linux系统中一个用于性能分析的工具,它可以帮助开发者找出程序中消耗CPU时间较多的函数,从而定位程序性能问题。

`profile-backtrace`的工作原理是:通过对程序进行采样,在采样的间隔时间内获取程序堆栈信息,生成函数调用栈,然后对这些调用栈进行统计和分析。最终输出调用次数、调用时间等统计信息,从而找出程序中会占用大量时间的函数,帮助我们进行优化。

在使用`profile-backtrace`进行性能分析时,一般需要以下步骤:

1.在编译时添加`-g`选项,生成调试信息,方便获取函数名称。

2.编写性能测试程序,并在其中需要进行性能分析的地方插入"标记点"。

3.编译性能测试程序。

4.运行`profile-backtrace`进行性能分析,如:`perf record -e cycles ./test`

5.对采样数据进行处理和分析,如:`perf report`

利用`profile-backtrace`可以在系统级别对程序进行性能分析,快速地找出程序中性能瓶颈所在的函数,并使用分析结果进行性能优化。

区别

profile-backtrace 和 backtrace的区别

`profile-backtrace`和`backtrace`都是用于获取函数调用栈信息的工具,但它们的设计目的和使用场景有所不同:

1.设计目的:`profile-backtrace`是用于性能分析的工具,可以帮助开发者找出程序中消耗CPU时间较多的函数,从而定位性能问题;`backtrace`是用于调试的工具,可以帮助开发者在程序崩溃或出现异常时,获取函数调用栈信息,从而定位问题所在。

2.使用场景:`profile-backtrace`一般用于优化程序运行性能,对于性能开销较小的程序可能效果不明显;`backtrace`一般用于调试程序,对于希望获取堆栈跟踪信息的开发人员非常有用。

3.实现方式:`profile-backtrace`是通过对程序进行采样,在采样的间隔时间内获取程序堆栈信息,生成函数调用栈,然后对这些调用栈进行统计和分析;`backtrace`是通过在程序崩溃或出现异常时,获取函数调用栈信息,从而定位问题所在。

因此,`profile-backtrace`和`backtrace`是两个不同的工具,虽然都是获取函数调用栈信息,但是适用的场景和目的不同。

`profile-backtrace`和`backtrace`的使用方法略有不同,下面分别介绍两个工具的使用方法。

1. `profile-backtrace`的使用方法

(1) 编译时添加`-g`选项,以生成调试信息:

```
gcc -o program -g program.c
```

(2) 运行命令行工具`perf`进行性能分析:

```
perf record -g -a -e cycles ./program
```

(3) 处理并查看采样数据:

```
perf report
```

其中,`-g`选项用于生成函数调用栈信息;`-a`选项用于记录所有的进程或线程;`-e cycles`选项用于指定采集的事件类型,此处为CPU周期计数器。

2. `backtrace`的使用方法

(1) 引入头文件`<execinfo.h>`。 

```
#include <execinfo.h>
```

(2) 添加函数`backtrace()`,以获取函数调用栈信息:

```
void *buffer[10];
int nptrs = backtrace(buffer, 10);
```

(3) 使用`backtrace_symbols()`函数将函数指针转换为函数名称:

```
char **strings;
strings = backtrace_symbols(buffer, nptrs);
```

(4) 打印函数调用栈信息:

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

在执行完上述步骤后,即可输出函数调用栈信息,并帮助我们定位程序崩溃或异常的来源。

需要注意的是,`backtrace`使用时需要在编译时开启调试信息,因此对于发布版程序,不能使用`backtrace`进行调试。

示例

profile-backtrace

以下是一个简单的`profile-backtrace`使用实例,演示如何使用`profile-backtrace`进行性能分析,找出程序中消耗CPU时间较多的函数。

假设有以下测试程序test.c:

```c
#include <stdio.h>

int func1()
{
    int a = 1, b = 2, c = 3, d = 4, e = 5;
    return a + b + c + d + e;
}

int func2()
{
    int i, sum = 0;
    for (i = 0; i < 10000; i++)
        sum += i;
    return sum;
}

int main()
{
    int i;
    for (i = 0; i < 100000; i++)
        func1();
    for (i = 0; i < 100000; i++)
        func2();
    return 0;
}
```

该程序包括两个函数`func1()`和`func2()`,分别计算一些数值的和,在`main()`函数中依次调用了100000次这两个函数。

现在我们使用`profile-backtrace`对该程序进行性能分析:

1.编译并链接程序,并添加`-g`选项:

```
gcc -g -o test test.c
```

2.运行`profile-backtrace`进行性能分析:

```
perf record -g -a -e cycles ./test
```

其中,`-g`选项用于生成函数调用栈信息;`-a`选项用于记录所有的进程或线程;`-e cycles`选项用于指定采集的事件类型,此处为CPU周期计数器。

3.对采样数据进行处理和分析:

```
perf report
```

运行上述命令后,会打开`perf report`工具,该工具会生成分析结果,并列出程序中各函数的运行次数、消耗CPU时间等信息。在本例中,`func2()`运行次数较多,且消耗CPU时间较长,是程序中耗时的主要函数。

这样,我们就可以使用`profile-backtrace`帮助我们快速定位程序中的性能问题,进而进行优化。

backtrace

以下是一个简单的backtrace使用实例,演示如何使用backtrace获取函数调用栈信息。

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

#define SIZE 10

void func1(void);
void func2(void);
void func3(void);

int main()
{
    func1();
    return 0;
}

void func1()
{
    void *buffer[SIZE];
    int nptrs = backtrace(buffer, SIZE);      //获取函数调用栈信息
    char **strings;

    printf("func1() function call stack:\n");

    strings = backtrace_symbols(buffer, nptrs); //将函数指针转换为函数名称

    if (strings == NULL)
    {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

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

    free(strings);
    func2();
}

void func2()
{
    func3();
}

void func3()
{
    void *buffer[SIZE];
    int nptrs = backtrace(buffer, SIZE);
    char **strings;

    printf("func3() function call stack:\n");

    strings = backtrace_symbols(buffer, nptrs);

    if (strings == NULL)
    {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

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

    free(strings);
}


该程序定义了三个函数func1()、func2()和func3(),main()函数中调用了func1()。每个函数中都调用了backtrace()函数,获取函数调用栈信息并输出。

运行上述程序,输出结果如下:

func1() function call stack:
./a.out(func3+0x15) [0x4006df]
./a.out(func2+0x9) [0x4006f6]
./a.out(func1+0x1d) [0x40070d]
./a.out(main+0xb) [0x40072c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0x7f37cb87b291]
./a.out() [0x4005da]
func3() function call stack:
./a.out(func3+0x25) [0x4006e9]
./a.out(func2+0x9) [0x4006f6]
./a.out(func1+0x23) [0x400713]
./a.out(main+0xb) [0x40072c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0x7f37cb87b291]
./a.out() [0x4005da]

上述输出便是程序中的函数调用栈信息,可以帮助我们快速定位程序崩溃或异常的原因。需要注意的是,backtrace使用时需要在编译时开启调试信息,不同编译器的开启方式略有不同。


 

猜你喜欢

转载自blog.csdn.net/bandaoyu/article/details/131238385