Fix Windows targets has bad addresses in stacktrace
^KT-49240 Fixed Merge-request: KT-MR-6883 Merged-by: Vladimir Sukharev <Vladimir.Sukharev@jetbrains.com>
This commit is contained in:
@@ -2760,6 +2760,11 @@ standaloneTest("stack_trace_inline") {
|
|||||||
outputChecker = { str -> str.split("\n").contains("0") }
|
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") {
|
standaloneTest("stack_trace_out_of_bounds") {
|
||||||
disabled = !supportsCoreSymbolication(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64')
|
disabled = !supportsCoreSymbolication(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64')
|
||||||
flags = ['-g', '-Xg-generate-debug-trampoline=enable', '-Xbinary=stripDebugInfoFromNativeLibs=false']
|
flags = ['-g', '-Xg-generate-debug-trampoline=enable', '-Xbinary=stripDebugInfoFromNativeLibs=false']
|
||||||
|
|||||||
+41
@@ -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#<init>(kotlin.String?){}",
|
||||||
|
"kfun:kotlin.Exception#<init>(kotlin.String?){}",
|
||||||
|
"kfun:kotlin.RuntimeException#<init>(kotlin.String?){}",
|
||||||
|
"kfun:kotlin.IllegalStateException#<init>(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 + <int>' in $actualLine")
|
||||||
|
assertEquals(goldFunName, funName)
|
||||||
|
assertTrue(offset.toInt() > 0)
|
||||||
|
}
|
||||||
@@ -41,6 +41,11 @@ object PlatformInfo {
|
|||||||
return platformManager.targetManager(targetName).target
|
return platformManager.targetManager(targetName).target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun supportsExceptions(project: Project): Boolean {
|
||||||
|
return getTarget(project).supportsExceptions()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun supportsLibBacktrace(project: Project): Boolean {
|
fun supportsLibBacktrace(project: Project): Boolean {
|
||||||
return getTarget(project).supportsLibBacktrace()
|
return getTarget(project).supportsLibBacktrace()
|
||||||
|
|||||||
@@ -267,14 +267,22 @@ class SymbolTable {
|
|||||||
return (const void*)(imageBase + sectionHeader->VirtualAddress + symbol->Value);
|
return (const void*)(imageBase + sectionHeader->VirtualAddress + symbol->Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds symbol having nearest smaller address
|
||||||
IMAGE_SYMBOL* findFunctionSymbol(const void* address) {
|
IMAGE_SYMBOL* findFunctionSymbol(const void* address) {
|
||||||
|
IMAGE_SYMBOL* result = nullptr;
|
||||||
|
auto addressPtr = reinterpret_cast<uintptr_t>(address);
|
||||||
|
auto resultPtr = reinterpret_cast<uintptr_t>(nullptr);
|
||||||
for (DWORD i = 0; i < numberOfSymbols; ++i) {
|
for (DWORD i = 0; i < numberOfSymbols; ++i) {
|
||||||
IMAGE_SYMBOL* symbol = &symbols[i];
|
IMAGE_SYMBOL* symbol = &symbols[i];
|
||||||
if (symbol->Type == 0x20 && address == getSymbolAddress(symbol)) {
|
if (symbol->Type == 0x20) {
|
||||||
return symbol;
|
auto symbolPtr = reinterpret_cast<uintptr_t>(getSymbolAddress(symbol));
|
||||||
|
if(resultPtr < symbolPtr && symbolPtr <= addressPtr) {
|
||||||
|
resultPtr = symbolPtr;
|
||||||
|
result = symbol;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -10,6 +10,15 @@
|
|||||||
#elif USE_GCC_UNWIND
|
#elif USE_GCC_UNWIND
|
||||||
// GCC unwinder for backtrace.
|
// GCC unwinder for backtrace.
|
||||||
#include <unwind.h>
|
#include <unwind.h>
|
||||||
|
#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 <windows.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Glibc backtrace() function.
|
// Glibc backtrace() function.
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
@@ -46,11 +55,7 @@ struct Backtrace {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_Unwind_Ptr getUnwindPtr(_Unwind_Context* context) {
|
_Unwind_Ptr getUnwindPtr(_Unwind_Context* context) {
|
||||||
#if (__MINGW32__ || __MINGW64__)
|
|
||||||
return _Unwind_GetRegionStart(context);
|
|
||||||
#else
|
|
||||||
return _Unwind_GetIP(context);
|
return _Unwind_GetIP(context);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) {
|
_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;
|
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<void*> 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<void*>(context.Rip);
|
||||||
|
++currentSize;
|
||||||
|
}
|
||||||
|
} while (context.Rip != 0 && (currentSize < resultSize || !doStoreIPs));
|
||||||
|
|
||||||
|
return currentSize;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
|
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
|
||||||
@@ -96,13 +134,8 @@ NO_INLINE std_support::vector<void*> kotlin::internal::GetCurrentStackTrace(size
|
|||||||
return {};
|
return {};
|
||||||
#else
|
#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.
|
// Skip GetCurrentStackTrace + anything asked by the caller.
|
||||||
const size_t kSkipFrames = 1 + skipFrames;
|
const size_t kSkipFrames = 1 + skipFrames;
|
||||||
#endif
|
|
||||||
|
|
||||||
std_support::vector<void*> result;
|
std_support::vector<void*> result;
|
||||||
#if USE_GCC_UNWIND
|
#if USE_GCC_UNWIND
|
||||||
@@ -115,6 +148,13 @@ NO_INLINE std_support::vector<void*> kotlin::internal::GetCurrentStackTrace(size
|
|||||||
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&traceHolder));
|
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&traceHolder));
|
||||||
RuntimeAssert(result.size() == traceHolder.currentSize, "Expected and collected sizes of the stacktrace differ");
|
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<void*>());
|
||||||
|
if (depth <= 0) return {};
|
||||||
|
result.resize(depth);
|
||||||
|
|
||||||
|
winAPIUnwind(kSkipFrames, std_support::span<void*>(result.data(), result.size()));
|
||||||
return result;
|
return result;
|
||||||
#else
|
#else
|
||||||
// Take into account this function and StackTrace::current.
|
// 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 {};
|
return {};
|
||||||
#else
|
#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.
|
// Skip GetCurrentStackTrace + anything asked by the caller.
|
||||||
const size_t kSkipFrames = 1 + skipFrames;
|
const size_t kSkipFrames = 1 + skipFrames;
|
||||||
#endif
|
|
||||||
|
|
||||||
#if USE_GCC_UNWIND
|
#if USE_GCC_UNWIND
|
||||||
Backtrace traceHolder(kSkipFrames, buffer);
|
Backtrace traceHolder(kSkipFrames, buffer);
|
||||||
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&traceHolder));
|
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&traceHolder));
|
||||||
return traceHolder.currentSize;
|
return traceHolder.currentSize;
|
||||||
|
#elif USE_WINAPI_UNWIND
|
||||||
|
return winAPIUnwind(kSkipFrames, buffer);
|
||||||
#else
|
#else
|
||||||
// Take into account this function and StackTrace::current.
|
// Take into account this function and StackTrace::current.
|
||||||
constexpr size_t maxSize = GetMaxStackTraceDepth<StackTraceCapacityKind::kFixed>() + 2;
|
constexpr size_t maxSize = GetMaxStackTraceDepth<StackTraceCapacityKind::kFixed>() + 2;
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ TEST(StackTraceTest, DeepStackTrace) {
|
|||||||
auto stackTrace = GetDeepStackTrace(knownStackDepth);
|
auto stackTrace = GetDeepStackTrace(knownStackDepth);
|
||||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||||
|
|
||||||
#if USE_GCC_UNWIND
|
#if USE_GCC_UNWIND || USE_WINAPI_UNWIND
|
||||||
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
||||||
size_t lastKnownIndex = knownStackDepth - 1;
|
size_t lastKnownIndex = knownStackDepth - 1;
|
||||||
#else
|
#else
|
||||||
@@ -177,7 +177,7 @@ TEST(StackTraceTest, StackAllocatedDeepTraceWithEnoughCapacity) {
|
|||||||
constexpr size_t capacity = 150;
|
constexpr size_t capacity = 150;
|
||||||
auto stackTrace = GetDeepStackTrace<capacity>(knownStackDepth);
|
auto stackTrace = GetDeepStackTrace<capacity>(knownStackDepth);
|
||||||
|
|
||||||
#if USE_GCC_UNWIND
|
#if USE_GCC_UNWIND || USE_WINAPI_UNWIND
|
||||||
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
||||||
#else
|
#else
|
||||||
// For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth).
|
// For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth).
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ sealed class ClangArgs(
|
|||||||
"__ANDROID__".takeIf { target.family == Family.ANDROID },
|
"__ANDROID__".takeIf { target.family == Family.ANDROID },
|
||||||
"USE_PE_COFF_SYMBOLS=1".takeIf { target.binaryFormat() == BinaryFormat.PE_COFF },
|
"USE_PE_COFF_SYMBOLS=1".takeIf { target.binaryFormat() == BinaryFormat.PE_COFF },
|
||||||
"UNICODE".takeIf { target.family == Family.MINGW },
|
"UNICODE".takeIf { target.family == Family.MINGW },
|
||||||
|
"USE_WINAPI_UNWIND=1".takeIf { target.supportsWinAPIUnwind() },
|
||||||
"USE_GCC_UNWIND=1".takeIf { target.supportsGccUnwind() }
|
"USE_GCC_UNWIND=1".takeIf { target.supportsGccUnwind() }
|
||||||
)
|
)
|
||||||
val customOptions = target.customArgsForKonanSources()
|
val customOptions = target.customArgsForKonanSources()
|
||||||
|
|||||||
+3
-2
@@ -72,8 +72,9 @@ fun KonanTarget.supportsCoreSymbolication(): Boolean =
|
|||||||
KonanTarget.WATCHOS_X86, KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_SIMULATOR_ARM64
|
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) {
|
fun KonanTarget.supportsThreads(): Boolean = when(this) {
|
||||||
is KonanTarget.WASM32 -> false
|
is KonanTarget.WASM32 -> false
|
||||||
|
|||||||
Reference in New Issue
Block a user