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;