Kscrash: Incorrect stack traces for C++ exceptions in embedded frameworks

Created on 20 Feb 2017  ·  12Comments  ·  Source: kstenerud/KSCrash

It looks like the __cxa_throw function in KSCrashMonitor_CPPException.c is not getting called when a C++ exception is thrown in an embedded framework, e.g. CrashLib in the Crash-Tester app.

I haven't been able to come up with a solution to this. To me it looks like any embedded library will always use __cxa_throw from libc++, and not the implementation in KSCrash.

Additionally: When an unhandled C++ exception is thrown in an embedded framework, KSCrash will crash during reporting, since stackCursor->advanceCursor will equal NULL in KSCrashReport.c:writeBacktrace().

Edit: Added information KSCrash crashing during reporting.

Most helpful comment

After further investigation it seems my previous comment was incorrect:

I think this issue should be renamed to something like _"Missing stack traces for C++ exceptions thrown from images not statically linked with KSCrash"_.

In the case of KSCrash being linked into the main application as a static library:

  • MyApplication (main.o, libKSCrash.a)

    • libDynamicLibrary.dylib (lib.o)

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

Any exceptions thrown from MyApplication will enter the libKSCrash.a::__cxa_throw. In additions, since libKSCrash.a declares __cxa_throw as weak, linking MyApplication doesn't fail if main.o has its own __cxa_throw override. So far so good. However, as this bug report has observed, any exceptions thrown from libDynamicLibrary.dylib will not trigger libKSCrash.a::__cxa_throw, and will end up in /usr/lib/libc++abi.dylib::__cxa_throw.

In the case of KSCrash being linked into the main application as a dynamic library:

  • MyApplication (main.o)

    • libKSCrash.dylib

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

    • libDynamicLibrary.dylib (lib.o)

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

Any exceptions thrown from MyApplication will enter the libKSCrash.dylib::__cxa_throw, but only if:

  1. __cxa_throw is exported from libKSCrash.dylib
  2. __cxa_throw is not marked as weak

If __cxa_throw is marked as weak (0000000000002e90 (__TEXT,__text) weak external ___cxa_throw), then when linking libKSCrash.dylib the linker seems to look at /usr/lib/libc++abi.dylib, find a non-weak __cxa_throw, and conclude that libKSCrash.dylib::__cxa_throw should be ignored. During runtime symbol lookup at throw time from MyApplication, libKSCrash.dylib::__cxa_throw is then ignored.

This leads me to belive that the weak attribute should be conditionally added only when building KSCrash as a static library.

Like for the first usecase, any exceptions thrown from libDynamicLibrary.dylib will not trigger libKSCrash.dylib::__cxa_throw, and will end up in /usr/lib/libc++abi.dylib::__cxa_throw.

This issue could potentially be fixed with some low level runtime patching of loaded images with undefined references to __cxa_throw. Using the test application referenced in the article I've verified that the technique does indeed work for __cxa_throw as well.

cooked_throw

All 12 comments

Any luck with this? I'm trying to integrate KSCrash in my app, but stuck because of embedded framework.
Please let me know if you have any work around for this.

No luck, and I don't believe it is possible, at least not in our use case. This is my understanding of the situation:

The way KSCrash records the stack trace is by creating its own implementation of __cxa_throw, which allow access to the stack before it is unwound by libc++. This works as long as KSCrash is statically linked with the C++ code, since the linker will find and use __cxa_throw form KSCrash instead of the implementation in libc++. But in the case of an embedded framework that links to libc++, the __cxa_throw implementation from libc++ will be used, since the framework doesn't know about the code in KSCrash. Maybe there is a work-around if you have control over the embedded framework, but this won't work in our use-case.

Please let me know if you figure out a solution.

I have control over the embedded framework. But not sure how to fix it.
I also tried PLCrashReporter, same problem with it.

Does this mean that KSCrash should ideally be built as a static library and linked into the application, to at least catch C++ exceptions thrown in the application code?

But that would still leave any exceptions thrown in any dynamic library uncaught by KSCrash if I understand your bug report correctly @pdrtrifork ? If we control the embedded dynamic libs, could we link in a static lib to each of them with just that __cxa_throw stub, which would just defer to a shared KSCrash handler?

Hah, so in investigating this I ended up here http://stackoverflow.com/questions/36846628/conditionally-overriding-cxa-throw, which was opened by our very own @kstenerud 😄

Anyways, PR #219 should at least prevent crashing during crash reporting.

@kstenerud I think this issue should be renamed to something like _"Missing stack traces for C++ exceptions thrown from images not statically linked with KSCrash"_. As far as I can tell, the __cxa_throw override only works in the same image as KSCrash, so:

  • if KSCrash is built as a static library and linked into the main app, you get C++ exception backtraces if the exception is thrown in the main app, but not when thrown from dynamically loaded libraries
  • if KSCrash is built as a static library and linked into a dynamic (wrapper) library, you get C++ exception backtraces if the exception is thrown from that dynamic library, but not when thrown from the main app, or other dynamically loaded libraries
  • if KSCrash is built as a shared library (framework), you get C++ exception backtraces if the exception is thrown from KSCrash, but not when thrown from the main app, or other dynamically loaded libraries

The latter would affect clients such as sentry-swift, https://docs.sentry.io/clients/cocoa/, which embeds KSCrash as a framework.

After further investigation it seems my previous comment was incorrect:

I think this issue should be renamed to something like _"Missing stack traces for C++ exceptions thrown from images not statically linked with KSCrash"_.

In the case of KSCrash being linked into the main application as a static library:

  • MyApplication (main.o, libKSCrash.a)

    • libDynamicLibrary.dylib (lib.o)

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

Any exceptions thrown from MyApplication will enter the libKSCrash.a::__cxa_throw. In additions, since libKSCrash.a declares __cxa_throw as weak, linking MyApplication doesn't fail if main.o has its own __cxa_throw override. So far so good. However, as this bug report has observed, any exceptions thrown from libDynamicLibrary.dylib will not trigger libKSCrash.a::__cxa_throw, and will end up in /usr/lib/libc++abi.dylib::__cxa_throw.

In the case of KSCrash being linked into the main application as a dynamic library:

  • MyApplication (main.o)

    • libKSCrash.dylib

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

    • libDynamicLibrary.dylib (lib.o)

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

Any exceptions thrown from MyApplication will enter the libKSCrash.dylib::__cxa_throw, but only if:

  1. __cxa_throw is exported from libKSCrash.dylib
  2. __cxa_throw is not marked as weak

If __cxa_throw is marked as weak (0000000000002e90 (__TEXT,__text) weak external ___cxa_throw), then when linking libKSCrash.dylib the linker seems to look at /usr/lib/libc++abi.dylib, find a non-weak __cxa_throw, and conclude that libKSCrash.dylib::__cxa_throw should be ignored. During runtime symbol lookup at throw time from MyApplication, libKSCrash.dylib::__cxa_throw is then ignored.

This leads me to belive that the weak attribute should be conditionally added only when building KSCrash as a static library.

Like for the first usecase, any exceptions thrown from libDynamicLibrary.dylib will not trigger libKSCrash.dylib::__cxa_throw, and will end up in /usr/lib/libc++abi.dylib::__cxa_throw.

This issue could potentially be fixed with some low level runtime patching of loaded images with undefined references to __cxa_throw. Using the test application referenced in the article I've verified that the technique does indeed work for __cxa_throw as well.

cooked_throw

I'm trying to check crash in a framework in my app. When I try to get the crash log I see that stack trace is not showing the actual crash, instead KSCrash is crashing.

Is this the same issue?

Thread 0 Crashed:
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 (abort + 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 CoreFoundation 0x0000000182072344 0x18206a000 + 33604 (CFRunLoopRunSpecific + 544)
11 GraphicsServices 0x0000000183f03f84 0x183ef9000 + 44932 (GSEventRunModal + 100)
12 UIKit 0x000000018b61e880 0x18b5ab000 + 473216 (UIApplicationMain + 208)

@torarnv Hi, could you share your successful mach-o-hook demo with us? I ran into EXC_BAD_ACCESS when I try to mach_hook __cxa_throw.

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[])
{
    @autoreleasepool {

        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();
    }
}

try this... good luck

I meet one crash with KSCrash ,
kscrash

how to resolve it?

We've created a PR implementing dynamic hook mentioned above by @huakucha: #375

Was this page helpful?
0 / 5 - 0 ratings

Related issues

1t2t3t4t picture 1t2t3t4t  ·  3Comments

ferrous777 picture ferrous777  ·  30Comments

kstenerud picture kstenerud  ·  4Comments

chzhij5 picture chzhij5  ·  17Comments

happy201993 picture happy201993  ·  10Comments