summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--absl/debugging/BUILD.bazel12
-rw-r--r--absl/debugging/CMakeLists.txt13
-rw-r--r--absl/debugging/internal/stacktrace_x86-inl.inc18
-rw-r--r--absl/debugging/stacktrace_test.cc47
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