From c704fd6ae75f60faac97a87cdd55229ee9e52254 Mon Sep 17 00:00:00 2001 From: Pavel Kunyavskiy Date: Tue, 14 Sep 2021 11:09:36 +0300 Subject: [PATCH] [K/N] Support stacktrace using libbacktrace --- .../kotlin/backend/konan/BinaryOptions.kt | 2 + .../kotlin/backend/konan/KonanConfig.kt | 12 ++- .../kotlin/backend/konan/SourceInfoType.kt | 12 +++ .../kotlin/backend/konan/llvm/IrToBitcode.kt | 11 +++ .../backend.native/tests/build.gradle | 62 +++++++++++++-- .../tests/framework/stacktrace/stacktrace.kt | 17 +++++ .../framework/stacktrace/stacktrace.swift | 23 ++++++ .../stacktraceByLibbacktrace.kt | 21 ++++++ .../stacktraceByLibbacktrace.swift | 25 +++++++ .../tests/runtime/exceptions/kt-37572.kt | 27 ++++--- .../runtime/exceptions/stack_trace_inline.kt | 21 ++++-- .../org/jetbrains/kotlin/PlatformInfo.kt | 5 ++ .../main/kotlin/org/jetbrains/kotlin/Utils.kt | 4 + kotlin-native/runtime/build.gradle.kts | 16 ++-- .../src/main/cpp/CompilerConstants.cpp | 2 + .../src/main/cpp/CompilerConstants.hpp | 11 +++ .../runtime/src/main/cpp/SourceInfo.h | 25 +++---- .../runtime/src/main/cpp/StackTrace.cpp | 75 +++++++++++-------- .../runtime/src/release/cpp/SourceInfo.cpp | 21 ------ .../core_symbolication}/cpp/SourceInfo.cpp | 30 ++------ .../libbacktrace/cpp/SourceInfo.cpp | 51 +++++++++++++ 21 files changed, 350 insertions(+), 123 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SourceInfoType.kt create mode 100644 kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.kt create mode 100644 kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.swift create mode 100644 kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.kt create mode 100644 kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.swift delete mode 100644 kotlin-native/runtime/src/release/cpp/SourceInfo.cpp rename kotlin-native/runtime/src/{debug => source_info/core_symbolication}/cpp/SourceInfo.cpp (85%) create mode 100644 kotlin-native/runtime/src/source_info/libbacktrace/cpp/SourceInfo.cpp diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt index 08276bcb643..6aa0d9dfce9 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt @@ -19,6 +19,8 @@ object BinaryOptions : BinaryOptionRegistry() { val freezing by option() val stripDebugInfoFromNativeLibs by booleanOption() + + val sourceInfoType by option() } open class BinaryOption( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index c2b649f2280..9fffea88b3f 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -94,6 +94,11 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration else -> freezingMode } } + val sourceInfoType: SourceInfoType + get() = configuration.get(BinaryOptions.sourceInfoType) + ?: SourceInfoType.CORESYMBOLICATION.takeIf { debug && target.family.isAppleFamily } + ?: SourceInfoType.NOOP + val needVerifyIr: Boolean get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true @@ -184,7 +189,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration private val shouldCoverLibraries = !configuration.getList(KonanConfigKeys.LIBRARIES_TO_COVER).isNullOrEmpty() internal val runtimeNativeLibraries: List = mutableListOf().apply { - add(if (debug) "debug.bc" else "release.bc") + if (debug) add("debug.bc") val useMimalloc = if (configuration.get(KonanConfigKeys.ALLOCATION_MODE) == "mimalloc") { if (target.supportsMimallocAllocator()) { true @@ -220,6 +225,11 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration } } if (shouldCoverLibraries || shouldCoverSources) add("profileRuntime.bc") + add("source_info_core_symbolication.bc") + if (target.supportsLibBacktrace()) { + add("source_info_libbacktrace.bc") + add("libbacktrace.bc") + } if (useMimalloc) { add("opt_alloc.bc") add("mimalloc.bc") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SourceInfoType.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SourceInfoType.kt new file mode 100644 index 00000000000..132d559df30 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SourceInfoType.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan + +enum class SourceInfoType { + CORESYMBOLICATION, + LIBBACKTRACE, + NOOP, +} \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index d9608ed70d4..bb9c7b0b992 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -2631,6 +2631,17 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map null + SourceInfoType.LIBBACKTRACE -> "Kotlin_getSourceInfo_libbacktrace" + SourceInfoType.CORESYMBOLICATION -> "Kotlin_getSourceInfo_core_symbolication" + } + if (getSourceInfoFunctionName != null) { + val getSourceInfoFunction = LLVMGetNamedFunction(context.llvmModule, getSourceInfoFunctionName) + ?: LLVMAddFunction(context.llvmModule, getSourceInfoFunctionName, + functionType(int32Type, false, int8TypePtr, int8TypePtr, int32Type)) + overrideRuntimeGlobal("Kotlin_getSourceInfo_Function", constValue(getSourceInfoFunction!!)) + } } //-------------------------------------------------------------------------// diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 91a3c155696..8b831ca7c12 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -2669,16 +2669,24 @@ task extend_exception(type: KonanLocalTest) { } standaloneTest("check_stacktrace_format") { - disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') flags = ['-g'] source = "runtime/exceptions/check_stacktrace_format.kt" } +standaloneTest("check_stacktrace_format_coresymbolication") { + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xbinary=sourceInfoType=coresymbolication'] + source = "runtime/exceptions/check_stacktrace_format.kt" +} + standaloneTest("stack_trace_inline") { - // TODO: Enable after the test has been fixed. - disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') - flags = ['-g', '-Xg-generate-debug-trampoline=enable'] + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xg-generate-debug-trampoline=enable', '-Xbinary=sourceInfoType=coresymbolication'] source = "runtime/exceptions/stack_trace_inline.kt" + // test on simulator is writing Invalid connection: com.apple.coresymbolicationd by unknown reason + // so we need to use custom checker as workaround + outputChecker = { str -> str.split("\n").contains("0") } } standaloneTest("stack_trace_out_of_bounds") { @@ -2689,9 +2697,32 @@ standaloneTest("stack_trace_out_of_bounds") { standaloneTest("kt-37572") { - disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') - flags = ['-g'] + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xbinary=sourceInfoType=coresymbolication'] source = "runtime/exceptions/kt-37572.kt" + // test on simulator is writing Invalid connection: com.apple.coresymbolicationd by unknown reason + // so we need to use custom checker as workaround + outputChecker = { str -> str.split("\n").contains("0") } +} + +standaloneTest("check_stacktrace_format_libbacktrace") { + disabled = !supportsLibBacktrace(project)|| project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xbinary=sourceInfoType=libbacktrace'] + source = "runtime/exceptions/check_stacktrace_format.kt" +} + +standaloneTest("stack_trace_inline_libbacktrace") { + disabled = !supportsLibBacktrace(project) || project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xbinary=sourceInfoType=libbacktrace'] + source = "runtime/exceptions/stack_trace_inline.kt" + outputChecker = { str -> str.split("\n").contains("1") } +} + +standaloneTest("kt-37572-libbacktrace") { + disabled = !supportsLibBacktrace(project) || project.globalTestArgs.contains('-opt') + flags = ['-g', '-Xbinary=sourceInfoType=libbacktrace'] + source = "runtime/exceptions/kt-37572.kt" + outputChecker = { str -> str.split("\n").contains("2") } } standaloneTest("custom_hook") { @@ -5420,6 +5451,25 @@ if (isAppleTarget(project)) { } swiftSources = ['framework/kt43517/'] } + + frameworkTest("testStackTraceFramework") { + enabled = !project.globalTestArgs.contains('-opt') + framework('Stacktrace') { + sources = ['framework/stacktrace'] + opts = ['-g'] + } + swiftSources = ['framework/stacktrace/'] + } + + frameworkTest("testStackTraceByLibbacktraceFramework") { + enabled = !project.globalTestArgs.contains('-opt') + framework('StacktraceByLibbacktrace') { + sources = ['framework/stacktraceByLibbacktrace'] + opts = ['-g', '-Xbinary=sourceInfoType=libbacktrace'] + } + swiftSources = ['framework/stacktraceByLibbacktrace/'] + } + } /** diff --git a/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.kt b/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.kt new file mode 100644 index 00000000000..d7efee581da --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun inner1() : Nothing { + throw Exception() +} + +fun getStackTrace() : List { + try { + inner1() + } catch (e: Exception) { + return e.getStackTrace().toList() + } + return emptyList() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.swift b/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.swift new file mode 100644 index 00000000000..2bccb90e970 --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/stacktrace/stacktrace.swift @@ -0,0 +1,23 @@ +import Foundation +import Stacktrace + +func testStackTrace() throws { + let trace = StacktraceKt.getStackTrace() + try assertTrue(trace[0].contains("Throwable.kt")) + try assertTrue(trace[1].contains("Exceptions.kt")) + try assertTrue(trace[2].contains("stacktrace.kt:7")) + try assertTrue(trace[3].contains("stacktrace.kt:12")) + try assertTrue(trace[5].contains("stacktrace.swift:4")) + try assertTrue(trace[6].contains("main.swift")) +} + +class StacktraceTests : TestProvider { + var tests: [TestCase] = [] + + init() { + providers.append(self) + tests = [ + TestCase(name: "Stacktrace", method: withAutorelease(testStackTrace)), + ] + } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.kt b/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.kt new file mode 100644 index 00000000000..03635e59637 --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +inline fun inner2() : Nothing { + throw Exception() +} + +fun inner1() : Nothing { + inner2() +} + +fun getStackTrace() : List { + try { + inner1() + } catch (e: Exception) { + return e.getStackTrace().toList() + } + return emptyList() +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.swift b/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.swift new file mode 100644 index 00000000000..426a297bb76 --- /dev/null +++ b/kotlin-native/backend.native/tests/framework/stacktraceByLibbacktrace/stacktraceByLibbacktrace.swift @@ -0,0 +1,25 @@ +import Foundation +import StacktraceByLibbacktrace + +func testStackTrace() throws { + let trace = StacktraceByLibbacktraceKt.getStackTrace() + try assertTrue(trace[0].contains("Throwable.kt")) + try assertTrue(trace[1].contains("Exceptions.kt")) + try assertTrue(trace[2].contains("stacktraceByLibbacktrace.kt:7")) + try assertTrue(trace[2].contains("[inlined]")) + try assertTrue(trace[3].contains("stacktraceByLibbacktrace.kt:11")) + try assertTrue(trace[4].contains("stacktraceByLibbacktrace.kt:16")) + try assertTrue(trace[6].contains("stacktraceByLibbacktrace.swift:5")) + try assertTrue(trace[7].contains("main.swift:126")) +} + +class StacktraceByLibbacktraceTests : TestProvider { + var tests: [TestCase] = [] + + init() { + providers.append(self) + tests = [ + TestCase(name: "Stacktrace", method: withAutorelease(testStackTrace)), + ] + } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/kt-37572.kt b/kotlin-native/backend.native/tests/runtime/exceptions/kt-37572.kt index eee281de776..01d2b05ac70 100644 --- a/kotlin-native/backend.native/tests/runtime/exceptions/kt-37572.kt +++ b/kotlin-native/backend.native/tests/runtime/exceptions/kt-37572.kt @@ -1,13 +1,17 @@ import kotlin.text.Regex import kotlin.test.* +var inlinesCount = 0 + fun main() { try { foo() } catch (tw:Throwable) { - val stackTrace = tw.getStackTrace() + val stackTrace = tw.getStackTrace(); + inlinesCount = stackTrace.count { it.contains("[inlined]")} stackTrace.take(6).forEach(::checkFrame) } + println(inlinesCount) } fun foo() { @@ -25,17 +29,22 @@ fun throwException() { throw Error() } internal val regex = Regex("^(\\d+)\\ +.*/(.*):(\\d+):.*$") -internal val goldValues = arrayOf?>( - null, - null, - "kt-37572.kt" to 25, - "kt-37572.kt" to 16, - "kt-37572.kt" to 6, - "kt-37572.kt" to 4) internal fun checkFrame(value:String) { + val goldValues = arrayOf?>( + null, + null, + "kt-37572.kt" to 29, + "kt-37572.kt" to 20, + *(if (inlinesCount != 0) arrayOf( + "kt-37572.kt" to 25, + "kt-37572.kt" to 18, + ) else emptyArray()), + "kt-37572.kt" to 8, + "kt-37572.kt" to 6) + val (pos, file, line) = regex.find(value)!!.destructured - goldValues[pos.toInt()]?.let{ + goldValues[pos.toInt()]?.let { assertEquals(it.first, file) assertEquals(it.second, line.toInt()) } diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_inline.kt b/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_inline.kt index 6b4514726cc..1b558c96425 100644 --- a/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_inline.kt +++ b/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_inline.kt @@ -1,6 +1,8 @@ import kotlin.text.Regex import kotlin.test.* +var inlinesCount = 0 + fun exception() { error("FAIL!") } @@ -11,20 +13,23 @@ fun main() { } catch (e:Exception) { val stackTrace = e.getStackTrace() + inlinesCount = stackTrace.count { it.contains("[inlined]")} stackTrace.take(6).forEach(::checkFrame) } + println(inlinesCount) } internal val regex = Regex("^(\\d+)\\ +.*/(.*):(\\d+):.*$") -internal val goldValues = arrayOf?>( - null, - null, - null, - null, - "stack_trace_inline.kt" to 5, - "stack_trace_inline.kt" to 10) internal fun checkFrame(value:String) { + val goldValues = arrayOf?>( + null, + null, + null, + null, + *(if (inlinesCount != 0) arrayOf(null) else emptyArray()), + "stack_trace_inline.kt" to 7, + "stack_trace_inline.kt" to 12) val (pos, file, line) = regex.find(value)!!.destructured - goldValues[pos.toInt()]?.let{ + goldValues[pos.toInt()]?.let { assertEquals(it.first, file) assertEquals(it.second, line.toInt()) } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt index de753ffbc39..7db3615d342 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/PlatformInfo.kt @@ -37,6 +37,11 @@ object PlatformInfo { return platformManager.targetManager(targetName).target } + @JvmStatic + fun supportsLibBacktrace(project: Project): Boolean { + return getTarget(project).supportsLibBacktrace() + } + @JvmStatic fun checkXcodeVersion(project: Project) { val properties = PropertiesProvider(project) diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt index 035e6172415..80e2d264ea5 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt @@ -325,6 +325,10 @@ fun compileSwift(project: Project, target: KonanTarget, sources: List, o fun targetSupportsMimallocAllocator(targetName: String) = HostManager().targetByName(targetName).supportsMimallocAllocator() +fun targetSupportsLibBacktrace(targetName: String) = + HostManager().targetByName(targetName).supportsLibBacktrace() + + fun Project.mergeManifestsByTargets(source: File, destination: File) { logger.info("Merging manifests: $source -> $destination") diff --git a/kotlin-native/runtime/build.gradle.kts b/kotlin-native/runtime/build.gradle.kts index 2bbbe533c9f..064f32f65f6 100644 --- a/kotlin-native/runtime/build.gradle.kts +++ b/kotlin-native/runtime/build.gradle.kts @@ -35,7 +35,8 @@ bitcode { "${target}Libbacktrace", "${target}Launcher", "${target}Debug", - "${target}Release", + "${target}SourceInfoCoreSymbolication", + "${target}SourceInfoLibbacktrace", "${target}Strict", "${target}Relaxed", "${target}ProfileRuntime", @@ -118,9 +119,14 @@ bitcode { includeRuntime() } - create("release") { + create("source_info_core_symbolication", file("src/source_info/core_symbolication")) { includeRuntime() } + create("source_info_libbacktrace", file("src/source_info/libbacktrace")) { + includeRuntime() + headersDirs += files("src/libbacktrace/c/include") + onlyIf { targetSupportsLibBacktrace(target) } + } create("strict") { includeRuntime() @@ -183,7 +189,6 @@ targetList.forEach { targetName -> "${targetName}Runtime", "${targetName}LegacyMemoryManager", "${targetName}Strict", - "${targetName}Release", "${targetName}StdAlloc", "${targetName}Objc" ) @@ -199,7 +204,6 @@ targetList.forEach { targetName -> "${targetName}Runtime", "${targetName}LegacyMemoryManager", "${targetName}Strict", - "${targetName}Release", "${targetName}Mimalloc", "${targetName}OptAlloc", "${targetName}Objc" @@ -217,7 +221,6 @@ targetList.forEach { targetName -> "${targetName}ExperimentalMemoryManagerStms", "${targetName}CommonGc", "${targetName}SameThreadMsGc", - "${targetName}Release", "${targetName}Mimalloc", "${targetName}OptAlloc", "${targetName}Objc" @@ -236,7 +239,6 @@ targetList.forEach { targetName -> "${targetName}ExperimentalMemoryManagerStms", "${targetName}CommonGc", "${targetName}SameThreadMsGc", - "${targetName}Release", "${targetName}StdAlloc", "${targetName}Objc" ) @@ -254,7 +256,6 @@ targetList.forEach { targetName -> "${targetName}ExperimentalMemoryManagerNoop", "${targetName}CommonGc", "${targetName}NoopGc", - "${targetName}Release", "${targetName}Mimalloc", "${targetName}OptAlloc", "${targetName}Objc" @@ -273,7 +274,6 @@ targetList.forEach { targetName -> "${targetName}ExperimentalMemoryManagerNoop", "${targetName}CommonGc", "${targetName}NoopGc", - "${targetName}Release", "${targetName}StdAlloc", "${targetName}Objc" ) diff --git a/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp b/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp index 98644a0ec61..091b4a74ffd 100644 --- a/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp +++ b/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp @@ -6,6 +6,7 @@ #include "CompilerConstants.hpp" #include "Common.h" +#include "SourceInfo.h" using namespace kotlin; @@ -14,6 +15,7 @@ RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1; RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0; RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0; RUNTIME_WEAK int32_t Kotlin_freezingEnabled = 1; +RUNTIME_WEAK const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function = nullptr; ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept { return static_cast(Kotlin_destroyRuntimeMode); diff --git a/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp b/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp index b21310be196..fbf55c56509 100644 --- a/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp +++ b/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp @@ -28,6 +28,9 @@ using string_view = std::experimental::string_view; extern "C" const int32_t KonanNeedDebugInfo; extern "C" const int32_t Kotlin_runtimeAssertsMode; extern "C" const char* const Kotlin_runtimeLogs; +class SourceInfo; +using Kotlin_getSourceInfo_FunctionType = int(*)(void * /*addr*/, SourceInfo* /*result*/, int /*result_size*/); +extern "C" const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function; namespace kotlin { namespace compiler { @@ -71,6 +74,14 @@ ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept { bool freezingEnabled() noexcept; +ALWAYS_INLINE inline int getSourceInfo(void* addr, SourceInfo *result, int result_size) { + if (Kotlin_getSourceInfo_Function == nullptr) { + return 0; + } else { + return Kotlin_getSourceInfo_Function(addr, result, result_size); + } +} + } // namespace compiler } // namespace kotlin diff --git a/kotlin-native/runtime/src/main/cpp/SourceInfo.h b/kotlin-native/runtime/src/main/cpp/SourceInfo.h index 41e33256acc..8d302be7c5c 100644 --- a/kotlin-native/runtime/src/main/cpp/SourceInfo.h +++ b/kotlin-native/runtime/src/main/cpp/SourceInfo.h @@ -17,22 +17,15 @@ #ifndef RUNTIME_SOURCEINFO_H #define RUNTIME_SOURCEINFO_H -struct SourceInfo { - const char* fileName; - int lineNumber; - int column; +#include + +class SourceInfo { + std::string fileName; +public: + int lineNumber = -1; + int column = -1; + std::string& getFileName() { return fileName; } + void setFilename(const char *newFileName) { fileName = newFileName ?: ""; } }; -#ifdef __cplusplus -extern "C" { -#endif - -// returns number of filled elements in buffer -// there can be several frames because of inlining -int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size); - -#ifdef __cplusplus -} // extern "C" -#endif - #endif // RUNTIME_SOURCEINFO_H diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp index 1ae99a1a8b3..e2eb84de6f0 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp @@ -69,9 +69,9 @@ _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false; -#if !KONAN_NO_BACKTRACE && !USE_GCC_UNWIND +#if !KONAN_NO_BACKTRACE int getSourceInfo(void* symbol, SourceInfo *result, int result_len) { - return disallowSourceInfo ? 0 : Kotlin_getSourceInfo(symbol, result, result_len); + return disallowSourceInfo ? 0 : compiler::getSourceInfo(symbol, result, result_len); } #endif @@ -118,61 +118,76 @@ KStdVector kotlin::GetStackTraceStrings(void* const* stackTrace, siz #else KStdVector strings; strings.reserve(stackTraceSize); -#if USE_GCC_UNWIND - for (size_t index = 0; index < stackTraceSize; ++index) { - KNativePtr address = stackTrace[index]; - char symbol[512]; - if (!AddressToSymbol(address, symbol, sizeof(symbol))) { - // Make empty string: - symbol[0] = '\0'; - } - char line[512]; - konan::snprintf(line, sizeof(line) - 1, "%s (%p)", symbol, (void*)(intptr_t)address); - strings.push_back(line); - } -#else if (stackTraceSize > 0) { +#ifndef USE_GCC_UNWIND char** symbols = backtrace_symbols(stackTrace, static_cast(stackTraceSize)); RuntimeCheck(symbols != nullptr, "Not enough memory to retrieve the stacktrace"); +#endif + SourceInfo buffer[10]; // outside of the loop to avoid calling constructors and destructors each time for (size_t index = 0; index < stackTraceSize; ++index) { KNativePtr address = stackTrace[index]; - SourceInfo buffer[10]; - int frames = getSourceInfo(address, buffer, std::size(buffer)); + if (!address || reinterpret_cast(address) == 1) continue; + int frames_or_overflow = getSourceInfo(address, buffer, std::size(buffer)); + int frames = std::min(frames_or_overflow, std::size(buffer)); +#if USE_GCC_UNWIND + char symbol_[512]; + if (!AddressToSymbol(address, symbol_, sizeof(symbol_))) { + // Make empty string: + symbol_[0] = '\0'; + } + const char* symbol = symbol_; +#else const char* symbol = symbols[index]; - // returned symbol starts with frame number - // we need to override it, because of inlining, so let's just drop first token - while (*symbol != '\0' && *symbol != ' ') symbol++; - while (*symbol == ' ') symbol++; + // On MacOs symbol name contain index, executable file containing symbol and address + // but it doesn't contain them on other platforms + // So we skip first three tokens to make things work similar on all platforms + for (int it = 0; it < 3; it++) { + while (*symbol != '\0' && *symbol != ' ') symbol++; + while (*symbol == ' ') symbol++; + } // probably, this can't happen, but let's print at least something if (*symbol == '\0') symbol = symbols[index]; +#endif bool isSomethingPrinted = false; char line[1024]; for (int frame = 0; frame < frames; frame++) { auto &sourceInfo = buffer[frame]; - if (sourceInfo.fileName != nullptr) { - const char* inline_tag = (frame == frames - 1) ? "" : "[inlined] "; + if (!sourceInfo.getFileName().empty()) { + bool is_last = frame == frames - 1; + if (is_last && frames_or_overflow != frames) { + konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s (some inlined frames skipped)", strings.size(), address, symbol); + strings.push_back(line); + } + const char* inline_tag = is_last ? "" : "[inlined] "; if (sourceInfo.lineNumber != -1) { - konan::snprintf( - line, sizeof(line) - 1, "%-4zd %s %s(%s:%d:%d)", strings.size(), symbol, inline_tag, - sourceInfo.fileName, sourceInfo.lineNumber, buffer[frame].column); + if (sourceInfo.column != -1) { + konan::snprintf( + line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:%d:%d)", strings.size(), address, symbol, inline_tag, + sourceInfo.getFileName().c_str(), sourceInfo.lineNumber, sourceInfo.column); + } else { + konan::snprintf( + line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:%d)", strings.size(), address, symbol, inline_tag, + sourceInfo.getFileName().c_str(), sourceInfo.lineNumber); + } } else { - konan::snprintf(line, sizeof(line) - 1, "%-4zd %s %s(%s:)", strings.size(), symbol, inline_tag, - sourceInfo.fileName); + konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:)", strings.size(), address, symbol, inline_tag, + sourceInfo.getFileName().c_str()); } isSomethingPrinted = true; strings.push_back(line); } } if (!isSomethingPrinted) { - konan::snprintf(line, sizeof(line) - 1, "%-4zd %s", strings.size(), symbol); + konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s", strings.size(), address, symbol); strings.push_back(line); } } // Not konan::free. Used to free memory allocated in backtrace_symbols where malloc is used. +#ifndef USE_GCC_UNWIND free(symbols); - } #endif + } return strings; #endif // !KONAN_NO_BACKTRACE } diff --git a/kotlin-native/runtime/src/release/cpp/SourceInfo.cpp b/kotlin-native/runtime/src/release/cpp/SourceInfo.cpp deleted file mode 100644 index b07098290e3..00000000000 --- a/kotlin-native/runtime/src/release/cpp/SourceInfo.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SourceInfo.h" - -int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size) { - return 0; -} \ No newline at end of file diff --git a/kotlin-native/runtime/src/debug/cpp/SourceInfo.cpp b/kotlin-native/runtime/src/source_info/core_symbolication/cpp/SourceInfo.cpp similarity index 85% rename from kotlin-native/runtime/src/debug/cpp/SourceInfo.cpp rename to kotlin-native/runtime/src/source_info/core_symbolication/cpp/SourceInfo.cpp index ca50bd1312b..02a50717231 100644 --- a/kotlin-native/runtime/src/debug/cpp/SourceInfo.cpp +++ b/kotlin-native/runtime/src/source_info/core_symbolication/cpp/SourceInfo.cpp @@ -1,17 +1,6 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ #include "SourceInfo.h" @@ -127,9 +116,9 @@ typedef struct { int end; } SymbolSourceInfoLimits; -extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int result_size) { +extern "C" int Kotlin_getSourceInfo_core_symbolication(void* addr, SourceInfo *result_buffer, int result_size) { if (result_size == 0) return 0; - __block SourceInfo result = { .fileName = nullptr, .lineNumber = -1, .column = -1 }; + __block SourceInfo result; __block bool continueUpdateResult = true; __block SymbolSourceInfoLimits limits = {.start = -1, .end = -1}; @@ -168,7 +157,7 @@ extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int r }); SYM_LOG("limits: {%s %d..%d}\n", limits.fileName, limits.start, limits.end); - result.fileName = limits.fileName; + result.setFilename(limits.fileName); CSSymbolForeachSourceInfo(symbol, ^(CSSourceInfoRef ref) { @@ -208,11 +197,4 @@ extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int r result_buffer[0] = result; return 1; } - -#else // KONAN_CORE_SYMBOLICATION - -extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size) { - return 0; -} - -#endif // KONAN_CORE_SYMBOLICATION +#endif // KONAN_CORE_SYMBOLICATION \ No newline at end of file diff --git a/kotlin-native/runtime/src/source_info/libbacktrace/cpp/SourceInfo.cpp b/kotlin-native/runtime/src/source_info/libbacktrace/cpp/SourceInfo.cpp new file mode 100644 index 00000000000..e425cac71ef --- /dev/null +++ b/kotlin-native/runtime/src/source_info/libbacktrace/cpp/SourceInfo.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +#include "SourceInfo.h" +#include "backtrace.h" + +#include + +extern "C" int Kotlin_getSourceInfo_libbacktrace(void* addr, SourceInfo *result, int result_size) { + if (result_size == 0) return 0; + /** + * This is hack for better traces. + * backtrace function returns address after call instruction, and address detection need call instruction itself + * For honest solution, we should distinguish backtrace symbols got from signal handlers frames, ordinary frames, + * and addresses got from somewhere else. But for now, we assume all addresses are ordinary backtrace frames. + */ + addr = reinterpret_cast(reinterpret_cast(addr) - 1); + auto ignore_error = [](void*, const char*, int){}; + static auto state = backtrace_create_state(nullptr, 1, ignore_error, nullptr); + if (!state) return 0; + struct callback_arg_t { + SourceInfo *result; + int result_ptr; + int result_size; + int total_count; + } callback_arg; + callback_arg.result = result; + callback_arg.result_ptr = 0; + callback_arg.result_size = result_size; + callback_arg.total_count = 0; + auto process_line = [](void *data, uintptr_t pc, const char *filename, int lineno, int column, const char *function) -> int { + auto &callback_arg = *static_cast(data); + // Non-inlined frame would be last one, it's better to have it, then intermediate ones + if (callback_arg.result_ptr == callback_arg.result_size) { + callback_arg.result_ptr--; + } + auto &info = callback_arg.result[callback_arg.result_ptr]; + info.setFilename(filename); + info.lineNumber = lineno; + info.column = column; + callback_arg.result_ptr++; + callback_arg.total_count++; + // Let's stop at least at some point + // Probably, this can happen only if debug info is corrupted + return callback_arg.total_count > callback_arg.result_size * 10; + }; + backtrace_pcinfo(state, reinterpret_cast(addr), process_line, ignore_error, &callback_arg); + return callback_arg.total_count; +} \ No newline at end of file