[K/N][runtime] Provide API for stack-allocated stacktraces
This commit is contained in:
@@ -42,12 +42,12 @@ KInt Kotlin_Any_hashCode(KConstRef thiz) {
|
||||
}
|
||||
|
||||
NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
|
||||
KStdVector<void*> stackTrace;
|
||||
kotlin::StackTrace stackTrace;
|
||||
{
|
||||
// Don't use `kotlin::CallWithThreadState` to avoid messing up callstack.
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
// Skip this function and primary `Throwable` constructor.
|
||||
stackTrace = kotlin::GetCurrentStackTrace(2);
|
||||
stackTrace = kotlin::StackTrace<>::current(2);
|
||||
}
|
||||
|
||||
ObjHolder resultHolder;
|
||||
@@ -61,7 +61,7 @@ NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
|
||||
OBJ_GETTER(Kotlin_getStackTraceStrings, KConstRef stackTrace) {
|
||||
const KNativePtr* array = PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), 0);
|
||||
size_t size = stackTrace->array()->count_;
|
||||
auto stackTraceStrings = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(kotlin::GetStackTraceStrings, array, size);
|
||||
auto stackTraceStrings = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(kotlin::GetStackTraceStrings, kotlin::std_support::span<void* const>(array, size));
|
||||
ObjHolder resultHolder;
|
||||
ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, stackTraceStrings.size(), resultHolder.slot());
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common.h"
|
||||
#include "ExecFormat.h"
|
||||
#include "Porting.h"
|
||||
@@ -29,22 +31,30 @@ namespace {
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
struct Backtrace {
|
||||
Backtrace(int count, int skip) : skipCount(skip) {
|
||||
uint32_t size = count - skipCount;
|
||||
if (size < 0) {
|
||||
size = 0;
|
||||
}
|
||||
array.reserve(size);
|
||||
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);
|
||||
}
|
||||
|
||||
void setNextElement(_Unwind_Ptr element) { array.push_back(reinterpret_cast<void*>(element)); }
|
||||
bool full() const { return currentSize >= buffer.size(); }
|
||||
|
||||
int skipCount;
|
||||
KStdVector<void*> array;
|
||||
size_t currentSize;
|
||||
size_t skipCount;
|
||||
std_support::span<void*> buffer;
|
||||
};
|
||||
|
||||
_Unwind_Ptr getUnwindPtr(_Unwind_Context* context) {
|
||||
#if (__MINGW32__ || __MINGW64__)
|
||||
return _Unwind_GetRegionStart(context);
|
||||
#else
|
||||
return _Unwind_GetIP(context);
|
||||
#endif
|
||||
}
|
||||
|
||||
_Unwind_Reason_Code depthCountCallback(struct _Unwind_Context* context, void* arg) {
|
||||
int* result = reinterpret_cast<int*>(arg);
|
||||
size_t* result = reinterpret_cast<size_t*>(arg);
|
||||
(*result)++;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
@@ -56,11 +66,12 @@ _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
#if (__MINGW32__ || __MINGW64__)
|
||||
_Unwind_Ptr address = _Unwind_GetRegionStart(context);
|
||||
#else
|
||||
_Unwind_Ptr address = _Unwind_GetIP(context);
|
||||
#endif
|
||||
// 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;
|
||||
@@ -79,34 +90,76 @@ int getSourceInfo(void* symbol, SourceInfo *result, int result_len) {
|
||||
|
||||
// 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 KStdVector<void*> kotlin::GetCurrentStackTrace(int extraSkipFrames) noexcept {
|
||||
NO_INLINE KStdVector<void*> kotlin::internal::GetCurrentStackTrace(size_t skipFrames) noexcept {
|
||||
#if KONAN_NO_BACKTRACE
|
||||
return {};
|
||||
#else
|
||||
// Skips this function frame + anything asked by the caller.
|
||||
const int kSkipFrames = 1 + extraSkipFrames;
|
||||
#if USE_GCC_UNWIND
|
||||
int depth = 0;
|
||||
_Unwind_Backtrace(depthCountCallback, static_cast<void*>(&depth));
|
||||
Backtrace result(depth, kSkipFrames);
|
||||
if (result.array.capacity() > 0) {
|
||||
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&result));
|
||||
}
|
||||
return std::move(result.array);
|
||||
#else
|
||||
const int maxSize = 32;
|
||||
void* buffer[maxSize];
|
||||
|
||||
int size = backtrace(buffer, maxSize);
|
||||
if (size < kSkipFrames) return {};
|
||||
#if (__MINGW32__ || __MINGW64__)
|
||||
// Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller.
|
||||
const size_t kSkipFrames = 2 + skipFrames;
|
||||
#else
|
||||
// Skip GetCurrentStackTrace + anything asked by the caller.
|
||||
const size_t kSkipFrames = 1 + skipFrames;
|
||||
#endif
|
||||
|
||||
KStdVector<void*> result;
|
||||
result.reserve(size - kSkipFrames);
|
||||
for (int index = kSkipFrames; index < size; ++index) {
|
||||
result.push_back(buffer[index]);
|
||||
}
|
||||
#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;
|
||||
#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
|
||||
#endif // !KONAN_NO_BACKTRACE
|
||||
}
|
||||
|
||||
// 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 {
|
||||
#if KONAN_NO_BACKTRACE
|
||||
return {};
|
||||
#else
|
||||
|
||||
#if (__MINGW32__ || __MINGW64__)
|
||||
// Skip GetCurrentStackTrace, _Unwind_Backtrace + anything asked by the caller.
|
||||
const size_t kSkipFrames = 2 + skipFrames;
|
||||
#else
|
||||
// Skip GetCurrentStackTrace + anything asked by the caller.
|
||||
const size_t kSkipFrames = 1 + skipFrames;
|
||||
#endif
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
Backtrace traceHolder(kSkipFrames, buffer);
|
||||
_Unwind_Backtrace(unwindCallback, static_cast<void*>(&traceHolder));
|
||||
return traceHolder.currentSize;
|
||||
#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
|
||||
#endif // !KONAN_NO_BACKTRACE
|
||||
}
|
||||
|
||||
@@ -183,17 +236,18 @@ KNativePtr adjustAddressForSourceInfo(KNativePtr address) {
|
||||
KNativePtr adjustAddressForSourceInfo(KNativePtr address) { return address; }
|
||||
#endif
|
||||
|
||||
KStdVector<KStdString> kotlin::GetStackTraceStrings(void* const* stackTrace, size_t stackTraceSize) noexcept {
|
||||
KStdVector<KStdString> kotlin::GetStackTraceStrings(std_support::span<void* const> stackTrace) noexcept {
|
||||
#if KONAN_NO_BACKTRACE
|
||||
KStdVector<KStdString> strings;
|
||||
strings.push_back("<UNIMPLEMENTED>");
|
||||
return strings;
|
||||
#else
|
||||
size_t size = stackTrace.size();
|
||||
KStdVector<KStdString> strings;
|
||||
strings.reserve(stackTraceSize);
|
||||
if (stackTraceSize > 0) {
|
||||
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 < stackTraceSize; ++index) {
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
KNativePtr address = stackTrace[index];
|
||||
if (!address || reinterpret_cast<uintptr_t>(address) == 1) continue;
|
||||
address = adjustAddressForSourceInfo(address);
|
||||
@@ -246,17 +300,10 @@ 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.
|
||||
|
||||
// TODO: This might have to go into `GetCurrentStackTrace`, but this changes the generated stacktrace for
|
||||
// `Throwable`.
|
||||
#if KONAN_WINDOWS
|
||||
// Skip this function and `_Unwind_Backtrace`.
|
||||
constexpr int kSkipFrames = 2;
|
||||
#else
|
||||
// Skip this function.
|
||||
constexpr int kSkipFrames = 1;
|
||||
#endif
|
||||
auto stackTrace = GetCurrentStackTrace(kSkipFrames);
|
||||
auto stackTraceStrings = GetStackTraceStrings(stackTrace.data(), stackTrace.size());
|
||||
StackTrace trace = StackTrace<>::current(kSkipFrames);
|
||||
auto stackTraceStrings = GetStackTraceStrings(trace.data());
|
||||
for (auto& frame : stackTraceStrings) {
|
||||
konan::consoleErrorUtf8(frame.c_str(), frame.size());
|
||||
konan::consoleErrorf("\n");
|
||||
|
||||
@@ -3,18 +3,143 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_STACK_TRACE_H
|
||||
#define RUNTIME_STACK_TRACE_H
|
||||
|
||||
#include "cpp_support/Span.hpp"
|
||||
#include "Memory.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace kotlin {
|
||||
|
||||
// TODO: Instead of KStd* provide allocator-customizable versions, to allow stack memory allocation.
|
||||
// TODO: Model API as in upcoming https://en.cppreference.com/w/cpp/utility/basic_stacktrace
|
||||
namespace internal {
|
||||
|
||||
KStdVector<void*> GetCurrentStackTrace(int extraSkipFrames) noexcept;
|
||||
NO_INLINE KStdVector<void*> GetCurrentStackTrace(size_t skipFrames) noexcept;
|
||||
NO_INLINE size_t GetCurrentStackTrace(size_t skipFrames, std_support::span<void*> buffer) noexcept;
|
||||
|
||||
// TODO: This is asking for a span.
|
||||
KStdVector<KStdString> GetStackTraceStrings(void* const* stackTrace, size_t stackTraceSize) noexcept;
|
||||
enum class StackTraceCapacityKind {
|
||||
kFixed, kDynamic
|
||||
};
|
||||
|
||||
template <StackTraceCapacityKind kind>
|
||||
constexpr size_t GetMaxStackTraceDepth() noexcept {
|
||||
#if KONAN_NO_BACKTRACE
|
||||
return 0;
|
||||
#elif USE_GCC_UNWIND
|
||||
return std::numeric_limits<size_t>::max();
|
||||
#else
|
||||
switch (kind) {
|
||||
case StackTraceCapacityKind::kFixed:
|
||||
return 32;
|
||||
case StackTraceCapacityKind::kDynamic:
|
||||
return 128;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static constexpr size_t kDynamicCapacity = std::numeric_limits<size_t>::max();
|
||||
|
||||
template <size_t Capacity = kDynamicCapacity>
|
||||
class StackTrace {
|
||||
public:
|
||||
using Iterator = void* const*;
|
||||
|
||||
StackTrace() noexcept : size_(0), buffer_{nullptr} {};
|
||||
StackTrace(const StackTrace& other) = default;
|
||||
StackTrace(StackTrace&& other) noexcept = default;
|
||||
|
||||
StackTrace& operator=(const StackTrace& other) = default;
|
||||
StackTrace& operator=(StackTrace&& other) noexcept = default;
|
||||
|
||||
size_t size() const noexcept {
|
||||
return size_;
|
||||
}
|
||||
|
||||
void* operator[](size_t index) const noexcept {
|
||||
return buffer_[index];
|
||||
}
|
||||
|
||||
Iterator begin() const noexcept {
|
||||
return buffer_.data();
|
||||
}
|
||||
|
||||
Iterator end() const noexcept {
|
||||
return buffer_.data() + size();
|
||||
}
|
||||
|
||||
std_support::span<void* const> data() const noexcept {
|
||||
return std_support::span<void* const>(buffer_.data(), size());
|
||||
}
|
||||
|
||||
// Maximal stacktrace depth that can be collected due to implementation limitations.
|
||||
// Note that this limitation doesn't take into account the skipFrames parameter.
|
||||
// I.e. real size of a returned stacktrace will be limited by (maxDepth - skipFrames).
|
||||
static constexpr size_t maxDepth =
|
||||
std::min(internal::GetMaxStackTraceDepth<internal::StackTraceCapacityKind::kFixed>(), Capacity);
|
||||
|
||||
NO_INLINE static StackTrace current(size_t skipFrames = 0) noexcept {
|
||||
StackTrace result;
|
||||
result.size_ = internal::GetCurrentStackTrace(
|
||||
skipFrames + 1, std_support::span<void*>(result.buffer_.data(), result.buffer_.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t size_;
|
||||
std::array<void*, Capacity> buffer_;
|
||||
};
|
||||
|
||||
template<>
|
||||
class StackTrace<kDynamicCapacity> {
|
||||
public:
|
||||
using Iterator = void* const*;
|
||||
|
||||
StackTrace() noexcept = default;
|
||||
StackTrace(const StackTrace& other) = default;
|
||||
StackTrace(StackTrace&& other) noexcept = default;
|
||||
|
||||
StackTrace& operator=(const StackTrace& other) noexcept = default;
|
||||
StackTrace& operator=(StackTrace&& other) noexcept = default;
|
||||
|
||||
size_t size() const noexcept {
|
||||
return buffer_.size();
|
||||
}
|
||||
|
||||
void* operator[](size_t index) const noexcept {
|
||||
return buffer_[index];
|
||||
}
|
||||
|
||||
Iterator begin() const noexcept {
|
||||
return buffer_.data();
|
||||
}
|
||||
|
||||
Iterator end() const noexcept {
|
||||
return buffer_.data() + size();
|
||||
}
|
||||
|
||||
std_support::span<void* const> data() const noexcept {
|
||||
return std_support::span<void* const>(buffer_.data(), size());
|
||||
}
|
||||
|
||||
// Maximal stacktrace depth that can be collected due to implementation limitations.
|
||||
// Note that this limitation doesn't take into account the skipFrames parameter.
|
||||
// I.e. real size of a returned stacktrace will be limited by (maxDepth - skipFrames).
|
||||
static constexpr size_t maxDepth = internal::GetMaxStackTraceDepth<internal::StackTraceCapacityKind::kDynamic>();
|
||||
|
||||
NO_INLINE static StackTrace current(size_t skipFrames = 0) {
|
||||
StackTrace result;
|
||||
result.buffer_ = internal::GetCurrentStackTrace(skipFrames + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
KStdVector<void*> buffer_;
|
||||
};
|
||||
|
||||
|
||||
KStdVector<KStdString> GetStackTraceStrings(std_support::span<void* const> stackTrace) noexcept;
|
||||
|
||||
// It's not always safe to extract SourceInfo during unhandled exception termination.
|
||||
void DisallowSourceInfo();
|
||||
@@ -22,3 +147,5 @@ void DisallowSourceInfo();
|
||||
void PrintStackTraceStderr();
|
||||
|
||||
} // namespace kotlin
|
||||
|
||||
#endif // RUNTIME_STACK_TRACE_H
|
||||
@@ -14,16 +14,35 @@
|
||||
#include "Porting.h"
|
||||
#include "TestSupport.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
namespace {
|
||||
|
||||
NO_INLINE KStdVector<void*> GetStackTrace1(int skipFrames) {
|
||||
return GetCurrentStackTrace(skipFrames);
|
||||
template <size_t Capacity = kDynamicCapacity>
|
||||
NO_INLINE StackTrace<Capacity> GetStackTrace1(size_t skipFrames = 0) {
|
||||
return StackTrace<Capacity>::current(skipFrames);
|
||||
}
|
||||
|
||||
NO_INLINE KStdVector<void*> GetStackTrace2(int skipFrames) {
|
||||
return GetStackTrace1(skipFrames);
|
||||
template <size_t Capacity = kDynamicCapacity>
|
||||
NO_INLINE StackTrace<Capacity> GetStackTrace2(size_t skipFrames = 0) {
|
||||
return GetStackTrace1<Capacity>(skipFrames);
|
||||
}
|
||||
|
||||
template <size_t Capacity = kDynamicCapacity>
|
||||
NO_INLINE StackTrace<Capacity> GetStackTrace3(size_t skipFrames = 0) {
|
||||
return GetStackTrace2<Capacity>(skipFrames);
|
||||
}
|
||||
|
||||
// Disable optimizations for these functions to avoid inlining and tail recursion optimization.
|
||||
template <size_t Capacity = kDynamicCapacity>
|
||||
[[clang::optnone]] StackTrace<Capacity> GetDeepStackTrace(size_t depth) {
|
||||
if (depth <= 1) {
|
||||
return StackTrace<Capacity>::current();
|
||||
} else {
|
||||
return GetDeepStackTrace<Capacity>(depth - 1);
|
||||
}
|
||||
}
|
||||
|
||||
NO_INLINE void AbortWithStackTrace(int) {
|
||||
@@ -34,29 +53,188 @@ NO_INLINE void AbortWithStackTrace(int) {
|
||||
} // namespace
|
||||
|
||||
TEST(StackTraceTest, StackTrace) {
|
||||
// TODO: Consider incorporating extra skipping to `GetCurrentStackTrace` on windows.
|
||||
#if KONAN_WINDOWS
|
||||
constexpr int kSkip = 1;
|
||||
#else
|
||||
constexpr int kSkip = 0;
|
||||
#endif
|
||||
auto stackTrace = GetStackTrace2(kSkip);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data(), stackTrace.size());
|
||||
ASSERT_GT(symbolicStackTrace.size(), 0ul);
|
||||
auto stackTrace = GetStackTrace3();
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
ASSERT_GT(symbolicStackTrace.size(), 1ul);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackTraceWithSkip) {
|
||||
// TODO: Consider incorporating extra skipping to `GetCurrentStackTrace` on windows.
|
||||
#if KONAN_WINDOWS
|
||||
constexpr int kSkip = 2;
|
||||
#else
|
||||
constexpr int kSkip = 1;
|
||||
#endif
|
||||
auto stackTrace = GetStackTrace2(kSkip);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data(), stackTrace.size());
|
||||
ASSERT_GT(symbolicStackTrace.size(), 0ul);
|
||||
auto stackTrace = GetStackTrace3(kSkip);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
ASSERT_GT(symbolicStackTrace.size(), 1ul);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace2"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace3"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedTrace) {
|
||||
auto stackTrace = GetStackTrace3<2>();
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
ASSERT_EQ(symbolicStackTrace.size(), 2ul);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedTraceWithSkip) {
|
||||
constexpr int kSkip = 1;
|
||||
auto stackTrace = GetStackTrace3<2>(kSkip);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
ASSERT_EQ(symbolicStackTrace.size(), 2ul);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace2"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace3"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, EmptyStackTrace) {
|
||||
constexpr size_t kSkip = 1000000;
|
||||
auto stackTrace = GetStackTrace1(kSkip);
|
||||
EXPECT_EQ(stackTrace.size(), 0ul);
|
||||
auto data = stackTrace.data();
|
||||
EXPECT_EQ(data.size(), 0ul);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(data);
|
||||
EXPECT_EQ(stackTrace.size(), 0ul);
|
||||
EXPECT_EQ(symbolicStackTrace.size(), 0ul);
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedEmptyTrace) {
|
||||
constexpr size_t kSkip = 1000000;
|
||||
auto stackTrace = GetStackTrace1<1>(kSkip);
|
||||
EXPECT_EQ(stackTrace.size(), 0ul);
|
||||
auto data = stackTrace.data();
|
||||
EXPECT_EQ(data.size(), 0ul);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(data);
|
||||
EXPECT_EQ(stackTrace.size(), 0ul);
|
||||
EXPECT_EQ(symbolicStackTrace.size(), 0ul);
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, DeepStackTrace) {
|
||||
constexpr size_t knownStackDepth = 150;
|
||||
auto stackTrace = GetDeepStackTrace(knownStackDepth);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
||||
size_t lastKnownIndex = knownStackDepth - 1;
|
||||
#else
|
||||
// For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth).
|
||||
EXPECT_EQ(stackTrace.size(), StackTrace<>::maxDepth);
|
||||
size_t lastKnownIndex = StackTrace<>::maxDepth - 1;
|
||||
#endif
|
||||
|
||||
ASSERT_GT(symbolicStackTrace.size(), lastKnownIndex);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetDeepStackTrace"));
|
||||
EXPECT_THAT(symbolicStackTrace[lastKnownIndex], testing::HasSubstr("GetDeepStackTrace"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedDeepTrace) {
|
||||
constexpr size_t knownStackDepth = 100;
|
||||
constexpr size_t capacity = 10;
|
||||
auto stackTrace = GetDeepStackTrace<capacity>(knownStackDepth);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(stackTrace.data());
|
||||
|
||||
EXPECT_EQ(StackTrace<capacity>::maxDepth, capacity);
|
||||
EXPECT_EQ(stackTrace.size(), capacity);
|
||||
|
||||
ASSERT_EQ(symbolicStackTrace.size(), capacity);
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetDeepStackTrace"));
|
||||
EXPECT_THAT(symbolicStackTrace[capacity - 1], testing::HasSubstr("GetDeepStackTrace"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedDeepTraceWithEnoughCapacity) {
|
||||
constexpr size_t knownStackDepth = 100;
|
||||
constexpr size_t capacity = 150;
|
||||
auto stackTrace = GetDeepStackTrace<capacity>(knownStackDepth);
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
EXPECT_GE(stackTrace.size(), knownStackDepth);
|
||||
#else
|
||||
// For platforms where the libc unwind is used (e.g. MacOS) the size of a collected trace is limited (see StackTrace::maxDepth).
|
||||
EXPECT_EQ(stackTrace.size(), StackTrace<capacity>::maxDepth);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, Iteration) {
|
||||
auto stackTrace = GetStackTrace2();
|
||||
|
||||
KStdVector<void*> actualAddresses;
|
||||
for (auto addr : stackTrace) {
|
||||
actualAddresses.push_back(addr);
|
||||
}
|
||||
|
||||
EXPECT_GT(actualAddresses.size(), 0ul);
|
||||
EXPECT_EQ(actualAddresses.size(), stackTrace.size());
|
||||
|
||||
auto symbolicStackTrace = GetStackTraceStrings(std_support::span<void*>(actualAddresses.data(), actualAddresses.size()));
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedIteration) {
|
||||
auto stackTrace = GetStackTrace2<2>();
|
||||
|
||||
KStdVector<void*> actualAddresses;
|
||||
for (auto addr : stackTrace) {
|
||||
actualAddresses.push_back(addr);
|
||||
}
|
||||
|
||||
EXPECT_EQ(actualAddresses.size(), 2ul);
|
||||
EXPECT_EQ(actualAddresses.size(), stackTrace.size());
|
||||
|
||||
auto symbolicStackTrace = GetStackTraceStrings(std_support::span<void*>(actualAddresses.data(), actualAddresses.size()));
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, IndexedAccess) {
|
||||
auto stackTrace = GetStackTrace2();
|
||||
|
||||
KStdVector<void*> actualAddresses;
|
||||
for (size_t i = 0; i < stackTrace.size(); i++) {
|
||||
actualAddresses.push_back(stackTrace[i]);
|
||||
}
|
||||
|
||||
EXPECT_GT(actualAddresses.size(), 0ul);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(std_support::span<void*>(actualAddresses.data(), actualAddresses.size()));
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedIndexedAccess) {
|
||||
auto stackTrace = GetStackTrace2<2>();
|
||||
|
||||
KStdVector<void*> actualAddresses;
|
||||
for (size_t i = 0; i < stackTrace.size(); i++) {
|
||||
actualAddresses.push_back(stackTrace[i]);
|
||||
}
|
||||
|
||||
EXPECT_EQ(actualAddresses.size(), 2ul);
|
||||
auto symbolicStackTrace = GetStackTraceStrings(std_support::span<void*>(actualAddresses.data(), actualAddresses.size()));
|
||||
EXPECT_THAT(symbolicStackTrace[0], testing::HasSubstr("GetStackTrace1"));
|
||||
EXPECT_THAT(symbolicStackTrace[1], testing::HasSubstr("GetStackTrace2"));
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, IndexedAccessAndIteration) {
|
||||
auto stackTrace = GetStackTrace2();
|
||||
|
||||
size_t i = 0;
|
||||
for (auto addr : stackTrace) {
|
||||
EXPECT_EQ(addr, stackTrace[i]);
|
||||
i++;
|
||||
}
|
||||
EXPECT_EQ(stackTrace.size(), i);
|
||||
}
|
||||
|
||||
TEST(StackTraceTest, StackAllocatedIndexedAccessAndIteration) {
|
||||
auto stackTrace = GetStackTrace2<2>();
|
||||
|
||||
size_t i = 0;
|
||||
for (auto addr : stackTrace) {
|
||||
EXPECT_EQ(addr, stackTrace[i]);
|
||||
i++;
|
||||
}
|
||||
EXPECT_EQ(stackTrace.size(), i);
|
||||
EXPECT_EQ(stackTrace.size(), 2ul);
|
||||
}
|
||||
|
||||
TEST(StackTraceDeathTest, PrintStackTrace) {
|
||||
|
||||
Reference in New Issue
Block a user