Commit c611e5ce by Abseil Team Committed by Copybara-Service

absl: add a stack unwinding test

END_PUBLIC

absl: relax frame size check in x86 stack unwinding

Currently the unwinder stops whenever it sees a frame >100000 bytes.
There may be such legitimate frames, the default stack size is O(megabytes).
Only do this check if we are not within the thread stack bounds.
The thread stack is assumed to be readable, so the worst thing that can happen
if the large stack frame is actually bogus is that we will add one/few wrong frames and stop.

PiperOrigin-RevId: 503374764
Change-Id: Icabb55d6468b12a42bf026c37c98dbe84977e659
parent 0d116682
...@@ -53,6 +53,18 @@ cc_library( ...@@ -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( cc_library(
name = "symbolize", name = "symbolize",
srcs = [ srcs = [
......
...@@ -45,6 +45,19 @@ absl_cc_library( ...@@ -45,6 +45,19 @@ absl_cc_library(
PUBLIC 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( absl_cc_library(
NAME NAME
symbolize symbolize
......
...@@ -112,6 +112,10 @@ static int CountPushInstructions(const unsigned char *const addr) { ...@@ -112,6 +112,10 @@ static int CountPushInstructions(const unsigned char *const addr) {
// Assume stack frames larger than 100,000 bytes are bogus. // Assume stack frames larger than 100,000 bytes are bogus.
static const int kMaxFrameBytes = 100000; 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. // Returns the stack frame pointer from signal context, 0 if unknown.
// vuc is a ucontext_t *. We use void* to avoid the use // 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, ...@@ -258,8 +262,18 @@ static void **NextStackFrame(void **old_fp, const void *uc,
// With the stack growing downwards, older stack frame must be // With the stack growing downwards, older stack frame must be
// at a greater address that the current one. // at a greater address that the current one.
if (new_fp_u <= old_fp_u) return nullptr; 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) { if (stack_low < old_fp_u && old_fp_u <= stack_high) {
// Old BP was in the expected stack region... // Old BP was in the expected stack region...
if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { 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, ...@@ -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. // Assume that the first page is not stack.
size_t stack_low = static_cast<size_t>(getpagesize()); 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) { while (fp && n < max_depth) {
if (*(fp + 1) == reinterpret_cast<void *>(0)) { if (*(fp + 1) == reinterpret_cast<void *>(0)) {
......
// 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment