Linux 信号捕获堆栈信息

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>     // 
#include <signal.h>     // sigaction
#include <execinfo.h>   // backtrace

static void on_sig_segv(int signo, siginfo_t* info, void* ctx)
{
    void* buf[256] = { 0 };
    int size, i;
    char** strings;
    char line[1024];
    char format[32] = { 0 };
    pid_t pid = getpid();
    StringBuilder<char> builder;

    // 0.参数校验

    // 1.获取崩溃调用栈
    size = backtrace(buf, 256);
    strings = backtrace_symbols(buf, size);

    // 2.获取时间信息
    format_local_time(format, 32);

    // 3.格式化输出
    // 3.1 输出信号信息
    memset(line, 0, 1024);
    snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @SIGNAL: %d\n", format, pid, signo);
    builder.Append(line);

    // 3.2 输出内存地址
    memset(line, 0, 1024);
    snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @Memory Location: %p\n", format, pid, info->si_addr);
    builder.Append(line);

    // 3.3 输出寄存器信息
    //memset(line, 0, 1024);
    //snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @Memory Location: %p\n", format, pid, context->uc_mcontext.gregs[REG_EFL]);
    //builder.Append(line);

    // 3.4 输出堆栈信息
    for (i = 0; i < size; i++)
    {
        memset(line, 0, 1024);
        snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] %s\n", format, pid, strings[i]);
        builder.Append(line);
    }

    free(strings);
    /*
    设计说明:
        每条信息单独打印,很多堆栈信息都被打印在一起,导致最终信息混乱,不容易观察,因此重新修改打印方式,改成一起输出
    */

    // 4.进程锁
    lockf(STDERR_FILENO, F_LOCK, 0);
    fprintf(stderr, "%s\n", builder.ToString().c_str());
    lockf(STDERR_FILENO, F_ULOCK, 0);

    /*
    设计说明
        使用信号捕捉函数可以捕捉信号,但是捕捉信号之后不会生成coredump文件,某些场景执行还是需要coredump文件帮助分析的
        因此设计如下代码,捕捉信号之后再次发送信号

        这里必须恢复默认信号处理,否则会造成信号处理死循环。

    */

#ifdef TEST_DEBUG

    signal(signo, SIG_DFL);    /* 恢复信号默认处理 */
    raise(signo);              /* 重新发送信号 */
#else
    _exit(1);  // Don't use exit() to avoid be hanged in functions registered with `atexit' and `on_exit'

#endif // TEST_DEBUG

}

static void signal_handler(int signo)
{
    LOGI("[ signal_handler ] pid=%d recv signo=%d", getpid(), signo);
    switch (signo)
    {
    case SIGINT:
    case SIGTERM:
        LOGI("[ signal_handler ] Service Ready to Exit .");
        exit(0);
        break;
    default:
        break;
    }
}

void signal_sigsegv()
{
    /*
    知识补充
        SIGSEGV即 11 信号,一般是内存错误引起的,打印堆栈信息有助于我们分析错误,coredump文件也可以,但是太费磁盘了。
    */
    struct sigaction action;

    sigemptyset(&action.sa_mask);
    action.sa_sigaction = on_sig_segv;
    action.sa_flags = SA_SIGINFO;

    sigaction(SIGABRT, &action, NULL);
    sigaction(SIGBUS, &action, NULL);
    sigaction(SIGSEGV, &action, NULL);

    /*
    知识补充
        SIGPIPE信号是服务必须忽略的信号,对端关闭套接字的场景太多了,必须忽略该信号,不然服务大概率会崩溃。
    */
    signal(SIGPIPE, SIG_IGN);

    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
}


int main(int argc, char* argv[])
{
    return 0;
}

 

备注:

    在多进程场景下打印堆栈信息,最好先用缓冲区将所有的堆栈信息存储起来,然后一起输出。

posted on 2022-06-04 15:01  寒魔影  阅读(319)  评论(0编辑  收藏  举报

导航