summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--absl/debugging/BUILD.bazel26
-rw-r--r--absl/debugging/internal/stack_consumption.cc172
-rw-r--r--absl/debugging/internal/stack_consumption.h45
-rw-r--r--absl/debugging/internal/stack_consumption_test.cc48
4 files changed, 291 insertions, 0 deletions
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel
index 7a56e496..d4cdf492 100644
--- a/absl/debugging/BUILD.bazel
+++ b/absl/debugging/BUILD.bazel
@@ -17,6 +17,7 @@
load(
"//absl:copts.bzl",
"ABSL_DEFAULT_COPTS",
+ "ABSL_TEST_COPTS",
)
package(
@@ -167,3 +168,28 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
+
+cc_library(
+ name = "stack_consumption",
+ testonly = 1,
+ srcs = ["internal/stack_consumption.cc"],
+ hdrs = ["internal/stack_consumption.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ deps = [
+ "//absl/base",
+ "//absl/base:core_headers",
+ ],
+ visibility = ["//visibility:private"],
+)
+
+cc_test(
+ name = "stack_consumption_test",
+ srcs = ["internal/stack_consumption_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ deps = [
+ ":stack_consumption",
+ "//absl/base",
+ "//absl/base:core_headers",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/absl/debugging/internal/stack_consumption.cc b/absl/debugging/internal/stack_consumption.cc
new file mode 100644
index 00000000..2b3b972e
--- /dev/null
+++ b/absl/debugging/internal/stack_consumption.cc
@@ -0,0 +1,172 @@
+//
+// Copyright 2018 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
+//
+// http://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/internal/stack_consumption.h"
+
+#ifdef ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
+
+#include <signal.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <string.h>
+
+#include "absl/base/attributes.h"
+#include "absl/base/internal/raw_logging.h"
+
+namespace absl {
+namespace debugging_internal {
+namespace {
+
+// This code requires that we know the direction in which the stack
+// grows. It is commonly believed that this can be detected by putting
+// a variable on the stack and then passing its address to a function
+// that compares the address of this variable to the address of a
+// variable on the function's own stack. However, this is unspecified
+// behavior in C++: If two pointers p and q of the same type point to
+// different objects that are not members of the same object or
+// elements of the same array or to different functions, or if only
+// one of them is null, the results of p<q, p>q, p<=q, and p>=q are
+// unspecified. Therefore, instead we hardcode the direction of the
+// stack on platforms we know about.
+#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__)
+constexpr bool kStackGrowsDown = true;
+#else
+#error Need to define kStackGrowsDown
+#endif
+
+// To measure the stack footprint of some code, we create a signal handler
+// (for SIGUSR2 say) that exercises this code on an alternate stack. This
+// alternate stack is initialized to some known pattern (0x55, 0x55, 0x55,
+// ...). We then self-send this signal, and after the signal handler returns,
+// look at the alternate stack buffer to see what portion has been touched.
+//
+// This trick gives us the the stack footprint of the signal handler. But the
+// signal handler, even before the code for it is exercised, consumes some
+// stack already. We however only want the stack usage of the code inside the
+// signal handler. To measure this accurately, we install two signal handlers:
+// one that does nothing and just returns, and the user-provided signal
+// handler. The difference between the stack consumption of these two signals
+// handlers should give us the stack foorprint of interest.
+
+void EmptySignalHandler(int) {}
+
+// This is arbitrary value, and could be increase further, at the cost of
+// memset()ting it all to known sentinel value.
+constexpr int kAlternateStackSize = 64 << 10; // 64KiB
+
+constexpr int kSafetyMargin = 32;
+constexpr char kAlternateStackFillValue = 0x55;
+
+// These helper functions look at the alternate stack buffer, and figure
+// out what portion of this buffer has been touched - this is the stack
+// consumption of the signal handler running on this alternate stack.
+// This function will return -1 if the alternate stack buffer has not been
+// touched. It will abort the program if the buffer has overflowed or is about
+// to overflow.
+int GetStackConsumption(const void* const altstack) {
+ const char* begin;
+ int increment;
+ if (kStackGrowsDown) {
+ begin = reinterpret_cast<const char*>(altstack);
+ increment = 1;
+ } else {
+ begin = reinterpret_cast<const char*>(altstack) + kAlternateStackSize - 1;
+ increment = -1;
+ }
+
+ for (int usage_count = kAlternateStackSize; usage_count > 0; --usage_count) {
+ if (*begin != kAlternateStackFillValue) {
+ ABSL_RAW_CHECK(usage_count <= kAlternateStackSize - kSafetyMargin,
+ "Buffer has overflowed or is about to overflow");
+ return usage_count;
+ }
+ begin += increment;
+ }
+
+ ABSL_RAW_LOG(FATAL, "Unreachable code");
+ return -1;
+}
+
+} // namespace
+
+int GetSignalHandlerStackConsumption(void (*signal_handler)(int)) {
+ // The alt-signal-stack cannot be heap allocated because there is a
+ // bug in glibc-2.2 where some signal handler setup code looks at the
+ // current stack pointer to figure out what thread is currently running.
+ // Therefore, the alternate stack must be allocated from the main stack
+ // itself.
+ void* altstack = mmap(nullptr, kAlternateStackSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ABSL_RAW_CHECK(altstack != MAP_FAILED, "mmap() failed");
+
+ // Set up the alt-signal-stack (and save the older one).
+ stack_t sigstk;
+ memset(&sigstk, 0, sizeof(sigstk));
+ stack_t old_sigstk;
+ sigstk.ss_sp = altstack;
+ sigstk.ss_size = kAlternateStackSize;
+ sigstk.ss_flags = 0;
+ ABSL_RAW_CHECK(sigaltstack(&sigstk, &old_sigstk) == 0,
+ "sigaltstack() failed");
+
+ // Set up SIGUSR1 and SIGUSR2 signal handlers (and save the older ones).
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ struct sigaction old_sa1, old_sa2;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_ONSTACK;
+
+ // SIGUSR1 maps to EmptySignalHandler.
+ sa.sa_handler = EmptySignalHandler;
+ ABSL_RAW_CHECK(sigaction(SIGUSR1, &sa, &old_sa1) == 0, "sigaction() failed");
+
+ // SIGUSR2 maps to signal_handler.
+ sa.sa_handler = signal_handler;
+ ABSL_RAW_CHECK(sigaction(SIGUSR2, &sa, &old_sa2) == 0, "sigaction() failed");
+
+ // Send SIGUSR1 signal and measure the stack consumption of the empty
+ // signal handler.
+ // The first signal might use more stack space. Run once and ignore the
+ // results to get that out of the way.
+ ABSL_RAW_CHECK(kill(getpid(), SIGUSR1) == 0, "kill() failed");
+
+ memset(altstack, kAlternateStackFillValue, kAlternateStackSize);
+ ABSL_RAW_CHECK(kill(getpid(), SIGUSR1) == 0, "kill() failed");
+ int base_stack_consumption = GetStackConsumption(altstack);
+
+ // Send SIGUSR2 signal and measure the stack consumption of signal_handler.
+ ABSL_RAW_CHECK(kill(getpid(), SIGUSR2) == 0, "kill() failed");
+ int signal_handler_stack_consumption = GetStackConsumption(altstack);
+
+ // Now restore the old alt-signal-stack and signal handlers.
+ ABSL_RAW_CHECK(sigaltstack(&old_sigstk, nullptr) == 0,
+ "sigaltstack() failed");
+ ABSL_RAW_CHECK(sigaction(SIGUSR1, &old_sa1, nullptr) == 0,
+ "sigaction() failed");
+ ABSL_RAW_CHECK(sigaction(SIGUSR2, &old_sa2, nullptr) == 0,
+ "sigaction() failed");
+
+ ABSL_RAW_CHECK(munmap(altstack, kAlternateStackSize) == 0, "munmap() failed");
+ if (signal_handler_stack_consumption != -1 && base_stack_consumption != -1) {
+ return signal_handler_stack_consumption - base_stack_consumption;
+ }
+ return -1;
+}
+
+} // namespace debugging_internal
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
diff --git a/absl/debugging/internal/stack_consumption.h b/absl/debugging/internal/stack_consumption.h
new file mode 100644
index 00000000..5aa1a109
--- /dev/null
+++ b/absl/debugging/internal/stack_consumption.h
@@ -0,0 +1,45 @@
+//
+// Copyright 2018 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
+//
+// http://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.
+
+// Helper function for measuring stack consumption of signal handlers.
+
+#ifndef ABSL_DEBUGGING_INTERNAL_STACK_CONSUMPTION_H_
+#define ABSL_DEBUGGING_INTERNAL_STACK_CONSUMPTION_H_
+
+// The code in this module is not portable.
+// Use this feature test macro to detect its availability.
+#ifdef ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
+#error ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION cannot be set directly
+#elif !defined(__APPLE__) && \
+ (defined(__i386__) || defined(__x86_64__) || defined(__ppc__))
+#define ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION 1
+
+namespace absl {
+namespace debugging_internal {
+
+// Returns the stack consumption in bytes for the code exercised by
+// signal_handler. To measure stack consumption, signal_handler is registered
+// as a signal handler, so the code that it exercises must be async-signal
+// safe. The argument of signal_handler is an implementation detail of signal
+// handlers and should ignored by the code for signal_handler. Use global
+// variables to pass information between your test code and signal_handler.
+int GetSignalHandlerStackConsumption(void (*signal_handler)(int));
+
+} // namespace debugging_internal
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
+
+#endif // ABSL_DEBUGGING_INTERNAL_STACK_CONSUMPTION_H_
diff --git a/absl/debugging/internal/stack_consumption_test.cc b/absl/debugging/internal/stack_consumption_test.cc
new file mode 100644
index 00000000..5ce3846e
--- /dev/null
+++ b/absl/debugging/internal/stack_consumption_test.cc
@@ -0,0 +1,48 @@
+//
+// Copyright 2018 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
+//
+// http://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/internal/stack_consumption.h"
+
+#ifdef ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
+
+#include <string.h>
+
+#include "gtest/gtest.h"
+#include "absl/base/internal/raw_logging.h"
+
+namespace absl {
+namespace debugging_internal {
+namespace {
+
+static void SimpleSignalHandler(int signo) {
+ char buf[100];
+ memset(buf, 'a', sizeof(buf));
+
+ // Never true, but prevents compiler from optimizing buf out.
+ if (signo == 0) {
+ ABSL_RAW_LOG(INFO, "%p", static_cast<void*>(buf));
+ }
+}
+
+TEST(SignalHandlerStackConsumptionTest, MeasuresStackConsumption) {
+ // Our handler should consume reasonable number of bytes.
+ EXPECT_GE(GetSignalHandlerStackConsumption(SimpleSignalHandler), 100);
+}
+
+} // namespace
+} // namespace debugging_internal
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION