【问题标题】:Catch C++ signal in Android NDK and still print crash dump在 Android NDK 中捕获 C++ 信号并仍然打印故障转储
【发布时间】:2013-06-23 23:50:50
【问题描述】:

我正在捕捉 C++ 信号,因此我打印了一些调试信息。但是这样做我无法获得崩溃时 NDK 打印的崩溃转储。

您能否手动打印故障转储。我看到 debuggerd.c (http://kobablog.wordpress.com/2011/05/12/debuggerd-of-android/) 完成了这项工作,但不确定我将如何使用它。否则,是否有某种方法可以在我的信号处理程序没有捕捉到它的情况下重新抛出信号并仍然获得故障转储。

这是我目前所做的:

struct sigaction psa, oldPsa;

void CESignalHandler::init() {
    CELogI("Crash handler started");

    psa.sa_sigaction = handleCrash;
    psa.sa_flags = SA_SIGINFO;

    //sigaction(SIGBUS, &psa, &oldPsa);
    sigaction(SIGSEGV, &psa, &oldPsa);
    //sigaction(SIGSYS, &psa, &oldPsa);
    //sigaction(SIGFPE, &psa, &oldPsa);
    //sigaction(SIGILL, &psa, &oldPsa);
    //sigaction(SIGHUP, &psa, &oldPsa);
}

void CESignalHandler::handleCrash(int signalNumber, siginfo_t *sigInfo, void *context) {
    static volatile sig_atomic_t fatal_error_in_progress = 0;
    if (fatal_error_in_progress) //Stop a signal loop.
        _exit(1);
    fatal_error_in_progress = 1;

    char* j;
    asprintf(&j, "Crash Signal: %d, crashed on: %x, UID: %ld\n", signalNumber, (long) sigInfo->si_addr, (long) sigInfo->si_uid);  //%x prints out the faulty memory address in hex
    CELogE(j);

    CESignalHandler::getStackTrace();
    sigaction(signalNumber, &oldPsa, NULL); 
}

【问题讨论】:

    标签: android c++ crash android-ndk


    【解决方案1】:

    您需要将信号处理程序重置为前一个函数,然后再次崩溃——最好是在最初抛出信号的地方。您可以通过将struct sigaction 作为第三个参数传入sigaction() 并使用保存的值来恢复信号处理程序中的原始行为来做到这一点。

    这可能有点棘手,因为 debuggerd 的工作方式(并且因为它的工作方式随着时间的推移而改变)。对于像分段错误这样的“硬”故障,从信号处理程序返回只会导致重新抛出相同的信号。 Android 崩溃处理程序通过联系 debuggerd,等待它与 ptrace 连接,然后恢复来利用这一点。然后 debuggerd 开始观察进程崩溃(第二次)。

    这不适用于“软”故障,例如有人手动向您的进程发送SIGABRT 或从write() 获取SIGPIPE。如果信号处理程序联系 debuggerd 并恢复,则该过程只是清除信号并继续,让 debuggerd 无限期地等待第二次从未发生过的崩溃。这部分修复了几个版本;现在调试代码重新发出信号本身(在信号处理程序返回之前它实际上不会做任何事情,因为在处理程序运行时信号被阻塞)。这通常有效,如果无效,调试器将超时并断开连接。

    所以。如果您收到分段错误或总线错误,您可以恢复原始信号处理程序,然后从您的返回,当进程再次崩溃时,调试器处理程序将处理它。如果有人给你发了SIGHUP,你应该完全自己处理,因为调试器根本不关心那个信号。

    SIGFPE 让事情变得很奇怪。这是一个“软”故障,因为大多数 ARM CPU 没有硬件整数除法指令,信号实际上是从 libgcc __div0 函数显式发送的。您可以恢复信号处理程序,然后自己重​​新发送信号;但根据您运行的 Android 版本,您可能需要发送两次。理想情况下,您希望从遇到算术问题的代码而不是信号处理程序中执行此操作,但这很棘手,除非您可以替换 __div0。您需要使用tgkill() 发送信号,而不是kill(),因为后者会导致信号被发送到进程的主线程,这会导致调试器将堆栈转储到错误的线程。

    您可能很想将处理程序从bionic/linker/debugger.cpp 中复制出来,但这是个坏主意——用于在仿生和调试器之间进行通信的协议过去已更改,并且可能会再次更改。

    【讨论】:

    • 现在让我们担心分段错误。我仍然无法让它工作。我像这样恢复旧的处理程序: sigaction(signalNumber, &oldPsa, NULL);但我仍然没有得到一个 android 崩溃转储。我已经更新了第一篇文章来展示这一点。它在 logcat 中显示“06-25 10:43:40.143: D/Zygote(159): Process 4588 terminate by signal (11)”。顺便说一句,很棒的帖子。
    • 为了完全正确,您应该为每个信号设置一个单独的 oldPsa。我认为如果您从现有代码中删除 sigaction(SIGHUP, &psa, &oldPsa) 它将起作用——这是最后执行的,这意味着 oldPsa 拥有原始的 SIGHUP 处理程序。 Bionic 没有为SIGHUP 安装调试器处理程序,所以你得到的是SIG_DFL
    • 现在我被困住的问题是它会进入崩溃循环。我不能从信号代码中抛出 Java 异常,否则我不会得到故障转储。当应用程序确实使用本机代码时,我尝试设置一个标志以使应用程序崩溃,但由于本机崩溃,我的标志设置为 null。关于解决方案的任何想法?
    • 我无法完全想象您要做什么。您可能想提交一个新问题,以便可以编写超过 600 个字符。您是否希望从SIGSEGV 中恢复过来?做“繁重”的事情,比如回调虚拟机,对于信号处理程序是不明智的——你无法知道应用程序在失败时处于什么状态。如果您只需要当前线程的堆栈转储,您可能想看看是否可以提取 libcorkscrew 并直接使用它(Dalvik VM 在收到SIGQUIT 时使用它来获取本机跟踪)。
    • 安卓为什么会重启?这是一项服务还是部分系统的替代品?有什么东西在积极地重新启动它吗?通常,当应用程序进程死亡时,系统不会重新启动它。 logcat -v threadtime 显示什么? (我想确保进程正在死亡并重新启动,而不是线程正在重新启动——有时当处理程序妨碍时,“致命”信号不会杀死进程。)
    【解决方案2】:

    你需要像这样手动调用oldPsa

    oldPsa(signalNumber, sigInfo, context)
    

    【讨论】:

      猜你喜欢
      • 2020-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多