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