| // Copyright 2017 The Abseil Authors. | |
| // | |
| // 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 | |
| // | |
| // https://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. | |
| // Produce stack trace. | |
| // | |
| // There are three different ways we can try to get the stack trace: | |
| // | |
| // 1) Our hand-coded stack-unwinder. This depends on a certain stack | |
| // layout, which is used by gcc (and those systems using a | |
| // gcc-compatible ABI) on x86 systems, at least since gcc 2.95. | |
| // It uses the frame pointer to do its work. | |
| // | |
| // 2) The libunwind library. This is still in development, and as a | |
| // separate library adds a new dependency, but doesn't need a frame | |
| // pointer. It also doesn't call malloc. | |
| // | |
| // 3) The gdb unwinder -- also the one used by the c++ exception code. | |
| // It's obviously well-tested, but has a fatal flaw: it can call | |
| // malloc() from the unwinder. This is a problem because we're | |
| // trying to use the unwinder to instrument malloc(). | |
| // | |
| // Note: if you add a new implementation here, make sure it works | |
| // correctly when absl::GetStackTrace() is called with max_depth == 0. | |
| // Some code may do that. | |
| #include "absl/debugging/stacktrace.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <algorithm> | |
| #include <atomic> | |
| #include "absl/base/attributes.h" | |
| #include "absl/base/config.h" | |
| #include "absl/base/optimization.h" | |
| #include "absl/base/port.h" | |
| #include "absl/debugging/internal/stacktrace_config.h" | |
| #if defined(ABSL_STACKTRACE_INL_HEADER) | |
| #include ABSL_STACKTRACE_INL_HEADER | |
| #else | |
| #error Cannot calculate stack trace: will need to write for your environment | |
| #include "absl/debugging/internal/stacktrace_aarch64-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_arm-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_emscripten-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_generic-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_powerpc-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_riscv-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_unimplemented-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_win32-inl.inc" | |
| #include "absl/debugging/internal/stacktrace_x86-inl.inc" | |
| #endif | |
| namespace absl { | |
| ABSL_NAMESPACE_BEGIN | |
| namespace { | |
| typedef int (*Unwinder)(void**, int*, int, int, const void*, int*); | |
| std::atomic<Unwinder> custom; | |
| template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT> | |
| ABSL_ATTRIBUTE_ALWAYS_INLINE inline int Unwind(void** result, uintptr_t* frames, | |
| int* sizes, size_t max_depth, | |
| int skip_count, const void* uc, | |
| int* min_dropped_frames, | |
| bool unwind_with_fixup = true) { | |
| unwind_with_fixup = | |
| unwind_with_fixup && internal_stacktrace::ShouldFixUpStack(); | |
| #ifdef _WIN32 | |
| if (unwind_with_fixup) { | |
| // TODO(b/434184677): Fixups are flaky and not supported on Windows | |
| unwind_with_fixup = false; | |
| #ifndef NDEBUG | |
| abort(); | |
| #endif | |
| } | |
| #endif | |
| Unwinder g = custom.load(std::memory_order_acquire); | |
| size_t size; | |
| // Add 1 to skip count for the unwinder function itself | |
| ++skip_count; | |
| if (g != nullptr) { | |
| size = static_cast<size_t>((*g)(result, sizes, static_cast<int>(max_depth), | |
| skip_count, uc, min_dropped_frames)); | |
| // Frame pointers aren't returned by existing hooks, so clear them. | |
| if (frames != nullptr) { | |
| std::fill(frames, frames + size, uintptr_t()); | |
| } | |
| } else { | |
| size = static_cast<size_t>(UnwindImpl<IS_STACK_FRAMES, IS_WITH_CONTEXT>( | |
| result, frames, sizes, static_cast<int>(max_depth), skip_count, uc, | |
| min_dropped_frames)); | |
| } | |
| if (unwind_with_fixup) { | |
| internal_stacktrace::FixUpStack(result, frames, sizes, max_depth, size); | |
| } | |
| ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); | |
| return static_cast<int>(size); | |
| } | |
| } // anonymous namespace | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int GetStackFrames( | |
| void** result, int* sizes, int max_depth, int skip_count) { | |
| return Unwind<true, false>(result, nullptr, sizes, | |
| static_cast<size_t>(max_depth), skip_count, | |
| nullptr, nullptr); | |
| } | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int | |
| internal_stacktrace::GetStackTraceNoFixup(void** result, int max_depth, | |
| int skip_count) { | |
| return Unwind<false, false>(result, nullptr, nullptr, | |
| static_cast<size_t>(max_depth), skip_count, | |
| nullptr, nullptr, /*unwind_with_fixup=*/false); | |
| } | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int | |
| GetStackFramesWithContext(void** result, int* sizes, int max_depth, | |
| int skip_count, const void* uc, | |
| int* min_dropped_frames) { | |
| return Unwind<true, true>(result, nullptr, sizes, | |
| static_cast<size_t>(max_depth), skip_count, uc, | |
| min_dropped_frames); | |
| } | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int GetStackTrace( | |
| void** result, int max_depth, int skip_count) { | |
| return Unwind<false, false>(result, nullptr, nullptr, | |
| static_cast<size_t>(max_depth), skip_count, | |
| nullptr, nullptr); | |
| } | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int | |
| GetStackTraceWithContext(void** result, int max_depth, int skip_count, | |
| const void* uc, int* min_dropped_frames) { | |
| return Unwind<false, true>(result, nullptr, nullptr, | |
| static_cast<size_t>(max_depth), skip_count, uc, | |
| min_dropped_frames); | |
| } | |
| void SetStackUnwinder(Unwinder w) { | |
| custom.store(w, std::memory_order_release); | |
| } | |
| // As of LLVM commit go/compilers/fc1afa6edce98a973ce2ce969c589da4f3aee975, | |
| // CFI functions are inlinable in the ThinLTO backend. As a result, these | |
| // wrappers were inlined into their callers, causing stack unwinding to skip an | |
| // extra caller frame. Prevent inlining to ensure their stack frames are | |
| // preserved when calculating `skip_count`. | |
| ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int DefaultStackUnwinder( | |
| void** pcs, int* sizes, int depth, int skip, const void* uc, | |
| int* min_dropped_frames) { | |
| skip++; // For this function | |
| decltype(&UnwindImpl<false, false>) f; | |
| if (sizes == nullptr) { | |
| if (uc == nullptr) { | |
| f = &UnwindImpl<false, false>; | |
| } else { | |
| f = &UnwindImpl<false, true>; | |
| } | |
| } else { | |
| if (uc == nullptr) { | |
| f = &UnwindImpl<true, false>; | |
| } else { | |
| f = &UnwindImpl<true, true>; | |
| } | |
| } | |
| int n = (*f)(pcs, nullptr, sizes, depth, skip, uc, min_dropped_frames); | |
| ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); | |
| return n; | |
| } | |
| ABSL_ATTRIBUTE_WEAK bool internal_stacktrace::ShouldFixUpStack() { | |
| return false; | |
| } | |
| // Fixes up the stack trace of the current thread, in the first `depth` frames | |
| // of each buffer. The buffers need to be larger than `depth`, to accommodate | |
| // any newly inserted elements. `depth` is updated to reflect the new number of | |
| // elements valid across all the buffers. (It is therefore recommended that all | |
| // buffer sizes be equal.) | |
| // | |
| // The `frames` and `sizes` parameters denote the bounds of the stack frame | |
| // corresponding to each instruction pointer in the `pcs`. | |
| // Any elements inside these buffers may be zero or null, in which case that | |
| // information is assumed to be absent/unavailable. | |
| ABSL_ATTRIBUTE_WEAK void internal_stacktrace::FixUpStack(void**, uintptr_t*, | |
| int*, size_t, | |
| size_t&) {} | |
| ABSL_NAMESPACE_END | |
| } // namespace absl |