You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用backtrace收集崩溃调用栈时缺失testCrash函数的问题排查

问题分析与解决方案

首先,你遇到的问题非常典型——backtrace()默认只会捕获当前信号处理函数所在线程的调用栈,而非触发崩溃的那个线程的原始调用栈

为什么看不到testCrash

testCrash触发空指针访问时,系统会发送SIGSEGV信号。此时内核会暂停崩溃线程,切换到专门的信号处理线程来执行你的SignalExceptionHandler。这时候调用backtrace(),拿到的是信号处理线程的调用链(也就是从SignalExceptionHandler往上的栈),而不是崩溃线程里testCrash所在的原始栈。日志里的_sigtramp是信号跳转的中间函数,后面的???就是未被捕获到的原始崩溃栈片段。

修复方案:捕获崩溃线程的上下文

要拿到原始崩溃栈,你需要在信号处理函数中获取崩溃线程的上下文信息,通过ucontext_t结构体提取崩溃时的寄存器状态,进而解析出原始调用栈。

1. 修改信号处理函数,加入上下文解析

#include <ucontext.h>

void SignalExceptionHandler(int signal, siginfo_t *info, void *context) {
    NSMutableString *mstr = [[NSMutableString alloc] init];
    [mstr appendString:@"Stack:\n"];

    // 获取崩溃时的线程上下文
    ucontext_t *uc = (ucontext_t *)context;
    void* callstack[128];
    int frames = 0;

    // 根据架构提取崩溃时的PC(程序计数器)和FP(帧指针)
#if defined(__arm64__)
    uintptr_t pc = uc->uc_mcontext.__ss.__pc;
    uintptr_t *frame = (uintptr_t *)uc->uc_mcontext.__ss.__fp;
#elif defined(__x86_64__)
    uintptr_t pc = uc->uc_mcontext->__ss.rip;
    uintptr_t *frame = (uintptr_t *)uc->uc_mcontext->__ss.rbp;
#endif

    // 手动遍历原始栈帧
    callstack[frames++] = (void *)pc;
    while (frame && frames < 128) {
        callstack[frames++] = (void *)frame[1]; // 取出下一个栈帧的PC
        frame = (uintptr_t *)frame[0]; // 移动到下一个帧指针
    }

    // 转换为可读符号
    char** strs = backtrace_symbols(callstack, frames);
    for (int i = 0; i < frames; ++i) {
        [mstr appendFormat:@"%s\n", strs[i]];
    }
    NSLog(@"%@", mstr);
    free(strs);

    // 重新触发信号,让系统正常处理崩溃(避免App卡死)
    signal(signal, SIG_DFL);
    raise(signal);
}

2. 改用sigaction安装信号处理器

原来的signal()函数无法传递上下文信息,需要换成sigaction

void InstallSignalHandler(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = SignalExceptionHandler;
    sa.sa_flags = SA_SIGINFO; // 启用上下文传递

    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
    sigaction(SIGILL, &sa, NULL);
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGFPE, &sa, NULL);
    sigaction(SIGBUS, &sa, NULL);
    sigaction(SIGPIPE, &sa, NULL);
}

额外注意事项

  • 符号化问题:如果是Release版本,App的符号表可能被剥离,需要用atos工具结合dSYM文件手动解析地址到函数名;Debug版本下应该能直接看到testCrash的函数名。
  • 架构兼容性:上面的代码适配了arm64和x86_64架构,不同架构的寄存器名称不同,需要对应调整。
  • 必须重新触发信号:处理完栈信息后,一定要恢复默认信号处理并重新触发崩溃,否则App会卡在信号处理函数中无法正常退出。

这样修改后,你就能在日志里看到testCrash函数出现在调用栈的顶部了。

内容的提问来源于stack exchange,提问作者wangqi060934

火山引擎 最新活动