// Copyright 2023 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. #include "absl/debugging/stacktrace.h" #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/optimization.h" #include "absl/types/span.h" static int g_should_fixup_calls = 0; static int g_fixup_calls = 0; static bool g_enable_fixup = false; #if ABSL_HAVE_ATTRIBUTE_WEAK bool absl::internal_stacktrace::ShouldFixUpStack() { ++g_should_fixup_calls; return g_enable_fixup; } void absl::internal_stacktrace::FixUpStack(void**, uintptr_t*, int*, size_t, size_t&) { ++g_fixup_calls; } #endif namespace { using ::testing::ContainerEq; using ::testing::Contains; using ::testing::internal::Cleanup; struct StackTrace { static constexpr int kStackCount = 64; int depth; void* result[kStackCount]; uintptr_t frames[kStackCount]; int sizes[kStackCount]; }; // This test is currently only known to pass on Linux x86_64/aarch64. #if defined(__linux__) && (defined(__x86_64__) || defined(__aarch64__)) ABSL_ATTRIBUTE_NOINLINE void Unwind(void* p) { ABSL_ATTRIBUTE_UNUSED static void* volatile sink = p; constexpr int kSize = 16; void* stack[kSize]; int frames[kSize]; absl::GetStackTrace(stack, kSize, 0); absl::GetStackFrames(stack, frames, kSize, 0); } ABSL_ATTRIBUTE_NOINLINE void HugeFrame() { char buffer[1 << 20]; Unwind(buffer); ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); } TEST(StackTrace, HugeFrame) { // Ensure that the unwinder is not confused by very large stack frames. HugeFrame(); ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); } #endif // This is a separate function to avoid inlining. ABSL_ATTRIBUTE_NOINLINE static void FixupNoFixupEquivalenceNoInline() { #if defined(__riscv) GTEST_SKIP() << "Skipping test on RISC-V due to pre-existing failure"; #endif #if ABSL_HAVE_ATTRIBUTE_WEAK // This test is known not to pass on MSVC (due to weak symbols) const Cleanup restore_state([enable_fixup = g_enable_fixup, fixup_calls = g_fixup_calls, should_fixup_calls = g_should_fixup_calls]() { g_enable_fixup = enable_fixup; g_fixup_calls = fixup_calls; g_should_fixup_calls = should_fixup_calls; }); constexpr int kSkip = 1; // Skip our own frame, whose return PCs won't match constexpr auto kStackCount = 1; StackTrace a; StackTrace b; // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::GetStackTrace(a.result, kStackCount, kSkip); g_enable_fixup = !g_enable_fixup; b.depth = absl::GetStackTrace(b.result, kStackCount, kSkip); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::GetStackFrames(a.result, a.sizes, kStackCount, kSkip); g_enable_fixup = !g_enable_fixup; b.depth = absl::GetStackFrames(b.result, b.sizes, kStackCount, kSkip); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.sizes, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.sizes, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::GetStackTraceWithContext(a.result, kStackCount, kSkip, nullptr, nullptr); g_enable_fixup = !g_enable_fixup; b.depth = absl::GetStackTraceWithContext(b.result, kStackCount, kSkip, nullptr, nullptr); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::GetStackFramesWithContext(a.result, a.sizes, kStackCount, kSkip, nullptr, nullptr); g_enable_fixup = !g_enable_fixup; b.depth = absl::GetStackFramesWithContext(b.result, b.sizes, kStackCount, kSkip, nullptr, nullptr); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.sizes, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.sizes, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::internal_stacktrace::GetStackFrames( a.result, a.frames, a.sizes, kStackCount, kSkip); g_enable_fixup = !g_enable_fixup; b.depth = absl::internal_stacktrace::GetStackFrames( b.result, b.frames, b.sizes, kStackCount, kSkip); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.sizes, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.sizes, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.frames, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.frames, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== g_fixup_calls = 0; g_should_fixup_calls = 0; a.depth = absl::internal_stacktrace::GetStackFramesWithContext( a.result, a.frames, a.sizes, kStackCount, kSkip, nullptr, nullptr); g_enable_fixup = !g_enable_fixup; b.depth = absl::internal_stacktrace::GetStackFramesWithContext( b.result, b.frames, b.sizes, kStackCount, kSkip, nullptr, nullptr); EXPECT_THAT( absl::MakeSpan(a.result, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.result, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.sizes, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.sizes, static_cast(b.depth)))); EXPECT_THAT( absl::MakeSpan(a.frames, static_cast(a.depth)), ContainerEq(absl::MakeSpan(b.frames, static_cast(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); // ========================================================================== #else GTEST_SKIP() << "Need weak symbol support"; #endif } TEST(StackTrace, FixupNoFixupEquivalence) { FixupNoFixupEquivalenceNoInline(); } #if ABSL_HAVE_BUILTIN(__builtin_frame_address) struct FrameInfo { const void* return_address; uintptr_t frame_address; }; // Returns the canonical frame address and return address for the current stack // frame, while capturing the stack trace at the same time. // This performs any platform-specific adjustments necessary to convert from the // compiler built-ins to the expected API outputs. ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack. ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack. ABSL_ATTRIBUTE_NOINLINE static FrameInfo CaptureBacktraceNoInline(StackTrace& backtrace) { FrameInfo result; result.return_address = __builtin_return_address(0); // Large enough to cover all realistic slots the return address could be in const int kMaxReturnAddressIndex = 5; void* const* bfa = static_cast(__builtin_frame_address(0)); backtrace.depth = absl::internal_stacktrace::GetStackFramesWithContext( backtrace.result, backtrace.frames, backtrace.sizes, StackTrace::kStackCount, /*skip_count=*/0, /*uc=*/nullptr, /*min_dropped_frames=*/nullptr); // Make sure the return address is at a reasonable location in the frame ptrdiff_t i; for (i = 0; i < kMaxReturnAddressIndex; ++i) { // Avoid std::find() here, since it lacks no-sanitize attributes. if (bfa[i] == result.return_address) { break; } } result.frame_address = i < kMaxReturnAddressIndex ? reinterpret_cast( bfa + i + 1 /* get the Canonical Frame Address (CFA) */) : 0; return result; } TEST(StackTrace, CanonicalFrameAddresses) { // Now capture a stack trace and verify that the return addresses and frame // addresses line up for one frame. StackTrace backtrace; const auto [return_address, frame_address] = CaptureBacktraceNoInline(backtrace); auto return_addresses = absl::MakeSpan(backtrace.result) .subspan(0, static_cast(backtrace.depth)); auto frame_addresses = absl::MakeSpan(backtrace.frames) .subspan(0, static_cast(backtrace.depth)); // Many platforms don't support this by default. bool support_is_expected = false; if (support_is_expected) { // If all zeros were returned, that is valid per the function's contract. // It just means we don't support returning frame addresses on this // platform. bool supported = static_cast(std::count(frame_addresses.begin(), frame_addresses.end(), 0)) < frame_addresses.size(); EXPECT_TRUE(supported); if (supported) { ASSERT_TRUE(frame_address) << "unable to obtain frame address corresponding to return address"; EXPECT_THAT(return_addresses, Contains(return_address).Times(1)); EXPECT_THAT(frame_addresses, Contains(frame_address).Times(1)); ptrdiff_t ifound = std::find(return_addresses.begin(), return_addresses.end(), return_address) - return_addresses.begin(); // Make sure we found the frame in the first place. ASSERT_LT(ifound, backtrace.depth); // Make sure the frame address actually corresponds to the return // address. EXPECT_EQ(frame_addresses[static_cast(ifound)], frame_address); // Make sure the addresses only appear once. } } } #endif // This test is Linux specific. #if defined(__linux__) const void* g_return_address = nullptr; bool g_sigusr2_raised = false; void SigUsr2Handler(int, siginfo_t*, void* uc) { // Many platforms don't support this by default. bool support_is_expected = false; constexpr int kMaxStackDepth = 64; void* result[kMaxStackDepth]; int depth = absl::GetStackTraceWithContext(result, kMaxStackDepth, 0, uc, nullptr); // Verify we can unwind past the nested signal handlers. if (support_is_expected) { EXPECT_THAT(absl::MakeSpan(result, static_cast(depth)), Contains(g_return_address).Times(1)); } depth = absl::GetStackTrace(result, kMaxStackDepth, 0); if (support_is_expected) { EXPECT_THAT(absl::MakeSpan(result, static_cast(depth)), Contains(g_return_address).Times(1)); } g_sigusr2_raised = true; } void SigUsr1Handler(int, siginfo_t*, void*) { raise(SIGUSR2); ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); } ABSL_ATTRIBUTE_NOINLINE void RaiseSignal() { g_return_address = __builtin_return_address(0); raise(SIGUSR1); ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); } ABSL_ATTRIBUTE_NOINLINE void TestNestedSignal() { constexpr size_t kAltstackSize = 1 << 14; // Allocate altstack on regular stack to make sure it'll have a higher // address than some of the regular stack frames. char space[kAltstackSize]; stack_t altstack; stack_t old_stack; altstack.ss_sp = space; altstack.ss_size = kAltstackSize; altstack.ss_flags = 0; ASSERT_EQ(sigaltstack(&altstack, &old_stack), 0) << strerror(errno); struct sigaction act; struct sigaction oldusr1act; struct sigaction oldusr2act; act.sa_sigaction = SigUsr1Handler; act.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&act.sa_mask); ASSERT_EQ(sigaction(SIGUSR1, &act, &oldusr1act), 0) << strerror(errno); act.sa_sigaction = SigUsr2Handler; ASSERT_EQ(sigaction(SIGUSR2, &act, &oldusr2act), 0) << strerror(errno); RaiseSignal(); ASSERT_EQ(sigaltstack(&old_stack, nullptr), 0) << strerror(errno); ASSERT_EQ(sigaction(SIGUSR1, &oldusr1act, nullptr), 0) << strerror(errno); ASSERT_EQ(sigaction(SIGUSR2, &oldusr2act, nullptr), 0) << strerror(errno); ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); } TEST(StackTrace, NestedSignal) { // Verify we can unwind past the nested signal handlers. TestNestedSignal(); EXPECT_TRUE(g_sigusr2_raised); } #endif } // namespace