summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAliaksey Kandratsenka <alkondratenko@gmail.com>2021-02-07 11:59:44 -0800
committerAliaksey Kandratsenka <alkondratenko@gmail.com>2021-02-14 22:11:09 -0800
commitf4aa2a435eed63fc047448635f705a9c6037bd97 (patch)
treee853cd2094dfad393acf2939edfef9592e7601ca
parent17bab484aea43cf1a5247c823e036dfb52f5d92b (diff)
downloadgperftools-f4aa2a435eed63fc047448635f705a9c6037bd97.tar.gz
implement generic frame pointer backtracer
This supports frame pointer backtracing on x86-64, aarch64 and riscv-s (should work for both 32 and 64 bits). Also added is detection of borked libunwind on aarch64-s. In this case frame pointer unwinder is preferred.
-rw-r--r--Makefile.am1
-rw-r--r--src/stacktrace.cc54
-rw-r--r--src/stacktrace_generic_fp-inl.h222
-rw-r--r--src/tests/stacktrace_unittest.cc131
4 files changed, 394 insertions, 14 deletions
diff --git a/Makefile.am b/Makefile.am
index 730582e..d93300d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -331,6 +331,7 @@ if WITH_STACK_TRACE
### The header files we use. We divide into categories based on directory
S_STACKTRACE_INCLUDES = src/stacktrace_impl_setup-inl.h \
src/stacktrace_generic-inl.h \
+ src/stacktrace_generic_fp-inl.h \
src/stacktrace_libgcc-inl.h \
src/stacktrace_libunwind-inl.h \
src/stacktrace_arm-inl.h \
diff --git a/src/stacktrace.cc b/src/stacktrace.cc
index 7e853d8..ab1c381 100644
--- a/src/stacktrace.cc
+++ b/src/stacktrace.cc
@@ -120,6 +120,27 @@ struct GetStackImplementation {
#define HAVE_GST_x86
#endif // i386 || x86_64
+// Sadly, different OSes have very different mcontexts even for
+// identical hardware arch. So keep it linux-only for now.
+#if defined(__GNUC__) && __linux__ && (defined(__x86_64__) || defined(__aarch64__) || defined(__riscv))
+#define STACKTRACE_INL_HEADER "stacktrace_generic_fp-inl.h"
+#define GST_SUFFIX generic_fp
+#include "stacktrace_impl_setup-inl.h"
+#undef GST_SUFFIX
+#undef STACKTRACE_INL_HEADER
+#define HAVE_GST_generic_fp
+
+#undef TCMALLOC_UNSAFE_GENERIC_FP_STACKTRACE
+#define TCMALLOC_UNSAFE_GENERIC_FP_STACKTRACE 1
+
+#define STACKTRACE_INL_HEADER "stacktrace_generic_fp-inl.h"
+#define GST_SUFFIX generic_fp_unsafe
+#include "stacktrace_impl_setup-inl.h"
+#undef GST_SUFFIX
+#undef STACKTRACE_INL_HEADER
+#define HAVE_GST_generic_fp_unsafe
+#endif
+
#if defined(__ppc__) || defined(__PPC__)
#if defined(__linux__)
#define STACKTRACE_INL_HEADER "stacktrace_powerpc-linux-inl.h"
@@ -169,6 +190,12 @@ static GetStackImplementation *all_impls[] = {
#ifdef HAVE_GST_generic
&impl__generic,
#endif
+#ifdef HAVE_GST_generic_fp
+ &impl__generic_fp,
+#endif
+#ifdef HAVE_GST_generic_fp
+ &impl__generic_fp_unsafe,
+#endif
#ifdef HAVE_GST_libunwind
&impl__libunwind,
#endif
@@ -204,6 +231,8 @@ static bool get_stack_impl_inited;
static GetStackImplementation *get_stack_impl = &impl__instrument;
#elif defined(HAVE_GST_win32)
static GetStackImplementation *get_stack_impl = &impl__win32;
+#elif defined(HAVE_GST_generic_fp) && (!defined(HAVE_GST_libunwind) || defined(TCMALLOC_DONT_PREFER_LIBUNWIND))
+static GetStackImplementation *get_stack_impl = &impl__generic_fp;
#elif defined(HAVE_GST_x86) && defined(TCMALLOC_DONT_PREFER_LIBUNWIND)
static GetStackImplementation *get_stack_impl = &impl__x86;
#elif defined(HAVE_GST_ppc) && defined(TCMALLOC_DONT_PREFER_LIBUNWIND)
@@ -306,6 +335,29 @@ PERFTOOLS_DLL_DECL int GetStackTraceWithContext(void** result, int max_depth,
result, max_depth, skip_count, uc));
}
+// As of this writing, aarch64 has completely borked libunwind, so
+// lets test this case and fall back to frame pointers (which is
+// nearly but not quite perfect).
+ATTRIBUTE_NOINLINE
+static void maybe_convert_libunwind_to_generic_fp() {
+#if defined(HAVE_GST_libunwind) && defined(HAVE_GST_generic_fp)
+ if (get_stack_impl != &impl__libunwind) {
+ return;
+ }
+
+ // Okay we're on libunwind and we have generic_fp, check if
+ // libunwind returns bogus results.
+ void* stack[4];
+ int rv = get_stack_impl->GetStackTracePtr(stack, 4, 0);
+ if (rv > 2) {
+ // Seems fine
+ return;
+ }
+ // bogus. So replacing with generic_fp
+ get_stack_impl = &impl__generic_fp;
+#endif
+}
+
static void init_default_stack_impl_inner(void) {
if (get_stack_impl_inited) {
return;
@@ -313,6 +365,7 @@ static void init_default_stack_impl_inner(void) {
get_stack_impl_inited = true;
const char *val = TCMallocGetenvSafe("TCMALLOC_STACKTRACE_METHOD");
if (!val || !*val) {
+ maybe_convert_libunwind_to_generic_fp();
return;
}
for (GetStackImplementation **p = all_impls; *p; p++) {
@@ -325,6 +378,7 @@ static void init_default_stack_impl_inner(void) {
fprintf(stderr, "Unknown or unsupported stacktrace method requested: %s. Ignoring it\n", val);
}
+ATTRIBUTE_NOINLINE
static void init_default_stack_impl(void) {
init_default_stack_impl_inner();
if (EnvToBool("TCMALLOC_STACKTRACE_METHOD_VERBOSE", false)) {
diff --git a/src/stacktrace_generic_fp-inl.h b/src/stacktrace_generic_fp-inl.h
new file mode 100644
index 0000000..d458109
--- /dev/null
+++ b/src/stacktrace_generic_fp-inl.h
@@ -0,0 +1,222 @@
+// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
+// Copyright (c) 2021, gperftools Contributors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file contains "generic" stack frame pointer backtracing
+// code. Attempt is made to minimize amount of arch- or os-specific
+// code and keep everything as generic as possible. Currently
+// supported are x86-64, aarch64 and riscv.
+#ifndef BASE_STACKTRACE_GENERIC_FP_INL_H_
+#define BASE_STACKTRACE_GENERIC_FP_INL_H_
+
+#if defined(HAVE_SYS_UCONTEXT_H)
+#include <sys/ucontext.h>
+#elif defined(HAVE_UCONTEXT_H)
+#include <ucontext.h>
+#endif
+
+// This is only used on OS-es with mmap support.
+#include <sys/mman.h>
+
+// Set this to true to disable "probing" of addresses that are read to
+// make backtracing less-safe, but faster.
+#ifndef TCMALLOC_UNSAFE_GENERIC_FP_STACKTRACE
+#define TCMALLOC_UNSAFE_GENERIC_FP_STACKTRACE 0
+#endif
+
+namespace {
+namespace stacktrace_generic_fp {
+
+struct frame {
+ uintptr_t parent;
+ void* pc;
+};
+
+frame* adjust_fp(frame* f) {
+#ifdef __riscv
+ return f - 1;
+#else
+ return f;
+#endif
+}
+
+static bool CheckPageIsReadable(void* ptr, void* checked_ptr) {
+ static uintptr_t pagesize;
+ if (pagesize == 0) {
+ pagesize = getpagesize();
+ }
+
+ uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+ uintptr_t parent_frame = reinterpret_cast<uintptr_t>(checked_ptr);
+
+ parent_frame &= ~(pagesize - 1);
+ addr &= ~(pagesize - 1);
+
+ if (parent_frame != 0 && addr == parent_frame) {
+ return true;
+ }
+
+ return (msync(reinterpret_cast<void*>(addr), pagesize, MS_ASYNC) == 0);
+}
+
+ATTRIBUTE_NOINLINE // forces architectures with link register to save it
+int capture(void **result, int max_depth, int skip_count,
+ void* initial_frame, void* const * initial_pc) {
+ int i = 0;
+
+ if (initial_pc != nullptr) {
+ // This is 'with ucontext' case. We take first pc from ucontext
+ // and then skip_count is ignored as we assume that caller only
+ // needed stack trace up to signal handler frame.
+ skip_count = 0;
+ if (max_depth == 0) {
+ return 0;
+ }
+ result[0] = *initial_pc;
+ i++;
+ }
+
+ constexpr uintptr_t kTooSmallAddr = 16 << 10;
+ constexpr uintptr_t kFrameSizeThreshold = 128 << 10;
+
+ // This is simplistic yet. Here we're targeting x86-64, aarch64 and
+ // riscv. All have 16 bytes stack alignment (even 32 bit
+ // riscv). This can be made more elaborate as we consider more
+ // architectures. Note, it allows us to only readability of check
+ // f->parent address.
+ constexpr uintptr_t kAlignment = 16;
+
+ uintptr_t initial_frame_addr = reinterpret_cast<uintptr_t>(initial_frame);
+ if ((initial_frame_addr & (kAlignment - 1)) != 0) {
+ return i;
+ }
+ if (initial_frame_addr < kTooSmallAddr) {
+ return i;
+ }
+
+ frame* prev_f = nullptr;
+ frame *f = adjust_fp(reinterpret_cast<frame*>(initial_frame));
+
+ while (i < max_depth) {
+ if (!TCMALLOC_UNSAFE_GENERIC_FP_STACKTRACE
+ && !CheckPageIsReadable(&f->parent, prev_f)) {
+ break;
+ }
+
+ void* pc = f->pc;
+ if (pc == nullptr) {
+ break;
+ }
+
+ if (i >= skip_count) {
+ result[i - skip_count] = pc;
+ }
+
+ i++;
+
+ uintptr_t parent_frame_addr = f->parent;
+ uintptr_t child_frame_addr = reinterpret_cast<uintptr_t>(f);
+
+ if (parent_frame_addr < kTooSmallAddr) {
+ break;
+ }
+ // stack grows towards smaller addresses, so if we didn't see
+ // frame address increased (going from child to parent), it is bad
+ // frame. We also test if frame is too big since that is another
+ // sign of bad stack frame.
+ if (parent_frame_addr - child_frame_addr > kFrameSizeThreshold) {
+ break;
+ }
+
+ if ((parent_frame_addr & (kAlignment - 1)) != 0) {
+ // not aligned, so we keep it safe and assume frame is bogus
+ break;
+ }
+
+ prev_f = f;
+
+ f = adjust_fp(reinterpret_cast<frame*>(parent_frame_addr));
+ }
+ return i;
+}
+
+} // namespace stacktrace_generic_fp
+} // namespace
+
+#endif // BASE_STACKTRACE_GENERIC_FP_INL_H_
+
+// Note: this part of the file is included several times.
+// Do not put globals below.
+
+// The following 4 functions are generated from the code below:
+// GetStack{Trace,Frames}()
+// GetStack{Trace,Frames}WithContext()
+//
+// These functions take the following args:
+// void** result: the stack-trace, as an array
+// int* sizes: the size of each stack frame, as an array
+// (GetStackFrames* only)
+// int max_depth: the size of the result (and sizes) array(s)
+// int skip_count: how many stack pointers to skip before storing in result
+// void* ucp: a ucontext_t* (GetStack{Trace,Frames}WithContext only)
+
+static int GET_STACK_TRACE_OR_FRAMES {
+#if IS_STACK_FRAMES
+ memset(sizes, 0, sizeof(*sizes) * max_depth);
+#endif
+
+ // one for this function
+ skip_count += 1;
+
+ void* const * initial_pc = nullptr;
+ void* initial_frame = __builtin_frame_address(0);
+
+#if IS_WITH_CONTEXT
+ if (ucp) {
+ auto uc = static_cast<const ucontext_t*>(ucp);
+#ifdef __riscv
+ initial_pc = reinterpret_cast<void* const *>(&uc->uc_mcontext.__gregs[REG_PC]);
+ initial_frame = reinterpret_cast<void*>(uc->uc_mcontext.__gregs[REG_S0]);
+#elif __aarch64__
+ initial_pc = reinterpret_cast<void* const *>(&uc->uc_mcontext.pc);
+ initial_frame = reinterpret_cast<void*>(uc->uc_mcontext.regs[29]);
+#else
+ initial_pc = reinterpret_cast<void* const *>(&uc->uc_mcontext.gregs[REG_RIP]);
+ initial_frame = reinterpret_cast<void*>(uc->uc_mcontext.gregs[REG_RBP]);
+#endif
+ }
+#endif // IS_WITH_CONTEXT
+
+ int n = stacktrace_generic_fp::capture(result, max_depth, skip_count,
+ initial_frame, initial_pc);
+
+ // make sure we don't tail-call capture
+ (void)*(const_cast<void * volatile *>(result));
+ return n;
+}
diff --git a/src/tests/stacktrace_unittest.cc b/src/tests/stacktrace_unittest.cc
index 6c764d0..e55a632 100644
--- a/src/tests/stacktrace_unittest.cc
+++ b/src/tests/stacktrace_unittest.cc
@@ -34,9 +34,18 @@
#endif
#include <stdio.h>
#include <stdlib.h>
+
+// On those architectures we can and should test if backtracing with
+// ucontext and from signal handler works
+#if __GNUC__ && __linux__ && (__x86_64__ || __aarch64__ || __riscv)
+#include <signal.h>
+#define TEST_UCONTEXT_BITS 1
+#endif
+
#include "base/commandlineflags.h"
#include "base/logging.h"
#include <gperftools/stacktrace.h>
+#include "tests/testutil.h"
namespace {
@@ -97,6 +106,7 @@ AddressRange expected_range[BACKTRACE_STEPS];
#define ADJUST_ADDRESS_RANGE_FROM_RA(prange) do { } while (0)
#endif // __GNUC__
+
//-----------------------------------------------------------------------//
void CheckRetAddrIsInFunction(void *ret_addr, const AddressRange &range)
@@ -107,24 +117,105 @@ void CheckRetAddrIsInFunction(void *ret_addr, const AddressRange &range)
//-----------------------------------------------------------------------//
-void ATTRIBUTE_NOINLINE CheckStackTrace(int);
-void ATTRIBUTE_NOINLINE CheckStackTraceLeaf(void) {
- const int STACK_LEN = 10;
- void *stack[STACK_LEN];
+#if TEST_UCONTEXT_BITS
+
+struct get_stack_trace_args {
+ int *size_ptr;
+ void **result;
+ int max_depth;
+ uintptr_t where;
+} gst_args;
+
+static
+void SignalHandler(int dummy, siginfo_t *si, void* ucv) {
+ auto uc = static_cast<ucontext_t*>(ucv);
+
+#ifdef __riscv
+ uc->uc_mcontext.__gregs[REG_PC] = gst_args.where;
+#elif __aarch64__
+ uc->uc_mcontext.pc = gst_args.where;
+#else
+ uc->uc_mcontext.gregs[REG_RIP] = gst_args.where;
+#endif
+
+ *gst_args.size_ptr = GetStackTraceWithContext(
+ gst_args.result,
+ gst_args.max_depth,
+ 2,
+ uc);
+}
+
+int ATTRIBUTE_NOINLINE CaptureLeafUContext(void **stack, int stack_len) {
+ INIT_ADDRESS_RANGE(CheckStackTraceLeaf, start, end, &expected_range[0]);
+ DECLARE_ADDRESS_LABEL(start);
+
int size;
- ADJUST_ADDRESS_RANGE_FROM_RA(&expected_range[1]);
+ printf("Capturing stack trace from signal's ucontext\n");
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = SignalHandler;
+ sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
+ int rv = sigaction(SIGSEGV, &sa, nullptr);
+ CHECK(rv == 0);
+
+ gst_args.size_ptr = &size;
+ gst_args.result = stack;
+ gst_args.max_depth = stack_len;
+ gst_args.where = reinterpret_cast<uintptr_t>(noopt(&&after));
+
+ // now, "write" to null pointer and trigger sigsegv to run signal
+ // handler. It'll then change PC to after, as if we jumped one line
+ // below.
+ *noopt(reinterpret_cast<void**>(0)) = 0;
+ // this is not reached, but gcc gets really odd if we don't actually
+ // use computed goto.
+ static void* jump_target = &&after;
+ goto *noopt(&jump_target);
+
+after:
+ printf("Obtained %d stack frames.\n", size);
+ CHECK_GE(size, 1);
+ CHECK_LE(size, stack_len);
+
+ DECLARE_ADDRESS_LABEL(end);
+
+ return size;
+}
+
+#endif // TEST_UCONTEXT_BITS
+
+int ATTRIBUTE_NOINLINE CaptureLeafPlain(void **stack, int stack_len) {
INIT_ADDRESS_RANGE(CheckStackTraceLeaf, start, end, &expected_range[0]);
DECLARE_ADDRESS_LABEL(start);
- size = GetStackTrace(stack, STACK_LEN, 0);
+
+ int size = GetStackTrace(stack, stack_len, 0);
+
printf("Obtained %d stack frames.\n", size);
CHECK_GE(size, 1);
- CHECK_LE(size, STACK_LEN);
+ CHECK_LE(size, stack_len);
+
+ DECLARE_ADDRESS_LABEL(end);
+
+ return size;
+}
+
+void ATTRIBUTE_NOINLINE CheckStackTrace(int);
+
+int (*leaf_capture_fn)(void**, int) = CaptureLeafPlain;
+
+void ATTRIBUTE_NOINLINE CheckStackTraceLeaf(int i) {
+ const int STACK_LEN = 20;
+ void *stack[STACK_LEN];
+ int size;
+
+ ADJUST_ADDRESS_RANGE_FROM_RA(&expected_range[1]);
+
+ size = leaf_capture_fn(stack, STACK_LEN);
#ifdef HAVE_EXECINFO_H
{
char **strings = backtrace_symbols(stack, size);
- printf("Obtained %d stack frames.\n", size);
for (int i = 0; i < size; i++)
printf("%s %p\n", strings[i], stack[i]);
printf("CheckStackTrace() addr: %p\n", &CheckStackTrace);
@@ -132,14 +223,18 @@ void ATTRIBUTE_NOINLINE CheckStackTraceLeaf(void) {
}
#endif
- for (int i = 0; i < BACKTRACE_STEPS; i++) {
+ for (int i = 0, j = 0; i < BACKTRACE_STEPS; i++, j++) {
+ if (i == 1 && j == 1) {
+ // this is expected to be our function for which we don't
+ // establish bounds. So skip.
+ j++;
+ }
printf("Backtrace %d: expected: %p..%p actual: %p ... ",
- i, expected_range[i].start, expected_range[i].end, stack[i]);
+ i, expected_range[i].start, expected_range[i].end, stack[j]);
fflush(stdout);
- CheckRetAddrIsInFunction(stack[i], expected_range[i]);
+ CheckRetAddrIsInFunction(stack[j], expected_range[i]);
printf("OK\n");
}
- DECLARE_ADDRESS_LABEL(end);
}
//-----------------------------------------------------------------------//
@@ -150,7 +245,7 @@ void ATTRIBUTE_NOINLINE CheckStackTrace4(int i) {
INIT_ADDRESS_RANGE(CheckStackTrace4, start, end, &expected_range[1]);
DECLARE_ADDRESS_LABEL(start);
for (int j = i; j >= 0; j--)
- CheckStackTraceLeaf();
+ CheckStackTraceLeaf(j);
DECLARE_ADDRESS_LABEL(end);
}
void ATTRIBUTE_NOINLINE CheckStackTrace3(int i) {
@@ -180,8 +275,9 @@ void ATTRIBUTE_NOINLINE CheckStackTrace1(int i) {
void ATTRIBUTE_NOINLINE CheckStackTrace(int i) {
INIT_ADDRESS_RANGE(CheckStackTrace, start, end, &expected_range[5]);
DECLARE_ADDRESS_LABEL(start);
- for (int j = i; j >= 0; j--)
+ for (int j = i; j >= 0; j--) {
CheckStackTrace1(j);
+ }
DECLARE_ADDRESS_LABEL(end);
}
@@ -191,5 +287,12 @@ void ATTRIBUTE_NOINLINE CheckStackTrace(int i) {
int main(int argc, char ** argv) {
CheckStackTrace(0);
printf("PASS\n");
+
+#if TEST_UCONTEXT_BITS
+ leaf_capture_fn = CaptureLeafUContext;
+ CheckStackTrace(0);
+ printf("PASS\n");
+#endif // TEST_UCONTEXT_BITS
+
return 0;
}