当前位置: 首页 > article >正文

《深入理解Linux:高效崩溃分析与实时栈回溯技巧》

【backtrace导读】Linux系统中,backtrace是一种在程序崩溃或异常时,捕获函数调用堆栈的调试工具,帮助开发者快速定位问题根源。假如我们在外网采集到的munidump不可用,我们就没法获取到正确的堆栈信息了。也许backtrace可以帮助到您。

    backtrace非常依赖信号处理机制,我们需要在程序中注册好指定的信号,比如:常见的SIGSEGV、SIGILL、SIGFPE等,在响应信号的回调函数中获取到用户空间的上下文信息。backtrace在Linux上主要有三个函数。

#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是栈帧回溯的深度,必须确保数组的大小和size都要大于当前栈帧的层数,方可回溯完整的栈帧。

   backtrace_symbols: 将backtrace获取到的buffer的内容转换成字符串通过返回值返回回来,返回值是个char**二级指针,也是指向了一个指针数组,数组中每个元素是一个地址,指向一块内存区域,存储转换后的符号信息。

   backtrace_symbols_fd:该函数同第二个,但是直接把转换后的符号信息写到文件中,建议使用,fd就是指向某个文件的文件描述符,当然你也可以把fd定义成1,直接以标准输出的方式打印出来。

    tips:  backtrace尽量不要使用mallloc,或使用线程非安全的函数。

需要收集的信息

    导致崩溃的信号、信号发生的原因、崩溃的地址、 寄存器信息(上下文信息)、栈回溯的栈帧。

    Linux信号处理框架直接提供到用户的只有信号,需要我们手动解析信号发生的原因,地址和寄存器信息。

崩溃的原因&地址

      信号处理函数提供的信息中,第二个参数siginfo_t,记录了为什么发生崩溃,崩溃发生的地址等信息,但比较麻烦的一点是,这个参数没有具体的系统api可以操作,靠我们翻源码来分析,要用到的两个系统头文件是:

/usr/include/bits/siginfo-consts.h/usr/include/bits/types/siginfo_t.h

      上述两个头文件详细的介绍了哪些成员变量获取哪些内容,我们手动发送signal 11(segment fault),它也有详细的记录。例如:其中的si_code成员变量就是发生崩溃的原因,大于0是kernel发送给用户态的信号,此时需要记录pid和uid就好,崩溃的地址不存在就会输出(nil)。

寄存器

     我们需要从ucontext中获取寄存器的信息,该功能对于崩溃现场是一个非常非常有用的。但需要注意,这个功能我们要启用GUN的特性才可使用。需要在整个源码的开头加入以下宏定义。    

#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#include <ucontext.h>

backtrace栈回溯演练

    我们在Linux平台下,可以按照如下方式写一个小程序backtrace.c,验证backtrace的能力。

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <signal.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ucontext.h>
#include <sys/ucontext.h>

#define BACKTRACE_SIZE 100  // 栈回溯深度
#define DPRINTF(fd, format, ...) \
	if (fd > STDERR_FILENO) \
		dprintf(fd, format, ##__VA_ARGS__); \
		dprintf(STDERR_FILENO, format, ##__VA_ARGS__)

// 信号处理程序
void signal_handler(int sig, siginfo_t *info, void *ucontext) {
    void *buffer[BACKTRACE_SIZE];
    int nptrs = BACKTRACE_SIZE;
	FILE *log_file = fopen("crash.log", "w");
	int fd = fileno(log_file);
	if (fd < 0) {
		perror("open log file");
		fd = STDERR_FILENO;
        }
    // 打印信号信息
    DPRINTF(fd, "========== crash report ========\n");
    DPRINTF(fd, "Error: signal %d:\n", sig);
    // 打印导致崩溃的原因地址
    DPRINTF(fd, "Fault address: %p\n", info->si_addr);
    DPRINTF(fd, "Fault code: %d\n", info->si_code);
    DPRINTF(fd, "========== register info ========\n");
    DPRINTF(fd, "register\thex value\t\tdec value\n");
    // 打印寄存器信息
    ucontext_t *uc = (ucontext_t *)ucontext;
#if defined(__x86_64__)
    DPRINTF(fd, "RIP:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RIP], (unsigned long long)uc->uc_mcontext.gregs[REG_RIP]);
    DPRINTF(fd, "RSP:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RSP], (unsigned long long)uc->uc_mcontext.gregs[REG_RSP]);
    DPRINTF(fd, "RBP:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RBP], (unsigned long long)uc->uc_mcontext.gregs[REG_RBP]);
    DPRINTF(fd, "RAX:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RAX], (unsigned long long)uc->uc_mcontext.gregs[REG_RAX]);
    DPRINTF(fd, "RBX:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RBX], (unsigned long long)uc->uc_mcontext.gregs[REG_RBX]);
 DPRINTF(fd, "RCX:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RCX], (unsigned long long)uc->uc_mcontext.gregs[REG_RCX]);
    DPRINTF(fd, "RDX:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RDX], (unsigned long long)uc->uc_mcontext.gregs[REG_RDX]);
    DPRINTF(fd, "RSI:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RSI], (unsigned long long)uc->uc_mcontext.gregs[REG_RSI]);
    DPRINTF(fd, "RDI:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_RDI], (unsigned long long)uc->uc_mcontext.gregs[REG_RDI]);
    DPRINTF(fd, "R8:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R8], (unsigned long long)uc->uc_mcontext.gregs[REG_R8]);
    DPRINTF(fd, "R9:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R9], (unsigned long long)uc->uc_mcontext.gregs[REG_R9]);
    DPRINTF(fd, "R10:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R10], (unsigned long long)uc->uc_mcontext.gregs[REG_R10]);
    DPRINTF(fd, "R11:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R11], (unsigned long long)uc->uc_mcontext.gregs[REG_R11]);
    DPRINTF(fd, "R12:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R12], (unsigned long long)uc->uc_mcontext.gregs[REG_R12]);
    DPRINTF(fd, "R13:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R13], (unsigned long long)uc->uc_mcontext.gregs[REG_R13]);
    DPRINTF(fd, "R14:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R14], (unsigned long long)uc->uc_mcontext.gregs[REG_R14]);
    DPRINTF(fd, "R15:\t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.gregs[REG_R15], (unsigned long long)uc->uc_mcontext.gregs[REG_R15]);
#elif defined(__aarch64__)
    DPRINTF(fd, "PC: \t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.pc, (unsigned long long)uc->uc_mcontext.pc);
    DPRINTF(fd, "SP: \t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.sp, (unsigned long long)uc->uc_mcontext.sp);
    DPRINTF(fd, "FP: \t%16llx\t%20lld\n", (unsigned long long)uc->uc_mcontext.regs[29], (unsigned long long)uc->uc_mcontext.regs[29]);
    for (int i = 0; i < 31; i++) {
 for (int i = 0; i < 31; i++) {
        DPRINTF(fd, "X%d: \t%16llx\t%20lld\n", i, (unsigned long long)uc->uc_mcontext.regs[i], (unsigned long long)uc->uc_mcontext.regs[i]);
    }
#endif
    
    DPRINTF(fd, "\n========== backtrace info ========\n");
    nptrs = backtrace(buffer, BACKTRACE_SIZE);
    if (nptrs == 0) {
	DPRINTF(fd, "backtrace failed\n");
    }
    // 打印堆栈跟踪信息
    backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO);
    if (log_file) {
	backtrace_symbols_fd(buffer, nptrs, fd);
    }
    fclose(log_file);
    // 退出程序
    _exit(EXIT_FAILURE);
}

// 注册信号处理器
void register_signal_handlers() {
    struct sigaction sa;
    sa.sa_sigaction = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO | SA_RESTART;  // 关键标志
    // 注册常见崩溃信号
    sigaction(SIGSEGV, &sa, NULL);  // 段错误
sigaction(SIGILL,  &sa, NULL);  // 非法指令
    sigaction(SIGFPE,  &sa, NULL);  // 算术异常
    sigaction(SIGABRT, &sa, NULL);  // 主动终止
}

// 测试函数(触发崩溃)
void trigger_crash() {
    volatile int *p = NULL;
    *p = 42;  // 触发 SIGSEGV
}

int main() {
    register_signal_handlers();
    trigger_crash();
    return 0;
}

编译成二进制程序backtrace,记得带上符号:

图片

 运行backtrace:

图片

      可以很清晰地看到,程序崩溃一刻地寄存器信息rbp、rsp、rip都能采集到,backtrace info下罗列出了每个栈帧中具体访问了哪个模块中哪个虚拟地址,通过addr2line可以把指定地符号位置给还原回来。


http://www.kler.cn/a/579860.html

相关文章:

  • 操作系统知识点25
  • OCR图片识别原理
  • C++:面向对象之多态(运算符重载)
  • AF3 squeeze_features函数解读
  • Vue3 Pinia 符合直觉的Vue.js状态管理库
  • 超越经典:量子通信技术的发展与未来
  • MySQL8.0窗口函数
  • HTML 表格详解(简单易懂较详细)
  • 云服务运维智能时代:阿里云操作系统控制台
  • 利用paddleocr解决图片旋转问题
  • 死锁的产生以及如何避免
  • PAT乙级(1091 N-自守数)C语言解析
  • 日期类、Date、Calendar、IO 流、File
  • Windows简易操作(二)
  • Science Advances 多功能粘性皮肤增强了机器人与环境的交互
  • JavaScript网页设计案例:打造动态与交互性并存的用户体验
  • 【3DMAX插件】3DMAX建筑大师插件MasterBuilder使用方法
  • Rabbitmq--延迟消息
  • 深入理解C语言预处理器:从原理到实战
  • 游戏引擎学习第148天