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