Allow usage of Symbolizer options for ExceptionStats printing
[folly.git] / folly / experimental / symbolizer / Symbolizer.cpp
1 /*
2  * Copyright 2016 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 <cstdio>
20 #include <cstdlib>
21 #include <iostream>
22 #include <limits.h>
23 #include <unistd.h>
24
25 #ifdef __GNUC__
26 #include <ext/stdio_filebuf.h>
27 #include <ext/stdio_sync_filebuf.h>
28 #endif
29
30 #include <folly/Conv.h>
31 #include <folly/FileUtil.h>
32 #include <folly/ScopeGuard.h>
33 #include <folly/String.h>
34
35 #include <folly/experimental/symbolizer/Elf.h>
36 #include <folly/experimental/symbolizer/Dwarf.h>
37 #include <folly/experimental/symbolizer/LineReader.h>
38
39
40 namespace folly {
41 namespace symbolizer {
42
43 namespace {
44
45 /**
46  * Read a hex value.
47  */
48 uintptr_t readHex(StringPiece& sp) {
49   uintptr_t val = 0;
50   const char* p = sp.begin();
51   for (; p != sp.end(); ++p) {
52     unsigned int v;
53     if (*p >= '0' && *p <= '9') {
54       v = (*p - '0');
55     } else if (*p >= 'a' && *p <= 'f') {
56       v = (*p - 'a') + 10;
57     } else if (*p >= 'A' && *p <= 'F') {
58       v = (*p - 'A') + 10;
59     } else {
60       break;
61     }
62     val = (val << 4) + v;
63   }
64   sp.assign(p, sp.end());
65   return val;
66 }
67
68 /**
69  * Skip over non-space characters.
70  */
71 void skipNS(StringPiece& sp) {
72   const char* p = sp.begin();
73   for (; p != sp.end() && (*p != ' ' && *p != '\t'); ++p) { }
74   sp.assign(p, sp.end());
75 }
76
77 /**
78  * Skip over space and tab characters.
79  */
80 void skipWS(StringPiece& sp) {
81   const char* p = sp.begin();
82   for (; p != sp.end() && (*p == ' ' || *p == '\t'); ++p) { }
83   sp.assign(p, sp.end());
84 }
85
86 /**
87  * Parse a line from /proc/self/maps
88  */
89 bool parseProcMapsLine(StringPiece line,
90                        uintptr_t& from,
91                        uintptr_t& to,
92                        uintptr_t& fileOff,
93                        bool& isSelf,
94                        StringPiece& fileName) {
95   isSelf = false;
96   // from     to       perm offset   dev   inode             path
97   // 00400000-00405000 r-xp 00000000 08:03 35291182          /bin/cat
98   if (line.empty()) {
99     return false;
100   }
101
102   // Remove trailing newline, if any
103   if (line.back() == '\n') {
104     line.pop_back();
105   }
106
107   // from
108   from = readHex(line);
109   if (line.empty() || line.front() != '-') {
110     return false;
111   }
112   line.pop_front();
113
114   // to
115   to = readHex(line);
116   if (line.empty() || line.front() != ' ') {
117     return false;
118   }
119   line.pop_front();
120
121   // perms
122   skipNS(line);
123   if (line.empty() || line.front() != ' ') {
124     return false;
125   }
126   line.pop_front();
127
128   uintptr_t fileOffset = readHex(line);
129   if (line.empty() || line.front() != ' ') {
130     return false;
131   }
132   line.pop_front();
133   // main mapping starts at 0 but there can be multi-segment binary
134   // such as
135   // from     to       perm offset   dev   inode             path
136   // 00400000-00405000 r-xp 00000000 08:03 54011424          /bin/foo
137   // 00600000-00605000 r-xp 00020000 08:03 54011424          /bin/foo
138   // 00800000-00805000 r-xp 00040000 08:03 54011424          /bin/foo
139   // if the offset > 0, this indicates to the caller that the baseAddress
140   // need to be used for undo relocation step.
141   fileOff = fileOffset;
142
143   // dev
144   skipNS(line);
145   if (line.empty() || line.front() != ' ') {
146     return false;
147   }
148   line.pop_front();
149
150   // inode
151   skipNS(line);
152   if (line.empty() || line.front() != ' ') {
153     return false;
154   }
155
156   // if inode is 0, such as in case of ANON pages, there should be atleast
157   // one white space before EOL
158   skipWS(line);
159   if (line.empty()) {
160     // There will be no fileName for ANON text pages
161     // if the parsing came this far without a fileName, then from/to address
162     // may contain text in ANON pages.
163     isSelf = true;
164     fileName.clear();
165     return true;
166   }
167
168   fileName = line;
169   return true;
170 }
171
172 ElfCache* defaultElfCache() {
173   static constexpr size_t defaultCapacity = 500;
174   static ElfCache cache(defaultCapacity);
175   return &cache;
176 }
177
178 }  // namespace
179
180 void SymbolizedFrame::set(const std::shared_ptr<ElfFile>& file,
181                           uintptr_t address) {
182   clear();
183   found = true;
184
185   address += file->getBaseAddress();
186   auto sym = file->getDefinitionByAddress(address);
187   if (!sym.first) {
188     return;
189   }
190
191   file_ = file;
192   name = file->getSymbolName(sym);
193
194   Dwarf(file.get()).findAddress(address, location);
195 }
196
197
198 Symbolizer::Symbolizer(ElfCacheBase* cache)
199   : cache_(cache ?: defaultElfCache()) {
200 }
201
202 void Symbolizer::symbolize(const uintptr_t* addresses,
203                            SymbolizedFrame* frames,
204                            size_t addressCount) {
205   size_t remaining = 0;
206   for (size_t i = 0; i < addressCount; ++i) {
207     auto& frame = frames[i];
208     if (!frame.found) {
209       ++remaining;
210       frame.clear();
211     }
212   }
213
214   if (remaining == 0) {  // we're done
215     return;
216   }
217
218   int fd = openNoInt("/proc/self/maps", O_RDONLY);
219   if (fd == -1) {
220     return;
221   }
222
223   char selfFile[PATH_MAX + 8];
224   ssize_t selfSize;
225   if ((selfSize = readlink("/proc/self/exe", selfFile, PATH_MAX + 1)) == -1) {
226     // something terribly wrong
227     return;
228   }
229   selfFile[selfSize] = '\0';
230
231   char buf[PATH_MAX + 100];  // Long enough for any line
232   LineReader reader(fd, buf, sizeof(buf));
233
234   while (remaining != 0) {
235     StringPiece line;
236     if (reader.readLine(line) != LineReader::kReading) {
237       break;
238     }
239
240     // Parse line
241     uintptr_t from;
242     uintptr_t to;
243     uintptr_t fileOff;
244     uintptr_t base;
245     bool isSelf = false; // fileName can potentially be '/proc/self/exe'
246     StringPiece fileName;
247     if (!parseProcMapsLine(line, from, to, fileOff, isSelf, fileName)) {
248       continue;
249     }
250
251     base = from;
252     bool first = true;
253     std::shared_ptr<ElfFile> elfFile;
254
255     // case of text on ANON?
256     // Recompute from/to/base from the executable
257     if (isSelf && fileName.empty()) {
258       elfFile = cache_->getFile(selfFile);
259
260       if (elfFile != nullptr) {
261         auto textSection = elfFile->getSectionByName(".text");
262         base = elfFile->getBaseAddress();
263         from = textSection->sh_addr;
264         to = from + textSection->sh_size;
265         fileName = selfFile;
266         first = false; // no need to get this file again from the cache
267       }
268     }
269
270     // See if any addresses are here
271     for (size_t i = 0; i < addressCount; ++i) {
272       auto& frame = frames[i];
273       if (frame.found) {
274         continue;
275       }
276
277       uintptr_t address = addresses[i];
278
279       if (from > address || address >= to) {
280         continue;
281       }
282
283       // Found
284       frame.found = true;
285       --remaining;
286
287       // Open the file on first use
288       if (first) {
289         first = false;
290         elfFile = cache_->getFile(fileName);
291
292         // Need to get the correct base address as from
293         // when fileOff > 0
294         if (fileOff && elfFile != nullptr) {
295           base = elfFile->getBaseAddress();
296         }
297       }
298
299       if (!elfFile) {
300         continue;
301       }
302
303       // Undo relocation
304       frame.set(elfFile, address - base);
305     }
306   }
307
308   closeNoInt(fd);
309 }
310
311 namespace {
312 constexpr char kHexChars[] = "0123456789abcdef";
313 constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE;
314 constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE;
315 constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT;
316 }  // namespace
317
318 constexpr char AddressFormatter::bufTemplate[];
319 constexpr std::array<const char*, SymbolizePrinter::Color::NUM>
320     SymbolizePrinter::kColorMap;
321
322 AddressFormatter::AddressFormatter() {
323   memcpy(buf_, bufTemplate, sizeof(buf_));
324 }
325
326 folly::StringPiece AddressFormatter::format(uintptr_t address) {
327   // Can't use sprintf, not async-signal-safe
328   static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
329   char* end = buf_ + sizeof(buf_) - 1 - (16 - 2 * sizeof(uintptr_t));
330   char* p = end;
331   *p-- = '\0';
332   while (address != 0) {
333     *p-- = kHexChars[address & 0xf];
334     address >>= 4;
335   }
336
337   return folly::StringPiece(buf_, end);
338 }
339
340 void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
341   if (options_ & TERSE) {
342     printTerse(address, frame);
343     return;
344   }
345
346   SCOPE_EXIT { color(Color::DEFAULT); };
347
348   if (!(options_ & NO_FRAME_ADDRESS)) {
349     color(kAddressColor);
350
351     AddressFormatter formatter;
352     doPrint(formatter.format(address));
353   }
354
355   const char padBuf[] = "                       ";
356   folly::StringPiece pad(padBuf,
357                          sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
358
359   color(kFunctionColor);
360   if (!frame.found) {
361     doPrint(" (not found)");
362     return;
363   }
364
365   if (!frame.name || frame.name[0] == '\0') {
366     doPrint(" (unknown)");
367   } else {
368     char demangledBuf[2048];
369     demangle(frame.name, demangledBuf, sizeof(demangledBuf));
370     doPrint(" ");
371     doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
372   }
373
374   if (!(options_ & NO_FILE_AND_LINE)) {
375     color(kFileColor);
376     char fileBuf[PATH_MAX];
377     fileBuf[0] = '\0';
378     if (frame.location.hasFileAndLine) {
379       frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
380       doPrint("\n");
381       doPrint(pad);
382       doPrint(fileBuf);
383
384       char buf[22];
385       uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
386       doPrint(":");
387       doPrint(StringPiece(buf, n));
388     }
389
390     if (frame.location.hasMainFile) {
391       char mainFileBuf[PATH_MAX];
392       mainFileBuf[0] = '\0';
393       frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
394       if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
395         doPrint("\n");
396         doPrint(pad);
397         doPrint("-> ");
398         doPrint(mainFileBuf);
399       }
400     }
401   }
402 }
403
404 void SymbolizePrinter::color(SymbolizePrinter::Color color) {
405   if ((options_ & COLOR) == 0 &&
406       ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
407     return;
408   }
409   if (color < 0 || color >= kColorMap.size()) {
410     return;
411   }
412   doPrint(kColorMap[color]);
413 }
414
415 void SymbolizePrinter::println(uintptr_t address,
416                                const SymbolizedFrame& frame) {
417   print(address, frame);
418   doPrint("\n");
419 }
420
421 void SymbolizePrinter::printTerse(uintptr_t address,
422                                   const SymbolizedFrame& frame) {
423   if (frame.found && frame.name && frame.name[0] != '\0') {
424     char demangledBuf[2048] = {0};
425     demangle(frame.name, demangledBuf, sizeof(demangledBuf));
426     doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
427   } else {
428     // Can't use sprintf, not async-signal-safe
429     static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
430     char buf[] = "0x0000000000000000";
431     char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
432     char* p = end;
433     *p-- = '\0';
434     while (address != 0) {
435       *p-- = kHexChars[address & 0xf];
436       address >>= 4;
437     }
438     doPrint(StringPiece(buf, end));
439   }
440 }
441
442 void SymbolizePrinter::println(const uintptr_t* addresses,
443                                const SymbolizedFrame* frames,
444                                size_t frameCount) {
445   for (size_t i = 0; i < frameCount; ++i) {
446     println(addresses[i], frames[i]);
447   }
448 }
449
450 namespace {
451
452 int getFD(const std::ios& stream) {
453 #ifdef __GNUC__
454   std::streambuf* buf = stream.rdbuf();
455   using namespace __gnu_cxx;
456
457   {
458     auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf);
459     if (sbuf) {
460       return fileno(sbuf->file());
461     }
462   }
463   {
464     auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf);
465     if (sbuf) {
466       return sbuf->fd();
467     }
468   }
469 #endif  // __GNUC__
470   return -1;
471 }
472
473 bool isColorfulTty(int options, int fd) {
474   if ((options & SymbolizePrinter::TERSE) != 0 ||
475       (options & SymbolizePrinter::COLOR_IF_TTY) == 0 ||
476       fd < 0 || !::isatty(fd)) {
477     return false;
478   }
479   auto term = ::getenv("TERM");
480   return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0);
481 }
482
483 }  // anonymous namespace
484
485 OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
486   : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
487     out_(out) {
488 }
489
490 void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
491   out_ << sp;
492 }
493
494 FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize)
495   : SymbolizePrinter(options, isColorfulTty(options, fd)),
496     fd_(fd),
497     buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {
498 }
499
500 FDSymbolizePrinter::~FDSymbolizePrinter() {
501   flush();
502 }
503
504 void FDSymbolizePrinter::doPrint(StringPiece sp) {
505   if (buffer_) {
506     if (sp.size() > buffer_->tailroom()) {
507       flush();
508       writeFull(fd_, sp.data(), sp.size());
509     } else {
510       memcpy(buffer_->writableTail(), sp.data(), sp.size());
511       buffer_->append(sp.size());
512     }
513   } else {
514     writeFull(fd_, sp.data(), sp.size());
515   }
516 }
517
518 void FDSymbolizePrinter::flush() {
519   if (buffer_ && !buffer_->empty()) {
520     writeFull(fd_, buffer_->data(), buffer_->length());
521     buffer_->clear();
522   }
523 }
524
525 FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
526   : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
527     file_(file) {
528 }
529
530 void FILESymbolizePrinter::doPrint(StringPiece sp) {
531   fwrite(sp.data(), 1, sp.size(), file_);
532 }
533
534 void StringSymbolizePrinter::doPrint(StringPiece sp) {
535   buf_.append(sp.data(), sp.size());
536 }
537
538 }  // namespace symbolizer
539 }  // namespace folly