1 | /* |
2 | * Copyright 2012-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/experimental/symbolizer/Symbolizer.h> |
18 | |
19 | #include <link.h> |
20 | #include <ucontext.h> |
21 | |
22 | #include <climits> |
23 | #include <cstdio> |
24 | #include <cstdlib> |
25 | #include <iostream> |
26 | |
27 | #ifdef __GLIBCXX__ |
28 | #include <ext/stdio_filebuf.h> |
29 | #include <ext/stdio_sync_filebuf.h> |
30 | #endif |
31 | |
32 | #include <folly/Conv.h> |
33 | #include <folly/FileUtil.h> |
34 | #include <folly/Memory.h> |
35 | #include <folly/ScopeGuard.h> |
36 | #include <folly/String.h> |
37 | |
38 | #include <folly/experimental/symbolizer/Dwarf.h> |
39 | #include <folly/experimental/symbolizer/Elf.h> |
40 | #include <folly/experimental/symbolizer/LineReader.h> |
41 | #include <folly/portability/SysMman.h> |
42 | #include <folly/portability/Unistd.h> |
43 | |
44 | /* |
45 | * This is declared in `link.h' on Linux platforms, but apparently not on the |
46 | * Mac version of the file. It's harmless to declare again, in any case. |
47 | * |
48 | * Note that declaring it with `extern "C"` results in linkage conflicts. |
49 | */ |
50 | extern struct r_debug _r_debug; |
51 | |
52 | namespace folly { |
53 | namespace symbolizer { |
54 | |
55 | namespace { |
56 | |
57 | ElfCache* defaultElfCache() { |
58 | static constexpr size_t defaultCapacity = 500; |
59 | static auto cache = new ElfCache(defaultCapacity); |
60 | return cache; |
61 | } |
62 | |
63 | } // namespace |
64 | |
65 | void SymbolizedFrame::set( |
66 | const std::shared_ptr<ElfFile>& file, |
67 | uintptr_t address, |
68 | Dwarf::LocationInfoMode mode) { |
69 | clear(); |
70 | found = true; |
71 | |
72 | address += file->getBaseAddress(); |
73 | auto sym = file->getDefinitionByAddress(address); |
74 | if (!sym.first) { |
75 | return; |
76 | } |
77 | |
78 | file_ = file; |
79 | name = file->getSymbolName(sym); |
80 | |
81 | Dwarf(file.get()).findAddress(address, location, mode); |
82 | } |
83 | |
84 | Symbolizer::Symbolizer( |
85 | ElfCacheBase* cache, |
86 | Dwarf::LocationInfoMode mode, |
87 | size_t symbolCacheSize) |
88 | : cache_(cache ? cache : defaultElfCache()), mode_(mode) { |
89 | if (symbolCacheSize > 0) { |
90 | symbolCache_.emplace(folly::in_place, symbolCacheSize); |
91 | } |
92 | } |
93 | |
94 | void Symbolizer::symbolize( |
95 | const uintptr_t* addresses, |
96 | SymbolizedFrame* frames, |
97 | size_t addrCount) { |
98 | size_t remaining = 0; |
99 | for (size_t i = 0; i < addrCount; ++i) { |
100 | auto& frame = frames[i]; |
101 | if (!frame.found) { |
102 | ++remaining; |
103 | frame.clear(); |
104 | } |
105 | } |
106 | |
107 | if (remaining == 0) { // we're done |
108 | return; |
109 | } |
110 | |
111 | if (_r_debug.r_version != 1) { |
112 | return; |
113 | } |
114 | |
115 | char selfPath[PATH_MAX + 8]; |
116 | ssize_t selfSize; |
117 | if ((selfSize = readlink("/proc/self/exe" , selfPath, PATH_MAX + 1)) == -1) { |
118 | // Something has gone terribly wrong. |
119 | return; |
120 | } |
121 | selfPath[selfSize] = '\0'; |
122 | |
123 | for (auto lmap = _r_debug.r_map; lmap != nullptr && remaining != 0; |
124 | lmap = lmap->l_next) { |
125 | // The empty string is used in place of the filename for the link_map |
126 | // corresponding to the running executable. Additionally, the `l_addr' is |
127 | // 0 and the link_map appears to be first in the list---but none of this |
128 | // behavior appears to be documented, so checking for the empty string is |
129 | // as good as anything. |
130 | auto const objPath = lmap->l_name[0] != '\0' ? lmap->l_name : selfPath; |
131 | |
132 | auto const elfFile = cache_->getFile(objPath); |
133 | if (!elfFile) { |
134 | continue; |
135 | } |
136 | |
137 | // Get the address at which the object is loaded. We have to use the ELF |
138 | // header for the running executable, since its `l_addr' is zero, but we |
139 | // should use `l_addr' for everything else---in particular, if the object |
140 | // is position-independent, getBaseAddress() (which is p_vaddr) will be 0. |
141 | auto const base = |
142 | lmap->l_addr != 0 ? lmap->l_addr : elfFile->getBaseAddress(); |
143 | |
144 | for (size_t i = 0; i < addrCount && remaining != 0; ++i) { |
145 | auto& frame = frames[i]; |
146 | if (frame.found) { |
147 | continue; |
148 | } |
149 | |
150 | auto const addr = addresses[i]; |
151 | if (symbolCache_) { |
152 | // Need a write lock, because EvictingCacheMap brings found item to |
153 | // front of eviction list. |
154 | auto lockedSymbolCache = symbolCache_->wlock(); |
155 | |
156 | auto const iter = lockedSymbolCache->find(addr); |
157 | if (iter != lockedSymbolCache->end()) { |
158 | frame = iter->second; |
159 | continue; |
160 | } |
161 | } |
162 | |
163 | // Get the unrelocated, ELF-relative address. |
164 | auto const adjusted = addr - base; |
165 | |
166 | if (elfFile->getSectionContainingAddress(adjusted)) { |
167 | frame.set(elfFile, adjusted, mode_); |
168 | --remaining; |
169 | if (symbolCache_) { |
170 | // frame may already have been set here. That's ok, we'll just |
171 | // overwrite, which doesn't cause a correctness problem. |
172 | symbolCache_->wlock()->set(addr, frame); |
173 | } |
174 | } |
175 | } |
176 | } |
177 | } |
178 | |
179 | namespace { |
180 | constexpr char kHexChars[] = "0123456789abcdef" ; |
181 | constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE; |
182 | constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE; |
183 | constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT; |
184 | } // namespace |
185 | |
186 | constexpr char AddressFormatter::bufTemplate[]; |
187 | constexpr std::array<const char*, SymbolizePrinter::Color::NUM> |
188 | SymbolizePrinter::kColorMap; |
189 | |
190 | AddressFormatter::AddressFormatter() { |
191 | memcpy(buf_, bufTemplate, sizeof(buf_)); |
192 | } |
193 | |
194 | folly::StringPiece AddressFormatter::format(uintptr_t address) { |
195 | // Can't use sprintf, not async-signal-safe |
196 | static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?" ); |
197 | char* end = buf_ + sizeof(buf_) - 1 - (16 - 2 * sizeof(uintptr_t)); |
198 | char* p = end; |
199 | *p-- = '\0'; |
200 | while (address != 0) { |
201 | *p-- = kHexChars[address & 0xf]; |
202 | address >>= 4; |
203 | } |
204 | |
205 | return folly::StringPiece(buf_, end); |
206 | } |
207 | |
208 | void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) { |
209 | if (options_ & TERSE) { |
210 | printTerse(address, frame); |
211 | return; |
212 | } |
213 | |
214 | SCOPE_EXIT { |
215 | color(Color::DEFAULT); |
216 | }; |
217 | |
218 | if (!(options_ & NO_FRAME_ADDRESS)) { |
219 | color(kAddressColor); |
220 | |
221 | AddressFormatter formatter; |
222 | doPrint(formatter.format(address)); |
223 | } |
224 | |
225 | const char padBuf[] = " " ; |
226 | folly::StringPiece pad( |
227 | padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t))); |
228 | |
229 | color(kFunctionColor); |
230 | if (!frame.found) { |
231 | doPrint(" (not found)" ); |
232 | return; |
233 | } |
234 | |
235 | if (!frame.name || frame.name[0] == '\0') { |
236 | doPrint(" (unknown)" ); |
237 | } else { |
238 | char demangledBuf[2048]; |
239 | demangle(frame.name, demangledBuf, sizeof(demangledBuf)); |
240 | doPrint(" " ); |
241 | doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf); |
242 | } |
243 | |
244 | if (!(options_ & NO_FILE_AND_LINE)) { |
245 | color(kFileColor); |
246 | char fileBuf[PATH_MAX]; |
247 | fileBuf[0] = '\0'; |
248 | if (frame.location.hasFileAndLine) { |
249 | frame.location.file.toBuffer(fileBuf, sizeof(fileBuf)); |
250 | doPrint("\n" ); |
251 | doPrint(pad); |
252 | doPrint(fileBuf); |
253 | |
254 | char buf[22]; |
255 | uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf); |
256 | doPrint(":" ); |
257 | doPrint(StringPiece(buf, n)); |
258 | } |
259 | |
260 | if (frame.location.hasMainFile) { |
261 | char mainFileBuf[PATH_MAX]; |
262 | mainFileBuf[0] = '\0'; |
263 | frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf)); |
264 | if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) { |
265 | doPrint("\n" ); |
266 | doPrint(pad); |
267 | doPrint("-> " ); |
268 | doPrint(mainFileBuf); |
269 | } |
270 | } |
271 | } |
272 | } |
273 | |
274 | void SymbolizePrinter::color(SymbolizePrinter::Color color) { |
275 | if ((options_ & COLOR) == 0 && ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) { |
276 | return; |
277 | } |
278 | if (static_cast<size_t>(color) >= kColorMap.size()) { // catches underflow too |
279 | return; |
280 | } |
281 | doPrint(kColorMap[color]); |
282 | } |
283 | |
284 | void SymbolizePrinter::println( |
285 | uintptr_t address, |
286 | const SymbolizedFrame& frame) { |
287 | print(address, frame); |
288 | doPrint("\n" ); |
289 | } |
290 | |
291 | void SymbolizePrinter::printTerse( |
292 | uintptr_t address, |
293 | const SymbolizedFrame& frame) { |
294 | if (frame.found && frame.name && frame.name[0] != '\0') { |
295 | char demangledBuf[2048] = {0}; |
296 | demangle(frame.name, demangledBuf, sizeof(demangledBuf)); |
297 | doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf); |
298 | } else { |
299 | // Can't use sprintf, not async-signal-safe |
300 | static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?" ); |
301 | char buf[] = "0x0000000000000000" ; |
302 | char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t)); |
303 | char* p = end; |
304 | *p-- = '\0'; |
305 | while (address != 0) { |
306 | *p-- = kHexChars[address & 0xf]; |
307 | address >>= 4; |
308 | } |
309 | doPrint(StringPiece(buf, end)); |
310 | } |
311 | } |
312 | |
313 | void SymbolizePrinter::println( |
314 | const uintptr_t* addresses, |
315 | const SymbolizedFrame* frames, |
316 | size_t frameCount) { |
317 | for (size_t i = 0; i < frameCount; ++i) { |
318 | println(addresses[i], frames[i]); |
319 | } |
320 | } |
321 | |
322 | namespace { |
323 | |
324 | int getFD(const std::ios& stream) { |
325 | #if defined(__GNUC__) && FOLLY_HAS_RTTI |
326 | std::streambuf* buf = stream.rdbuf(); |
327 | using namespace __gnu_cxx; |
328 | |
329 | { |
330 | auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf); |
331 | if (sbuf) { |
332 | return fileno(sbuf->file()); |
333 | } |
334 | } |
335 | { |
336 | auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf); |
337 | if (sbuf) { |
338 | return sbuf->fd(); |
339 | } |
340 | } |
341 | #else |
342 | (void)stream; |
343 | #endif // __GNUC__ |
344 | return -1; |
345 | } |
346 | |
347 | bool isColorfulTty(int options, int fd) { |
348 | if ((options & SymbolizePrinter::TERSE) != 0 || |
349 | (options & SymbolizePrinter::COLOR_IF_TTY) == 0 || fd < 0 || |
350 | !::isatty(fd)) { |
351 | return false; |
352 | } |
353 | auto term = ::getenv("TERM" ); |
354 | return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb" ) == 0); |
355 | } |
356 | |
357 | } // namespace |
358 | |
359 | OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options) |
360 | : SymbolizePrinter(options, isColorfulTty(options, getFD(out))), |
361 | out_(out) {} |
362 | |
363 | void OStreamSymbolizePrinter::doPrint(StringPiece sp) { |
364 | out_ << sp; |
365 | } |
366 | |
367 | FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize) |
368 | : SymbolizePrinter(options, isColorfulTty(options, fd)), |
369 | fd_(fd), |
370 | buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {} |
371 | |
372 | FDSymbolizePrinter::~FDSymbolizePrinter() { |
373 | flush(); |
374 | } |
375 | |
376 | void FDSymbolizePrinter::doPrint(StringPiece sp) { |
377 | if (buffer_) { |
378 | if (sp.size() > buffer_->tailroom()) { |
379 | flush(); |
380 | writeFull(fd_, sp.data(), sp.size()); |
381 | } else { |
382 | memcpy(buffer_->writableTail(), sp.data(), sp.size()); |
383 | buffer_->append(sp.size()); |
384 | } |
385 | } else { |
386 | writeFull(fd_, sp.data(), sp.size()); |
387 | } |
388 | } |
389 | |
390 | void FDSymbolizePrinter::flush() { |
391 | if (buffer_ && !buffer_->empty()) { |
392 | writeFull(fd_, buffer_->data(), buffer_->length()); |
393 | buffer_->clear(); |
394 | } |
395 | } |
396 | |
397 | FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options) |
398 | : SymbolizePrinter(options, isColorfulTty(options, fileno(file))), |
399 | file_(file) {} |
400 | |
401 | void FILESymbolizePrinter::doPrint(StringPiece sp) { |
402 | fwrite(sp.data(), 1, sp.size(), file_); |
403 | } |
404 | |
405 | void StringSymbolizePrinter::doPrint(StringPiece sp) { |
406 | buf_.append(sp.data(), sp.size()); |
407 | } |
408 | |
409 | SafeStackTracePrinter::SafeStackTracePrinter( |
410 | size_t minSignalSafeElfCacheSize, |
411 | int fd) |
412 | : fd_(fd), |
413 | elfCache_(std::max(countLoadedElfFiles(), minSignalSafeElfCacheSize)), |
414 | printer_( |
415 | fd, |
416 | SymbolizePrinter::COLOR_IF_TTY, |
417 | size_t(64) << 10), // 64KiB |
418 | addresses_(std::make_unique<FrameArray<kMaxStackTraceDepth>>()) {} |
419 | |
420 | void SafeStackTracePrinter::flush() { |
421 | printer_.flush(); |
422 | fsyncNoInt(fd_); |
423 | } |
424 | |
425 | void SafeStackTracePrinter::printSymbolizedStackTrace() { |
426 | // This function might run on an alternative stack allocated by |
427 | // UnsafeSelfAllocateStackTracePrinter. Capturing a stack from |
428 | // here is probably wrong. |
429 | |
430 | // Do our best to populate location info, process is going to terminate, |
431 | // so performance isn't critical. |
432 | Symbolizer symbolizer(&elfCache_, Dwarf::LocationInfoMode::FULL); |
433 | symbolizer.symbolize(*addresses_); |
434 | |
435 | // Skip the top 2 frames captured by printStackTrace: |
436 | // getStackTraceSafe |
437 | // SafeStackTracePrinter::printStackTrace (captured stack) |
438 | // |
439 | // Leaving signalHandler on the stack for clarity, I think. |
440 | printer_.println(*addresses_, 2); |
441 | } |
442 | |
443 | void SafeStackTracePrinter::printStackTrace(bool symbolize) { |
444 | SCOPE_EXIT { |
445 | flush(); |
446 | }; |
447 | |
448 | // Skip the getStackTrace frame |
449 | if (!getStackTraceSafe(*addresses_)) { |
450 | print("(error retrieving stack trace)\n" ); |
451 | } else if (symbolize) { |
452 | printSymbolizedStackTrace(); |
453 | } else { |
454 | print("(safe mode, symbolizer not available)\n" ); |
455 | AddressFormatter formatter; |
456 | for (size_t i = 0; i < addresses_->frameCount; ++i) { |
457 | print(formatter.format(addresses_->addresses[i])); |
458 | print("\n" ); |
459 | } |
460 | } |
461 | } |
462 | |
463 | FastStackTracePrinter::FastStackTracePrinter( |
464 | std::unique_ptr<SymbolizePrinter> printer, |
465 | size_t elfCacheSize, |
466 | size_t symbolCacheSize) |
467 | : elfCache_( |
468 | elfCacheSize == 0 |
469 | ? nullptr |
470 | : new ElfCache{std::max(countLoadedElfFiles(), elfCacheSize)}), |
471 | printer_(std::move(printer)), |
472 | symbolizer_( |
473 | elfCache_ ? elfCache_.get() : defaultElfCache(), |
474 | Dwarf::LocationInfoMode::FULL, |
475 | symbolCacheSize) {} |
476 | |
477 | FastStackTracePrinter::~FastStackTracePrinter() {} |
478 | |
479 | void FastStackTracePrinter::printStackTrace(bool symbolize) { |
480 | SCOPE_EXIT { |
481 | printer_->flush(); |
482 | }; |
483 | |
484 | FrameArray<kMaxStackTraceDepth> addresses; |
485 | |
486 | if (!getStackTraceSafe(addresses)) { |
487 | printer_->print("(error retrieving stack trace)\n" ); |
488 | } else if (symbolize) { |
489 | symbolizer_.symbolize(addresses); |
490 | |
491 | // Skip the top 2 frames: |
492 | // getStackTraceSafe |
493 | // FastStackTracePrinter::printStackTrace (here) |
494 | printer_->println(addresses, 2); |
495 | } else { |
496 | printer_->print("(safe mode, symbolizer not available)\n" ); |
497 | AddressFormatter formatter; |
498 | for (size_t i = 0; i < addresses.frameCount; ++i) { |
499 | printer_->print(formatter.format(addresses.addresses[i])); |
500 | printer_->print("\n" ); |
501 | } |
502 | } |
503 | } |
504 | |
505 | void FastStackTracePrinter::flush() { |
506 | printer_->flush(); |
507 | } |
508 | |
509 | // Stack utilities used by UnsafeSelfAllocateStackTracePrinter |
510 | namespace { |
511 | // Size of mmap-allocated stack. Not to confuse with sigaltstack. |
512 | const size_t kMmapStackSize = 1 * 1024 * 1024; |
513 | |
514 | using MmapPtr = std::unique_ptr<char, void (*)(char*)>; |
515 | |
516 | MmapPtr getNull() { |
517 | return MmapPtr(nullptr, [](char*) {}); |
518 | } |
519 | |
520 | // Assign a mmap-allocated stack to oucp. |
521 | // Return a non-empty smart pointer on success. |
522 | MmapPtr allocateStack(ucontext_t* oucp, size_t pageSize) { |
523 | MmapPtr p( |
524 | (char*)mmap( |
525 | nullptr, |
526 | kMmapStackSize, |
527 | PROT_WRITE | PROT_READ, |
528 | MAP_ANONYMOUS | MAP_PRIVATE, |
529 | /* fd */ -1, |
530 | /* offset */ 0), |
531 | [](char* addr) { |
532 | // Usually runs inside a fatal signal handler. |
533 | // Error handling is skipped. |
534 | munmap(addr, kMmapStackSize); |
535 | }); |
536 | |
537 | if (!p) { |
538 | return getNull(); |
539 | } |
540 | |
541 | // Prepare read-only guard pages on both ends |
542 | if (pageSize * 2 >= kMmapStackSize) { |
543 | return getNull(); |
544 | } |
545 | size_t upperBound = ((kMmapStackSize - 1) / pageSize) * pageSize; |
546 | if (mprotect(p.get(), pageSize, PROT_NONE) != 0) { |
547 | return getNull(); |
548 | } |
549 | if (mprotect(p.get() + upperBound, kMmapStackSize - upperBound, PROT_NONE) != |
550 | 0) { |
551 | return getNull(); |
552 | } |
553 | |
554 | oucp->uc_stack.ss_sp = p.get() + pageSize; |
555 | oucp->uc_stack.ss_size = upperBound - pageSize; |
556 | oucp->uc_stack.ss_flags = 0; |
557 | |
558 | return p; |
559 | } |
560 | |
561 | } // namespace |
562 | |
563 | void UnsafeSelfAllocateStackTracePrinter::printSymbolizedStackTrace() { |
564 | if (pageSizeUnchecked_ <= 0) { |
565 | return; |
566 | } |
567 | |
568 | ucontext_t cur; |
569 | memset(&cur, 0, sizeof(cur)); |
570 | ucontext_t alt; |
571 | memset(&alt, 0, sizeof(alt)); |
572 | |
573 | if (getcontext(&alt) != 0) { |
574 | return; |
575 | } |
576 | alt.uc_link = &cur; |
577 | |
578 | MmapPtr p = allocateStack(&alt, (size_t)pageSizeUnchecked_); |
579 | if (!p) { |
580 | return; |
581 | } |
582 | |
583 | auto contextStart = [](UnsafeSelfAllocateStackTracePrinter* that) { |
584 | that->SafeStackTracePrinter::printSymbolizedStackTrace(); |
585 | }; |
586 | |
587 | makecontext( |
588 | &alt, |
589 | (void (*)())(void (*)(UnsafeSelfAllocateStackTracePrinter*))( |
590 | contextStart), |
591 | /* argc */ 1, |
592 | /* arg */ this); |
593 | // NOTE: swapcontext is not async-signal-safe |
594 | if (swapcontext(&cur, &alt) != 0) { |
595 | return; |
596 | } |
597 | } |
598 | |
599 | } // namespace symbolizer |
600 | } // namespace folly |
601 | |