| 1 | //===-- sanitizer_unwind_linux_libcdep.cpp --------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file contains the unwind.h-based (aka "slow") stack unwinding routines |
| 10 | // available to the tools on Linux, Android, NetBSD, FreeBSD, and Solaris. |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "sanitizer_platform.h" |
| 14 | #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || \ |
| 15 | SANITIZER_SOLARIS || SANITIZER_HAIKU |
| 16 | #include "sanitizer_common.h" |
| 17 | #include "sanitizer_stacktrace.h" |
| 18 | |
| 19 | #if SANITIZER_ANDROID |
| 20 | #include <dlfcn.h> // for dlopen() |
| 21 | #endif |
| 22 | |
| 23 | #if SANITIZER_FREEBSD |
| 24 | #define _GNU_SOURCE // to declare _Unwind_Backtrace() from <unwind.h> |
| 25 | #endif |
| 26 | #include <unwind.h> |
| 27 | |
| 28 | namespace __sanitizer { |
| 29 | |
| 30 | namespace { |
| 31 | |
| 32 | //---------------------------- UnwindSlow -------------------------------------- |
| 33 | |
| 34 | typedef struct { |
| 35 | uptr absolute_pc; |
| 36 | uptr stack_top; |
| 37 | uptr stack_size; |
| 38 | } backtrace_frame_t; |
| 39 | |
| 40 | extern "C" { |
| 41 | typedef void *(*acquire_my_map_info_list_func)(); |
| 42 | typedef void (*release_my_map_info_list_func)(void *map); |
| 43 | typedef sptr (*unwind_backtrace_signal_arch_func)( |
| 44 | void *siginfo, void *sigcontext, void *map_info_list, |
| 45 | backtrace_frame_t *backtrace, uptr ignore_depth, uptr max_depth); |
| 46 | acquire_my_map_info_list_func acquire_my_map_info_list; |
| 47 | release_my_map_info_list_func release_my_map_info_list; |
| 48 | unwind_backtrace_signal_arch_func unwind_backtrace_signal_arch; |
| 49 | } // extern "C" |
| 50 | |
| 51 | #if defined(__arm__) && !SANITIZER_NETBSD |
| 52 | // NetBSD uses dwarf EH |
| 53 | #define UNWIND_STOP _URC_END_OF_STACK |
| 54 | #define UNWIND_CONTINUE _URC_NO_REASON |
| 55 | #else |
| 56 | #define UNWIND_STOP _URC_NORMAL_STOP |
| 57 | #define UNWIND_CONTINUE _URC_NO_REASON |
| 58 | #endif |
| 59 | |
| 60 | uptr Unwind_GetIP(struct _Unwind_Context *ctx) { |
| 61 | #if defined(__arm__) && !SANITIZER_APPLE |
| 62 | uptr val; |
| 63 | _Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE, |
| 64 | 15 /* r15 = PC */, _UVRSD_UINT32, &val); |
| 65 | CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed" ); |
| 66 | // Clear the Thumb bit. |
| 67 | return val & ~(uptr)1; |
| 68 | #else |
| 69 | return (uptr)_Unwind_GetIP(ctx); |
| 70 | #endif |
| 71 | } |
| 72 | |
| 73 | struct UnwindTraceArg { |
| 74 | BufferedStackTrace *stack; |
| 75 | u32 max_depth; |
| 76 | }; |
| 77 | |
| 78 | _Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, void *param) { |
| 79 | UnwindTraceArg *arg = (UnwindTraceArg*)param; |
| 80 | CHECK_LT(arg->stack->size, arg->max_depth); |
| 81 | uptr pc = Unwind_GetIP(ctx); |
| 82 | const uptr kPageSize = GetPageSizeCached(); |
| 83 | // Let's assume that any pointer in the 0th page (i.e. <0x1000 on i386 and |
| 84 | // x86_64) is invalid and stop unwinding here. If we're adding support for |
| 85 | // a platform where this isn't true, we need to reconsider this check. |
| 86 | if (pc < kPageSize) return UNWIND_STOP; |
| 87 | arg->stack->trace_buffer[arg->stack->size++] = pc; |
| 88 | if (arg->stack->size == arg->max_depth) return UNWIND_STOP; |
| 89 | return UNWIND_CONTINUE; |
| 90 | } |
| 91 | |
| 92 | } // namespace |
| 93 | |
| 94 | #if SANITIZER_ANDROID |
| 95 | void SanitizerInitializeUnwinder() { |
| 96 | if (AndroidGetApiLevel() >= ANDROID_LOLLIPOP_MR1) return; |
| 97 | |
| 98 | // Pre-lollipop Android can not unwind through signal handler frames with |
| 99 | // libgcc unwinder, but it has a libcorkscrew.so library with the necessary |
| 100 | // workarounds. |
| 101 | void *p = dlopen("libcorkscrew.so" , RTLD_LAZY); |
| 102 | if (!p) { |
| 103 | VReport(1, |
| 104 | "Failed to open libcorkscrew.so. You may see broken stack traces " |
| 105 | "in SEGV reports." ); |
| 106 | return; |
| 107 | } |
| 108 | acquire_my_map_info_list = |
| 109 | (acquire_my_map_info_list_func)(uptr)dlsym(p, "acquire_my_map_info_list" ); |
| 110 | release_my_map_info_list = |
| 111 | (release_my_map_info_list_func)(uptr)dlsym(p, "release_my_map_info_list" ); |
| 112 | unwind_backtrace_signal_arch = (unwind_backtrace_signal_arch_func)(uptr)dlsym( |
| 113 | p, "unwind_backtrace_signal_arch" ); |
| 114 | if (!acquire_my_map_info_list || !release_my_map_info_list || |
| 115 | !unwind_backtrace_signal_arch) { |
| 116 | VReport(1, |
| 117 | "Failed to find one of the required symbols in libcorkscrew.so. " |
| 118 | "You may see broken stack traces in SEGV reports." ); |
| 119 | acquire_my_map_info_list = 0; |
| 120 | unwind_backtrace_signal_arch = 0; |
| 121 | release_my_map_info_list = 0; |
| 122 | } |
| 123 | } |
| 124 | #endif |
| 125 | |
| 126 | void BufferedStackTrace::UnwindSlow(uptr pc, u32 max_depth) { |
| 127 | CHECK_GE(max_depth, 2); |
| 128 | size = 0; |
| 129 | UnwindTraceArg arg = {.stack: this, .max_depth: Min(a: max_depth + 1, b: kStackTraceMax)}; |
| 130 | _Unwind_Backtrace(Unwind_Trace, &arg); |
| 131 | // We need to pop a few frames so that pc is on top. |
| 132 | uptr to_pop = LocatePcInTrace(pc); |
| 133 | // trace_buffer[0] belongs to the current function so we always pop it, |
| 134 | // unless there is only 1 frame in the stack trace (1 frame is always better |
| 135 | // than 0!). |
| 136 | // 1-frame stacks don't normally happen, but this depends on the actual |
| 137 | // unwinder implementation (libgcc, libunwind, etc) which is outside of our |
| 138 | // control. |
| 139 | if (to_pop == 0 && size > 1) |
| 140 | to_pop = 1; |
| 141 | PopStackFrames(count: to_pop); |
| 142 | trace_buffer[0] = pc; |
| 143 | } |
| 144 | |
| 145 | void BufferedStackTrace::UnwindSlow(uptr pc, void *context, u32 max_depth) { |
| 146 | CHECK(context); |
| 147 | CHECK_GE(max_depth, 2); |
| 148 | if (!unwind_backtrace_signal_arch) { |
| 149 | UnwindSlow(pc, max_depth); |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | void *map = acquire_my_map_info_list(); |
| 154 | CHECK(map); |
| 155 | InternalMmapVector<backtrace_frame_t> frames(kStackTraceMax); |
| 156 | // siginfo argument appears to be unused. |
| 157 | sptr res = unwind_backtrace_signal_arch(/* siginfo */ 0, context, map, |
| 158 | frames.data(), |
| 159 | /* ignore_depth */ 0, max_depth); |
| 160 | release_my_map_info_list(map); |
| 161 | if (res < 0) return; |
| 162 | CHECK_LE((uptr)res, kStackTraceMax); |
| 163 | |
| 164 | size = 0; |
| 165 | // +2 compensate for libcorkscrew unwinder returning addresses of call |
| 166 | // instructions instead of raw return addresses. |
| 167 | for (sptr i = 0; i < res; ++i) |
| 168 | trace_buffer[size++] = frames[i].absolute_pc + 2; |
| 169 | } |
| 170 | |
| 171 | } // namespace __sanitizer |
| 172 | |
| 173 | #endif // SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || |
| 174 | // SANITIZER_SOLARIS || SANITIZER_HAIKU |
| 175 | |