c语言程序输出调试信息

当大型C语言程序遇到致命错误,如SIGSEGV,无法生成coredump时,可以通过两种方式输出诊断信息:使用backtrace函数和借助gdb。backtrace提供基本堆栈信息,而gdb则更强大,通过fork进程、attach主进程并执行调试命令来输出详细信息。本文介绍了如何在C程序中实现这一功能。
摘要由CSDN通过智能技术生成

随着服务器的内存逐渐增大,从16GB、32GB逐步拓展到64GB、128GB甚至256GB或更多。大型程序在遇到一些致命错误(比如SIGSEGV段错误、stack overflow)时,受限于磁盘空间大小、IO速度、恢复时间等限制,往往不具备条件把内存映像(coredump)转储到磁盘上,这为后续排查出错原因带来了很大困难。这时候就需要程序在遇到致命错误时,能够及时输出诊断信息。一些语言的运行时,比如java、golang等内建了错误堆栈输出的功能。那么c语言程序如何实现错误堆栈输出的功能呢?

请移步到小宇的博客获取更多技术文章

先睹为快

启动程序,然后发送SIGSEGV信号来模拟程序遇到非法内存访问错误的情形。

$ ./main &
[new process 2018]
$ kill -11 2018
#0  0x00007f30d7487680 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1  0x0000000000400b6b in system_ex ()
#2  0x0000000000400c87 in run ()
#3  0x0000000000400dcb in run_gdb ()
#4  0x0000000000400f37 in GetCrashInfo ()
#5  <signal handler called>
#6  0x0000000000400f9a in main ()

我们看到了函数的堆栈,进一步还可以打出诸如本地变量,局部变量,寄存器等等信息。

基本思路

运行中的程序输出堆栈的方法有2种。
第一种:借助backtrace函数。
第二种:借助gdb。

backtrace函数

backtrace用法比较简单,但是功能也比较有限,只能输出基本的堆栈。
比如:

$ cc -rdynamic prog.c -o prog
$ ./prog 3
backtrace() returned 8 addresses
./prog(myfunc3+0x5c) [0x80487f0]
./prog [0x8048871]
./prog(myfunc+0x21) [0x8048894]
./prog(myfunc+0x1a) [0x804888d]
./prog(myfunc+0x1a) [0x804888d]
./prog(main+0x65) [0x80488fb]
/lib/libc.so.6(__libc_start_main+0xdc) [0xb7e38f9c]
./prog [0x8048711]
#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 void   /* "static" means don't export the symbol... */
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);
}

借助gdb

gdb是专为调试诞生的工具,更为强大。但是如何在程序中调用gdb?系统已经提供了system调用,但是该调用不能把程序的信息输出给调用者。需要我们自己写一个system调用,能够把子进程输出的信息发送给主进程。
基本思路是fork一个进程,在子进程execv运行gdb,attach到主进程,运行一系列调试命令来输出调试信息。把子进程输出的信息发送给主进程的方法,涉及到了多线程通信的问题。一个比较简单的解决方法是在主进程和子进程之间建立管道,在子进程里面把标准输出重定向到管道的写入端,主进程从管道读取信息。为此专门写了一个system_ex函数来完成这个工作。

/*
 * system_ex -- system with something extra
 *
 * like original system but can save the
 * cmd output value to buf. the memory
 * used by buf is allocated by system_ex
 * using malloc and realloc, make sure
 * you free the buf memory after the
 * function returns.
 *
 *                       yshen@2016
 * Argument:
 *       cmd: the command as a null terminated
 *       string to execute
 *       buf: the buffer to save the stdout
 *       printed by the command
 * Return:
 *       on sucess system_ex returns the
 *       command exit code
 *       on
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值