folly copyright 2015 -> copyright 2016
[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   color(kAddressColor);
349
350   AddressFormatter formatter;
351   doPrint(formatter.format(address));
352
353   const char padBuf[] = "                       ";
354   folly::StringPiece pad(padBuf,
355                          sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
356
357   color(kFunctionColor);
358   if (!frame.found) {
359     doPrint(" (not found)");
360     return;
361   }
362
363   if (!frame.name || frame.name[0] == '\0') {
364     doPrint(" (unknown)");
365   } else {
366     char demangledBuf[2048];
367     demangle(frame.name, demangledBuf, sizeof(demangledBuf));
368     doPrint(" ");
369     doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
370   }
371
372   if (!(options_ & NO_FILE_AND_LINE)) {
373     color(kFileColor);
374     char fileBuf[PATH_MAX];
375     fileBuf[0] = '\0';
376     if (frame.location.hasFileAndLine) {
377       frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
378       doPrint("\n");
379       doPrint(pad);
380       doPrint(fileBuf);
381
382       char buf[22];
383       uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
384       doPrint(":");
385       doPrint(StringPiece(buf, n));
386     }
387
388     if (frame.location.hasMainFile) {
389       char mainFileBuf[PATH_MAX];
390       mainFileBuf[0] = '\0';
391       frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
392       if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
393         doPrint("\n");
394         doPrint(pad);
395         doPrint("-> ");
396         doPrint(mainFileBuf);
397       }
398     }
399   }
400 }
401
402 void SymbolizePrinter::color(SymbolizePrinter::Color color) {
403   if ((options_ & COLOR) == 0 &&
404       ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
405     return;
406   }
407   if (color < 0 || color >= kColorMap.size()) {
408     return;
409   }
410   doPrint(kColorMap[color]);
411 }
412
413 void SymbolizePrinter::println(uintptr_t address,
414                                const SymbolizedFrame& frame) {
415   print(address, frame);
416   doPrint("\n");
417 }
418
419 void SymbolizePrinter::printTerse(uintptr_t address,
420                                   const SymbolizedFrame& frame) {
421   if (frame.found && frame.name && frame.name[0] != '\0') {
422     char demangledBuf[2048] = {0};
423     demangle(frame.name, demangledBuf, sizeof(demangledBuf));
424     doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
425   } else {
426     // Can't use sprintf, not async-signal-safe
427     static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
428     char buf[] = "0x0000000000000000";
429     char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
430     char* p = end;
431     *p-- = '\0';
432     while (address != 0) {
433       *p-- = kHexChars[address & 0xf];
434       address >>= 4;
435     }
436     doPrint(StringPiece(buf, end));
437   }
438 }
439
440 void SymbolizePrinter::println(const uintptr_t* addresses,
441                                const SymbolizedFrame* frames,
442                                size_t frameCount) {
443   for (size_t i = 0; i < frameCount; ++i) {
444     println(addresses[i], frames[i]);
445   }
446 }
447
448 namespace {
449
450 int getFD(const std::ios& stream) {
451 #ifdef __GNUC__
452   std::streambuf* buf = stream.rdbuf();
453   using namespace __gnu_cxx;
454
455   {
456     auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf);
457     if (sbuf) {
458       return fileno(sbuf->file());
459     }
460   }
461   {
462     auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf);
463     if (sbuf) {
464       return sbuf->fd();
465     }
466   }
467 #endif  // __GNUC__
468   return -1;
469 }
470
471 bool isColorfulTty(int options, int fd) {
472   if ((options & SymbolizePrinter::TERSE) != 0 ||
473       (options & SymbolizePrinter::COLOR_IF_TTY) == 0 ||
474       fd < 0 || !::isatty(fd)) {
475     return false;
476   }
477   auto term = ::getenv("TERM");
478   return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0);
479 }
480
481 }  // anonymous namespace
482
483 OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
484   : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
485     out_(out) {
486 }
487
488 void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
489   out_ << sp;
490 }
491
492 FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize)
493   : SymbolizePrinter(options, isColorfulTty(options, fd)),
494     fd_(fd),
495     buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {
496 }
497
498 FDSymbolizePrinter::~FDSymbolizePrinter() {
499   flush();
500 }
501
502 void FDSymbolizePrinter::doPrint(StringPiece sp) {
503   if (buffer_) {
504     if (sp.size() > buffer_->tailroom()) {
505       flush();
506       writeFull(fd_, sp.data(), sp.size());
507     } else {
508       memcpy(buffer_->writableTail(), sp.data(), sp.size());
509       buffer_->append(sp.size());
510     }
511   } else {
512     writeFull(fd_, sp.data(), sp.size());
513   }
514 }
515
516 void FDSymbolizePrinter::flush() {
517   if (buffer_ && !buffer_->empty()) {
518     writeFull(fd_, buffer_->data(), buffer_->length());
519     buffer_->clear();
520   }
521 }
522
523 FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
524   : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
525     file_(file) {
526 }
527
528 void FILESymbolizePrinter::doPrint(StringPiece sp) {
529   fwrite(sp.data(), 1, sp.size(), file_);
530 }
531
532 void StringSymbolizePrinter::doPrint(StringPiece sp) {
533   buf_.append(sp.data(), sp.size());
534 }
535
536 }  // namespace symbolizer
537 }  // namespace folly