From 7f1ab29668738ef34b254e47383977f4ad6a0e7a Mon Sep 17 00:00:00 2001 From: Vladimir Sukharev Date: Wed, 24 Aug 2022 09:05:06 +0000 Subject: [PATCH] Fix Windows targets has bad addresses in stacktrace ^KT-49240 Fixed Merge-request: KT-MR-6883 Merged-by: Vladimir Sukharev --- .../backend.native/tests/build.gradle | 5 ++ .../kt-49240-stack-trace-completeness.kt | 41 ++++++++++++ .../org/jetbrains/kotlin/PlatformInfo.kt | 5 ++ .../runtime/src/main/cpp/ExecFormat.cpp | 14 +++- .../runtime/src/main/cpp/StackTrace.cpp | 65 +++++++++++++++---- .../runtime/src/main/cpp/StackTraceTest.cpp | 4 +- .../kotlin/konan/target/ClangArgs.kt | 1 + .../konan/target/KonanTargetExtenstions.kt | 5 +- 8 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 kotlin-native/backend.native/tests/runtime/exceptions/kt-49240-stack-trace-completeness.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 44ea8e37307..338105472c2 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -2760,6 +2760,11 @@ standaloneTest("stack_trace_inline") { outputChecker = { str -> str.split("\n").contains("0") } } +standaloneTest("kt-49240-stack-trace-completeness") { + disabled = !supportsExceptions(project) || project.globalTestArgs.contains('-opt') + source = "runtime/exceptions/kt-49240-stack-trace-completeness.kt" +} + standaloneTest("stack_trace_out_of_bounds") { disabled = !supportsCoreSymbolication(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') flags = ['-g', '-Xg-generate-debug-trampoline=enable', '-Xbinary=stripDebugInfoFromNativeLibs=false'] diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/kt-49240-stack-trace-completeness.kt b/kotlin-native/backend.native/tests/runtime/exceptions/kt-49240-stack-trace-completeness.kt new file mode 100644 index 00000000000..bdda0adbcd5 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/exceptions/kt-49240-stack-trace-completeness.kt @@ -0,0 +1,41 @@ +import kotlin.text.Regex +import kotlin.test.* + +var inlinesCount = 0 + +fun exception() { + error("FAIL!") +} + +fun main() { + try { + exception() + } + catch (e:Exception) { + val stackTrace = e.getStackTrace().filter { "kfun:" in it } + println("Kotlin part of call stack is:") + for (entry in stackTrace) + println(entry) + println("Verifying...") + val goldValues = arrayOf( + "kfun:kotlin.Throwable#(kotlin.String?){}", + "kfun:kotlin.Exception#(kotlin.String?){}", + "kfun:kotlin.RuntimeException#(kotlin.String?){}", + "kfun:kotlin.IllegalStateException#(kotlin.String?){}", + "kfun:#exception(){}", + "kfun:#main(){}", + ) + assertEquals(goldValues.size, stackTrace.size) + goldValues.zip(stackTrace).forEach { checkFrame(it.first, it.second) } + println("Passed") + } +} + +internal val regex = Regex("(kfun.+) \\+ (\\d+)") +internal fun checkFrame(goldFunName: String, actualLine: String) { + val findResult = regex.find(actualLine) + + val (funName, offset) = findResult?.destructured ?: throw Error("Cannot find '$goldFunName + ' in $actualLine") + assertEquals(goldFunName, funName) + assertTrue(offset.toInt() > 0) +} \ No newline at end of file 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 cd0e91145b4..c9cb7076c45 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 @@ -41,6 +41,11 @@ object PlatformInfo { return platformManager.targetManager(targetName).target } + @JvmStatic + fun supportsExceptions(project: Project): Boolean { + return getTarget(project).supportsExceptions() + } + @JvmStatic fun supportsLibBacktrace(project: Project): Boolean { return getTarget(project).supportsLibBacktrace() diff --git a/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp b/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp index 5b1e694dd83..7222c0cc706 100644 --- a/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp +++ b/kotlin-native/runtime/src/main/cpp/ExecFormat.cpp @@ -267,14 +267,22 @@ class SymbolTable { return (const void*)(imageBase + sectionHeader->VirtualAddress + symbol->Value); } + // Finds symbol having nearest smaller address IMAGE_SYMBOL* findFunctionSymbol(const void* address) { + IMAGE_SYMBOL* result = nullptr; + auto addressPtr = reinterpret_cast(address); + auto resultPtr = reinterpret_cast(nullptr); for (DWORD i = 0; i < numberOfSymbols; ++i) { IMAGE_SYMBOL* symbol = &symbols[i]; - if (symbol->Type == 0x20 && address == getSymbolAddress(symbol)) { - return symbol; + if (symbol->Type == 0x20) { + auto symbolPtr = reinterpret_cast(getSymbolAddress(symbol)); + if(resultPtr < symbolPtr && symbolPtr <= addressPtr) { + resultPtr = symbolPtr; + result = symbol; + } } } - return nullptr; + return result; } public: diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp index 54a93866834..f107d55814f 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp @@ -10,6 +10,15 @@ #elif USE_GCC_UNWIND // GCC unwinder for backtrace. #include +#if __MINGW64__ +#error // GCC unwinder in MinGW64/libgcc has a bugfix only in version 12. With previous libgcc versions, use RTL unwinder instead. +#endif + +#elif USE_WINAPI_UNWIND +// Use RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind +#include +#include + #else // Glibc backtrace() function. #include @@ -46,11 +55,7 @@ struct Backtrace { }; _Unwind_Ptr getUnwindPtr(_Unwind_Context* context) { -#if (__MINGW32__ || __MINGW64__) - return _Unwind_GetRegionStart(context); -#else return _Unwind_GetIP(context); -#endif } _Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) { @@ -76,6 +81,39 @@ _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { return _URC_NO_REASON; } +#elif USE_WINAPI_UNWIND +// winAPIUnwind() does: +// - if `result` is not empty -> stores IPs of stacktrace(ignoring first `skipCount` entries) into `result`, and returns amount of stored IPs +// - if `result` is empty -> returns depth of stacktrace(ignoring first `skipCount` entries) +NO_INLINE size_t winAPIUnwind(size_t skipCount, std_support::span result) +{ + size_t resultSize = result.size(); + bool doStoreIPs = resultSize > 0; + size_t currentSize = 0; + CONTEXT context = {}; + context.ContextFlags = CONTEXT_ALL; + RtlCaptureContext (&context); + do { + DWORD64 imageBase = 0; + UNWIND_HISTORY_TABLE historyTable = {}; + PRUNTIME_FUNCTION FunctionEntry = RtlLookupFunctionEntry (context.Rip, &imageBase, &historyTable); + if (!FunctionEntry) + break; + PVOID handlerData = nullptr; + ULONG64 establisherFramePointers[2] = { 0, 0 }; + RtlVirtualUnwind (UNW_FLAG_NHANDLER, imageBase, context.Rip, FunctionEntry, &context, &handlerData, establisherFramePointers, nullptr); + + if (skipCount > 0) { + skipCount--; + } else { + if(doStoreIPs) + result[currentSize] = reinterpret_cast(context.Rip); + ++currentSize; + } + } while (context.Rip != 0 && (currentSize < resultSize || !doStoreIPs)); + + return currentSize; +} #endif THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false; @@ -96,13 +134,8 @@ NO_INLINE std_support::vector kotlin::internal::GetCurrentStackTrace(size return {}; #else -#if (__MINGW32__ || __MINGW64__) - // Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller. - const size_t kSkipFrames = 2 + skipFrames; -#else // Skip GetCurrentStackTrace + anything asked by the caller. const size_t kSkipFrames = 1 + skipFrames; -#endif std_support::vector result; #if USE_GCC_UNWIND @@ -115,6 +148,13 @@ NO_INLINE std_support::vector kotlin::internal::GetCurrentStackTrace(size _Unwind_Backtrace(unwindCallback, static_cast(&traceHolder)); RuntimeAssert(result.size() == traceHolder.currentSize, "Expected and collected sizes of the stacktrace differ"); + return result; +#elif USE_WINAPI_UNWIND + size_t depth = winAPIUnwind(kSkipFrames, std_support::span()); + if (depth <= 0) return {}; + result.resize(depth); + + winAPIUnwind(kSkipFrames, std_support::span(result.data(), result.size())); return result; #else // Take into account this function and StackTrace::current. @@ -139,18 +179,15 @@ NO_INLINE size_t kotlin::internal::GetCurrentStackTrace(size_t skipFrames, std_s return {}; #else -#if (__MINGW32__ || __MINGW64__) - // Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller. - const size_t kSkipFrames = 2 + skipFrames; -#else // Skip GetCurrentStackTrace + anything asked by the caller. const size_t kSkipFrames = 1 + skipFrames; -#endif #if USE_GCC_UNWIND Backtrace traceHolder(kSkipFrames, buffer); _Unwind_Backtrace(unwindCallback, static_cast(&traceHolder)); return traceHolder.currentSize; +#elif USE_WINAPI_UNWIND + return winAPIUnwind(kSkipFrames, buffer); #else // Take into account this function and StackTrace::current. constexpr size_t maxSize = GetMaxStackTraceDepth() + 2; diff --git a/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp index 3649c865844..86f205b222b 100644 --- a/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp @@ -144,7 +144,7 @@ TEST(StackTraceTest, DeepStackTrace) { auto stackTrace = GetDeepStackTrace(knownStackDepth); auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data()); -#if USE_GCC_UNWIND +#if USE_GCC_UNWIND || USE_WINAPI_UNWIND EXPECT_GE(stackTrace.size(), knownStackDepth); size_t lastKnownIndex = knownStackDepth - 1; #else @@ -177,7 +177,7 @@ TEST(StackTraceTest, StackAllocatedDeepTraceWithEnoughCapacity) { constexpr size_t capacity = 150; auto stackTrace = GetDeepStackTrace(knownStackDepth); -#if USE_GCC_UNWIND +#if USE_GCC_UNWIND || USE_WINAPI_UNWIND EXPECT_GE(stackTrace.size(), knownStackDepth); #else // For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth). diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt index 30fe5dfc782..0dec6440262 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt @@ -64,6 +64,7 @@ sealed class ClangArgs( "__ANDROID__".takeIf { target.family == Family.ANDROID }, "USE_PE_COFF_SYMBOLS=1".takeIf { target.binaryFormat() == BinaryFormat.PE_COFF }, "UNICODE".takeIf { target.family == Family.MINGW }, + "USE_WINAPI_UNWIND=1".takeIf { target.supportsWinAPIUnwind() }, "USE_GCC_UNWIND=1".takeIf { target.supportsGccUnwind() } ) val customOptions = target.customArgsForKonanSources() diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt index a9011a50fd9..23036d33579 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt @@ -72,8 +72,9 @@ fun KonanTarget.supportsCoreSymbolication(): Boolean = KonanTarget.WATCHOS_X86, KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_SIMULATOR_ARM64 ) -fun KonanTarget.supportsGccUnwind(): Boolean = family == Family.ANDROID || family == Family.LINUX || family == Family.MINGW - +fun KonanTarget.supportsGccUnwind(): Boolean = family == Family.ANDROID || family == Family.LINUX || this is KonanTarget.MINGW_X86 +// MINGW_X64 target does not support GCC unwind, since its sysroot contains libgcc version < 12 having misfeature, see KT-49240 +fun KonanTarget.supportsWinAPIUnwind(): Boolean = this is KonanTarget.MINGW_X64 fun KonanTarget.supportsThreads(): Boolean = when(this) { is KonanTarget.WASM32 -> false