1 | //===-- sanitizer_symbolizer_posix_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 is shared between AddressSanitizer and ThreadSanitizer |
10 | // run-time libraries. |
11 | // POSIX-specific implementation of symbolizer parts. |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "sanitizer_platform.h" |
15 | #include "sanitizer_symbolizer_markup.h" |
16 | #if SANITIZER_POSIX |
17 | # include <dlfcn.h> // for dlsym() |
18 | # include <errno.h> |
19 | # include <stdint.h> |
20 | # include <stdlib.h> |
21 | # include <sys/wait.h> |
22 | # include <unistd.h> |
23 | |
24 | # include "sanitizer_allocator_internal.h" |
25 | # include "sanitizer_common.h" |
26 | # include "sanitizer_file.h" |
27 | # include "sanitizer_flags.h" |
28 | # include "sanitizer_internal_defs.h" |
29 | # include "sanitizer_linux.h" |
30 | # include "sanitizer_placement_new.h" |
31 | # include "sanitizer_posix.h" |
32 | # include "sanitizer_procmaps.h" |
33 | # include "sanitizer_symbolizer_internal.h" |
34 | # include "sanitizer_symbolizer_libbacktrace.h" |
35 | # include "sanitizer_symbolizer_mac.h" |
36 | |
37 | // C++ demangling function, as required by Itanium C++ ABI. This is weak, |
38 | // because we do not require a C++ ABI library to be linked to a program |
39 | // using sanitizers; if it's not present, we'll just use the mangled name. |
40 | namespace __cxxabiv1 { |
41 | extern "C" SANITIZER_WEAK_ATTRIBUTE char *__cxa_demangle(const char *mangled, |
42 | char *buffer, |
43 | size_t *length, |
44 | int *status); |
45 | } |
46 | |
47 | namespace __sanitizer { |
48 | |
49 | // Attempts to demangle the name via __cxa_demangle from __cxxabiv1. |
50 | const char *DemangleCXXABI(const char *name) { |
51 | // FIXME: __cxa_demangle aggressively insists on allocating memory. |
52 | // There's not much we can do about that, short of providing our |
53 | // own demangler (libc++abi's implementation could be adapted so that |
54 | // it does not allocate). For now, we just call it anyway, and we leak |
55 | // the returned value. |
56 | if (&__cxxabiv1::__cxa_demangle) |
57 | if (const char *demangled_name = __cxxabiv1::__cxa_demangle(mangled: name, buffer: 0, length: 0, status: 0)) |
58 | return demangled_name; |
59 | |
60 | return nullptr; |
61 | } |
62 | |
63 | // As of now, there are no headers for the Swift runtime. Once they are |
64 | // present, we will weakly link since we do not require Swift runtime to be |
65 | // linked. |
66 | typedef char *(*swift_demangle_ft)(const char *mangledName, |
67 | size_t mangledNameLength, char *outputBuffer, |
68 | size_t *outputBufferSize, uint32_t flags); |
69 | static swift_demangle_ft swift_demangle_f; |
70 | |
71 | // This must not happen lazily at symbolication time, because dlsym uses |
72 | // malloc and thread-local storage, which is not a good thing to do during |
73 | // symbolication. |
74 | static void InitializeSwiftDemangler() { |
75 | swift_demangle_f = (swift_demangle_ft)dlsym(RTLD_DEFAULT, name: "swift_demangle" ); |
76 | } |
77 | |
78 | // Attempts to demangle a Swift name. The demangler will return nullptr if a |
79 | // non-Swift name is passed in. |
80 | const char *DemangleSwift(const char *name) { |
81 | if (swift_demangle_f) |
82 | return swift_demangle_f(name, internal_strlen(s: name), 0, 0, 0); |
83 | |
84 | return nullptr; |
85 | } |
86 | |
87 | const char *DemangleSwiftAndCXX(const char *name) { |
88 | if (!name) |
89 | return nullptr; |
90 | if (const char *swift_demangled_name = DemangleSwift(name)) |
91 | return swift_demangled_name; |
92 | return DemangleCXXABI(name); |
93 | } |
94 | |
95 | static bool CreateTwoHighNumberedPipes(int *infd_, int *outfd_) { |
96 | int *infd = NULL; |
97 | int *outfd = NULL; |
98 | // The client program may close its stdin and/or stdout and/or stderr |
99 | // thus allowing socketpair to reuse file descriptors 0, 1 or 2. |
100 | // In this case the communication between the forked processes may be |
101 | // broken if either the parent or the child tries to close or duplicate |
102 | // these descriptors. The loop below produces two pairs of file |
103 | // descriptors, each greater than 2 (stderr). |
104 | int sock_pair[5][2]; |
105 | for (int i = 0; i < 5; i++) { |
106 | if (pipe(pipedes: sock_pair[i]) == -1) { |
107 | for (int j = 0; j < i; j++) { |
108 | internal_close(fd: sock_pair[j][0]); |
109 | internal_close(fd: sock_pair[j][1]); |
110 | } |
111 | return false; |
112 | } else if (sock_pair[i][0] > 2 && sock_pair[i][1] > 2) { |
113 | if (infd == NULL) { |
114 | infd = sock_pair[i]; |
115 | } else { |
116 | outfd = sock_pair[i]; |
117 | for (int j = 0; j < i; j++) { |
118 | if (sock_pair[j] == infd) |
119 | continue; |
120 | internal_close(fd: sock_pair[j][0]); |
121 | internal_close(fd: sock_pair[j][1]); |
122 | } |
123 | break; |
124 | } |
125 | } |
126 | } |
127 | CHECK(infd); |
128 | CHECK(outfd); |
129 | infd_[0] = infd[0]; |
130 | infd_[1] = infd[1]; |
131 | outfd_[0] = outfd[0]; |
132 | outfd_[1] = outfd[1]; |
133 | return true; |
134 | } |
135 | |
136 | bool SymbolizerProcess::StartSymbolizerSubprocess() { |
137 | if (!FileExists(filename: path_)) { |
138 | if (!reported_invalid_path_) { |
139 | Report(format: "WARNING: invalid path to external symbolizer!\n" ); |
140 | reported_invalid_path_ = true; |
141 | } |
142 | return false; |
143 | } |
144 | |
145 | const char *argv[kArgVMax]; |
146 | GetArgV(path_to_binary: path_, argv); |
147 | pid_t pid; |
148 | |
149 | // Report how symbolizer is being launched for debugging purposes. |
150 | if (Verbosity() >= 3) { |
151 | // Only use `Report` for first line so subsequent prints don't get prefixed |
152 | // with current PID. |
153 | Report(format: "Launching Symbolizer process: " ); |
154 | for (unsigned index = 0; index < kArgVMax && argv[index]; ++index) |
155 | Printf(format: "%s " , argv[index]); |
156 | Printf(format: "\n" ); |
157 | } |
158 | |
159 | if (use_posix_spawn_) { |
160 | # if SANITIZER_APPLE |
161 | fd_t fd = internal_spawn(argv, const_cast<const char **>(GetEnvP()), &pid); |
162 | if (fd == kInvalidFd) { |
163 | Report("WARNING: failed to spawn external symbolizer (errno: %d)\n" , |
164 | errno); |
165 | return false; |
166 | } |
167 | |
168 | input_fd_ = fd; |
169 | output_fd_ = fd; |
170 | # else // SANITIZER_APPLE |
171 | UNIMPLEMENTED(); |
172 | # endif // SANITIZER_APPLE |
173 | } else { |
174 | fd_t infd[2] = {}, outfd[2] = {}; |
175 | if (!CreateTwoHighNumberedPipes(infd_: infd, outfd_: outfd)) { |
176 | Report( |
177 | format: "WARNING: Can't create a socket pair to start " |
178 | "external symbolizer (errno: %d)\n" , |
179 | errno); |
180 | return false; |
181 | } |
182 | |
183 | pid = StartSubprocess(filename: path_, argv, envp: GetEnvP(), /* stdin */ stdin_fd: outfd[0], |
184 | /* stdout */ stdout_fd: infd[1]); |
185 | if (pid < 0) { |
186 | internal_close(fd: infd[0]); |
187 | internal_close(fd: outfd[1]); |
188 | return false; |
189 | } |
190 | |
191 | input_fd_ = infd[0]; |
192 | output_fd_ = outfd[1]; |
193 | } |
194 | |
195 | CHECK_GT(pid, 0); |
196 | |
197 | // Check that symbolizer subprocess started successfully. |
198 | SleepForMillis(millis: kSymbolizerStartupTimeMillis); |
199 | if (!IsProcessRunning(pid)) { |
200 | // Either waitpid failed, or child has already exited. |
201 | Report(format: "WARNING: external symbolizer didn't start up correctly!\n" ); |
202 | return false; |
203 | } |
204 | |
205 | return true; |
206 | } |
207 | |
208 | class Addr2LineProcess final : public SymbolizerProcess { |
209 | public: |
210 | Addr2LineProcess(const char *path, const char *module_name) |
211 | : SymbolizerProcess(path), module_name_(internal_strdup(s: module_name)) {} |
212 | |
213 | const char *module_name() const { return module_name_; } |
214 | |
215 | private: |
216 | void GetArgV(const char *path_to_binary, |
217 | const char *(&argv)[kArgVMax]) const override { |
218 | int i = 0; |
219 | argv[i++] = path_to_binary; |
220 | if (common_flags()->demangle) |
221 | argv[i++] = "-C" ; |
222 | if (common_flags()->symbolize_inline_frames) |
223 | argv[i++] = "-i" ; |
224 | argv[i++] = "-fe" ; |
225 | argv[i++] = module_name_; |
226 | argv[i++] = nullptr; |
227 | CHECK_LE(i, kArgVMax); |
228 | } |
229 | |
230 | bool ReachedEndOfOutput(const char *buffer, uptr length) const override; |
231 | |
232 | bool ReadFromSymbolizer() override { |
233 | if (!SymbolizerProcess::ReadFromSymbolizer()) |
234 | return false; |
235 | auto &buff = GetBuff(); |
236 | // We should cut out output_terminator_ at the end of given buffer, |
237 | // appended by addr2line to mark the end of its meaningful output. |
238 | // We cannot scan buffer from it's beginning, because it is legal for it |
239 | // to start with output_terminator_ in case given offset is invalid. So, |
240 | // scanning from second character. |
241 | char *garbage = internal_strstr(haystack: buff.data() + 1, needle: output_terminator_); |
242 | // This should never be NULL since buffer must end up with |
243 | // output_terminator_. |
244 | CHECK(garbage); |
245 | |
246 | // Trim the buffer. |
247 | uintptr_t new_size = garbage - buff.data(); |
248 | GetBuff().resize(new_size); |
249 | GetBuff().push_back(element: '\0'); |
250 | return true; |
251 | } |
252 | |
253 | const char *module_name_; // Owned, leaked. |
254 | static const char output_terminator_[]; |
255 | }; |
256 | |
257 | const char Addr2LineProcess::output_terminator_[] = "??\n??:0\n" ; |
258 | |
259 | bool Addr2LineProcess::ReachedEndOfOutput(const char *buffer, |
260 | uptr length) const { |
261 | const size_t kTerminatorLen = sizeof(output_terminator_) - 1; |
262 | // Skip, if we read just kTerminatorLen bytes, because Addr2Line output |
263 | // should consist at least of two pairs of lines: |
264 | // 1. First one, corresponding to given offset to be symbolized |
265 | // (may be equal to output_terminator_, if offset is not valid). |
266 | // 2. Second one for output_terminator_, itself to mark the end of output. |
267 | if (length <= kTerminatorLen) |
268 | return false; |
269 | // Addr2Line output should end up with output_terminator_. |
270 | return !internal_memcmp(s1: buffer + length - kTerminatorLen, s2: output_terminator_, |
271 | n: kTerminatorLen); |
272 | } |
273 | |
274 | class Addr2LinePool final : public SymbolizerTool { |
275 | public: |
276 | explicit Addr2LinePool(const char *addr2line_path, |
277 | LowLevelAllocator *allocator) |
278 | : addr2line_path_(addr2line_path), allocator_(allocator) { |
279 | addr2line_pool_.reserve(new_size: 16); |
280 | } |
281 | |
282 | bool SymbolizePC(uptr addr, SymbolizedStack *stack) override { |
283 | if (const char *buf = |
284 | SendCommand(module_name: stack->info.module, module_offset: stack->info.module_offset)) { |
285 | ParseSymbolizePCOutput(str: buf, res: stack); |
286 | return true; |
287 | } |
288 | return false; |
289 | } |
290 | |
291 | bool SymbolizeData(uptr addr, DataInfo *info) override { return false; } |
292 | |
293 | private: |
294 | const char *SendCommand(const char *module_name, uptr module_offset) { |
295 | Addr2LineProcess *addr2line = 0; |
296 | for (uptr i = 0; i < addr2line_pool_.size(); ++i) { |
297 | if (0 == |
298 | internal_strcmp(s1: module_name, s2: addr2line_pool_[i]->module_name())) { |
299 | addr2line = addr2line_pool_[i]; |
300 | break; |
301 | } |
302 | } |
303 | if (!addr2line) { |
304 | addr2line = |
305 | new (*allocator_) Addr2LineProcess(addr2line_path_, module_name); |
306 | addr2line_pool_.push_back(element: addr2line); |
307 | } |
308 | CHECK_EQ(0, internal_strcmp(module_name, addr2line->module_name())); |
309 | char buffer[kBufferSize]; |
310 | internal_snprintf(buffer, length: kBufferSize, format: "0x%zx\n0x%zx\n" , module_offset, |
311 | dummy_address_); |
312 | return addr2line->SendCommand(command: buffer); |
313 | } |
314 | |
315 | static const uptr kBufferSize = 64; |
316 | const char *addr2line_path_; |
317 | LowLevelAllocator *allocator_; |
318 | InternalMmapVector<Addr2LineProcess *> addr2line_pool_; |
319 | static const uptr dummy_address_ = FIRST_32_SECOND_64(UINT32_MAX, UINT64_MAX); |
320 | }; |
321 | |
322 | # if SANITIZER_SUPPORTS_WEAK_HOOKS |
323 | extern "C" { |
324 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
325 | __sanitizer_symbolize_code(const char *ModuleName, u64 ModuleOffset, |
326 | char *Buffer, int MaxLength); |
327 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
328 | __sanitizer_symbolize_data(const char *ModuleName, u64 ModuleOffset, |
329 | char *Buffer, int MaxLength); |
330 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
331 | __sanitizer_symbolize_frame(const char *ModuleName, u64 ModuleOffset, |
332 | char *Buffer, int MaxLength); |
333 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void |
334 | __sanitizer_symbolize_flush(); |
335 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
336 | __sanitizer_symbolize_demangle(const char *Name, char *Buffer, int MaxLength); |
337 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
338 | __sanitizer_symbolize_set_demangle(bool Demangle); |
339 | SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE bool |
340 | __sanitizer_symbolize_set_inline_frames(bool InlineFrames); |
341 | } // extern "C" |
342 | |
343 | class InternalSymbolizer final : public SymbolizerTool { |
344 | public: |
345 | static InternalSymbolizer *get(LowLevelAllocator *alloc) { |
346 | // These one is the most used one, so we will use it to detect a presence of |
347 | // internal symbolizer. |
348 | if (&__sanitizer_symbolize_code == nullptr) |
349 | return nullptr; |
350 | CHECK(__sanitizer_symbolize_set_demangle(common_flags()->demangle)); |
351 | CHECK(__sanitizer_symbolize_set_inline_frames( |
352 | common_flags()->symbolize_inline_frames)); |
353 | return new (*alloc) InternalSymbolizer(); |
354 | } |
355 | |
356 | bool SymbolizePC(uptr addr, SymbolizedStack *stack) override { |
357 | bool result = __sanitizer_symbolize_code(ModuleName: stack->info.module, |
358 | ModuleOffset: stack->info.module_offset, Buffer: buffer_, |
359 | MaxLength: sizeof(buffer_)); |
360 | if (result) |
361 | ParseSymbolizePCOutput(str: buffer_, res: stack); |
362 | return result; |
363 | } |
364 | |
365 | bool SymbolizeData(uptr addr, DataInfo *info) override { |
366 | bool result = __sanitizer_symbolize_data(ModuleName: info->module, ModuleOffset: info->module_offset, |
367 | Buffer: buffer_, MaxLength: sizeof(buffer_)); |
368 | if (result) { |
369 | ParseSymbolizeDataOutput(str: buffer_, info); |
370 | info->start += (addr - info->module_offset); // Add the base address. |
371 | } |
372 | return result; |
373 | } |
374 | |
375 | bool SymbolizeFrame(uptr addr, FrameInfo *info) override { |
376 | bool result = __sanitizer_symbolize_frame(ModuleName: info->module, ModuleOffset: info->module_offset, |
377 | Buffer: buffer_, MaxLength: sizeof(buffer_)); |
378 | if (result) |
379 | ParseSymbolizeFrameOutput(str: buffer_, locals: &info->locals); |
380 | return result; |
381 | } |
382 | |
383 | void Flush() override { __sanitizer_symbolize_flush(); } |
384 | |
385 | const char *Demangle(const char *name) override { |
386 | if (__sanitizer_symbolize_demangle(Name: name, Buffer: buffer_, MaxLength: sizeof(buffer_))) { |
387 | char *res_buff = nullptr; |
388 | ExtractToken(str: buffer_, delims: "" , result: &res_buff); |
389 | return res_buff; |
390 | } |
391 | return nullptr; |
392 | } |
393 | |
394 | private: |
395 | InternalSymbolizer() {} |
396 | |
397 | char buffer_[16 * 1024]; |
398 | }; |
399 | # else // SANITIZER_SUPPORTS_WEAK_HOOKS |
400 | |
401 | class InternalSymbolizer final : public SymbolizerTool { |
402 | public: |
403 | static InternalSymbolizer *get(LowLevelAllocator *alloc) { return 0; } |
404 | }; |
405 | |
406 | # endif // SANITIZER_SUPPORTS_WEAK_HOOKS |
407 | |
408 | const char *Symbolizer::PlatformDemangle(const char *name) { |
409 | return DemangleSwiftAndCXX(name); |
410 | } |
411 | |
412 | static SymbolizerTool *ChooseExternalSymbolizer(LowLevelAllocator *allocator) { |
413 | const char *path = common_flags()->external_symbolizer_path; |
414 | |
415 | if (path && internal_strchr(s: path, c: '%')) { |
416 | char *new_path = (char *)InternalAlloc(size: kMaxPathLength); |
417 | SubstituteForFlagValue(s: path, out: new_path, out_size: kMaxPathLength); |
418 | path = new_path; |
419 | } |
420 | |
421 | const char *binary_name = path ? StripModuleName(module: path) : "" ; |
422 | static const char kLLVMSymbolizerPrefix[] = "llvm-symbolizer" ; |
423 | if (path && path[0] == '\0') { |
424 | VReport(2, "External symbolizer is explicitly disabled.\n" ); |
425 | return nullptr; |
426 | } else if (!internal_strncmp(s1: binary_name, s2: kLLVMSymbolizerPrefix, |
427 | n: internal_strlen(s: kLLVMSymbolizerPrefix))) { |
428 | VReport(2, "Using llvm-symbolizer at user-specified path: %s\n" , path); |
429 | return new (*allocator) LLVMSymbolizer(path, allocator); |
430 | } else if (!internal_strcmp(s1: binary_name, s2: "atos" )) { |
431 | # if SANITIZER_APPLE |
432 | VReport(2, "Using atos at user-specified path: %s\n" , path); |
433 | return new (*allocator) AtosSymbolizer(path, allocator); |
434 | # else // SANITIZER_APPLE |
435 | Report(format: "ERROR: Using `atos` is only supported on Darwin.\n" ); |
436 | Die(); |
437 | # endif // SANITIZER_APPLE |
438 | } else if (!internal_strcmp(s1: binary_name, s2: "addr2line" )) { |
439 | VReport(2, "Using addr2line at user-specified path: %s\n" , path); |
440 | return new (*allocator) Addr2LinePool(path, allocator); |
441 | } else if (path) { |
442 | Report( |
443 | format: "ERROR: External symbolizer path is set to '%s' which isn't " |
444 | "a known symbolizer. Please set the path to the llvm-symbolizer " |
445 | "binary or other known tool.\n" , |
446 | path); |
447 | Die(); |
448 | } |
449 | |
450 | // Otherwise symbolizer program is unknown, let's search $PATH |
451 | # ifdef SANITIZER_DISABLE_SYMBOLIZER_PATH_SEARCH |
452 | VReport(2, |
453 | "Symbolizer path search is disabled in the runtime " |
454 | "build configuration.\n" ); |
455 | return nullptr; |
456 | # else |
457 | CHECK(path == nullptr); |
458 | # if SANITIZER_APPLE |
459 | if (const char *found_path = FindPathToBinary("atos" )) { |
460 | VReport(2, "Using atos found at: %s\n" , found_path); |
461 | return new (*allocator) AtosSymbolizer(found_path, allocator); |
462 | } |
463 | # endif // SANITIZER_APPLE |
464 | if (const char *found_path = FindPathToBinary(name: "llvm-symbolizer" )) { |
465 | VReport(2, "Using llvm-symbolizer found at: %s\n" , found_path); |
466 | return new (*allocator) LLVMSymbolizer(found_path, allocator); |
467 | } |
468 | if (common_flags()->allow_addr2line) { |
469 | if (const char *found_path = FindPathToBinary(name: "addr2line" )) { |
470 | VReport(2, "Using addr2line found at: %s\n" , found_path); |
471 | return new (*allocator) Addr2LinePool(found_path, allocator); |
472 | } |
473 | } |
474 | return nullptr; |
475 | # endif // SANITIZER_DISABLE_SYMBOLIZER_PATH_SEARCH |
476 | } |
477 | |
478 | static void ChooseSymbolizerTools(IntrusiveList<SymbolizerTool> *list, |
479 | LowLevelAllocator *allocator) { |
480 | if (!common_flags()->symbolize) { |
481 | VReport(2, "Symbolizer is disabled.\n" ); |
482 | return; |
483 | } |
484 | if (common_flags()->enable_symbolizer_markup) { |
485 | VReport(2, "Using symbolizer markup" ); |
486 | SymbolizerTool *tool = new (*allocator) MarkupSymbolizerTool(); |
487 | CHECK(tool); |
488 | list->push_back(x: tool); |
489 | } |
490 | if (IsAllocatorOutOfMemory()) { |
491 | VReport(2, "Cannot use internal symbolizer: out of memory\n" ); |
492 | } else if (SymbolizerTool *tool = InternalSymbolizer::get(alloc: allocator)) { |
493 | VReport(2, "Using internal symbolizer.\n" ); |
494 | list->push_back(x: tool); |
495 | return; |
496 | } |
497 | if (SymbolizerTool *tool = LibbacktraceSymbolizer::get(alloc: allocator)) { |
498 | VReport(2, "Using libbacktrace symbolizer.\n" ); |
499 | list->push_back(x: tool); |
500 | return; |
501 | } |
502 | |
503 | if (SymbolizerTool *tool = ChooseExternalSymbolizer(allocator)) { |
504 | list->push_back(x: tool); |
505 | } |
506 | |
507 | # if SANITIZER_APPLE |
508 | VReport(2, "Using dladdr symbolizer.\n" ); |
509 | list->push_back(new (*allocator) DlAddrSymbolizer()); |
510 | # endif // SANITIZER_APPLE |
511 | } |
512 | |
513 | Symbolizer *Symbolizer::PlatformInit() { |
514 | IntrusiveList<SymbolizerTool> list; |
515 | list.clear(); |
516 | ChooseSymbolizerTools(list: &list, allocator: &symbolizer_allocator_); |
517 | return new (symbolizer_allocator_) Symbolizer(list); |
518 | } |
519 | |
520 | void Symbolizer::LateInitialize() { |
521 | Symbolizer::GetOrInit(); |
522 | InitializeSwiftDemangler(); |
523 | } |
524 | |
525 | } // namespace __sanitizer |
526 | |
527 | #endif // SANITIZER_POSIX |
528 | |