// -*- 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 #elif defined(HAVE_UCONTEXT_H) #include #endif // This is only used on OS-es with mmap support. #include // 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(ptr); uintptr_t parent_frame = reinterpret_cast(checked_ptr); parent_frame &= ~(pagesize - 1); addr &= ~(pagesize - 1); if (parent_frame != 0 && addr == parent_frame) { return true; } return (msync(reinterpret_cast(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(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(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(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(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(ucp); #ifdef __riscv initial_pc = reinterpret_cast(&uc->uc_mcontext.__gregs[REG_PC]); initial_frame = reinterpret_cast(uc->uc_mcontext.__gregs[REG_S0]); #elif __aarch64__ initial_pc = reinterpret_cast(&uc->uc_mcontext.pc); initial_frame = reinterpret_cast(uc->uc_mcontext.regs[29]); #else initial_pc = reinterpret_cast(&uc->uc_mcontext.gregs[REG_RIP]); initial_frame = reinterpret_cast(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(result)); return n; }