1 | //===-- sanitizer_common.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 sanitizers' run-time libraries. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "sanitizer_stacktrace_printer.h" |
14 | |
15 | #include "sanitizer_common.h" |
16 | #include "sanitizer_file.h" |
17 | #include "sanitizer_flags.h" |
18 | #include "sanitizer_fuchsia.h" |
19 | #include "sanitizer_symbolizer_markup.h" |
20 | |
21 | namespace __sanitizer { |
22 | |
23 | StackTracePrinter *StackTracePrinter::GetOrInit() { |
24 | static StackTracePrinter *stacktrace_printer; |
25 | static StaticSpinMutex init_mu; |
26 | SpinMutexLock l(&init_mu); |
27 | if (stacktrace_printer) |
28 | return stacktrace_printer; |
29 | |
30 | stacktrace_printer = StackTracePrinter::NewStackTracePrinter(); |
31 | |
32 | CHECK(stacktrace_printer); |
33 | return stacktrace_printer; |
34 | } |
35 | |
36 | const char *StackTracePrinter::StripFunctionName(const char *function) { |
37 | if (!common_flags()->demangle) |
38 | return function; |
39 | if (!function) |
40 | return nullptr; |
41 | auto try_strip = [function](const char *prefix) -> const char * { |
42 | const uptr prefix_len = internal_strlen(s: prefix); |
43 | if (!internal_strncmp(s1: function, s2: prefix, n: prefix_len)) |
44 | return function + prefix_len; |
45 | return nullptr; |
46 | }; |
47 | if (SANITIZER_APPLE) { |
48 | if (const char *s = try_strip("wrap_" )) |
49 | return s; |
50 | } else if (SANITIZER_WINDOWS) { |
51 | if (const char *s = try_strip("__asan_wrap_" )) |
52 | return s; |
53 | } else { |
54 | if (const char *s = try_strip("___interceptor_" )) |
55 | return s; |
56 | if (const char *s = try_strip("__interceptor_" )) |
57 | return s; |
58 | } |
59 | return function; |
60 | } |
61 | |
62 | // sanitizer_symbolizer_markup.cpp implements these differently. |
63 | #if !SANITIZER_SYMBOLIZER_MARKUP |
64 | |
65 | StackTracePrinter *StackTracePrinter::NewStackTracePrinter() { |
66 | if (common_flags()->enable_symbolizer_markup) |
67 | return new (GetGlobalLowLevelAllocator()) MarkupStackTracePrinter(); |
68 | |
69 | return new (GetGlobalLowLevelAllocator()) FormattedStackTracePrinter(); |
70 | } |
71 | |
72 | static const char *DemangleFunctionName(const char *function) { |
73 | if (!common_flags()->demangle) |
74 | return function; |
75 | if (!function) |
76 | return nullptr; |
77 | |
78 | // NetBSD uses indirection for old threading functions for historical reasons |
79 | // The mangled names are internal implementation detail and should not be |
80 | // exposed even in backtraces. |
81 | #if SANITIZER_NETBSD |
82 | if (!internal_strcmp(function, "__libc_mutex_init" )) |
83 | return "pthread_mutex_init" ; |
84 | if (!internal_strcmp(function, "__libc_mutex_lock" )) |
85 | return "pthread_mutex_lock" ; |
86 | if (!internal_strcmp(function, "__libc_mutex_trylock" )) |
87 | return "pthread_mutex_trylock" ; |
88 | if (!internal_strcmp(function, "__libc_mutex_unlock" )) |
89 | return "pthread_mutex_unlock" ; |
90 | if (!internal_strcmp(function, "__libc_mutex_destroy" )) |
91 | return "pthread_mutex_destroy" ; |
92 | if (!internal_strcmp(function, "__libc_mutexattr_init" )) |
93 | return "pthread_mutexattr_init" ; |
94 | if (!internal_strcmp(function, "__libc_mutexattr_settype" )) |
95 | return "pthread_mutexattr_settype" ; |
96 | if (!internal_strcmp(function, "__libc_mutexattr_destroy" )) |
97 | return "pthread_mutexattr_destroy" ; |
98 | if (!internal_strcmp(function, "__libc_cond_init" )) |
99 | return "pthread_cond_init" ; |
100 | if (!internal_strcmp(function, "__libc_cond_signal" )) |
101 | return "pthread_cond_signal" ; |
102 | if (!internal_strcmp(function, "__libc_cond_broadcast" )) |
103 | return "pthread_cond_broadcast" ; |
104 | if (!internal_strcmp(function, "__libc_cond_wait" )) |
105 | return "pthread_cond_wait" ; |
106 | if (!internal_strcmp(function, "__libc_cond_timedwait" )) |
107 | return "pthread_cond_timedwait" ; |
108 | if (!internal_strcmp(function, "__libc_cond_destroy" )) |
109 | return "pthread_cond_destroy" ; |
110 | if (!internal_strcmp(function, "__libc_rwlock_init" )) |
111 | return "pthread_rwlock_init" ; |
112 | if (!internal_strcmp(function, "__libc_rwlock_rdlock" )) |
113 | return "pthread_rwlock_rdlock" ; |
114 | if (!internal_strcmp(function, "__libc_rwlock_wrlock" )) |
115 | return "pthread_rwlock_wrlock" ; |
116 | if (!internal_strcmp(function, "__libc_rwlock_tryrdlock" )) |
117 | return "pthread_rwlock_tryrdlock" ; |
118 | if (!internal_strcmp(function, "__libc_rwlock_trywrlock" )) |
119 | return "pthread_rwlock_trywrlock" ; |
120 | if (!internal_strcmp(function, "__libc_rwlock_unlock" )) |
121 | return "pthread_rwlock_unlock" ; |
122 | if (!internal_strcmp(function, "__libc_rwlock_destroy" )) |
123 | return "pthread_rwlock_destroy" ; |
124 | if (!internal_strcmp(function, "__libc_thr_keycreate" )) |
125 | return "pthread_key_create" ; |
126 | if (!internal_strcmp(function, "__libc_thr_setspecific" )) |
127 | return "pthread_setspecific" ; |
128 | if (!internal_strcmp(function, "__libc_thr_getspecific" )) |
129 | return "pthread_getspecific" ; |
130 | if (!internal_strcmp(function, "__libc_thr_keydelete" )) |
131 | return "pthread_key_delete" ; |
132 | if (!internal_strcmp(function, "__libc_thr_once" )) |
133 | return "pthread_once" ; |
134 | if (!internal_strcmp(function, "__libc_thr_self" )) |
135 | return "pthread_self" ; |
136 | if (!internal_strcmp(function, "__libc_thr_exit" )) |
137 | return "pthread_exit" ; |
138 | if (!internal_strcmp(function, "__libc_thr_setcancelstate" )) |
139 | return "pthread_setcancelstate" ; |
140 | if (!internal_strcmp(function, "__libc_thr_equal" )) |
141 | return "pthread_equal" ; |
142 | if (!internal_strcmp(function, "__libc_thr_curcpu" )) |
143 | return "pthread_curcpu_np" ; |
144 | if (!internal_strcmp(function, "__libc_thr_sigsetmask" )) |
145 | return "pthread_sigmask" ; |
146 | #endif |
147 | |
148 | return function; |
149 | } |
150 | |
151 | static void MaybeBuildIdToBuffer(const AddressInfo &info, bool PrefixSpace, |
152 | InternalScopedString *buffer) { |
153 | if (info.uuid_size) { |
154 | if (PrefixSpace) |
155 | buffer->Append(str: " " ); |
156 | buffer->Append(str: "(BuildId: " ); |
157 | for (uptr i = 0; i < info.uuid_size; ++i) { |
158 | buffer->AppendF(format: "%02x" , info.uuid[i]); |
159 | } |
160 | buffer->Append(str: ")" ); |
161 | } |
162 | } |
163 | |
164 | static const char kDefaultFormat[] = " #%n %p %F %L" ; |
165 | |
166 | void FormattedStackTracePrinter::RenderFrame(InternalScopedString *buffer, |
167 | const char *format, int frame_no, |
168 | uptr address, |
169 | const AddressInfo *info, |
170 | bool vs_style, |
171 | const char *strip_path_prefix) { |
172 | // info will be null in the case where symbolization is not needed for the |
173 | // given format. This ensures that the code below will get a hard failure |
174 | // rather than print incorrect information in case RenderNeedsSymbolization |
175 | // ever ends up out of sync with this function. If non-null, the addresses |
176 | // should match. |
177 | CHECK(!info || address == info->address); |
178 | if (0 == internal_strcmp(s1: format, s2: "DEFAULT" )) |
179 | format = kDefaultFormat; |
180 | for (const char *p = format; *p != '\0'; p++) { |
181 | if (*p != '%') { |
182 | buffer->AppendF(format: "%c" , *p); |
183 | continue; |
184 | } |
185 | p++; |
186 | switch (*p) { |
187 | case '%': |
188 | buffer->Append(str: "%" ); |
189 | break; |
190 | // Frame number and all fields of AddressInfo structure. |
191 | case 'n': |
192 | buffer->AppendF(format: "%u" , frame_no); |
193 | break; |
194 | case 'p': |
195 | buffer->AppendF(format: "0x%zx" , address); |
196 | break; |
197 | case 'm': |
198 | buffer->AppendF(format: "%s" , StripPathPrefix(filepath: info->module, strip_file_prefix: strip_path_prefix)); |
199 | break; |
200 | case 'o': |
201 | buffer->AppendF(format: "0x%zx" , info->module_offset); |
202 | break; |
203 | case 'b': |
204 | MaybeBuildIdToBuffer(info: *info, /*PrefixSpace=*/false, buffer); |
205 | break; |
206 | case 'f': |
207 | buffer->AppendF(format: "%s" , |
208 | DemangleFunctionName(function: StripFunctionName(function: info->function))); |
209 | break; |
210 | case 'q': |
211 | buffer->AppendF(format: "0x%zx" , info->function_offset != AddressInfo::kUnknown |
212 | ? info->function_offset |
213 | : 0x0); |
214 | break; |
215 | case 's': |
216 | buffer->AppendF(format: "%s" , StripPathPrefix(filepath: info->file, strip_file_prefix: strip_path_prefix)); |
217 | break; |
218 | case 'l': |
219 | buffer->AppendF(format: "%d" , info->line); |
220 | break; |
221 | case 'c': |
222 | buffer->AppendF(format: "%d" , info->column); |
223 | break; |
224 | // Smarter special cases. |
225 | case 'F': |
226 | // Function name and offset, if file is unknown. |
227 | if (info->function) { |
228 | buffer->AppendF( |
229 | format: "in %s" , DemangleFunctionName(function: StripFunctionName(function: info->function))); |
230 | if (!info->file && info->function_offset != AddressInfo::kUnknown) |
231 | buffer->AppendF(format: "+0x%zx" , info->function_offset); |
232 | } |
233 | break; |
234 | case 'S': |
235 | // File/line information. |
236 | RenderSourceLocation(buffer, file: info->file, line: info->line, column: info->column, |
237 | vs_style, strip_path_prefix); |
238 | break; |
239 | case 'L': |
240 | // Source location, or module location. |
241 | if (info->file) { |
242 | RenderSourceLocation(buffer, file: info->file, line: info->line, column: info->column, |
243 | vs_style, strip_path_prefix); |
244 | } else if (info->module) { |
245 | RenderModuleLocation(buffer, module: info->module, offset: info->module_offset, |
246 | arch: info->module_arch, strip_path_prefix); |
247 | |
248 | #if !SANITIZER_APPLE |
249 | MaybeBuildIdToBuffer(info: *info, /*PrefixSpace=*/true, buffer); |
250 | #endif |
251 | } else { |
252 | buffer->Append(str: "(<unknown module>)" ); |
253 | } |
254 | break; |
255 | case 'M': |
256 | // Module basename and offset, or PC. |
257 | if (address & kExternalPCBit) { |
258 | // There PCs are not meaningful. |
259 | } else if (info->module) { |
260 | // Always strip the module name for %M. |
261 | RenderModuleLocation(buffer, module: StripModuleName(module: info->module), |
262 | offset: info->module_offset, arch: info->module_arch, strip_path_prefix: "" ); |
263 | #if !SANITIZER_APPLE |
264 | MaybeBuildIdToBuffer(info: *info, /*PrefixSpace=*/true, buffer); |
265 | #endif |
266 | } else { |
267 | buffer->AppendF(format: "(%p)" , (void *)address); |
268 | } |
269 | break; |
270 | default: |
271 | Report(format: "Unsupported specifier in stack frame format: %c (%p)!\n" , *p, |
272 | (void *)p); |
273 | Die(); |
274 | } |
275 | } |
276 | } |
277 | |
278 | bool FormattedStackTracePrinter::RenderNeedsSymbolization(const char *format) { |
279 | if (0 == internal_strcmp(s1: format, s2: "DEFAULT" )) |
280 | format = kDefaultFormat; |
281 | for (const char *p = format; *p != '\0'; p++) { |
282 | if (*p != '%') |
283 | continue; |
284 | p++; |
285 | switch (*p) { |
286 | case '%': |
287 | break; |
288 | case 'n': |
289 | // frame_no |
290 | break; |
291 | case 'p': |
292 | // address |
293 | break; |
294 | default: |
295 | return true; |
296 | } |
297 | } |
298 | return false; |
299 | } |
300 | |
301 | void FormattedStackTracePrinter::RenderData(InternalScopedString *buffer, |
302 | const char *format, |
303 | const DataInfo *DI, |
304 | const char *strip_path_prefix) { |
305 | for (const char *p = format; *p != '\0'; p++) { |
306 | if (*p != '%') { |
307 | buffer->AppendF(format: "%c" , *p); |
308 | continue; |
309 | } |
310 | p++; |
311 | switch (*p) { |
312 | case '%': |
313 | buffer->Append(str: "%" ); |
314 | break; |
315 | case 's': |
316 | buffer->AppendF(format: "%s" , StripPathPrefix(filepath: DI->file, strip_file_prefix: strip_path_prefix)); |
317 | break; |
318 | case 'l': |
319 | buffer->AppendF(format: "%zu" , DI->line); |
320 | break; |
321 | case 'g': |
322 | buffer->AppendF(format: "%s" , DI->name); |
323 | break; |
324 | default: |
325 | Report(format: "Unsupported specifier in stack frame format: %c (%p)!\n" , *p, |
326 | (void *)p); |
327 | Die(); |
328 | } |
329 | } |
330 | } |
331 | |
332 | #endif // !SANITIZER_SYMBOLIZER_MARKUP |
333 | |
334 | void StackTracePrinter::RenderSourceLocation(InternalScopedString *buffer, |
335 | const char *file, int line, |
336 | int column, bool vs_style, |
337 | const char *strip_path_prefix) { |
338 | if (vs_style && line > 0) { |
339 | buffer->AppendF(format: "%s(%d" , StripPathPrefix(filepath: file, strip_file_prefix: strip_path_prefix), line); |
340 | if (column > 0) |
341 | buffer->AppendF(format: ",%d" , column); |
342 | buffer->Append(str: ")" ); |
343 | return; |
344 | } |
345 | |
346 | buffer->AppendF(format: "%s" , StripPathPrefix(filepath: file, strip_file_prefix: strip_path_prefix)); |
347 | if (line > 0) { |
348 | buffer->AppendF(format: ":%d" , line); |
349 | if (column > 0) |
350 | buffer->AppendF(format: ":%d" , column); |
351 | } |
352 | } |
353 | |
354 | void StackTracePrinter::RenderModuleLocation(InternalScopedString *buffer, |
355 | const char *module, uptr offset, |
356 | ModuleArch arch, |
357 | const char *strip_path_prefix) { |
358 | buffer->AppendF(format: "(%s" , StripPathPrefix(filepath: module, strip_file_prefix: strip_path_prefix)); |
359 | if (arch != kModuleArchUnknown) { |
360 | buffer->AppendF(format: ":%s" , ModuleArchToString(arch)); |
361 | } |
362 | buffer->AppendF(format: "+0x%zx)" , offset); |
363 | } |
364 | |
365 | } // namespace __sanitizer |
366 | |