Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show original C++ exception stack on ObjC #106

Open
q790838939 opened this issue Aug 31, 2022 · 6 comments
Open

Show original C++ exception stack on ObjC #106

q790838939 opened this issue Aug 31, 2022 · 6 comments

Comments

@q790838939
Copy link

I'm trying to disable C++ exception translation in ObjC because I want to see the original C++ stack. However, when the stack is thrown to the ObjC layer, it will be captured by __cxa_throw, and then uniformly converted into the same stack information as bellow:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x9750ea6a 0x974fa000 + 84586 (__pthread_kill + 10)
1   libsystem_sim_c.dylib           0x04d56578 0x4d0f000 + 292216 (abort + 137)
2   libc++abi.dylib                 0x04ed6f78 0x4ed4000 + 12152 (abort_message + 102)
3   libc++abi.dylib                 0x04ed4a20 0x4ed4000 + 2592 (_ZL17default_terminatev + 29)
4   libobjc.A.dylib                 0x013110d0 0x130b000 + 24784 (_ZL15_objc_terminatev + 109)
5   libc++abi.dylib                 0x04ed4a60 0x4ed4000 + 2656 (_ZL19safe_handler_callerPFvvE + 8)
6   libc++abi.dylib                 0x04ed4ac8 0x4ed4000 + 2760 (_ZSt9terminatev + 18)
7   libc++abi.dylib                 0x04ed5c48 0x4ed4000 + 7240 (__cxa_rethrow + 77)
8   libobjc.A.dylib                 0x01310fb8 0x130b000 + 24504 (objc_exception_rethrow + 42)
9   CoreFoundation                  0x01f2af98 0x1ef9000 + 204696 (CFRunLoopRunSpecific + 360)

Is there any way to save the original stack of C++ and display it in the ObjC layer? Thank you very much.

@finalpatch
Copy link
Contributor

There is a command line flag "objcpp-disable-exception-translation" to remove the translation. After that you should be able to capture the exception in xcode.

@q790838939
Copy link
Author

There is a command line flag "objcpp-disable-exception-translation" to remove the translation. After that you should be able to capture the exception in xcode.

Thank you for your answer, but I hope that C++ stack information can be displayed in crash stack, not in xcode. So set "objcpp-disable-exception-translation = true" can not achieve my purpose.

@li-feng-sc
Copy link
Contributor

C++ exceptions do not automatically capture their crash stack (unlike ObjC and Java exceptions, which do). So the only way to see where the exception happens is to run the program in a debugger and has the debugger stop on exceptions. This is not a limitation of Djinni, but a C++ thing.

@tom-skydio
Copy link

tom-skydio commented Sep 2, 2022

C++ exceptions do not automatically capture their crash stack (unlike ObjC and Java exceptions, which do).

HACK:
This is correct, but you can get C++ exceptions showing up in production stack traces if you crash within the external throw interface in the c++ support lib.

in header (e.g: this_is_a_crash_handler.h)

heres an example

#ifdef __APPLE___
#include <cxxabi.h>
extern "C" void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*));
#endif

in impl: this_is_a_crash_handler.cc

#ifdef __APPLE__
// thrown_exception: Address of thrown exception object                                              
// tinfo: static type of thrown object                                                               
// dest: destructor of the exception.                                                                
void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) {               
  if (tinfo) {                                                                                       
    int status;                                                                                      
    char* real_name = abi::__cxa_demangle(tinfo->name(), 0, 0, &status);                             
    if (status != 0) {                                                                               
      // -1: malloc failed                                                                           
      // -2: mangled_name is not a valid name under the C++ ABI mangling rules.                      
      // -3: one of args is invalid                                                                  
      PrintWarning("Demangling of object type failed - err: '{}'", status);                    
    } else {                                                                                         
      PrintWarning("Static type name: '{}'", real_name);                                       
      std::string real_name_s{real_name};                                                            
      // !!All exceptions generated by the standard library inherit from std::exception!!            
      // so we can safely cast from void* -> std::exception*                                         
      const bool can_safely_cast =                                                                   
          real_name_s.find("std::") != std::string::npos ||             
          // Or one of your own c++ exceptions!                             
          real_name_s.find("SnapChatException::MyCustomSnapChatException") != std::string::npos;                        
      if (can_safely_cast) {                                                                         
        std::exception* e_ptr = static_cast<std::exception*>(thrown_exception);                      
        PrintWarning("std::exception thrown - e.what(): '{}'", e_ptr->what());                 
      }                                                                                              
      // Since you can throw any type in C++ (i.e. throw int/char/RandomClass), dont cast            
      // to anything else and just free.                                                             
      free(real_name);                                                                               
    }                                                                                                
  }                                                                                                  
  PrintWarning(                                                                                
      "[Custom Exception] Crashing on custom exception -- see stacktrace + logs in your crash reporting tools  "    
      "for info.");              
      // This is the C++ equivalent of `fatalError()`                                                                    
  __builtin_unreachable();                                                                           
}                                                                                                    
}                                                                                                    
#endif                                                                                               

This means anytime you throw <exception>..., the app will crash and the stack trace will be visible.

Lowkey some bullshit c++ black magic but hey, atleast you have a stack trace

@q790838939
Copy link
Author

Thanks @tom-skydio , I have try your advice like bellow, here I use a global variable last_frames to save the stack information of C++ exception and put it in the message of NSException.

#define BACKTRACE_MAX_FRAME_NUMBER 8
void * last_frames[BACKTRACE_MAX_FRAME_NUMBER];
size_t last_size;

typedef void (*cxa_throw_type)(void*, std::type_info*, void (*)(void*));
cxa_throw_type original_cxa_throw = nullptr;
extern "C" void __cxa_throw(void *exception, std::type_info *typeinfo, void (*dest)(void *)) {
    if (original_cxa_throw == nullptr) {
        original_cxa_throw = (cxa_throw_type)(dlsym(RTLD_NEXT, "__cxa_throw"));
    }
    last_size = backtrace(last_frames, BACKTRACE_MAX_FRAME_NUMBER);
    original_cxa_throw(exception, typeinfo, dest);
}
[[noreturn]] __attribute__((weak)) void throwNSExceptionFromCurrent(const char * /*ctx*/) {
    try {
        throw;
    } catch (const std::exception & e) {
        char **symbols = backtrace_symbols(last_frames, static_cast<int>(last_size));
        NSMutableArray<NSString *> * stack = [NSMutableArray new];
        for (std::size_t i = 0; i < last_size; i++) {
            NSString *symbol = [[NSString alloc] initWithUTF8String:symbols[i]];
            [stack addObject:symbol];
        }
        free(symbols);
        NSString *what = [[NSString alloc] initWithUTF8String:e.what()];
        NSString *stacks = [stack componentsJoinedByString:@"\n"];
        NSString *message = [NSString stringWithFormat:@"%@\n%@", what, stacks];
        [NSException raise:message format:@"%@", message];
        __builtin_unreachable();
    }
}

However, rewrite __cxa_throw() will hook the global throw event, so it is possible that multiple threads throw at the same time will overwrite the global variable last_frames, and finally the stack information contained in it may not be what we expected. Do you have any advice?

@tom-skydio
Copy link

you can use a std::lock_guard<std::mutex> to lock r/w on the array --

also what's the reasoning behind catching, then rethrowing the exception? that results in the original stack trace being missing, no?

[[noreturn]] __attribute__((weak)) void throwNSExceptionFromCurrent(const char * /*ctx*/) {
    try {
        throw;
    }
    ```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants