diff options
-rw-r--r-- | absl/debugging/BUILD.bazel | 12 | ||||
-rw-r--r-- | absl/debugging/CMakeLists.txt | 13 | ||||
-rw-r--r-- | absl/debugging/internal/stacktrace_x86-inl.inc | 18 | ||||
-rw-r--r-- | absl/debugging/stacktrace_test.cc | 47 |
4 files changed, 88 insertions, 2 deletions
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index a40285c8..edbb3698 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -53,6 +53,18 @@ cc_library( ], ) +cc_test( + name = "stacktrace_test", + srcs = ["stacktrace_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":stacktrace", + "//absl/base:core_headers", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "symbolize", srcs = [ diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index e823f15b..8f29cc07 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -45,6 +45,19 @@ absl_cc_library( PUBLIC ) +absl_cc_test( + NAME + stacktrace_test + SRCS + "stacktrace_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::stacktrace + absl::core_headers + GTest::gmock_main +) + absl_cc_library( NAME symbolize diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 2f8bf428..ada2628d 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc @@ -112,6 +112,10 @@ static int CountPushInstructions(const unsigned char *const addr) { // Assume stack frames larger than 100,000 bytes are bogus. static const int kMaxFrameBytes = 100000; +// Stack end to use when we don't know the actual stack end +// (effectively just the end of address space). +constexpr uintptr_t kUnknownStackEnd = + std::numeric_limits<size_t>::max() - sizeof(void *); // Returns the stack frame pointer from signal context, 0 if unknown. // vuc is a ucontext_t *. We use void* to avoid the use @@ -258,8 +262,18 @@ static void **NextStackFrame(void **old_fp, const void *uc, // With the stack growing downwards, older stack frame must be // at a greater address that the current one. if (new_fp_u <= old_fp_u) return nullptr; - if (new_fp_u - old_fp_u > kMaxFrameBytes) return nullptr; + // If we get a very large frame size, it may be an indication that we + // guessed frame pointers incorrectly and now risk a paging fault + // dereferencing a wrong frame pointer. Or maybe not because large frames + // are possible as well. The main stack is assumed to be readable, + // so we assume the large frame is legit if we know the stack bounds and are + // within the stack. + if (new_fp_u - old_fp_u > kMaxFrameBytes && + (stack_high == kUnknownStackEnd || + !(stack_low < new_fp_u && new_fp_u <= stack_high))) { + return nullptr; + } if (stack_low < old_fp_u && old_fp_u <= stack_high) { // Old BP was in the expected stack region... if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { @@ -312,7 +326,7 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, // Assume that the first page is not stack. size_t stack_low = static_cast<size_t>(getpagesize()); - size_t stack_high = std::numeric_limits<size_t>::max() - sizeof(void *); + size_t stack_high = kUnknownStackEnd; while (fp && n < max_depth) { if (*(fp + 1) == reinterpret_cast<void *>(0)) { diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc new file mode 100644 index 00000000..78ce7ad0 --- /dev/null +++ b/absl/debugging/stacktrace_test.cc @@ -0,0 +1,47 @@ +// 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 "gtest/gtest.h" +#include "absl/base/macros.h" +#include "absl/base/optimization.h" + +namespace { + +// This test is currently only known to pass on linux/x86_64. +#if defined(__linux__) && defined(__x86_64__) +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 + +} // namespace |