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 "SourceInfo.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "ObjCExceptions.h"
|
||||||
|
|
||||||
namespace {
|
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) {
|
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
||||||
OnUnhandledException(throwable);
|
OnUnhandledException(throwable);
|
||||||
|
|
||||||
|
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||||
|
{
|
||||||
|
LockGuard<SimpleMutex> lock(terminatingMutex);
|
||||||
|
if (!terminating) {
|
||||||
|
ReportBacktraceToIosCrashLog(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
konan::abort();
|
konan::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,12 +256,23 @@ RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
|||||||
|
|
||||||
static void (*oldTerminateHandler)() = nullptr;
|
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() {
|
static void KonanTerminateHandler() {
|
||||||
auto currentException = std::current_exception();
|
auto currentException = std::current_exception();
|
||||||
RuntimeCheck(oldTerminateHandler != nullptr, "Underlying exception handler is not set.");
|
|
||||||
if (!currentException) {
|
if (!currentException) {
|
||||||
// No current exception.
|
// No current exception.
|
||||||
oldTerminateHandler();
|
callOldTerminateHandler();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(currentException);
|
std::rethrow_exception(currentException);
|
||||||
@@ -253,7 +280,7 @@ static void KonanTerminateHandler() {
|
|||||||
TerminateWithUnhandledException(e.obj());
|
TerminateWithUnhandledException(e.obj());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// Not a Kotlin exception.
|
// 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
|
package kotlin
|
||||||
|
|
||||||
|
import kotlin.native.internal.ExportForCppRuntime
|
||||||
import kotlin.native.internal.ExportTypeInfo
|
import kotlin.native.internal.ExportTypeInfo
|
||||||
import kotlin.native.internal.NativePtrArray
|
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)
|
constructor() : this(null, null)
|
||||||
|
|
||||||
|
@get:ExportForCppRuntime("Kotlin_Throwable_getStackTrace")
|
||||||
private val stackTrace = getCurrentStackTrace()
|
private val stackTrace = getCurrentStackTrace()
|
||||||
|
|
||||||
private val stackTraceStrings: Array<String> by lazy {
|
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!
|
* 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)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
public annotation class ExportForCppRuntime(val name: String = "")
|
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 ->
|
KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 ->
|
||||||
listOf("-DKONAN_OBJC_INTEROP=1",
|
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 ->
|
KonanTarget.IOS_X64 ->
|
||||||
listOf("-DKONAN_OBJC_INTEROP=1",
|
listOf("-DKONAN_OBJC_INTEROP=1",
|
||||||
|
|||||||
Reference in New Issue
Block a user