Files
kotlin-fork/kotlin-native/runtime/src/main/cpp/StackTrace.cpp
T
2023-09-07 09:32:00 +00:00

330 lines
12 KiB
C++

/*
* 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 <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
// Glibc backtrace() function.
#include <execinfo.h>
#endif
#include <algorithm>
#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<void*> buffer): currentSize(0), skipCount(skip), buffer(buffer) {}
void setNextElement(_Unwind_Ptr element) {
RuntimeAssert(currentSize < buffer.size(), "Buffer overflow");
buffer[currentSize++] = reinterpret_cast<void*>(element);
}
bool full() const { return currentSize >= buffer.size(); }
size_t currentSize;
size_t skipCount;
std_support::span<void*> 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<size_t*>(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 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<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
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<void*> kotlin::internal::GetCurrentStackTrace(size_t skipFrames) noexcept {
// Skip GetCurrentStackTrace + anything asked by the caller.
const size_t kSkipFrames = 1 + skipFrames;
std::vector<void*> result;
#if USE_GCC_UNWIND
size_t depth = 0;
_Unwind_Backtrace(depthCountCallback, static_cast<void*>(&depth));
if (depth <= kSkipFrames) return {};
result.resize(depth - kSkipFrames);
Backtrace traceHolder(kSkipFrames, std_support::span<void*>(result.data(), result.size()));
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&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<void*>());
if (depth <= 0) return {};
result.resize(depth);
winAPIUnwind(kSkipFrames, std_support::span<void*>(result.data(), result.size()));
return result;
#else
// Take into account this function and StackTrace::current.
constexpr size_t maxSize = GetMaxStackTraceDepth<StackTraceCapacityKind::kDynamic>() + 2;
result.resize(maxSize);
auto size = static_cast<size_t>(backtrace(result.data(), static_cast<int>(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<void*> 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<void*>(&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<StackTraceCapacityKind::kFixed>() + 2;
void* tmpBuffer[maxSize];
size_t size = backtrace(tmpBuffer, static_cast<int>(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 <cstdarg>
#include <cstring>
#include "std_support/Span.hpp"
#include "Format.h"
#if __has_include("dlfcn.h")
#include <dlfcn.h>
#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<char> buffer{buf, size};
const char* image = "???";
char symbol[512];
strcpy(symbol, "0x0");
ptrdiff_t symbol_offset = reinterpret_cast<ptrdiff_t>(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<KNativePtr>(reinterpret_cast<uintptr_t>(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<KNativePtr>((reinterpret_cast<uintptr_t>(address) & ~1) - 1);
}
#else
KNativePtr adjustAddressForSourceInfo(KNativePtr address) { return address; }
#endif
std::vector<std::string> kotlin::GetStackTraceStrings(std_support::span<void* const> stackTrace) noexcept {
size_t size = stackTrace.size();
std::vector<std::string> 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<uintptr_t>(address) == 1) continue;
address = adjustAddressForSourceInfo(address);
int frames_or_overflow = getSourceInfo(address, buffer, std::size(buffer));
int frames = std::min<int>(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:<unknown>)",
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");
}
}