djinni
djinni copied to clipboard
Show original C++ exception stack on ObjC
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.
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.
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.
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.
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
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?
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;
}
```