[K/N] Support stacktrace using libbacktrace

This commit is contained in:
Pavel Kunyavskiy
2021-09-14 11:09:36 +03:00
committed by Space
parent 4fa5be3e58
commit c704fd6ae7
21 changed files with 350 additions and 123 deletions
+8 -8
View File
@@ -35,7 +35,8 @@ bitcode {
"${target}Libbacktrace",
"${target}Launcher",
"${target}Debug",
"${target}Release",
"${target}SourceInfoCoreSymbolication",
"${target}SourceInfoLibbacktrace",
"${target}Strict",
"${target}Relaxed",
"${target}ProfileRuntime",
@@ -118,9 +119,14 @@ bitcode {
includeRuntime()
}
create("release") {
create("source_info_core_symbolication", file("src/source_info/core_symbolication")) {
includeRuntime()
}
create("source_info_libbacktrace", file("src/source_info/libbacktrace")) {
includeRuntime()
headersDirs += files("src/libbacktrace/c/include")
onlyIf { targetSupportsLibBacktrace(target) }
}
create("strict") {
includeRuntime()
@@ -183,7 +189,6 @@ targetList.forEach { targetName ->
"${targetName}Runtime",
"${targetName}LegacyMemoryManager",
"${targetName}Strict",
"${targetName}Release",
"${targetName}StdAlloc",
"${targetName}Objc"
)
@@ -199,7 +204,6 @@ targetList.forEach { targetName ->
"${targetName}Runtime",
"${targetName}LegacyMemoryManager",
"${targetName}Strict",
"${targetName}Release",
"${targetName}Mimalloc",
"${targetName}OptAlloc",
"${targetName}Objc"
@@ -217,7 +221,6 @@ targetList.forEach { targetName ->
"${targetName}ExperimentalMemoryManagerStms",
"${targetName}CommonGc",
"${targetName}SameThreadMsGc",
"${targetName}Release",
"${targetName}Mimalloc",
"${targetName}OptAlloc",
"${targetName}Objc"
@@ -236,7 +239,6 @@ targetList.forEach { targetName ->
"${targetName}ExperimentalMemoryManagerStms",
"${targetName}CommonGc",
"${targetName}SameThreadMsGc",
"${targetName}Release",
"${targetName}StdAlloc",
"${targetName}Objc"
)
@@ -254,7 +256,6 @@ targetList.forEach { targetName ->
"${targetName}ExperimentalMemoryManagerNoop",
"${targetName}CommonGc",
"${targetName}NoopGc",
"${targetName}Release",
"${targetName}Mimalloc",
"${targetName}OptAlloc",
"${targetName}Objc"
@@ -273,7 +274,6 @@ targetList.forEach { targetName ->
"${targetName}ExperimentalMemoryManagerNoop",
"${targetName}CommonGc",
"${targetName}NoopGc",
"${targetName}Release",
"${targetName}StdAlloc",
"${targetName}Objc"
)
@@ -6,6 +6,7 @@
#include "CompilerConstants.hpp"
#include "Common.h"
#include "SourceInfo.h"
using namespace kotlin;
@@ -14,6 +15,7 @@ RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1;
RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0;
RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0;
RUNTIME_WEAK int32_t Kotlin_freezingEnabled = 1;
RUNTIME_WEAK const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function = nullptr;
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
@@ -28,6 +28,9 @@ using string_view = std::experimental::string_view;
extern "C" const int32_t KonanNeedDebugInfo;
extern "C" const int32_t Kotlin_runtimeAssertsMode;
extern "C" const char* const Kotlin_runtimeLogs;
class SourceInfo;
using Kotlin_getSourceInfo_FunctionType = int(*)(void * /*addr*/, SourceInfo* /*result*/, int /*result_size*/);
extern "C" const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function;
namespace kotlin {
namespace compiler {
@@ -71,6 +74,14 @@ ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept {
bool freezingEnabled() noexcept;
ALWAYS_INLINE inline int getSourceInfo(void* addr, SourceInfo *result, int result_size) {
if (Kotlin_getSourceInfo_Function == nullptr) {
return 0;
} else {
return Kotlin_getSourceInfo_Function(addr, result, result_size);
}
}
} // namespace compiler
} // namespace kotlin
@@ -17,22 +17,15 @@
#ifndef RUNTIME_SOURCEINFO_H
#define RUNTIME_SOURCEINFO_H
struct SourceInfo {
const char* fileName;
int lineNumber;
int column;
#include <string>
class SourceInfo {
std::string fileName;
public:
int lineNumber = -1;
int column = -1;
std::string& getFileName() { return fileName; }
void setFilename(const char *newFileName) { fileName = newFileName ?: ""; }
};
#ifdef __cplusplus
extern "C" {
#endif
// returns number of filled elements in buffer
// there can be several frames because of inlining
int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // RUNTIME_SOURCEINFO_H
@@ -69,9 +69,9 @@ _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
#if !KONAN_NO_BACKTRACE && !USE_GCC_UNWIND
#if !KONAN_NO_BACKTRACE
int getSourceInfo(void* symbol, SourceInfo *result, int result_len) {
return disallowSourceInfo ? 0 : Kotlin_getSourceInfo(symbol, result, result_len);
return disallowSourceInfo ? 0 : compiler::getSourceInfo(symbol, result, result_len);
}
#endif
@@ -118,61 +118,76 @@ KStdVector<KStdString> kotlin::GetStackTraceStrings(void* const* stackTrace, siz
#else
KStdVector<KStdString> strings;
strings.reserve(stackTraceSize);
#if USE_GCC_UNWIND
for (size_t index = 0; index < stackTraceSize; ++index) {
KNativePtr address = stackTrace[index];
char symbol[512];
if (!AddressToSymbol(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);
strings.push_back(line);
}
#else
if (stackTraceSize > 0) {
#ifndef USE_GCC_UNWIND
char** symbols = backtrace_symbols(stackTrace, static_cast<int>(stackTraceSize));
RuntimeCheck(symbols != nullptr, "Not enough memory to retrieve the stacktrace");
#endif
SourceInfo buffer[10]; // outside of the loop to avoid calling constructors and destructors each time
for (size_t index = 0; index < stackTraceSize; ++index) {
KNativePtr address = stackTrace[index];
SourceInfo buffer[10];
int frames = getSourceInfo(address, buffer, std::size(buffer));
if (!address || reinterpret_cast<uintptr_t>(address) == 1) continue;
int frames_or_overflow = getSourceInfo(address, buffer, std::size(buffer));
int frames = std::min<int>(frames_or_overflow, std::size(buffer));
#if USE_GCC_UNWIND
char symbol_[512];
if (!AddressToSymbol(address, symbol_, sizeof(symbol_))) {
// Make empty string:
symbol_[0] = '\0';
}
const char* symbol = symbol_;
#else
const char* symbol = symbols[index];
// returned symbol starts with frame number
// we need to override it, because of inlining, so let's just drop first token
while (*symbol != '\0' && *symbol != ' ') symbol++;
while (*symbol == ' ') symbol++;
// On MacOs symbol name contain index, executable file containing symbol and address
// but it doesn't contain them on other platforms
// So we skip first three tokens to make things work similar on all platforms
for (int it = 0; it < 3; it++) {
while (*symbol != '\0' && *symbol != ' ') symbol++;
while (*symbol == ' ') symbol++;
}
// probably, this can't happen, but let's print at least something
if (*symbol == '\0') symbol = symbols[index];
#endif
bool isSomethingPrinted = false;
char line[1024];
for (int frame = 0; frame < frames; frame++) {
auto &sourceInfo = buffer[frame];
if (sourceInfo.fileName != nullptr) {
const char* inline_tag = (frame == frames - 1) ? "" : "[inlined] ";
if (!sourceInfo.getFileName().empty()) {
bool is_last = frame == frames - 1;
if (is_last && frames_or_overflow != frames) {
konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s (some inlined frames skipped)", strings.size(), address, symbol);
strings.push_back(line);
}
const char* inline_tag = is_last ? "" : "[inlined] ";
if (sourceInfo.lineNumber != -1) {
konan::snprintf(
line, sizeof(line) - 1, "%-4zd %s %s(%s:%d:%d)", strings.size(), symbol, inline_tag,
sourceInfo.fileName, sourceInfo.lineNumber, buffer[frame].column);
if (sourceInfo.column != -1) {
konan::snprintf(
line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:%d:%d)", strings.size(), address, symbol, inline_tag,
sourceInfo.getFileName().c_str(), sourceInfo.lineNumber, sourceInfo.column);
} else {
konan::snprintf(
line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:%d)", strings.size(), address, symbol, inline_tag,
sourceInfo.getFileName().c_str(), sourceInfo.lineNumber);
}
} else {
konan::snprintf(line, sizeof(line) - 1, "%-4zd %s %s(%s:<unknown>)", strings.size(), symbol, inline_tag,
sourceInfo.fileName);
konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s %s(%s:<unknown>)", strings.size(), address, symbol, inline_tag,
sourceInfo.getFileName().c_str());
}
isSomethingPrinted = true;
strings.push_back(line);
}
}
if (!isSomethingPrinted) {
konan::snprintf(line, sizeof(line) - 1, "%-4zd %s", strings.size(), symbol);
konan::snprintf(line, sizeof(line) - 1, "%-4zd %-18p %s", strings.size(), address, symbol);
strings.push_back(line);
}
}
// Not konan::free. Used to free memory allocated in backtrace_symbols where malloc is used.
#ifndef USE_GCC_UNWIND
free(symbols);
}
#endif
}
return strings;
#endif // !KONAN_NO_BACKTRACE
}
@@ -1,21 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SourceInfo.h"
int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size) {
return 0;
}
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
#include "SourceInfo.h"
@@ -127,9 +116,9 @@ typedef struct {
int end;
} SymbolSourceInfoLimits;
extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int result_size) {
extern "C" int Kotlin_getSourceInfo_core_symbolication(void* addr, SourceInfo *result_buffer, int result_size) {
if (result_size == 0) return 0;
__block SourceInfo result = { .fileName = nullptr, .lineNumber = -1, .column = -1 };
__block SourceInfo result;
__block bool continueUpdateResult = true;
__block SymbolSourceInfoLimits limits = {.start = -1, .end = -1};
@@ -168,7 +157,7 @@ extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int r
});
SYM_LOG("limits: {%s %d..%d}\n", limits.fileName, limits.start, limits.end);
result.fileName = limits.fileName;
result.setFilename(limits.fileName);
CSSymbolForeachSourceInfo(symbol,
^(CSSourceInfoRef ref) {
@@ -208,11 +197,4 @@ extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result_buffer, int r
result_buffer[0] = result;
return 1;
}
#else // KONAN_CORE_SYMBOLICATION
extern "C" int Kotlin_getSourceInfo(void* addr, SourceInfo *result, int result_size) {
return 0;
}
#endif // KONAN_CORE_SYMBOLICATION
#endif // KONAN_CORE_SYMBOLICATION
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
#include "SourceInfo.h"
#include "backtrace.h"
#include <cstring>
extern "C" int Kotlin_getSourceInfo_libbacktrace(void* addr, SourceInfo *result, int result_size) {
if (result_size == 0) return 0;
/**
* This is hack for better traces.
* backtrace function returns address after call instruction, and address detection need call instruction itself
* 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.
*/
addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) - 1);
auto ignore_error = [](void*, const char*, int){};
static auto state = backtrace_create_state(nullptr, 1, ignore_error, nullptr);
if (!state) return 0;
struct callback_arg_t {
SourceInfo *result;
int result_ptr;
int result_size;
int total_count;
} callback_arg;
callback_arg.result = result;
callback_arg.result_ptr = 0;
callback_arg.result_size = result_size;
callback_arg.total_count = 0;
auto process_line = [](void *data, uintptr_t pc, const char *filename, int lineno, int column, const char *function) -> int {
auto &callback_arg = *static_cast<callback_arg_t*>(data);
// Non-inlined frame would be last one, it's better to have it, then intermediate ones
if (callback_arg.result_ptr == callback_arg.result_size) {
callback_arg.result_ptr--;
}
auto &info = callback_arg.result[callback_arg.result_ptr];
info.setFilename(filename);
info.lineNumber = lineno;
info.column = column;
callback_arg.result_ptr++;
callback_arg.total_count++;
// Let's stop at least at some point
// Probably, this can happen only if debug info is corrupted
return callback_arg.total_count > callback_arg.result_size * 10;
};
backtrace_pcinfo(state, reinterpret_cast<uintptr_t>(addr), process_line, ignore_error, &callback_arg);
return callback_arg.total_count;
}