From 98e4d679009bacf0301c56734944953087a10c43 Mon Sep 17 00:00:00 2001 From: Pavel Kunyavskiy Date: Wed, 25 Aug 2021 16:56:08 +0300 Subject: [PATCH] [K/N] Add opt-in flag to use debug info from native libs Unfortunately, llvm removes full debug info from module on any error. Different version debug info in bitcode is not always compatible, also it adds this debug info additional requirements on generated debug info. So this feature is quite unstable and shouldn't be enabled by default, although it has almost no downsides when worked correctly. --- .../kotlin/backend/konan/BinaryOptions.kt | 2 + .../kotlin/backend/konan/CacheSupport.kt | 1 + .../kotlin/backend/konan/CompilerOutput.kt | 3 ++ .../jetbrains/kotlin/backend/konan/Context.kt | 2 + .../backend/konan/llvm/objc/linkObjC.kt | 4 ++ .../backend.native/tests/build.gradle | 7 ++++ .../exceptions/stack_trace_out_of_bounds.kt | 37 +++++++++++++++++++ kotlin-native/runtime/src/main/cpp/Common.h | 1 + .../runtime/src/mm/cpp/CallsChecker.cpp | 2 +- 9 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_out_of_bounds.kt 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 3741765908c..08276bcb643 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 @@ -17,6 +17,8 @@ object BinaryOptions : BinaryOptionRegistry() { val memoryModel by option() val freezing by option() + + val stripDebugInfoFromNativeLibs by booleanOption() } open class BinaryOption( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt index 36187d24aad..b77ed5c4157 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CacheSupport.kt @@ -56,6 +56,7 @@ class CacheSupport( configuration.getBoolean(KonanConfigKeys.OPTIMIZATION) -> "for optimized compilation" configuration.get(BinaryOptions.memoryModel) == MemoryModel.EXPERIMENTAL -> "with experimental memory model" configuration.getBoolean(KonanConfigKeys.PROPERTY_LAZY_INITIALIZATION) -> "with experimental lazy top levels initialization" + configuration.get(BinaryOptions.stripDebugInfoFromNativeLibs) == false -> "with native libs debug info" else -> null } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index 21f4494d8ff..b26109d3533 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -185,6 +185,9 @@ internal fun produceOutput(context: Context) { private fun parseAndLinkBitcodeFile(context: Context, llvmModule: LLVMModuleRef, path: String) { val parsedModule = parseBitcodeFile(path) + if (!context.shouldUseDebugInfoFromNativeLibs()) { + LLVMStripModuleDebugInfo(parsedModule) + } val failed = llvmLinkModules2(context, llvmModule, parsedModule) if (failed != 0) { throw Error("failed to link $path") // TODO: retrieve error message from LLVM. diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 31be973f001..68a15af5fcc 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -443,6 +443,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { fun shouldContainDebugInfo() = config.debug fun shouldContainLocationDebugInfo() = shouldContainDebugInfo() || config.lightDebug fun shouldContainAnyDebugInfo() = shouldContainDebugInfo() || shouldContainLocationDebugInfo() + fun shouldUseDebugInfoFromNativeLibs() = shouldContainAnyDebugInfo() && + config.configuration.get(BinaryOptions.stripDebugInfoFromNativeLibs) == false fun shouldOptimize() = config.configuration.getBoolean(KonanConfigKeys.OPTIMIZATION) fun ghaEnabled() = ::globalHierarchyAnalysisResult.isInitialized diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt index 31886c8f73b..d1d1fc815b8 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt @@ -25,6 +25,10 @@ internal fun linkObjC(context: Context) { patchBuilder.buildAndApply(parsedModule) + if (!context.shouldUseDebugInfoFromNativeLibs()) { + LLVMStripModuleDebugInfo(parsedModule) + } + val failed = llvmLinkModules2(context, context.llvmModule!!, parsedModule) if (failed != 0) { throw Error("failed to link $bitcodeFile") diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 394650dd75f..be29c5cc4a0 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -2618,6 +2618,13 @@ standaloneTest("stack_trace_inline") { source = "runtime/exceptions/stack_trace_inline.kt" } +standaloneTest("stack_trace_out_of_bounds") { + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') + flags = ['-g', '-Xg-generate-debug-trampoline=enable', '-Xbinary=stripDebugInfoFromNativeLibs=false'] + source = "runtime/exceptions/stack_trace_out_of_bounds.kt" +} + + standaloneTest("kt-37572") { disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') flags = ['-g'] diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_out_of_bounds.kt b/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_out_of_bounds.kt new file mode 100644 index 00000000000..f1b023f53f8 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/exceptions/stack_trace_out_of_bounds.kt @@ -0,0 +1,37 @@ +import kotlin.text.Regex +import kotlin.test.* + +fun main() { + try { + val array = intArrayOf(1, 2, 3, 4) + println(array[4]) + } + catch (e:Exception) { + val stackTrace = e.getStackTrace() + stackTrace.take(goldValues.size).forEach(::checkFrame) + } +} + + +internal val regex = Regex("^(\\d+)\\ +.*/(.*):(\\d+):.*$") +internal val goldValues = arrayOf( + "Throwable.kt" to null, + "Exceptions.kt" to null, + "Exceptions.kt" to null, + "Exceptions.kt" to null, + "Exceptions.kt" to null, + "RuntimeUtils.kt" to null, + "Arrays.cpp" to null, + "stack_trace_out_of_bounds.kt" to 7, + "stack_trace_out_of_bounds.kt" to 4, + "launcher.cpp" to null, +) +internal fun checkFrame(value:String) { + val (pos, file, line) = regex.find(value)!!.destructured + goldValues[pos.toInt()]?.let{ + assertEquals(it.first, file) + if (it.second != null) { + assertEquals(it.second, line.toInt()) + } + } +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/cpp/Common.h b/kotlin-native/runtime/src/main/cpp/Common.h index 3f3eb20fd61..d931e9e2b2c 100644 --- a/kotlin-native/runtime/src/main/cpp/Common.h +++ b/kotlin-native/runtime/src/main/cpp/Common.h @@ -23,6 +23,7 @@ #define RUNTIME_PURE __attribute__((pure)) #define RUNTIME_USED __attribute__((used)) #define RUNTIME_WEAK __attribute__((weak)) +#define RUNTIME_NODEBUG __attribute__((nodebug)) #define ALWAYS_INLINE __attribute__((always_inline)) #define NO_INLINE __attribute__((noinline)) diff --git a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp index 2f113f78b48..1702dcc4415 100644 --- a/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp +++ b/kotlin-native/runtime/src/mm/cpp/CallsChecker.cpp @@ -320,7 +320,7 @@ constexpr int CALLED_LLVM_BUILTIN = -2; * should not be accessed. So before guard checking we need to check is thread destructor is running, * which requires special handling of recursive calls from this check. */ -extern "C" RUNTIME_NOTHROW void Kotlin_mm_checkStateAtExternalFunctionCall(const char* caller, const char *callee, const void *calleePtr) noexcept { +extern "C" RUNTIME_NOTHROW RUNTIME_NODEBUG void Kotlin_mm_checkStateAtExternalFunctionCall(const char* caller, const char *callee, const void *calleePtr) noexcept { if (reinterpret_cast(calleePtr) == MSG_SEND_TO_NULL) return; // objc_sendMsg called on nil, it does nothing, so it's ok if (konan::isOnThreadExitNotSetOrAlreadyStarted()) return; static thread_local bool recursiveCallGuard = false;