diff options
Diffstat (limited to 'absl/debugging')
-rw-r--r-- | absl/debugging/BUILD.bazel | 17 | ||||
-rw-r--r-- | absl/debugging/CMakeLists.txt | 16 | ||||
-rw-r--r-- | absl/debugging/failure_signal_handler.cc | 21 | ||||
-rw-r--r-- | absl/debugging/failure_signal_handler.h | 2 | ||||
-rw-r--r-- | absl/debugging/internal/demangle_test.cc | 2 | ||||
-rw-r--r-- | absl/debugging/internal/stack_consumption.cc | 2 | ||||
-rw-r--r-- | absl/debugging/internal/stacktrace_aarch64-inl.inc | 43 | ||||
-rw-r--r-- | absl/debugging/internal/stacktrace_powerpc-inl.inc | 2 | ||||
-rw-r--r-- | absl/debugging/internal/stacktrace_x86-inl.inc | 29 | ||||
-rw-r--r-- | absl/debugging/internal/symbolize.h | 2 | ||||
-rw-r--r-- | absl/debugging/leak_check.cc | 4 | ||||
-rw-r--r-- | absl/debugging/leak_check.h | 2 | ||||
-rw-r--r-- | absl/debugging/stacktrace_test.cc | 47 | ||||
-rw-r--r-- | absl/debugging/symbolize_elf.inc | 10 |
14 files changed, 167 insertions, 32 deletions
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index a40285c8..e89dbae8 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -49,10 +49,23 @@ cc_library( ":debugging_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:dynamic_annotations", "//absl/base:raw_logging_internal", ], ) +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( name = "symbolize", srcs = [ @@ -71,6 +84,10 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS + select({ "//absl:msvc_compiler": ["-DEFAULTLIB:dbghelp.lib"], "//absl:clang-cl_compiler": ["-DEFAULTLIB:dbghelp.lib"], + "//absl:mingw_compiler": [ + "-DEFAULTLIB:dbghelp.lib", + "-ldbghelp", + ], "//conditions:default": [], }), deps = [ diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 051e7017..ef6b4965 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -41,10 +41,24 @@ absl_cc_library( absl::debugging_internal absl::config absl::core_headers + absl::dynamic_annotations absl::raw_logging_internal 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( NAME symbolize @@ -62,7 +76,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} - $<$<BOOL:${MINGW}>:"dbghelp"> + $<$<BOOL:${MINGW}>:-ldbghelp> DEPS absl::debugging_internal absl::demangle_internal diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index 5e8f0b05..9f399d02 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -33,6 +33,10 @@ #include <sys/mman.h> #endif +#ifdef __linux__ +#include <sys/prctl.h> +#endif + #include <algorithm> #include <atomic> #include <cerrno> @@ -138,7 +142,8 @@ static bool SetupAlternateStackOnce() { const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1; #endif size_t stack_size = - (std::max<size_t>(SIGSTKSZ, 65536) + page_mask) & ~page_mask; + (std::max(static_cast<size_t>(SIGSTKSZ), size_t{65536}) + page_mask) & + ~page_mask; #if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) // Account for sanitizer instrumentation requiring additional stack space. @@ -171,6 +176,20 @@ static bool SetupAlternateStackOnce() { if (sigaltstack(&sigstk, nullptr) != 0) { ABSL_RAW_LOG(FATAL, "sigaltstack() failed with errno=%d", errno); } + +#ifdef __linux__ +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) + // Make a best-effort attempt to name the allocated region in + // /proc/$PID/smaps. + // + // The call to prctl() may fail if the kernel was not configured with the + // CONFIG_ANON_VMA_NAME kernel option. This is OK since the call is + // primarily a debugging aid. + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, sigstk.ss_sp, sigstk.ss_size, + "absl-signalstack"); +#endif +#endif // __linux__ + return true; } diff --git a/absl/debugging/failure_signal_handler.h b/absl/debugging/failure_signal_handler.h index 500115c0..5e034785 100644 --- a/absl/debugging/failure_signal_handler.h +++ b/absl/debugging/failure_signal_handler.h @@ -62,7 +62,7 @@ struct FailureSignalHandlerOptions { // If true, try to run signal handlers on an alternate stack (if supported on // the given platform). An alternate stack is useful for program crashes due // to a stack overflow; by running on a alternate stack, the signal handler - // may run even when normal stack space has been exausted. The downside of + // may run even when normal stack space has been exhausted. The downside of // using an alternate stack is that extra memory for the alternate stack needs // to be pre-allocated. bool use_alternate_stack = true; diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc index 8463a2b7..2fa4143a 100644 --- a/absl/debugging/internal/demangle_test.cc +++ b/absl/debugging/internal/demangle_test.cc @@ -38,7 +38,7 @@ static const char *DemangleIt(const char * const mangled) { } } -// Test corner cases of bounary conditions. +// Test corner cases of boundary conditions. TEST(Demangle, CornerCases) { char tmp[10]; EXPECT_TRUE(Demangle("_Z6foobarv", tmp, sizeof(tmp))); diff --git a/absl/debugging/internal/stack_consumption.cc b/absl/debugging/internal/stack_consumption.cc index 51348649..6d974992 100644 --- a/absl/debugging/internal/stack_consumption.cc +++ b/absl/debugging/internal/stack_consumption.cc @@ -162,7 +162,7 @@ int GetSignalHandlerStackConsumption(void (*signal_handler)(int)) { // versions of musl have a bug that rejects ss_size==0. Work around this by // setting ss_size to MINSIGSTKSZ, which should be ignored by the kernel // when SS_DISABLE is set. - old_sigstk.ss_size = MINSIGSTKSZ; + old_sigstk.ss_size = static_cast<size_t>(MINSIGSTKSZ); } ABSL_RAW_CHECK(sigaltstack(&old_sigstk, nullptr) == 0, "sigaltstack() failed"); diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc index 891942c0..b66beba2 100644 --- a/absl/debugging/internal/stacktrace_aarch64-inl.inc +++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc @@ -19,7 +19,7 @@ #include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems #include "absl/debugging/stacktrace.h" -static const uintptr_t kUnknownFrameSize = 0; +static const size_t kUnknownFrameSize = 0; #if defined(__linux__) // Returns the address of the VDSO __kernel_rt_sigreturn function, if present. @@ -65,11 +65,12 @@ static const unsigned char* GetKernelRtSigreturnAddress() { // Compute the size of a stack frame in [low..high). We assume that // low < high. Return size of kUnknownFrameSize. template<typename T> -static inline uintptr_t ComputeStackFrameSize(const T* low, - const T* high) { +static inline size_t ComputeStackFrameSize(const T* low, + const T* high) { const char* low_char_ptr = reinterpret_cast<const char *>(low); const char* high_char_ptr = reinterpret_cast<const char *>(high); - return low < high ? high_char_ptr - low_char_ptr : kUnknownFrameSize; + return low < high ? static_cast<size_t>(high_char_ptr - low_char_ptr) + : kUnknownFrameSize; } // Given a pointer to a stack frame, locate and return the calling @@ -117,8 +118,8 @@ static void **NextStackFrame(void **old_frame_pointer, const void *uc) { // Check frame size. In strict mode, we assume frames to be under // 100,000 bytes. In non-strict mode, we relax the limit to 1MB. if (check_frame_size) { - const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000; - const uintptr_t frame_size = + const size_t max_size = STRICT_UNWINDING ? 100000 : 1000000; + const size_t frame_size = ComputeStackFrameSize(old_frame_pointer, new_frame_pointer); if (frame_size == kUnknownFrameSize || frame_size > max_size) return nullptr; @@ -137,41 +138,45 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, #else # error reading stack point not yet supported on this platform. #endif - skip_count++; // Skip the frame for this function. int n = 0; // The frame pointer points to low address of a frame. The first 64-bit // word of a frame points to the next frame up the call chain, which normally // is just after the high address of the current frame. The second word of - // a frame contains return adress of to the caller. To find a pc value + // a frame contains return address of to the caller. To find a pc value // associated with the current frame, we need to go down a level in the call // chain. So we remember return the address of the last frame seen. This // does not work for the first stack frame, which belongs to UnwindImp() but // we skip the frame for UnwindImp() anyway. void* prev_return_address = nullptr; + // The nth frame size is the difference between the nth frame pointer and the + // the frame pointer below it in the call chain. There is no frame below the + // leaf frame, but this function is the leaf anyway, and we skip it. + void** prev_frame_pointer = nullptr; - while (frame_pointer && n < max_depth) { - // The absl::GetStackFrames routine is called when we are in some - // informational context (the failure signal handler for example). - // Use the non-strict unwinding rules to produce a stack trace - // that is as complete as possible (even if it contains a few bogus - // entries in some rare cases). - void **next_frame_pointer = - NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp); - + while (frame_pointer && n < max_depth) { if (skip_count > 0) { skip_count--; } else { result[n] = prev_return_address; if (IS_STACK_FRAMES) { - sizes[n] = ComputeStackFrameSize(frame_pointer, next_frame_pointer); + sizes[n] = static_cast<int>( + ComputeStackFrameSize(prev_frame_pointer, frame_pointer)); } n++; } prev_return_address = frame_pointer[1]; - frame_pointer = next_frame_pointer; + prev_frame_pointer = frame_pointer; + // The absl::GetStackFrames routine is called when we are in some + // informational context (the failure signal handler for example). + // Use the non-strict unwinding rules to produce a stack trace + // that is as complete as possible (even if it contains a few bogus + // entries in some rare cases). + frame_pointer = + NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp); } + if (min_dropped_frames != nullptr) { // Implementation detail: we clamp the max of frames we are willing to // count, so as not to spend too much time in the loop below. diff --git a/absl/debugging/internal/stacktrace_powerpc-inl.inc b/absl/debugging/internal/stacktrace_powerpc-inl.inc index 085cef67..a49ed2f7 100644 --- a/absl/debugging/internal/stacktrace_powerpc-inl.inc +++ b/absl/debugging/internal/stacktrace_powerpc-inl.inc @@ -57,7 +57,7 @@ static inline void *StacktracePowerPCGetLR(void **sp) { // This check is in case the compiler doesn't define _CALL_SYSV. return *(sp+1); #else -#error Need to specify the PPC ABI for your archiecture. +#error Need to specify the PPC ABI for your architecture. #endif } diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 9fbfcf76..1975ba74 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc @@ -29,6 +29,7 @@ #include <cstdint> #include <limits> +#include "absl/base/attributes.h" #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/debugging/internal/address_is_readable.h" @@ -39,7 +40,7 @@ using absl::debugging_internal::AddressIsReadable; #if defined(__linux__) && defined(__i386__) // Count "push %reg" instructions in VDSO __kernel_vsyscall(), -// preceeding "syscall" or "sysenter". +// preceding "syscall" or "sysenter". // If __kernel_vsyscall uses frame pointer, answer 0. // // kMaxBytes tells how many instruction bytes of __kernel_vsyscall @@ -111,6 +112,10 @@ static int CountPushInstructions(const unsigned char *const addr) { // Assume stack frames larger than 100,000 bytes are bogus. 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. // vuc is a ucontext_t *. We use void* to avoid the use @@ -257,8 +262,26 @@ static void **NextStackFrame(void **old_fp, const void *uc, // With the stack growing downwards, older stack frame must be // at a greater address that the current one. 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 real stack bounds + // and are within the stack. + if (new_fp_u - old_fp_u > kMaxFrameBytes) { + if (stack_high < kUnknownStackEnd && + static_cast<size_t>(getpagesize()) < stack_low) { + // Stack bounds are known. + if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { + // new_fp_u is not within the known stack. + return nullptr; + } + } else { + // Stack bounds are unknown, prefer truncated stack to possible crash. + return nullptr; + } + } if (stack_low < old_fp_u && old_fp_u <= stack_high) { // Old BP was in the expected stack region... if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { @@ -311,7 +334,7 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, // Assume that the first page is not stack. 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) { if (*(fp + 1) == reinterpret_cast<void *>(0)) { diff --git a/absl/debugging/internal/symbolize.h b/absl/debugging/internal/symbolize.h index 27d5e652..5593fde6 100644 --- a/absl/debugging/internal/symbolize.h +++ b/absl/debugging/internal/symbolize.h @@ -115,7 +115,7 @@ bool RemoveSymbolDecorator(int ticket); // Remove all installed decorators. Returns true if successful, false if // symbolization is currently in progress. -bool RemoveAllSymbolDecorators(void); +bool RemoveAllSymbolDecorators(); // Registers an address range to a file mapping. // diff --git a/absl/debugging/leak_check.cc b/absl/debugging/leak_check.cc index 195e82bf..fdb8798b 100644 --- a/absl/debugging/leak_check.cc +++ b/absl/debugging/leak_check.cc @@ -65,8 +65,8 @@ bool LeakCheckerIsActive() { return false; } void DoIgnoreLeak(const void*) { } void RegisterLivePointers(const void*, size_t) { } void UnRegisterLivePointers(const void*, size_t) { } -LeakCheckDisabler::LeakCheckDisabler() { } -LeakCheckDisabler::~LeakCheckDisabler() { } +LeakCheckDisabler::LeakCheckDisabler() = default; +LeakCheckDisabler::~LeakCheckDisabler() = default; ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/debugging/leak_check.h b/absl/debugging/leak_check.h index eff162f6..6bd79406 100644 --- a/absl/debugging/leak_check.h +++ b/absl/debugging/leak_check.h @@ -37,7 +37,7 @@ // not also use AddressSanitizer). To use the mode, simply pass // `-fsanitize=leak` to both the compiler and linker. Since GCC does not // currently provide a way of detecting this mode at compile-time, GCC users -// must also pass -DLEAK_SANIITIZER to the compiler. An example Bazel command +// must also pass -DLEAK_SANITIZER to the compiler. An example Bazel command // could be // // $ bazel test --copt=-DLEAK_SANITIZER --copt=-fsanitize=leak diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc new file mode 100644 index 00000000..78ce7ad0 --- /dev/null +++ b/absl/debugging/stacktrace_test.cc @@ -0,0 +1,47 @@ +// 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 diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index ffb4eecf..0fee89f2 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -532,6 +532,11 @@ bool ForEachSection(int fd, return false; } + // Technically it can be larger, but in practice this never happens. + if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) { + return false; + } + ElfW(Shdr) shstrtab; off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * elf_header.e_shstrndx; @@ -584,6 +589,11 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, return false; } + // Technically it can be larger, but in practice this never happens. + if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) { + return false; + } + ElfW(Shdr) shstrtab; off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * elf_header.e_shstrndx; |