Kscrash: 嵌入式框架中 C++ 异常的错误堆栈跟踪

创建于 2017-02-20  ·  12评论  ·  资料来源: kstenerud/KSCrash

当嵌入式框架(例如 Crash-Tester 应用程序中的 CrashLib)中抛出 C++ 异常时,似乎没有调用 KSCrashMonitor_CPPException.c 中的 __cxa_throw 函数。

我一直无法想出解决方案。 在我看来,任何嵌入式库都将始终使用 libc++ 中的 __cxa_throw,而不是 KSCrash 中的实现。

另外:当嵌入式框架中抛出未处理的 C++ 异常时,KSCrash 将在报告期间崩溃,因为在 KSCrashReport.c:writeBacktrace() 中 stackCursor->advanceCursor 将等于 NULL。

编辑:添加了报告期间 KSCrash 崩溃的信息。

最有用的评论

经过进一步调查,我之前的评论似乎不正确:

我认为这个问题应该重命名为 _"Missing stack traces for C++ exceptions 从未与 KSCrash 静态链接的图像中抛出"_。

在将 KSCrash 作为静态库链接到主应用程序的情况下:

  • MyApplication ( main.o , libKSCrash.a )

    • libDynamicLibrary.dylib ( lib.o )

    • /usr/lib/libc++abi.dylib

MyApplication抛出的任何异常都将进入libKSCrash.a::__cxa_throw 。 此外,由于libKSCrash.a__cxa_throw $ 声明为weak ,因此如果main.o有自己的__cxa_throw ,则链接MyApplication不会失败__cxa_throw覆盖。 到现在为止还挺好。 但是,正如这个错误报告所观察到的,从libDynamicLibrary.dylib引发的任何异常都不会触发libKSCrash.a::__cxa_throw ,而是会以/usr/lib/libc++abi.dylib::__cxa_throw结束。

在将 KSCrash 作为动态库链接到主应用程序的情况下:

  • MyApplication ( main.o )

    • libKSCrash.dylib

    • /usr/lib/libc++abi.dylib

    • libDynamicLibrary.dylib ( lib.o )

    • /usr/lib/libc++abi.dylib

MyApplication抛出的任何异常都将进入libKSCrash.dylib::__cxa_throw ,但前提是:

  1. __cxa_throw是从libKSCrash.dylib导出的
  2. __cxa_throw未标记为weak

如果__cxa_throw被标记为weak ( 0000000000002e90 (__TEXT,__text) weak external ___cxa_throw ),那么在链接libKSCrash.dylib时,链接器似乎会查看/usr/lib/libc++abi.dylib ,找到一个非-weak __cxa_throw ,并得出结论libKSCrash.dylib::__cxa_throw应该被忽略。 在从MyApplication的抛出时间查找运行时符号期间,然后忽略libKSCrash.dylib::__cxa_throw

这让我相信只有在将 KSCrash 构建为静态库时才应有条件地添加weak属性。

与第一个用例一样,从libDynamicLibrary.dylib引发的任何异常都不会触发libKSCrash.dylib::__cxa_throw ,并且会以/usr/lib/libc++abi.dylib::__cxa_throw结束。

这个问题可能会通过对未定义的__cxa_throw引用的加载图像进行一些低级别的运行时修补来解决。 使用文章中引用的测试应用程序,我已经验证该技术确实也适用于__cxa_throw

cooked_throw

所有12条评论

这有什么好运气吗? 我正在尝试将 KSCrash 集成到我的应用程序中,但由于嵌入式框架而卡住了。
如果您对此有任何解决方法,请告诉我。

没有运气,我不相信这是可能的,至少在我们的用例中是不可能的。 这是我对情况的理解:

KSCrash 记录堆栈跟踪的方式是创建它自己的 __cxa_throw 实现,它允许在堆栈被 libc++ 展开之前访问堆栈。 只要 KSCrash 与 C++ 代码静态链接,此方法就可以工作,因为链接器将查找并使用 KSCrash 形式的 __cxa_throw 而不是 libc++ 中的实现。 但是对于链接到 libc++ 的嵌入式框架,将使用来自 libc++ 的 __cxa_throw 实现,因为该框架不知道 KSCrash 中的代码。 如果您可以控制嵌入式框架,也许有一种解决方法,但这在我们的用例中不起作用。

如果您找到解决方案,请告诉我。

我可以控制嵌入式框架。 但不确定如何解决。
我也试过PLCrashReporter,同样的问题。

这是否意味着 KSCrash 应该理想地构建为静态库并链接到应用程序中,以至少捕获应用程序代码中抛出的 C++ 异常?

但是,如果我正确理解了您的错误报告@pdrtrifork ,那仍然会在任何动态库中抛出任何未被 KSCrash 捕获的异常? 如果我们控制嵌入的动态库,我们是否可以仅使用 __cxa_throw 存根将静态库链接到它们中的每一个,它只会推迟到共享的 KSCrash 处理程序?

哈,所以在调查这个问题时,我最终来到了http://stackoverflow.com/questions/36846628/conditionally-overriding-cxa-throw ,这是由我们自己的@kstenerud开设的😄

无论如何,PR #219 至少应该防止在崩溃报告期间崩溃。

@kstenerud我认为这个问题应该重命名为 _"Missing stack traces for C++ exceptions 从未与 KSCrash 静态链接的图像中抛出"_。 据我所知, __cxa_throw覆盖仅适用于与 KSCrash 相同的图像,因此:

  • 如果 KSCrash 构建为静态库并链接到主应用程序,则如果在主应用程序中抛出异常,您将获得 C++ 异常回溯,但从动态加载的库中抛出时则不会
  • 如果 KSCrash 构建为静态库并链接到动态(包装器)库,则如果从该动态库抛出异常,您将获得 C++ 异常回溯,但从主应用程序或其他动态加载的库抛出时不会
  • 如果 KSCrash 是作为共享库(框架)构建的,那么如果从 KSCrash 抛出异常,您将获得 C++ 异常回溯,但从主应用程序或其他动态加载的库抛出时则不会

后者会影响诸如sentry-swifthttps://docs.sentry.io/clients/cocoa/之类的客户端,它们将 KSCrash 嵌入为框架。

经过进一步调查,我之前的评论似乎不正确:

我认为这个问题应该重命名为 _"Missing stack traces for C++ exceptions 从未与 KSCrash 静态链接的图像中抛出"_。

在将 KSCrash 作为静态库链接到主应用程序的情况下:

  • MyApplication ( main.o , libKSCrash.a )

    • libDynamicLibrary.dylib ( lib.o )

    • /usr/lib/libc++abi.dylib

MyApplication抛出的任何异常都将进入libKSCrash.a::__cxa_throw 。 此外,由于libKSCrash.a__cxa_throw $ 声明为weak ,因此如果main.o有自己的__cxa_throw ,则链接MyApplication不会失败__cxa_throw覆盖。 到现在为止还挺好。 但是,正如这个错误报告所观察到的,从libDynamicLibrary.dylib引发的任何异常都不会触发libKSCrash.a::__cxa_throw ,而是会以/usr/lib/libc++abi.dylib::__cxa_throw结束。

在将 KSCrash 作为动态库链接到主应用程序的情况下:

  • MyApplication ( main.o )

    • libKSCrash.dylib

    • /usr/lib/libc++abi.dylib

    • libDynamicLibrary.dylib ( lib.o )

    • /usr/lib/libc++abi.dylib

MyApplication抛出的任何异常都将进入libKSCrash.dylib::__cxa_throw ,但前提是:

  1. __cxa_throw是从libKSCrash.dylib导出的
  2. __cxa_throw未标记为weak

如果__cxa_throw被标记为weak ( 0000000000002e90 (__TEXT,__text) weak external ___cxa_throw ),那么在链接libKSCrash.dylib时,链接器似乎会查看/usr/lib/libc++abi.dylib ,找到一个非-weak __cxa_throw ,并得出结论libKSCrash.dylib::__cxa_throw应该被忽略。 在从MyApplication的抛出时间查找运行时符号期间,然后忽略libKSCrash.dylib::__cxa_throw

这让我相信只有在将 KSCrash 构建为静态库时才应有条件地添加weak属性。

与第一个用例一样,从libDynamicLibrary.dylib引发的任何异常都不会触发libKSCrash.dylib::__cxa_throw ,并且会以/usr/lib/libc++abi.dylib::__cxa_throw结束。

这个问题可能会通过对未定义的__cxa_throw引用的加载图像进行一些低级别的运行时修补来解决。 使用文章中引用的测试应用程序,我已经验证该技术确实也适用于__cxa_throw

cooked_throw

我正在尝试在我的应用程序的框架中检查崩溃。 当我尝试获取崩溃日志时,我看到堆栈跟踪没有显示实际的崩溃,而是 KSCrash 正在崩溃。

这是同一个问题吗?

线程 0 崩溃:
0 libsystem_kernel.dylib 0x0000000181cc5348 0x181ca4000 + 136008 (__pthread_kill + 8)
1 libsystem_pthread.dylib 0x0000000181ddd7a4 0x181dd6000 + 30628 (+ 360)
2 libsystem_c.dylib 0x0000000181c34fd8 0x181bd2000 + 405464(中止 + 140)
3 libc++abi.dylib 0x0000000181698068 0x181696000 + 8296 (+ 132)
4 libc++abi.dylib 0x0000000181698210 0x181696000 + 8720 ( + 304)
5 libobjc.A.dylib 0x00000001816c0810 0x1816b8000 + 34832 (+ 124)
6 KSCrash 0x00000001054a0590 0x10549c000 + 17808 (kscm_cppexception_getAPI + 280)
7 libc++abi.dylib 0x00000001816b054c 0x181696000 + 107852 (+ 16)
8 libc++abi.dylib 0x00000001816b0158 0x181696000 + 106840 (__cxa_rethrow + 144)
9 libobjc.A.dylib 0x00000001816c06e8 0x1816b8000 + 34536 (objc_exception_rethrow + 44)
10 核心基础 0x0000000182072344 0x18206a000 + 33604 (CFRunLoopRunSpecific + 544)
11 图形服务 0x0000000183f03f84 0x183ef9000 + 44932 (GSEventRunModal + 100)
12 UIKit 0x000000018b61e880 0x18b5ab000 + 473216 (UIApplicationMain + 208)

@torarnv嗨,你能和我们分享你成功的 mach-o-hook 演示吗? 当我尝试mach_hook __cxa_throw 时遇到了 EXC_BAD_ACCESS。

void ter_handler(){
    printf("custom handler\n");
}

void test(){
    throw std::runtime_error("test function");
}

static void (*orig_throw)(void * thrown_exception, std::type_info *tinfo, void (*dest)(void *));

void hooked_throw(void * thrown_exception, std::type_info *tinfo, void (*dest)(void *)){
    printf("hooked_throw...\n");
    return orig_throw(thrown_exception, tinfo, dest);
}

int main(int argc, char * argv[])
{
    <strong i="5">@autoreleasepool</strong> {

        struct rebinding binds[1];
        struct rebinding bind1 = {"__cxa_throw", (void *)hooked_throw, (void **)&orig_throw};
        binds[0] = bind1;
        rebind_symbols(binds, 1);

        std::set_terminate(ter_handler);
        try {
            throw std::runtime_error("test error");
        }
        catch (...){
            printf  ("catch exception\n");
        }

        test();
    }
}

试试这个……祝你好运

我遇到了一次 KSCrash 崩溃,
kscrash

如何解决?

我们已经创建了一个 PR 实现上面提到的@huakucha的动态钩子:#375

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

chzhij5 picture chzhij5  ·  17评论

1t2t3t4t picture 1t2t3t4t  ·  3评论

kstenerud picture kstenerud  ·  4评论

happy201993 picture happy201993  ·  10评论

ferrous777 picture ferrous777  ·  30评论