about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAbseil Team <absl-team@google.com>2018-02-05T13·38-0800
committerkatzdm <katzdm@google.com>2018-02-05T14·53-0500
commitbf7fc9986e20f664958fc227547fd8d2fdcf863e (patch)
treecd5e7f5f46963209db2efb6676ef00f927915461
parent0fa86cac406d12de03ed670bbd2af3be7c848c03 (diff)
Changes imported from Abseil "staging" branch:
  - b00fb48c537d494ab775bc6701acfb8e100d8b88 Add a test utility for measuring stack consumption. by Derek Mauro <dmauro@google.com>

GitOrigin-RevId: b00fb48c537d494ab775bc6701acfb8e100d8b88
Change-Id: I00466a6d6c14911c12d34f62a4144925748e3f61
-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 7a56e4962e7b..d4cdf49223b3 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 000000000000..2b3b972ea539
--- /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 000000000000..5aa1a109b657
--- /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 000000000000..5ce3846ec449
--- /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