Extract StackTrace from Exceptions
This commit is contained in:
committed by
Space
parent
bd0f48e07a
commit
c286a9d8c6
@@ -961,7 +961,7 @@ clangDebugFlags.wasm32 = -O0
|
|||||||
lld.wasm32 = --allow-undefined --no-entry --global-base=0 --no-threads --export-dynamic
|
lld.wasm32 = --allow-undefined --no-entry --global-base=0 --no-threads --export-dynamic
|
||||||
runtimeDefinitions.wasm32 = KONAN_WASM=1 KONAN_NO_FFI=1 KONAN_NO_THREADS=1 \
|
runtimeDefinitions.wasm32 = KONAN_WASM=1 KONAN_NO_FFI=1 KONAN_NO_THREADS=1 \
|
||||||
KONAN_NO_EXCEPTIONS=1 KONAN_INTERNAL_DLMALLOC=1 KONAN_INTERNAL_SNPRINTF=1 \
|
KONAN_NO_EXCEPTIONS=1 KONAN_INTERNAL_DLMALLOC=1 KONAN_INTERNAL_SNPRINTF=1 \
|
||||||
KONAN_INTERNAL_NOW=1 KONAN_NO_MEMMEM KONAN_NO_CTORS_SECTION=1
|
KONAN_INTERNAL_NOW=1 KONAN_NO_MEMMEM KONAN_NO_CTORS_SECTION=1 KONAN_NO_BACKTRACE=1
|
||||||
|
|
||||||
# The version of Kotlin/Native compiler
|
# The version of Kotlin/Native compiler
|
||||||
compilerVersion=1.5-dev
|
compilerVersion=1.5-dev
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ clangFlags = -Os
|
|||||||
runtimeDefinitions.zephyr_stm32f4_disco = KONAN_ZEPHYR=1 KONAN_NO_FFI=1 \
|
runtimeDefinitions.zephyr_stm32f4_disco = KONAN_ZEPHYR=1 KONAN_NO_FFI=1 \
|
||||||
KONAN_NO_THREADS=1 KONAN_NO_EXCEPTIONS=1 KONAN_NO_MATH=1 \
|
KONAN_NO_THREADS=1 KONAN_NO_EXCEPTIONS=1 KONAN_NO_MATH=1 \
|
||||||
KONAN_INTERNAL_SNPRINTF=1 KONAN_INTERNAL_NOW=1 KONAN_NO_MEMMEM=1 \
|
KONAN_INTERNAL_SNPRINTF=1 KONAN_INTERNAL_NOW=1 KONAN_NO_MEMMEM=1 \
|
||||||
KONAN_NO_CTORS_SECTION=1 KONAN_NO_UNALIGNED_ACCESS=1
|
KONAN_NO_CTORS_SECTION=1 KONAN_NO_UNALIGNED_ACCESS=1 KONAN_NO_BACKTRACE=1
|
||||||
|
|
||||||
targetToolchain.linux_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-linux/arm-none-eabi
|
targetToolchain.linux_x64-zephyr_stm32f4_disco = gcc-arm-none-eabi-7-2017-q4-major-linux/arm-none-eabi
|
||||||
dependencies.linux_x64-zephyr_stm32f4_disco = \
|
dependencies.linux_x64-zephyr_stm32f4_disco = \
|
||||||
|
|||||||
@@ -21,192 +21,15 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#if KONAN_NO_EXCEPTIONS
|
|
||||||
#define OMIT_BACKTRACE 1
|
|
||||||
#endif
|
|
||||||
#ifndef OMIT_BACKTRACE
|
|
||||||
#if USE_GCC_UNWIND
|
|
||||||
// GCC unwinder for backtrace.
|
|
||||||
#include <unwind.h>
|
|
||||||
#else
|
|
||||||
// Glibc backtrace() function.
|
|
||||||
#include <execinfo.h>
|
|
||||||
#endif
|
|
||||||
#endif // OMIT_BACKTRACE
|
|
||||||
|
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
#include "ExecFormat.h"
|
#include "ExecFormat.h"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
#include "Natives.h"
|
|
||||||
#include "KString.h"
|
|
||||||
#include "SourceInfo.h"
|
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "ObjCExceptions.h"
|
#include "ObjCExceptions.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
#if USE_GCC_UNWIND
|
|
||||||
struct Backtrace {
|
|
||||||
Backtrace(int count, int skip) : index(0), skipCount(skip) {
|
|
||||||
uint32_t size = count - skipCount;
|
|
||||||
if (size < 0) {
|
|
||||||
size = 0;
|
|
||||||
}
|
|
||||||
auto result = AllocArrayInstance(theNativePtrArrayTypeInfo, size, arrayHolder.slot());
|
|
||||||
// TODO: throw cached OOME?
|
|
||||||
RuntimeCheck(result != nullptr, "Cannot create backtrace array");
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNextElement(_Unwind_Ptr element) {
|
|
||||||
Kotlin_NativePtrArray_set(obj(), index++, (KNativePtr) element);
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjHeader* obj() { return arrayHolder.obj(); }
|
|
||||||
|
|
||||||
int index;
|
|
||||||
int skipCount;
|
|
||||||
ObjHolder arrayHolder;
|
|
||||||
};
|
|
||||||
|
|
||||||
_Unwind_Reason_Code depthCountCallback(
|
|
||||||
struct _Unwind_Context * context, void* arg) {
|
|
||||||
int* result = reinterpret_cast<int*>(arg);
|
|
||||||
(*result)++;
|
|
||||||
return _URC_NO_REASON;
|
|
||||||
}
|
|
||||||
|
|
||||||
_Unwind_Reason_Code unwindCallback(
|
|
||||||
struct _Unwind_Context* context, void* arg) {
|
|
||||||
Backtrace* backtrace = reinterpret_cast<Backtrace*>(arg);
|
|
||||||
if (backtrace->skipCount > 0) {
|
|
||||||
backtrace->skipCount--;
|
|
||||||
return _URC_NO_REASON;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if (__MINGW32__ || __MINGW64__)
|
|
||||||
_Unwind_Ptr address = _Unwind_GetRegionStart(context);
|
|
||||||
#else
|
|
||||||
_Unwind_Ptr address = _Unwind_GetIP(context);
|
|
||||||
#endif
|
|
||||||
// We run the unwinding process in the native thread state. But setting a next element
|
|
||||||
// requires writing to a Kotlin array which must be performed in the runnable thread state.
|
|
||||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable);
|
|
||||||
backtrace->setNextElement(address);
|
|
||||||
|
|
||||||
return _URC_NO_REASON;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
|
|
||||||
|
|
||||||
#if !OMIT_BACKTRACE && !USE_GCC_UNWIND
|
|
||||||
SourceInfo getSourceInfo(KConstRef stackTrace, int32_t index) {
|
|
||||||
return disallowSourceInfo
|
|
||||||
? SourceInfo { .fileName = nullptr, .lineNumber = -1, .column = -1 }
|
|
||||||
: Kotlin_getSourceInfo(*PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), index));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// TODO: this implementation is just a hack, e.g. the result is inexact;
|
|
||||||
// however it is better to have an inexact stacktrace than not to have any.
|
|
||||||
NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
|
|
||||||
using namespace kotlin;
|
|
||||||
#if OMIT_BACKTRACE
|
|
||||||
return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
|
|
||||||
#else
|
|
||||||
// Skips first 2 elements as irrelevant: this function and primary Throwable constructor.
|
|
||||||
constexpr int kSkipFrames = 2;
|
|
||||||
#if USE_GCC_UNWIND
|
|
||||||
int depth = 0;
|
|
||||||
CallWithThreadState<ThreadState::kNative>(_Unwind_Backtrace, depthCountCallback, static_cast<void*>(&depth));
|
|
||||||
Backtrace result(depth, kSkipFrames);
|
|
||||||
if (result.obj()->array()->count_ > 0) {
|
|
||||||
CallWithThreadState<ThreadState::kNative>(_Unwind_Backtrace, unwindCallback, static_cast<void*>(&result));
|
|
||||||
}
|
|
||||||
RETURN_OBJ(result.obj());
|
|
||||||
#else
|
|
||||||
const int maxSize = 32;
|
|
||||||
void* buffer[maxSize];
|
|
||||||
|
|
||||||
int size = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(backtrace, buffer, maxSize);
|
|
||||||
if (size < kSkipFrames)
|
|
||||||
return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
|
|
||||||
|
|
||||||
ObjHolder resultHolder;
|
|
||||||
ObjHeader* result = AllocArrayInstance(theNativePtrArrayTypeInfo, size - kSkipFrames, resultHolder.slot());
|
|
||||||
for (int index = kSkipFrames; index < size; ++index) {
|
|
||||||
Kotlin_NativePtrArray_set(result, index - kSkipFrames, buffer[index]);
|
|
||||||
}
|
|
||||||
RETURN_OBJ(result);
|
|
||||||
#endif
|
|
||||||
#endif // !OMIT_BACKTRACE
|
|
||||||
}
|
|
||||||
|
|
||||||
OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace) {
|
|
||||||
using namespace kotlin;
|
|
||||||
#if OMIT_BACKTRACE
|
|
||||||
ObjHeader* result = AllocArrayInstance(theArrayTypeInfo, 1, OBJ_RESULT);
|
|
||||||
ObjHolder holder;
|
|
||||||
CreateStringFromCString("<UNIMPLEMENTED>", holder.slot());
|
|
||||||
UpdateHeapRef(ArrayAddressOfElementAt(result->array(), 0), holder.obj());
|
|
||||||
return result;
|
|
||||||
#else
|
|
||||||
int32_t size = static_cast<int32_t>(stackTrace->array()->count_);
|
|
||||||
ObjHolder resultHolder;
|
|
||||||
ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, size, resultHolder.slot());
|
|
||||||
#if USE_GCC_UNWIND
|
|
||||||
for (int32_t index = 0; index < size; ++index) {
|
|
||||||
KNativePtr address = Kotlin_NativePtrArray_get(stackTrace, index);
|
|
||||||
char symbol[512];
|
|
||||||
if (!CallWithThreadState<ThreadState::kNative>(AddressToSymbol, (const void*) 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);
|
|
||||||
ObjHolder holder;
|
|
||||||
CreateStringFromCString(line, holder.slot());
|
|
||||||
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (size > 0) {
|
|
||||||
char **symbols = CallWithThreadState<ThreadState::kNative>(
|
|
||||||
backtrace_symbols, PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), 0), size);
|
|
||||||
RuntimeCheck(symbols != nullptr, "Not enough memory to retrieve the stacktrace");
|
|
||||||
|
|
||||||
for (int32_t index = 0; index < size; ++index) {
|
|
||||||
auto sourceInfo = CallWithThreadState<ThreadState::kNative>(getSourceInfo, stackTrace, index);
|
|
||||||
const char* symbol = symbols[index];
|
|
||||||
const char* result;
|
|
||||||
char line[1024];
|
|
||||||
if (sourceInfo.fileName != nullptr) {
|
|
||||||
if (sourceInfo.lineNumber != -1) {
|
|
||||||
konan::snprintf(line, sizeof(line) - 1, "%s (%s:%d:%d)",
|
|
||||||
symbol, sourceInfo.fileName, sourceInfo.lineNumber, sourceInfo.column);
|
|
||||||
} else {
|
|
||||||
konan::snprintf(line, sizeof(line) - 1, "%s (%s:<unknown>)", symbol, sourceInfo.fileName);
|
|
||||||
}
|
|
||||||
result = line;
|
|
||||||
} else {
|
|
||||||
result = symbol;
|
|
||||||
}
|
|
||||||
ObjHolder holder;
|
|
||||||
CreateStringFromCString(result, holder.slot());
|
|
||||||
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
|
|
||||||
}
|
|
||||||
// Not konan::free. Used to free memory allocated in backtrace_symbols where malloc is used.
|
|
||||||
free(symbols);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
RETURN_OBJ(strings);
|
|
||||||
#endif // !OMIT_BACKTRACE
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThrowException(KRef exception) {
|
void ThrowException(KRef exception) {
|
||||||
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
||||||
"Throwing something non-throwable");
|
"Throwing something non-throwable");
|
||||||
@@ -331,7 +154,3 @@ void SetKonanTerminateHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // !KONAN_NO_EXCEPTIONS
|
#endif // !KONAN_NO_EXCEPTIONS
|
||||||
|
|
||||||
void DisallowSourceInfo() {
|
|
||||||
disallowSourceInfo = true;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Returns current stacktrace as Array<String>.
|
|
||||||
OBJ_GETTER0(Kotlin_getCurrentStackTrace);
|
|
||||||
|
|
||||||
OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace);
|
|
||||||
|
|
||||||
// Throws arbitrary exception.
|
// Throws arbitrary exception.
|
||||||
void ThrowException(KRef exception);
|
void ThrowException(KRef exception);
|
||||||
|
|
||||||
@@ -72,7 +67,4 @@ void PrintThrowable(KRef);
|
|||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// It's not always safe to extract SourceInfo during unhandled exception termination.
|
|
||||||
void DisallowSourceInfo();
|
|
||||||
|
|
||||||
#endif // RUNTIME_NAMES_H
|
#endif // RUNTIME_NAMES_H
|
||||||
|
|||||||
@@ -4,7 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
|
#include "StackTrace.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// TODO: Enable stacktraces for asserts when stacktrace printing is more mature.
|
||||||
|
inline constexpr bool kEnableStacktraces = false;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
RUNTIME_NORETURN void RuntimeAssertFailed(const char* location, const char* format, ...) {
|
RUNTIME_NORETURN void RuntimeAssertFailed(const char* location, const char* format, ...) {
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
@@ -27,7 +36,9 @@ RUNTIME_NORETURN void RuntimeAssertFailed(const char* location, const char* form
|
|||||||
|
|
||||||
konan::consoleErrorUtf8(buf, konan::strnlen(buf, sizeof(buf)));
|
konan::consoleErrorUtf8(buf, konan::strnlen(buf, sizeof(buf)));
|
||||||
konan::consoleErrorf("\n");
|
konan::consoleErrorf("\n");
|
||||||
// TODO: Write the stacktrace.
|
if constexpr (kEnableStacktraces) {
|
||||||
|
kotlin::PrintStackTraceStderr();
|
||||||
|
}
|
||||||
konan::abort();
|
konan::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
#include "Exceptions.h"
|
#include "StackTrace.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Natives.h"
|
#include "Natives.h"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
@@ -41,7 +41,7 @@ KInt Kotlin_Any_hashCode(KConstRef thiz) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OBJ_GETTER(Kotlin_getStackTraceStrings, KConstRef stackTrace) {
|
OBJ_GETTER(Kotlin_getStackTraceStrings, KConstRef stackTrace) {
|
||||||
RETURN_RESULT_OF(GetStackTraceStrings, stackTrace);
|
RETURN_RESULT_OF(kotlin::GetStackTraceStrings, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consider handling it with compiler magic instead.
|
// TODO: consider handling it with compiler magic instead.
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "ObjCInterop.h"
|
#include "ObjCInterop.h"
|
||||||
#include "ObjCExportPrivate.h"
|
#include "ObjCExportPrivate.h"
|
||||||
#include "ObjCMMAPI.h"
|
#include "ObjCMMAPI.h"
|
||||||
|
#include "StackTrace.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Mutex.hpp"
|
#include "Mutex.hpp"
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ BOOL _tryRetainImp(id self, SEL _cmd) {
|
|||||||
// TODO: Refactor to be more explicit. Instead of relying on an unhandled exception termination
|
// TODO: Refactor to be more explicit. Instead of relying on an unhandled exception termination
|
||||||
// (and effectively setting a global to alter its behavior), just call an appropriate termination
|
// (and effectively setting a global to alter its behavior), just call an appropriate termination
|
||||||
// function by hand.
|
// function by hand.
|
||||||
DisallowSourceInfo();
|
kotlin::DisallowSourceInfo();
|
||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "StackTrace.hpp"
|
||||||
|
|
||||||
|
#if KONAN_NO_BACKTRACE
|
||||||
|
// Nothing to include
|
||||||
|
#elif USE_GCC_UNWIND
|
||||||
|
// GCC unwinder for backtrace.
|
||||||
|
#include <unwind.h>
|
||||||
|
#else
|
||||||
|
// Glibc backtrace() function.
|
||||||
|
#include <execinfo.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "ExecFormat.h"
|
||||||
|
#include "Memory.h"
|
||||||
|
#include "KString.h"
|
||||||
|
#include "Natives.h"
|
||||||
|
#include "SourceInfo.h"
|
||||||
|
|
||||||
|
#include "utf8.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if USE_GCC_UNWIND
|
||||||
|
struct Backtrace {
|
||||||
|
Backtrace(int count, int skip) : index(0), skipCount(skip) {
|
||||||
|
uint32_t size = count - skipCount;
|
||||||
|
if (size < 0) {
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
auto result = AllocArrayInstance(theNativePtrArrayTypeInfo, size, arrayHolder.slot());
|
||||||
|
// TODO: throw cached OOME?
|
||||||
|
RuntimeCheck(result != nullptr, "Cannot create backtrace array");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNextElement(_Unwind_Ptr element) { Kotlin_NativePtrArray_set(obj(), index++, (KNativePtr)element); }
|
||||||
|
|
||||||
|
ObjHeader* obj() { return arrayHolder.obj(); }
|
||||||
|
|
||||||
|
int index;
|
||||||
|
int skipCount;
|
||||||
|
ObjHolder arrayHolder;
|
||||||
|
};
|
||||||
|
|
||||||
|
_Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) {
|
||||||
|
int* result = reinterpret_cast<int*>(arg);
|
||||||
|
(*result)++;
|
||||||
|
return _URC_NO_REASON;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
|
||||||
|
Backtrace* backtrace = reinterpret_cast<Backtrace*>(arg);
|
||||||
|
if (backtrace->skipCount > 0) {
|
||||||
|
backtrace->skipCount--;
|
||||||
|
return _URC_NO_REASON;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (__MINGW32__ || __MINGW64__)
|
||||||
|
_Unwind_Ptr address = _Unwind_GetRegionStart(context);
|
||||||
|
#else
|
||||||
|
_Unwind_Ptr address = _Unwind_GetIP(context);
|
||||||
|
#endif
|
||||||
|
// We run the unwinding process in the native thread state. But setting a next element
|
||||||
|
// requires writing to a Kotlin array which must be performed in the runnable thread state.
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable);
|
||||||
|
backtrace->setNextElement(address);
|
||||||
|
|
||||||
|
return _URC_NO_REASON;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
|
||||||
|
|
||||||
|
#if !KONAN_NO_BACKTRACE && !USE_GCC_UNWIND
|
||||||
|
SourceInfo getSourceInfo(KConstRef stackTrace, int32_t index) {
|
||||||
|
return disallowSourceInfo ? SourceInfo{.fileName = nullptr, .lineNumber = -1, .column = -1}
|
||||||
|
: Kotlin_getSourceInfo(*PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), index));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// TODO: this implementation is just a hack, e.g. the result is inexact;
|
||||||
|
// however it is better to have an inexact stacktrace than not to have any.
|
||||||
|
extern "C" NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
|
||||||
|
#if KONAN_NO_BACKTRACE
|
||||||
|
return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
|
||||||
|
#else
|
||||||
|
// Skips first 2 elements as irrelevant: this function and primary Throwable constructor.
|
||||||
|
constexpr int kSkipFrames = 2;
|
||||||
|
#if USE_GCC_UNWIND
|
||||||
|
int depth = 0;
|
||||||
|
CallWithThreadState<ThreadState::kNative>(_Unwind_Backtrace, depthCountCallback, static_cast<void*>(&depth));
|
||||||
|
Backtrace result(depth, kSkipFrames);
|
||||||
|
if (result.obj()->array()->count_ > 0) {
|
||||||
|
CallWithThreadState<ThreadState::kNative>(_Unwind_Backtrace, unwindCallback, static_cast<void*>(&result));
|
||||||
|
}
|
||||||
|
RETURN_OBJ(result.obj());
|
||||||
|
#else
|
||||||
|
const int maxSize = 32;
|
||||||
|
void* buffer[maxSize];
|
||||||
|
|
||||||
|
int size = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(backtrace, buffer, maxSize);
|
||||||
|
if (size < kSkipFrames) return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
|
||||||
|
|
||||||
|
ObjHolder resultHolder;
|
||||||
|
ObjHeader* result = AllocArrayInstance(theNativePtrArrayTypeInfo, size - kSkipFrames, resultHolder.slot());
|
||||||
|
for (int index = kSkipFrames; index < size; ++index) {
|
||||||
|
Kotlin_NativePtrArray_set(result, index - kSkipFrames, buffer[index]);
|
||||||
|
}
|
||||||
|
RETURN_OBJ(result);
|
||||||
|
#endif
|
||||||
|
#endif // !KONAN_NO_BACKTRACE
|
||||||
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER(kotlin::GetStackTraceStrings, KConstRef stackTrace) {
|
||||||
|
#if KONAN_NO_BACKTRACE
|
||||||
|
ObjHeader* result = AllocArrayInstance(theArrayTypeInfo, 1, OBJ_RESULT);
|
||||||
|
ObjHolder holder;
|
||||||
|
CreateStringFromCString("<UNIMPLEMENTED>", holder.slot());
|
||||||
|
UpdateHeapRef(ArrayAddressOfElementAt(result->array(), 0), holder.obj());
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
int32_t size = static_cast<int32_t>(stackTrace->array()->count_);
|
||||||
|
ObjHolder resultHolder;
|
||||||
|
ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, size, resultHolder.slot());
|
||||||
|
#if USE_GCC_UNWIND
|
||||||
|
for (int32_t index = 0; index < size; ++index) {
|
||||||
|
KNativePtr address = Kotlin_NativePtrArray_get(stackTrace, index);
|
||||||
|
char symbol[512];
|
||||||
|
if (!CallWithThreadState<ThreadState::kNative>(AddressToSymbol, (const void*)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);
|
||||||
|
ObjHolder holder;
|
||||||
|
CreateStringFromCString(line, holder.slot());
|
||||||
|
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (size > 0) {
|
||||||
|
char** symbols = CallWithThreadState<ThreadState::kNative>(
|
||||||
|
backtrace_symbols, PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), 0), size);
|
||||||
|
RuntimeCheck(symbols != nullptr, "Not enough memory to retrieve the stacktrace");
|
||||||
|
|
||||||
|
for (int32_t index = 0; index < size; ++index) {
|
||||||
|
auto sourceInfo = CallWithThreadState<ThreadState::kNative>(getSourceInfo, stackTrace, index);
|
||||||
|
const char* symbol = symbols[index];
|
||||||
|
const char* result;
|
||||||
|
char line[1024];
|
||||||
|
if (sourceInfo.fileName != nullptr) {
|
||||||
|
if (sourceInfo.lineNumber != -1) {
|
||||||
|
konan::snprintf(
|
||||||
|
line, sizeof(line) - 1, "%s (%s:%d:%d)", symbol, sourceInfo.fileName, sourceInfo.lineNumber, sourceInfo.column);
|
||||||
|
} else {
|
||||||
|
konan::snprintf(line, sizeof(line) - 1, "%s (%s:<unknown>)", symbol, sourceInfo.fileName);
|
||||||
|
}
|
||||||
|
result = line;
|
||||||
|
} else {
|
||||||
|
result = symbol;
|
||||||
|
}
|
||||||
|
ObjHolder holder;
|
||||||
|
CreateStringFromCString(result, holder.slot());
|
||||||
|
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
|
||||||
|
}
|
||||||
|
// Not konan::free. Used to free memory allocated in backtrace_symbols where malloc is used.
|
||||||
|
free(symbols);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
RETURN_OBJ(strings);
|
||||||
|
#endif // !KONAN_NO_BACKTRACE
|
||||||
|
}
|
||||||
|
|
||||||
|
void kotlin::DisallowSourceInfo() {
|
||||||
|
disallowSourceInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kotlin::PrintStackTraceStderr() {
|
||||||
|
// TODO: This is intended for runtime use. Try to avoid memory allocations and signal unsafe functions.
|
||||||
|
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable, true);
|
||||||
|
|
||||||
|
ObjHolder stackTrace;
|
||||||
|
Kotlin_getCurrentStackTrace(stackTrace.slot());
|
||||||
|
ObjHolder stackTraceStrings;
|
||||||
|
kotlin::GetStackTraceStrings(stackTrace.obj(), stackTraceStrings.slot());
|
||||||
|
ArrayHeader* stackTraceStringsArray = stackTraceStrings.obj()->array();
|
||||||
|
for (uint32_t i = 0; i < stackTraceStringsArray->count_; ++i) {
|
||||||
|
ArrayHeader* symbol = (*ArrayAddressOfElementAt(stackTraceStringsArray, i))->array();
|
||||||
|
auto* utf16 = CharArrayAddressOfElementAt(symbol, 0);
|
||||||
|
KStdString utf8;
|
||||||
|
utf8::with_replacement::utf16to8(utf16, utf16 + symbol->count_, std::back_inserter(utf8));
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||||
|
konan::consoleErrorUtf8(utf8.c_str(), utf8.size());
|
||||||
|
konan::consoleErrorf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Memory.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
namespace kotlin {
|
||||||
|
|
||||||
|
OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace);
|
||||||
|
|
||||||
|
// It's not always safe to extract SourceInfo during unhandled exception termination.
|
||||||
|
void DisallowSourceInfo();
|
||||||
|
|
||||||
|
void PrintStackTraceStderr();
|
||||||
|
|
||||||
|
} // namespace kotlin
|
||||||
|
|
||||||
|
// Returns current stacktrace as Array<String>.
|
||||||
|
extern "C" OBJ_GETTER0(Kotlin_getCurrentStackTrace);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "StackTrace.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Porting.h"
|
||||||
|
#include "TestSupport.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
NO_INLINE void AbortWithStackTrace() {
|
||||||
|
PrintStackTraceStderr();
|
||||||
|
konan::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(StackTraceDeathTest, PrintStackTrace) {
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{ kotlin::RunInNewThread(AbortWithStackTrace); },
|
||||||
|
#if KONAN_WINDOWS
|
||||||
|
// TODO: Fix Windows to match other platforms.
|
||||||
|
testing::AllOf(testing::HasSubstr("AbortWithStackTrace"), testing::HasSubstr("PrintStackTraceStderr"))
|
||||||
|
#else
|
||||||
|
testing::AllOf(testing::HasSubstr("AbortWithStackTrace"), testing::Not(testing::HasSubstr("PrintStackTraceStderr")))
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user