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 | InternalMmapVector<BufferedStackTrace> stack_buffer(1); |
231 | BufferedStackTrace *stack = stack_buffer.data(); |
232 | stack->Reset(); |
233 | unwind(sig, unwind_context, stack); |
234 | stack->Print(); |
235 | ReportErrorSummary(error_type: kDescription, stack); |
236 | } |
237 | |
238 | static void ReportDeadlySignalImpl(const SignalContext &sig, u32 tid, |
239 | UnwindSignalStackCallbackType unwind, |
240 | const void *unwind_context) { |
241 | SanitizerCommonDecorator d; |
242 | Printf(format: "%s" , d.Warning()); |
243 | const char *description = sig.Describe(); |
244 | if (sig.is_memory_access && !sig.is_true_faulting_addr) |
245 | Report(format: "ERROR: %s: %s on unknown address (pc %p bp %p sp %p T%d)\n" , |
246 | SanitizerToolName, description, (void *)sig.pc, (void *)sig.bp, |
247 | (void *)sig.sp, tid); |
248 | else |
249 | Report(format: "ERROR: %s: %s on unknown address %p (pc %p bp %p sp %p T%d)\n" , |
250 | SanitizerToolName, description, (void *)sig.addr, (void *)sig.pc, |
251 | (void *)sig.bp, (void *)sig.sp, tid); |
252 | Printf(format: "%s" , d.Default()); |
253 | if (sig.pc < GetPageSizeCached()) |
254 | Report(format: "Hint: pc points to the zero page.\n" ); |
255 | if (sig.is_memory_access) { |
256 | const char *access_type = |
257 | sig.write_flag == SignalContext::Write |
258 | ? "WRITE" |
259 | : (sig.write_flag == SignalContext::Read ? "READ" : "UNKNOWN" ); |
260 | Report(format: "The signal is caused by a %s memory access.\n" , access_type); |
261 | if (!sig.is_true_faulting_addr) |
262 | Report(format: "Hint: this fault was caused by a dereference of a high value " |
263 | "address (see register values below). Disassemble the provided " |
264 | "pc to learn which register was used.\n" ); |
265 | else if (sig.addr < GetPageSizeCached()) |
266 | Report(format: "Hint: address points to the zero page.\n" ); |
267 | } |
268 | MaybeReportNonExecRegion(pc: sig.pc); |
269 | InternalMmapVector<BufferedStackTrace> stack_buffer(1); |
270 | BufferedStackTrace *stack = stack_buffer.data(); |
271 | stack->Reset(); |
272 | unwind(sig, unwind_context, stack); |
273 | stack->Print(); |
274 | MaybeDumpInstructionBytes(pc: sig.pc); |
275 | MaybeDumpRegisters(context: sig.context); |
276 | Printf(format: "%s can not provide additional info.\n" , SanitizerToolName); |
277 | ReportErrorSummary(error_type: description, stack); |
278 | } |
279 | |
280 | void ReportDeadlySignal(const SignalContext &sig, u32 tid, |
281 | UnwindSignalStackCallbackType unwind, |
282 | const void *unwind_context) { |
283 | if (sig.IsStackOverflow()) |
284 | ReportStackOverflowImpl(sig, tid, unwind, unwind_context); |
285 | else |
286 | ReportDeadlySignalImpl(sig, tid, unwind, unwind_context); |
287 | } |
288 | |
289 | void HandleDeadlySignal(void *siginfo, void *context, u32 tid, |
290 | UnwindSignalStackCallbackType unwind, |
291 | const void *unwind_context) { |
292 | StartReportDeadlySignal(); |
293 | ScopedErrorReportLock rl; |
294 | SignalContext sig(siginfo, context); |
295 | ReportDeadlySignal(sig, tid, unwind, unwind_context); |
296 | Report(format: "ABORTING\n" ); |
297 | Die(); |
298 | } |
299 | |
300 | #endif // !SANITIZER_FUCHSIA && !SANITIZER_GO |
301 | |
302 | atomic_uintptr_t ScopedErrorReportLock::reporting_thread_ = {.val_dont_use: 0}; |
303 | StaticSpinMutex ScopedErrorReportLock::mutex_; |
304 | |
305 | void ScopedErrorReportLock::Lock() { |
306 | uptr current = GetThreadSelf(); |
307 | for (;;) { |
308 | uptr expected = 0; |
309 | if (atomic_compare_exchange_strong(a: &reporting_thread_, cmp: &expected, xchg: current, |
310 | mo: memory_order_relaxed)) { |
311 | // We've claimed reporting_thread so proceed. |
312 | mutex_.Lock(); |
313 | return; |
314 | } |
315 | |
316 | if (expected == current) { |
317 | // This is either asynch signal or nested error during error reporting. |
318 | // Fail simple to avoid deadlocks in Report(). |
319 | |
320 | // Can't use Report() here because of potential deadlocks in nested |
321 | // signal handlers. |
322 | CatastrophicErrorWrite(buffer: SanitizerToolName, |
323 | length: internal_strlen(s: SanitizerToolName)); |
324 | static const char msg[] = ": nested bug in the same thread, aborting.\n" ; |
325 | CatastrophicErrorWrite(buffer: msg, length: sizeof(msg) - 1); |
326 | |
327 | internal__exit(exitcode: common_flags()->exitcode); |
328 | } |
329 | |
330 | internal_sched_yield(); |
331 | } |
332 | } |
333 | |
334 | void ScopedErrorReportLock::Unlock() { |
335 | mutex_.Unlock(); |
336 | atomic_store_relaxed(a: &reporting_thread_, v: 0); |
337 | } |
338 | |
339 | void ScopedErrorReportLock::CheckLocked() { mutex_.CheckLocked(); } |
340 | |
341 | } // namespace __sanitizer |
342 | |