diff --git a/kotlin-native/konan/konan.properties b/kotlin-native/konan/konan.properties index 332264173bc..dc06de4574c 100644 --- a/kotlin-native/konan/konan.properties +++ b/kotlin-native/konan/konan.properties @@ -961,7 +961,7 @@ clangDebugFlags.wasm32 = -O0 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 \ 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 compilerVersion=1.5-dev diff --git a/kotlin-native/konan/platforms/zephyr/stm32f4_disco b/kotlin-native/konan/platforms/zephyr/stm32f4_disco index 6690bd1c790..723d4eac6af 100644 --- a/kotlin-native/konan/platforms/zephyr/stm32f4_disco +++ b/kotlin-native/konan/platforms/zephyr/stm32f4_disco @@ -27,7 +27,7 @@ clangFlags = -Os runtimeDefinitions.zephyr_stm32f4_disco = KONAN_ZEPHYR=1 KONAN_NO_FFI=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_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 dependencies.linux_x64-zephyr_stm32f4_disco = \ diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp index 30daabcd98d..f5a4185263c 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp @@ -21,192 +21,15 @@ #include #include -#if KONAN_NO_EXCEPTIONS -#define OMIT_BACKTRACE 1 -#endif -#ifndef OMIT_BACKTRACE -#if USE_GCC_UNWIND -// GCC unwinder for backtrace. -#include -#else -// Glibc backtrace() function. -#include -#endif -#endif // OMIT_BACKTRACE - #include "KAssert.h" #include "Exceptions.h" #include "ExecFormat.h" #include "Memory.h" #include "Mutex.hpp" -#include "Natives.h" -#include "KString.h" -#include "SourceInfo.h" #include "Types.h" #include "Utils.hpp" #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(arg); - (*result)++; - return _URC_NO_REASON; -} - -_Unwind_Reason_Code unwindCallback( - struct _Unwind_Context* context, void* arg) { - Backtrace* backtrace = reinterpret_cast(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(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(_Unwind_Backtrace, depthCountCallback, static_cast(&depth)); - Backtrace result(depth, kSkipFrames); - if (result.obj()->array()->count_ > 0) { - CallWithThreadState(_Unwind_Backtrace, unwindCallback, static_cast(&result)); - } - RETURN_OBJ(result.obj()); -#else - const int maxSize = 32; - void* buffer[maxSize]; - - int size = kotlin::CallWithThreadState(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("", holder.slot()); - UpdateHeapRef(ArrayAddressOfElementAt(result->array(), 0), holder.obj()); - return result; -#else - int32_t size = static_cast(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(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( - backtrace_symbols, PrimitiveArrayAddressOfElementAt(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(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:)", 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) { RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo), "Throwing something non-throwable"); @@ -331,7 +154,3 @@ void SetKonanTerminateHandler() { } #endif // !KONAN_NO_EXCEPTIONS - -void DisallowSourceInfo() { - disallowSourceInfo = true; -} diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.h b/kotlin-native/runtime/src/main/cpp/Exceptions.h index c5cb866dc56..770621e2f03 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.h +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.h @@ -23,11 +23,6 @@ extern "C" { #endif -// Returns current stacktrace as Array. -OBJ_GETTER0(Kotlin_getCurrentStackTrace); - -OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace); - // Throws arbitrary exception. void ThrowException(KRef exception); @@ -72,7 +67,4 @@ void PrintThrowable(KRef); } // extern "C" #endif -// It's not always safe to extract SourceInfo during unhandled exception termination. -void DisallowSourceInfo(); - #endif // RUNTIME_NAMES_H diff --git a/kotlin-native/runtime/src/main/cpp/KAssert.cpp b/kotlin-native/runtime/src/main/cpp/KAssert.cpp index 96df6e11abc..876067ff8d7 100644 --- a/kotlin-native/runtime/src/main/cpp/KAssert.cpp +++ b/kotlin-native/runtime/src/main/cpp/KAssert.cpp @@ -4,7 +4,16 @@ */ #include + #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, ...) { 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::consoleErrorf("\n"); - // TODO: Write the stacktrace. + if constexpr (kEnableStacktraces) { + kotlin::PrintStackTraceStderr(); + } konan::abort(); } diff --git a/kotlin-native/runtime/src/main/cpp/Natives.cpp b/kotlin-native/runtime/src/main/cpp/Natives.cpp index 191c322c216..22738ced0ec 100644 --- a/kotlin-native/runtime/src/main/cpp/Natives.cpp +++ b/kotlin-native/runtime/src/main/cpp/Natives.cpp @@ -22,7 +22,7 @@ #include #include "KAssert.h" -#include "Exceptions.h" +#include "StackTrace.hpp" #include "Memory.h" #include "Natives.h" #include "Types.h" @@ -41,7 +41,7 @@ KInt Kotlin_Any_hashCode(KConstRef thiz) { } 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. diff --git a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm index 3c433c9ec23..4991acf3bcb 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm @@ -33,6 +33,7 @@ #include "ObjCInterop.h" #include "ObjCExportPrivate.h" #include "ObjCMMAPI.h" +#include "StackTrace.hpp" #include "Types.h" #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 // (and effectively setting a global to alter its behavior), just call an appropriate termination // function by hand. - DisallowSourceInfo(); + kotlin::DisallowSourceInfo(); std::terminate(); } } diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.cpp b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp new file mode 100644 index 00000000000..a24b36c6d2f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.cpp @@ -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 +#else +// Glibc backtrace() function. +#include +#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(arg); + (*result)++; + return _URC_NO_REASON; +} + +_Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { + Backtrace* backtrace = reinterpret_cast(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(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(_Unwind_Backtrace, depthCountCallback, static_cast(&depth)); + Backtrace result(depth, kSkipFrames); + if (result.obj()->array()->count_ > 0) { + CallWithThreadState(_Unwind_Backtrace, unwindCallback, static_cast(&result)); + } + RETURN_OBJ(result.obj()); +#else + const int maxSize = 32; + void* buffer[maxSize]; + + int size = kotlin::CallWithThreadState(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("", holder.slot()); + UpdateHeapRef(ArrayAddressOfElementAt(result->array(), 0), holder.obj()); + return result; +#else + int32_t size = static_cast(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(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( + backtrace_symbols, PrimitiveArrayAddressOfElementAt(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(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:)", 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"); + } +} diff --git a/kotlin-native/runtime/src/main/cpp/StackTrace.hpp b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp new file mode 100644 index 00000000000..a35fa672f4d --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/StackTrace.hpp @@ -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. +extern "C" OBJ_GETTER0(Kotlin_getCurrentStackTrace); diff --git a/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp new file mode 100644 index 00000000000..1687793b410 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/StackTraceTest.cpp @@ -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 + ); +}