| 1 | //===-- sanitizer_symbolizer_report.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 is shared between AddressSanitizer and other sanitizer run-time |
| 10 | /// libraries and implements symbolized reports related functions. |
| 11 | /// |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "sanitizer_common.h" |
| 15 | #include "sanitizer_file.h" |
| 16 | #include "sanitizer_flags.h" |
| 17 | #include "sanitizer_procmaps.h" |
| 18 | #include "sanitizer_report_decorator.h" |
| 19 | #include "sanitizer_stacktrace.h" |
| 20 | #include "sanitizer_stacktrace_printer.h" |
| 21 | #include "sanitizer_symbolizer.h" |
| 22 | |
| 23 | #if SANITIZER_POSIX |
| 24 | # include "sanitizer_posix.h" |
| 25 | # include <sys/mman.h> |
| 26 | #endif |
| 27 | |
| 28 | namespace __sanitizer { |
| 29 | |
| 30 | #if !SANITIZER_GO |
| 31 | |
| 32 | static bool FrameIsInternal(const SymbolizedStack *frame) { |
| 33 | if (!frame) |
| 34 | return true; |
| 35 | const char *file = frame->info.file; |
| 36 | const char *module = frame->info.module; |
| 37 | // On Gentoo, the path is g++-*, so there's *not* a missing /. |
| 38 | if (file && (internal_strstr(haystack: file, needle: "/compiler-rt/lib/" ) || |
| 39 | internal_strstr(haystack: file, needle: "/include/c++/" ) || |
| 40 | internal_strstr(haystack: file, needle: "/include/g++" ))) |
| 41 | return true; |
| 42 | if (file && internal_strstr(haystack: file, needle: "\\compiler-rt\\lib\\" )) |
| 43 | return true; |
| 44 | if (module && (internal_strstr(haystack: module, needle: "libclang_rt." ))) |
| 45 | return true; |
| 46 | if (module && (internal_strstr(haystack: module, needle: "clang_rt." ))) |
| 47 | return true; |
| 48 | return false; |
| 49 | } |
| 50 | |
| 51 | const SymbolizedStack *SkipInternalFrames(const SymbolizedStack *frames) { |
| 52 | for (const SymbolizedStack *f = frames; f; f = f->next) |
| 53 | if (!FrameIsInternal(frame: f)) |
| 54 | return f; |
| 55 | return nullptr; |
| 56 | } |
| 57 | |
| 58 | void ReportErrorSummary(const char *error_type, const AddressInfo &info, |
| 59 | const char *alt_tool_name) { |
| 60 | if (!common_flags()->print_summary) return; |
| 61 | InternalScopedString buff; |
| 62 | buff.AppendF(format: "%s " , error_type); |
| 63 | StackTracePrinter::GetOrInit()->RenderFrame( |
| 64 | buffer: &buff, format: "%L %F" , frame_no: 0, address: info.address, info: &info, |
| 65 | vs_style: common_flags()->symbolize_vs_style, strip_path_prefix: common_flags()->strip_path_prefix); |
| 66 | ReportErrorSummary(error_message: buff.data(), alt_tool_name); |
| 67 | } |
| 68 | #endif |
| 69 | |
| 70 | #if !SANITIZER_FUCHSIA |
| 71 | |
| 72 | bool ReportFile::SupportsColors() { |
| 73 | SpinMutexLock l(mu); |
| 74 | ReopenIfNecessary(); |
| 75 | return SupportsColoredOutput(fd); |
| 76 | } |
| 77 | |
| 78 | static inline bool ReportSupportsColors() { |
| 79 | return report_file.SupportsColors(); |
| 80 | } |
| 81 | |
| 82 | #else // SANITIZER_FUCHSIA |
| 83 | |
| 84 | // Fuchsia's logs always go through post-processing that handles colorization. |
| 85 | static inline bool ReportSupportsColors() { return true; } |
| 86 | |
| 87 | #endif // !SANITIZER_FUCHSIA |
| 88 | |
| 89 | bool ColorizeReports() { |
| 90 | // FIXME: Add proper Windows support to AnsiColorDecorator and re-enable color |
| 91 | // printing on Windows. |
| 92 | if (SANITIZER_WINDOWS) |
| 93 | return false; |
| 94 | |
| 95 | const char *flag = common_flags()->color; |
| 96 | return internal_strcmp(s1: flag, s2: "always" ) == 0 || |
| 97 | (internal_strcmp(s1: flag, s2: "auto" ) == 0 && ReportSupportsColors()); |
| 98 | } |
| 99 | |
| 100 | void ReportErrorSummary(const char *error_type, const StackTrace *stack, |
| 101 | const char *alt_tool_name) { |
| 102 | #if !SANITIZER_GO |
| 103 | if (!common_flags()->print_summary) |
| 104 | return; |
| 105 | |
| 106 | // Find first non-internal stack frame. |
| 107 | for (uptr i = 0; i < stack->size; ++i) { |
| 108 | uptr pc = StackTrace::GetPreviousInstructionPc(pc: stack->trace[i]); |
| 109 | SymbolizedStackHolder symbolized_stack( |
| 110 | Symbolizer::GetOrInit()->SymbolizePC(address: pc)); |
| 111 | if (const SymbolizedStack *frame = symbolized_stack.get()) { |
| 112 | if (const SymbolizedStack *summary_frame = SkipInternalFrames(frames: frame)) { |
| 113 | ReportErrorSummary(error_type, info: summary_frame->info, alt_tool_name); |
| 114 | return; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | // Fallback to the top one. |
| 120 | if (stack->size) { |
| 121 | uptr pc = StackTrace::GetPreviousInstructionPc(pc: stack->trace[0]); |
| 122 | SymbolizedStackHolder symbolized_stack( |
| 123 | Symbolizer::GetOrInit()->SymbolizePC(address: pc)); |
| 124 | if (const SymbolizedStack *frame = symbolized_stack.get()) { |
| 125 | ReportErrorSummary(error_type, info: frame->info, alt_tool_name); |
| 126 | return; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // Fallback to a summary without location. |
| 131 | ReportErrorSummary(error_message: error_type); |
| 132 | #endif |
| 133 | } |
| 134 | |
| 135 | void ReportMmapWriteExec(int prot, int flags) { |
| 136 | #if SANITIZER_POSIX && (!SANITIZER_GO && !SANITIZER_ANDROID) |
| 137 | int pflags = (PROT_WRITE | PROT_EXEC); |
| 138 | if ((prot & pflags) != pflags) |
| 139 | return; |
| 140 | |
| 141 | # if SANITIZER_APPLE && defined(MAP_JIT) |
| 142 | if ((flags & MAP_JIT) == MAP_JIT) |
| 143 | return; |
| 144 | # endif |
| 145 | |
| 146 | ScopedErrorReportLock l; |
| 147 | SanitizerCommonDecorator d; |
| 148 | |
| 149 | InternalMmapVector<BufferedStackTrace> stack_buffer(1); |
| 150 | BufferedStackTrace *stack = stack_buffer.data(); |
| 151 | stack->Reset(); |
| 152 | uptr top = 0; |
| 153 | uptr bottom = 0; |
| 154 | GET_CALLER_PC_BP; |
| 155 | bool fast = common_flags()->fast_unwind_on_fatal; |
| 156 | if (StackTrace::WillUseFastUnwind(request_fast_unwind: fast)) { |
| 157 | GetThreadStackTopAndBottom(at_initialization: false, stack_top: &top, stack_bottom: &bottom); |
| 158 | stack->Unwind(max_depth: kStackTraceMax, pc, bp, context: nullptr, stack_top: top, stack_bottom: bottom, request_fast_unwind: true); |
| 159 | } else { |
| 160 | stack->Unwind(max_depth: kStackTraceMax, pc, bp: 0, context: nullptr, stack_top: 0, stack_bottom: 0, request_fast_unwind: false); |
| 161 | } |
| 162 | |
| 163 | Printf(format: "%s" , d.Warning()); |
| 164 | Report(format: "WARNING: %s: writable-executable page usage\n" , SanitizerToolName); |
| 165 | Printf(format: "%s" , d.Default()); |
| 166 | |
| 167 | stack->Print(); |
| 168 | ReportErrorSummary(error_type: "w-and-x-usage" , stack); |
| 169 | #endif |
| 170 | } |
| 171 | |
| 172 | #if !SANITIZER_FUCHSIA && !SANITIZER_GO |
| 173 | void StartReportDeadlySignal() { |
| 174 | // Write the first message using fd=2, just in case. |
| 175 | // It may actually fail to write in case stderr is closed. |
| 176 | CatastrophicErrorWrite(buffer: SanitizerToolName, length: internal_strlen(s: SanitizerToolName)); |
| 177 | static const char kDeadlySignal[] = ":DEADLYSIGNAL\n" ; |
| 178 | CatastrophicErrorWrite(buffer: kDeadlySignal, length: sizeof(kDeadlySignal) - 1); |
| 179 | } |
| 180 | |
| 181 | static void MaybeReportNonExecRegion(uptr pc) { |
| 182 | #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD |
| 183 | MemoryMappingLayout proc_maps(/*cache_enabled*/ true); |
| 184 | MemoryMappedSegment segment; |
| 185 | while (proc_maps.Next(segment: &segment)) { |
| 186 | if (pc >= segment.start && pc < segment.end && !segment.IsExecutable()) |
| 187 | Report(format: "Hint: PC is at a non-executable region. Maybe a wild jump?\n" ); |
| 188 | } |
| 189 | #endif |
| 190 | } |
| 191 | |
| 192 | static void PrintMemoryByte(InternalScopedString *str, const char *before, |
| 193 | u8 byte) { |
| 194 | SanitizerCommonDecorator d; |
| 195 | str->AppendF(format: "%s%s%x%x%s " , before, d.MemoryByte(), byte >> 4, byte & 15, |
| 196 | d.Default()); |
| 197 | } |
| 198 | |
| 199 | static void MaybeDumpInstructionBytes(uptr pc) { |
| 200 | if (!common_flags()->dump_instruction_bytes || (pc < GetPageSizeCached())) |
| 201 | return; |
| 202 | InternalScopedString str; |
| 203 | str.AppendF(format: "First 16 instruction bytes at pc: " ); |
| 204 | if (IsAccessibleMemoryRange(beg: pc, size: 16)) { |
| 205 | for (int i = 0; i < 16; ++i) { |
| 206 | PrintMemoryByte(str: &str, before: "" , byte: ((u8 *)pc)[i]); |
| 207 | } |
| 208 | str.AppendF(format: "\n" ); |
| 209 | } else { |
| 210 | str.AppendF(format: "unaccessible\n" ); |
| 211 | } |
| 212 | Report(format: "%s" , str.data()); |
| 213 | } |
| 214 | |
| 215 | static void MaybeDumpRegisters(void *context) { |
| 216 | if (!common_flags()->dump_registers) return; |
| 217 | SignalContext::DumpAllRegisters(context); |
| 218 | } |
| 219 | |
| 220 | static void ReportStackOverflowImpl(const SignalContext &sig, u32 tid, |
| 221 | UnwindSignalStackCallbackType unwind, |
| 222 | const void *unwind_context) { |
| 223 | SanitizerCommonDecorator d; |
| 224 | Printf(format: "%s" , d.Warning()); |
| 225 | static const char kDescription[] = "stack-overflow" ; |
| 226 | Report(format: "ERROR: %s: %s on address %p (pc %p bp %p sp %p T%d)\n" , |
| 227 | SanitizerToolName, kDescription, (void *)sig.addr, (void *)sig.pc, |
| 228 | (void *)sig.bp, (void *)sig.sp, tid); |
| 229 | Printf(format: "%s" , d.Default()); |
| 230 | // Avoid SEGVs in the unwinder when bp couldn't be determined. |
| 231 | if (sig.bp) { |
| 232 | InternalMmapVector<BufferedStackTrace> stack_buffer(1); |
| 233 | BufferedStackTrace *stack = stack_buffer.data(); |
| 234 | stack->Reset(); |
| 235 | unwind(sig, unwind_context, stack); |
| 236 | stack->Print(); |
| 237 | ReportErrorSummary(error_type: kDescription, stack); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | static void ReportDeadlySignalImpl(const SignalContext &sig, u32 tid, |
| 242 | UnwindSignalStackCallbackType unwind, |
| 243 | const void *unwind_context) { |
| 244 | SanitizerCommonDecorator d; |
| 245 | Printf(format: "%s" , d.Warning()); |
| 246 | const char *description = sig.Describe(); |
| 247 | if (sig.is_memory_access && !sig.is_true_faulting_addr) |
| 248 | Report(format: "ERROR: %s: %s on unknown address (pc %p bp %p sp %p T%d)\n" , |
| 249 | SanitizerToolName, description, (void *)sig.pc, (void *)sig.bp, |
| 250 | (void *)sig.sp, tid); |
| 251 | else |
| 252 | Report(format: "ERROR: %s: %s on unknown address %p (pc %p bp %p sp %p T%d)\n" , |
| 253 | SanitizerToolName, description, (void *)sig.addr, (void *)sig.pc, |
| 254 | (void *)sig.bp, (void *)sig.sp, tid); |
| 255 | Printf(format: "%s" , d.Default()); |
| 256 | if (sig.pc < GetPageSizeCached()) |
| 257 | Report(format: "Hint: pc points to the zero page.\n" ); |
| 258 | if (sig.is_memory_access) { |
| 259 | const char *access_type = |
| 260 | sig.write_flag == SignalContext::Write |
| 261 | ? "WRITE" |
| 262 | : (sig.write_flag == SignalContext::Read ? "READ" : "UNKNOWN" ); |
| 263 | Report(format: "The signal is caused by a %s memory access.\n" , access_type); |
| 264 | if (!sig.is_true_faulting_addr) |
| 265 | Report(format: "Hint: this fault was caused by a dereference of a high value " |
| 266 | "address (see register values below). Disassemble the provided " |
| 267 | "pc to learn which register was used.\n" ); |
| 268 | else if (sig.addr < GetPageSizeCached()) |
| 269 | Report(format: "Hint: address points to the zero page.\n" ); |
| 270 | } |
| 271 | MaybeReportNonExecRegion(pc: sig.pc); |
| 272 | InternalMmapVector<BufferedStackTrace> stack_buffer(1); |
| 273 | BufferedStackTrace *stack = stack_buffer.data(); |
| 274 | stack->Reset(); |
| 275 | unwind(sig, unwind_context, stack); |
| 276 | stack->Print(); |
| 277 | MaybeDumpInstructionBytes(pc: sig.pc); |
| 278 | MaybeDumpRegisters(context: sig.context); |
| 279 | Printf(format: "%s can not provide additional info.\n" , SanitizerToolName); |
| 280 | ReportErrorSummary(error_type: description, stack); |
| 281 | } |
| 282 | |
| 283 | void ReportDeadlySignal(const SignalContext &sig, u32 tid, |
| 284 | UnwindSignalStackCallbackType unwind, |
| 285 | const void *unwind_context) { |
| 286 | if (sig.IsStackOverflow()) |
| 287 | ReportStackOverflowImpl(sig, tid, unwind, unwind_context); |
| 288 | else |
| 289 | ReportDeadlySignalImpl(sig, tid, unwind, unwind_context); |
| 290 | } |
| 291 | |
| 292 | void HandleDeadlySignal(void *siginfo, void *context, u32 tid, |
| 293 | UnwindSignalStackCallbackType unwind, |
| 294 | const void *unwind_context) { |
| 295 | StartReportDeadlySignal(); |
| 296 | ScopedErrorReportLock rl; |
| 297 | SignalContext sig(siginfo, context); |
| 298 | ReportDeadlySignal(sig, tid, unwind, unwind_context); |
| 299 | Report(format: "ABORTING\n" ); |
| 300 | Die(); |
| 301 | } |
| 302 | |
| 303 | #endif // !SANITIZER_FUCHSIA && !SANITIZER_GO |
| 304 | |
| 305 | atomic_uintptr_t ScopedErrorReportLock::reporting_thread_ = {.val_dont_use: 0}; |
| 306 | StaticSpinMutex ScopedErrorReportLock::mutex_; |
| 307 | |
| 308 | void ScopedErrorReportLock::Lock() { |
| 309 | uptr current = GetThreadSelf(); |
| 310 | for (;;) { |
| 311 | uptr expected = 0; |
| 312 | if (atomic_compare_exchange_strong(a: &reporting_thread_, cmp: &expected, xchg: current, |
| 313 | mo: memory_order_relaxed)) { |
| 314 | // We've claimed reporting_thread so proceed. |
| 315 | mutex_.Lock(); |
| 316 | return; |
| 317 | } |
| 318 | |
| 319 | if (expected == current) { |
| 320 | // This is either asynch signal or nested error during error reporting. |
| 321 | // Fail simple to avoid deadlocks in Report(). |
| 322 | |
| 323 | // Can't use Report() here because of potential deadlocks in nested |
| 324 | // signal handlers. |
| 325 | CatastrophicErrorWrite(buffer: SanitizerToolName, |
| 326 | length: internal_strlen(s: SanitizerToolName)); |
| 327 | static const char msg[] = ": nested bug in the same thread, aborting.\n" ; |
| 328 | CatastrophicErrorWrite(buffer: msg, length: sizeof(msg) - 1); |
| 329 | |
| 330 | internal__exit(exitcode: common_flags()->exitcode); |
| 331 | } |
| 332 | |
| 333 | internal_sched_yield(); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | void ScopedErrorReportLock::Unlock() { |
| 338 | mutex_.Unlock(); |
| 339 | atomic_store_relaxed(a: &reporting_thread_, v: 0); |
| 340 | } |
| 341 | |
| 342 | void ScopedErrorReportLock::CheckLocked() { mutex_.CheckLocked(); } |
| 343 | |
| 344 | } // namespace __sanitizer |
| 345 | |