diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp index ad7911026dc..1b852218eb1 100644 --- a/runtime/src/main/cpp/Exceptions.cpp +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -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 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 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(); } } } diff --git a/runtime/src/main/cpp/ObjCExceptions.cpp b/runtime/src/main/cpp/ObjCExceptions.cpp new file mode 100644 index 00000000000..ca54e794263 --- /dev/null +++ b/runtime/src/main/cpp/ObjCExceptions.cpp @@ -0,0 +1,125 @@ +#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG + +#include +#include +#include +#include +#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(stackTrace, index); + int bytes = snprintf(bufferPointer, remainingBytes, "0x%" PRIxPTR " ", reinterpret_cast(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(&CFRunLoopRun), &info) == 0) return nullptr; + + return reinterpret_cast(info.dli_fbase); +} + +template +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(header + 1); + for (uint32_t loadCommandIndex = 0; loadCommandIndex < header->ncmds; ++loadCommandIndex) { + if (loadCommand->cmd == LC_SEGMENT_TARGET) { + segment_command_target* segmentCommand = reinterpret_cast(loadCommand); + if (bufferEqualsString(segmentCommand->segname, "__TEXT")) { + textVmaddr = segmentCommand->vmaddr; + } + + if (bufferEqualsString(segmentCommand->segname, "__DATA")) { + section_target* sections = reinterpret_cast(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(reinterpret_cast(header) + section->addr - textVmaddr); + } + } + } + } + + loadCommand = reinterpret_cast(reinterpret_cast(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 \ No newline at end of file diff --git a/runtime/src/main/cpp/ObjCExceptions.h b/runtime/src/main/cpp/ObjCExceptions.h new file mode 100644 index 00000000000..6b1d0ae77ad --- /dev/null +++ b/runtime/src/main/cpp/ObjCExceptions.h @@ -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 \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Throwable.kt b/runtime/src/main/kotlin/kotlin/Throwable.kt index 1f4fb4f19ee..fc2830cf2d3 100644 --- a/runtime/src/main/kotlin/kotlin/Throwable.kt +++ b/runtime/src/main/kotlin/kotlin/Throwable.kt @@ -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 by lazy { diff --git a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index c600fbe42cb..a214b34b50c 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -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 = "") diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt index 820e0943205..b39a071f0eb 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt @@ -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",