Write Kotlin uncaught exception stacktrace to iOS crash log (#2645)

This commit is contained in:
SvyatoslavScherbina
2019-02-12 11:09:28 +03:00
committed by GitHub
parent 8d1ef86270
commit 9955b2cbf1
6 changed files with 179 additions and 5 deletions
+30 -3
View File
@@ -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();
}
}
}
+125
View File
@@ -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 = &sections[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
+13
View File
@@ -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",