首页 资讯 应用 高压 设计 行业 低压 电路图 关于

嵌入式

旗下栏目: PLC 嵌入式 单片机 DCS

linux backtrace()详细使用说明,分析Segmentation fault

嵌入式 | 发布时间:2018-11-21 | 人气: | #评论# | 本文关键字:linux,eCos,函数,linux backtrace,Segmentation fault
摘要:在此之前,开发eCos应用程序时,经常碰到程序挂掉后,串口打印输出一大串让人看不懂的数据。今天才明白,原来这些数据是程序挂掉时的堆栈帧数据(stackframedata)。 通过这些堆栈帧数据可

在此之前,开发eCos应用程序时,经常碰到程序挂掉后,串口打印输出一大串让人看不懂的数据。今天才明白,原来这些数据是程序挂掉时的堆栈帧数据(stack frame data)。通过这些堆栈帧数据可以分析出程序当时的运行状态和定位程序哪里出现了问题。这就是本文要讲的—backtrace()和backtrace_symbols()函数的使用。

backtrace()和backtrace_symbols()函数

backtrace()和backtrace_symbols()函数,包括另一个函数:backtrace_symbols_fd(),它们是GNU对程序调试的扩展支持。

头文件

#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);

函数描述

backtrace()函数,获取函数调用堆栈帧数据,即回溯函数调用列表。数据将放在buffer中。参数size用来指定buffer中可以保存多少个void*元素(表示相应栈帧的地址,一个返回地址)。如果回溯的函数调用大于size,则size个函数调用地址被返回。为了取得全部的函数调用列表,应保证buffer和size足够大。

backtrace_symbols()函数,参数buffer是从backtrace()函数获取的数组指针,size是该数组中的元素个数(backtrace()函数的返回值)。该函数主要功能:将从backtrace()函数获取的地址转为描述这些地址的字符串数组。每个地址的字符串信息包含对应函数的名字、在函数内的十六进制偏移地址、以及实际的返回地址(十六进制)。需注意的是,当前,只有使用elf二进制格式的程序才能获取函数名称和偏移地址,此外,为支持函数名功能,可能需要添加相应的编译链接选项如-rdynamic;否则,只有十六进制的返回地址能被获取。backtrace_symbols()函数返回值是一个字符串指针,是通过malloc函数申请的空间,使用完后,调用者必需把它释放掉。注:如果不能为字符串获取足够的空间,该函数的返回值为NULL。

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

返  回  值

backtrace()函数返回通过buffer返回的地址个数,这个数目不会超过size。如果这个返回值小于size,那么所有的函数调用列表都被保存;如果等于size,那么函数调用列表可能被截断,此时,一些最开始的函数调用没有被返回。

成功时,backtrace_symbols()函数返回一个由malloc分配的数组;失败时,返回NULL。

注意事项

这些函数对函数返回地址如何保存在栈内有一些假设,注意如下:

  • 忽略帧指针(由gcc任何非零优化级别处理了)可能引起这些假设的混乱。

  • 内联函数没有栈帧。

  • Tail-call(尾调用)优化会导致栈帧被其它调用覆盖。

  • 为支持函数名功能,可能需要添加相应的编译链接选项如-rdynamic;否则,只有十六进制的返回地址能被获取。

  • “static”函数名是不会导出的,也不会出现在函数调用列表里,即使指定了-rdynamic链接选项。

程序用例

001/**
002 * \brief backtrace测试程序
003 *
004 * 编译指令:gcc -g -rdynamic backtrace.c -o backtrace
005 */
006
007#include <stdio.h>
008#include <stdlib.h>
009#include <unistd.h>
010#include <signal.h>   /* for signal */
011#include <execinfo.h> /* for backtrace() */
012
013#define SIZE    100
014
015void dump(void)
016{
017    int j, nptrs;
018    void *buffer[100];
019    char **strings;
020
021    nptrs = backtrace(buffer, SIZE);
022    printf("backtrace() returned %d addresses\n", nptrs);
023
024    strings = backtrace_symbols(buffer, nptrs);
025    if (strings == NULL) {
026        perror("backtrace_symbols");
027        exit(EXIT_FAILURE);
028    }
029
030    for (j = 0; j < nptrs; j++)
031        printf("  [%02d] %s\n", j, strings[j]);
032
033    free(strings);
034}
035
036void handler(int signo)
037{
038    printf("\n=========>>>catch signal %d (%s) <<<=========\n",
039                signo, (char *)strsignal(signo));
040    printf("Dump stack start...\n");
041    dump();
042    printf("Dump stack end...\n");
043
044    /* 恢复并发送信号 */
045    signal(signo, SIG_DFL);
046    raise(signo);
047}
048
049void printSigno(int signo)
050{
051    static int i = 0;
052
053    printf("\n=========>>>catch signal %d (%s) i = %d <<<=========\n",
054                signo, (char *)strsignal(signo), i++);
055    printf("Dump stack start...\n");
056    dump();
057    printf("Dump stack end...\n");
058}
059
060void myfunc3(void)
061{
062    /* 为SIGINT安装信号处理函数,通过Ctrl + C发出该信号 */
063    signal(SIGINT, handler);
064    signal(SIGSEGV, handler);   /* 为安装SIGSEGV信号处理函数 */
065    signal(SIGUSR1, printSigno);
066
067    /* 打印当前函数调用栈 */
068    printf("Current function calls list is: \n");
069    printf("----------------------------------\n");
070    dump();
071    printf("----------------------------------\n\n");
072
073    while (1) {
074        ;/* 在这里通过Ctrl + C发出一个SIGINT信号来结束程序的运行 */
075    }
076}
077
078/**
079 * \brief
080 * 使用static修饰函数,表明不导出这个符号。
081 * 即使用-rdynamic选项,看到的只能是个地址。
082 */
083static void myfunc2(void)
084{
085    myfunc3();
086}
087
088void myfunc(int ncalls)
089{
090    if (ncalls > 1)
091        myfunc(ncalls -1);
092    else
093        myfunc2();
094}
095
096int main(int argc, char *argv[])
097{
098    if (argc != 2) {
099        fprintf(stderr, "%s num-calls\n", argv[0]);
100        exit(EXIT_FAILURE);
101    }
102
103    myfunc(atoi(argv[1]));
104    exit(EXIT_SUCCESS);
105}
106
107// ----------------------------------------------------------------------------
108// End of backtrace.c

编译指令:gcc -g -rdynamic backtrace.c -o backtrace

运行结果:

[email protected]:backtrace$ ./backtrace 3
Current function calls list is:
———————————-
backtrace() returned 9 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(myfunc3+0x4b) [0x8048a88]
[02] ./backtrace [0x8048aa1]
[03] ./backtrace(myfunc+0x21) [0x8048ac4]
[04] ./backtrace(myfunc+0x1a) [0x8048abd]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(main+0x52) [0x8048b18]
[07] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[08] ./backtrace [0x8048891]
———————————-

^C (按钮Ctrl + C键)
=========>>>catch signal 2 (Interrupt) <<<=========
Dump stack start…
backtrace() returned 10 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(handler+0x3c) [0x8048a11]
[02] [0x1f4400]
[03] ./backtrace [0x8048aa1]
[04] ./backtrace(myfunc+0x21) [0x8048ac4]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(myfunc+0x1a) [0x8048abd]
[07] ./backtrace(main+0x52) [0x8048b18]
[08] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[09] ./backtrace [0x8048891]
Dump stack end…

[email protected]:backtrace$
[email protected]:backtrace$

分析Segmentation fault

对于Segmentation fault错误,有多种分析定位方法,如利用linux产生的core文件、使用gdb进行分析定位。但对于一直运行的程序,特别是大型程序,当意外出现Segmentation fault错误时,其分析定位,则比较棘手。

我们知道,产生Segmentation fault错误时,一般会产生一个SIGSEGV信号。利用这个机制,上述问题传统的做法是,在程序中安装SIGSEGV信号,然后在该信号处理函数中,回溯函数调用列表,从而分析定位错误,一劳永逸。

上面的程序用例中,已安装了SIGSEGV信号。

责任编辑:linux backtrace
首页 | 电气资讯 | 应用技术 | 高压电器 | 电气设计 | 行业应用 | 低压电器 | 电路图 | 关于我们 | 版权声明

Copyright 2017-2018 电气自动化网 版权所有 辽ICP备17010593号-1

电脑版 | 移动版 原创声明:本站大部分内容为原创,转载请注明电气自动化网转载;部分内容来源网络,如侵犯您的权益请发送邮件到[email protected]联系我们删除。