/* * 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 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 #endif #include #include "Common.h" #include "ExecFormat.h" #include "Porting.h" #include "SourceInfo.h" #include "Types.h" #include "utf8.h" using namespace kotlin; namespace { #if USE_GCC_UNWIND struct Backtrace { Backtrace(size_t skip, std_support::span buffer): currentSize(0), skipCount(skip), buffer(buffer) {} void setNextElement(_Unwind_Ptr element) { RuntimeAssert(currentSize < buffer.size(), "Buffer overflow"); buffer[currentSize++] = reinterpret_cast(element); } bool full() const { return currentSize >= buffer.size(); } size_t currentSize; size_t skipCount; std_support::span buffer; }; _Unwind_Ptr getUnwindPtr(_Unwind_Context* context) { return _Unwind_GetIP(context); } _Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) { size_t* 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 the buffer is full, skip the remaining frames. if (backtrace->full()) { return _URC_NO_REASON; } _Unwind_Ptr address = getUnwindPtr(context); backtrace->setNextElement(address); 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; int getSourceInfo(void* symbol, SourceInfo *result, int result_len) { return disallowSourceInfo ? 0 : compiler::getSourceInfo(symbol, result, result_len); } } // 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 std::vector kotlin::internal::GetCurrentStackTrace(size_t skipFrames) noexcept { // Skip GetCurrentStackTrace + anything asked by the caller. const size_t kSkipFrames = 1 + skipFrames; std::vector result; #if USE_GCC_UNWIND size_t depth = 0; _Unwind_Backtrace(depthCountCallback, static_cast(&depth)); if (depth <= kSkipFrames) return {}; result.resize(depth - kSkipFrames); Backtrace traceHolder(kSkipFrames, std_support::span(result.data(), result.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. constexpr size_t maxSize = GetMaxStackTraceDepth() + 2; result.resize(maxSize); auto size = static_cast(backtrace(result.data(), static_cast(result.size()))); if (size <= kSkipFrames) return {}; result.resize(size); // Drop first kSkipFrames elements. result.erase(result.begin(), std::next(result.begin(), kSkipFrames)); return result; #endif // !USE_GCC_UNWIND } // 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 size_t kotlin::internal::GetCurrentStackTrace(size_t skipFrames, std_support::span buffer) noexcept { // Skip GetCurrentStackTrace + anything asked by the caller. const size_t kSkipFrames = 1 + skipFrames; #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; void* tmpBuffer[maxSize]; size_t size = backtrace(tmpBuffer, static_cast(maxSize)); if (size <= kSkipFrames) return 0; size_t elementsCount = std::min(buffer.size(), size - kSkipFrames); std::copy_n(std::begin(tmpBuffer) + kSkipFrames, elementsCount, std::begin(buffer)); return elementsCount; #endif // !USE_GCC_UNWIND } #include #include #include "std_support/Span.hpp" #include "Format.h" #if __has_include("dlfcn.h") #include #endif __attribute__((format(printf, 6, 7))) static size_t snprintf_with_addr(char* buf, size_t size, size_t frame, const void* addr, bool is_inline, const char *format, ...) { std_support::span buffer{buf, size}; const char* image = "???"; char symbol[512]; strcpy(symbol, "0x0"); ptrdiff_t symbol_offset = reinterpret_cast(addr); #if __has_include("dlfcn.h") Dl_info info; memset(&info, 0, sizeof(info)); dladdr(addr, &info); if (info.dli_fname) { const char* tmp = strrchr(info.dli_fname, '/'); if (tmp == nullptr) { image = info.dli_fname; } else { image = tmp + 1; } } #endif AddressToSymbol(addr, symbol, sizeof(symbol), symbol_offset); buffer = FormatToSpan(buffer, "%-4zd%-35s %-18p %s + %td ", frame, image, addr, symbol, symbol_offset); if (is_inline) { buffer = FormatToSpan(buffer, "[inlined] "); } std::va_list args; va_start(args, format); buffer = VFormatToSpan(buffer, format, args); va_end(args); return size - buffer.size(); } /* * This is hack for better traces. * In some cases backtrace function returns address after call instruction, while address detection need call instruction itself. * adjustAddressForSourceInfo function tries to fix it with some heuristics. * * For honest solution, we should distinguish backtrace symbols got from signal handlers frames, ordinary frames, * and addresses got from somewhere else. But for now, we assume all addresses are ordinary backtrace frames. */ #if (defined(KONAN_X64) || defined(KONAN_X86)) && !defined(KONAN_WINDOWS) KNativePtr adjustAddressForSourceInfo(KNativePtr address) { return reinterpret_cast(reinterpret_cast(address) - 1); } #elif (defined(KONAN_ARM32) || defined(KONAN_ARM64)) && !defined(KONAN_WINDOWS) KNativePtr adjustAddressForSourceInfo(KNativePtr address) { /* * On arm instructions are always 2-bytes aligned. But odd bit can be used to encode instruction set. * Not sure, if this can happen in our code, but let's just clear it. */ return reinterpret_cast((reinterpret_cast(address) & ~1) - 1); } #else KNativePtr adjustAddressForSourceInfo(KNativePtr address) { return address; } #endif std::vector kotlin::GetStackTraceStrings(std_support::span stackTrace) noexcept { size_t size = stackTrace.size(); std::vector strings; strings.reserve(size); if (size > 0) { SourceInfo buffer[10]; // outside of the loop to avoid calling constructors and destructors each time for (size_t index = 0; index < size; ++index) { KNativePtr address = stackTrace[index]; if (!address || reinterpret_cast(address) == 1) continue; address = adjustAddressForSourceInfo(address); int frames_or_overflow = getSourceInfo(address, buffer, std::size(buffer)); int frames = std::min(frames_or_overflow, std::size(buffer)); bool isSomethingPrinted = false; bool isSomethingHidden = false; char line[1024]; for (int frame = 0; frame < frames; frame++) { auto &sourceInfo = buffer[frame]; if (sourceInfo.nodebug) { isSomethingHidden = true; } else if (!sourceInfo.getFileName().empty()) { bool is_last = frame == frames - 1; if (is_last && frames_or_overflow != frames) { snprintf_with_addr(line, sizeof(line) - 1, strings.size(), address, false, "[some inlined frames skipped]"); strings.push_back(line); } if (sourceInfo.lineNumber != -1) { if (sourceInfo.column != -1) { snprintf_with_addr( line, sizeof(line) - 1, strings.size(), address, !is_last, "(%s:%d:%d)", sourceInfo.getFileName().c_str(), sourceInfo.lineNumber, sourceInfo.column); } else { snprintf_with_addr( line, sizeof(line) - 1, strings.size(), address, !is_last, "(%s:%d)", sourceInfo.getFileName().c_str(), sourceInfo.lineNumber); } } else { snprintf_with_addr( line, sizeof(line) - 1, strings.size(), address, !is_last, "(%s:)", sourceInfo.getFileName().c_str()); } isSomethingPrinted = true; strings.push_back(line); } } if (!isSomethingPrinted && !isSomethingHidden) { snprintf_with_addr(line, sizeof(line) - 1, strings.size(), address, false, "%s", ""); strings.push_back(line); } } } return strings; } void kotlin::DisallowSourceInfo() { disallowSourceInfo = true; } NO_INLINE void kotlin::PrintStackTraceStderr() { // NOTE: This might be called from both runnable and native states (including in uninitialized runtime) // TODO: This is intended for runtime use. Try to avoid memory allocations and signal unsafe functions. // Skip this function. constexpr int kSkipFrames = 1; StackTrace trace = StackTrace<>::current(kSkipFrames); auto stackTraceStrings = GetStackTraceStrings(trace.data()); for (auto& frame : stackTraceStrings) { konan::consoleErrorUtf8(frame.c_str(), frame.size()); konan::consoleErrorf("\n"); } }