Write Kotlin uncaught exception stacktrace to iOS crash log (#2645)
This commit is contained in:
committed by
GitHub
parent
8d1ef86270
commit
9955b2cbf1
@@ -42,6 +42,7 @@
|
||||
#include "SourceInfo.h"
|
||||
#include "Types.h"
|
||||
#include "Utils.h"
|
||||
#include "ObjCExceptions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -229,8 +230,23 @@ void OnUnhandledException(KRef throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
static bool terminating = false;
|
||||
static SimpleMutex terminatingMutex;
|
||||
#endif
|
||||
|
||||
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
||||
OnUnhandledException(throwable);
|
||||
|
||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
{
|
||||
LockGuard<SimpleMutex> lock(terminatingMutex);
|
||||
if (!terminating) {
|
||||
ReportBacktraceToIosCrashLog(throwable);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
konan::abort();
|
||||
}
|
||||
|
||||
@@ -240,12 +256,23 @@ RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
||||
|
||||
static void (*oldTerminateHandler)() = nullptr;
|
||||
|
||||
static void callOldTerminateHandler() {
|
||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
{
|
||||
LockGuard<SimpleMutex> lock(terminatingMutex);
|
||||
terminating = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
RuntimeCheck(oldTerminateHandler != nullptr, "Underlying exception handler is not set.");
|
||||
oldTerminateHandler();
|
||||
}
|
||||
|
||||
static void KonanTerminateHandler() {
|
||||
auto currentException = std::current_exception();
|
||||
RuntimeCheck(oldTerminateHandler != nullptr, "Underlying exception handler is not set.");
|
||||
if (!currentException) {
|
||||
// No current exception.
|
||||
oldTerminateHandler();
|
||||
callOldTerminateHandler();
|
||||
} else {
|
||||
try {
|
||||
std::rethrow_exception(currentException);
|
||||
@@ -253,7 +280,7 @@ static void KonanTerminateHandler() {
|
||||
TerminateWithUnhandledException(e.obj());
|
||||
} catch (...) {
|
||||
// Not a Kotlin exception.
|
||||
oldTerminateHandler();
|
||||
callOldTerminateHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <inttypes.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <CoreFoundation/CFRunLoop.h>
|
||||
#include "Natives.h"
|
||||
#include "ObjCExceptions.h"
|
||||
#include "Types.h"
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_Throwable_getStackTrace, KRef throwable);
|
||||
|
||||
static void writeStackTraceToBuffer(KRef throwable, char* buffer, unsigned long bufferSize) {
|
||||
if (bufferSize < 2) return;
|
||||
|
||||
ObjHolder stackTraceHolder;
|
||||
ArrayHeader* stackTrace = Kotlin_Throwable_getStackTrace(throwable, stackTraceHolder.slot())->array();
|
||||
|
||||
char* bufferPointer = buffer;
|
||||
unsigned long remainingBytes = bufferSize;
|
||||
|
||||
*(bufferPointer++) = '(';
|
||||
--remainingBytes;
|
||||
|
||||
for (int index = 0; index < stackTrace->count_; ++index) {
|
||||
KNativePtr ptr = *PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace, index);
|
||||
int bytes = snprintf(bufferPointer, remainingBytes, "0x%" PRIxPTR " ", reinterpret_cast<uintptr_t>(ptr));
|
||||
|
||||
if (bytes >= remainingBytes) {
|
||||
break;
|
||||
}
|
||||
|
||||
bufferPointer += bytes;
|
||||
remainingBytes -= bytes;
|
||||
}
|
||||
|
||||
*(bufferPointer - 1) = ')'; // Replace last space.
|
||||
*bufferPointer = '\0';
|
||||
}
|
||||
|
||||
#if !defined(MACHSIZE)
|
||||
#error "Define MACHSIZE to 32 or 64"
|
||||
#endif
|
||||
|
||||
#if MACHSIZE == 32
|
||||
|
||||
typedef struct mach_header mach_header_target;
|
||||
typedef struct segment_command segment_command_target;
|
||||
typedef struct section section_target;
|
||||
static const uint32_t MH_MAGIC_TARGET = MH_MAGIC;
|
||||
static const uint32_t LC_SEGMENT_TARGET = LC_SEGMENT;
|
||||
|
||||
#elif MACHSIZE == 64
|
||||
|
||||
typedef struct mach_header_64 mach_header_target;
|
||||
typedef struct segment_command_64 segment_command_target;
|
||||
typedef struct section_64 section_target;
|
||||
static const uint32_t MH_MAGIC_TARGET = MH_MAGIC_64;
|
||||
static const uint32_t LC_SEGMENT_TARGET = LC_SEGMENT_64;
|
||||
|
||||
#else
|
||||
|
||||
#error "Impossible MACHSIZE"
|
||||
|
||||
#endif
|
||||
|
||||
static mach_header_target* findCoreFoundationMachHeader() {
|
||||
Dl_info info;
|
||||
if (dladdr(reinterpret_cast<void*>(&CFRunLoopRun), &info) == 0) return nullptr;
|
||||
|
||||
return reinterpret_cast<mach_header_target*>(info.dli_fbase);
|
||||
}
|
||||
|
||||
template<int n>
|
||||
bool bufferEqualsString(const char (&buffer)[n], const char* str) {
|
||||
return strncmp(buffer, str, n) == 0;
|
||||
}
|
||||
|
||||
static char* findExceptionBacktraceSection(unsigned long *size) {
|
||||
mach_header_target* header = findCoreFoundationMachHeader();
|
||||
if (header == nullptr) return nullptr;
|
||||
if (header->magic != MH_MAGIC_TARGET) return nullptr;
|
||||
|
||||
uintptr_t textVmaddr = 0;
|
||||
|
||||
load_command* loadCommand = reinterpret_cast<load_command*>(header + 1);
|
||||
for (uint32_t loadCommandIndex = 0; loadCommandIndex < header->ncmds; ++loadCommandIndex) {
|
||||
if (loadCommand->cmd == LC_SEGMENT_TARGET) {
|
||||
segment_command_target* segmentCommand = reinterpret_cast<segment_command_target*>(loadCommand);
|
||||
if (bufferEqualsString(segmentCommand->segname, "__TEXT")) {
|
||||
textVmaddr = segmentCommand->vmaddr;
|
||||
}
|
||||
|
||||
if (bufferEqualsString(segmentCommand->segname, "__DATA")) {
|
||||
section_target* sections = reinterpret_cast<section_target*>(segmentCommand + 1);
|
||||
for (uint32_t sectionIndex = 0; sectionIndex < segmentCommand->nsects; ++sectionIndex) {
|
||||
section_target* section = §ions[sectionIndex];
|
||||
|
||||
if (bufferEqualsString(section->sectname, "__cf_except_bt") && bufferEqualsString(section->segname, "__DATA")) {
|
||||
*size = section->size;
|
||||
return reinterpret_cast<char*>(reinterpret_cast<uintptr_t>(header) + section->addr - textVmaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadCommand = reinterpret_cast<load_command*>(reinterpret_cast<uintptr_t>(loadCommand) + loadCommand->cmdsize);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReportBacktraceToIosCrashLog(KRef throwable) {
|
||||
unsigned long bufferSize = 0;
|
||||
char* buffer = findExceptionBacktraceSection(&bufferSize);
|
||||
if (buffer == nullptr) return;
|
||||
|
||||
// Note: access to this buffer is protected by a lock, but it is not easily accessible.
|
||||
// Instead assume that typically this buffer is accessed only during termination, and
|
||||
// rely on caller guaranteeing this code to be executed only before system termination handlers.
|
||||
|
||||
writeStackTraceToBuffer(throwable, buffer, bufferSize);
|
||||
}
|
||||
|
||||
#endif // KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef RUNTIME_OBJCEXCEPTIONS_H
|
||||
#define RUNTIME_OBJCEXCEPTIONS_H
|
||||
|
||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
|
||||
#include "Common.h"
|
||||
#include "Types.h"
|
||||
|
||||
void ReportBacktraceToIosCrashLog(KRef throwable);
|
||||
|
||||
#endif // KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||
|
||||
#endif // RUNTIME_OBJCEXCEPTIONS_H
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package kotlin
|
||||
|
||||
import kotlin.native.internal.ExportForCppRuntime
|
||||
import kotlin.native.internal.ExportTypeInfo
|
||||
import kotlin.native.internal.NativePtrArray
|
||||
|
||||
@@ -23,6 +24,7 @@ public open class Throwable(open val message: String?, open val cause: Throwable
|
||||
|
||||
constructor() : this(null, null)
|
||||
|
||||
@get:ExportForCppRuntime("Kotlin_Throwable_getStackTrace")
|
||||
private val stackTrace = getCurrentStackTrace()
|
||||
|
||||
private val stackTraceStrings: Array<String> by lazy {
|
||||
|
||||
@@ -15,7 +15,12 @@ package kotlin.native.internal
|
||||
*
|
||||
* This annotation is not intended for the general consumption and is public only for the launcher!
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
|
||||
@Target(
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.PROPERTY_SETTER
|
||||
)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class ExportForCppRuntime(val name: String = "")
|
||||
|
||||
|
||||
@@ -157,7 +157,9 @@ class ClangArgs(private val configurables: Configurables) : Configurables by con
|
||||
|
||||
KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 ->
|
||||
listOf("-DKONAN_OBJC_INTEROP=1",
|
||||
"-DKONAN_HAS_CXX11_EXCEPTION_FUNCTIONS=1")
|
||||
"-DKONAN_HAS_CXX11_EXCEPTION_FUNCTIONS=1",
|
||||
"-DKONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG=1",
|
||||
"-DMACHSIZE=${target.architecture.bitness}")
|
||||
|
||||
KonanTarget.IOS_X64 ->
|
||||
listOf("-DKONAN_OBJC_INTEROP=1",
|
||||
|
||||
Reference in New Issue
Block a user