Print (2 more) if stack trace truncated
[folly.git] / folly / experimental / symbolizer / Symbolizer.cpp
1 /*
2  * Copyright 2013 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 // Must be first to ensure that UNW_LOCAL_ONLY is defined
18 #define UNW_LOCAL_ONLY 1
19 #include <libunwind.h>
20
21 #include "folly/experimental/symbolizer/Symbolizer.h"
22
23 #include <limits.h>
24
25 #include "folly/Conv.h"
26 #include "folly/FileUtil.h"
27 #include "folly/String.h"
28
29 #include "folly/experimental/symbolizer/Elf.h"
30 #include "folly/experimental/symbolizer/Dwarf.h"
31 #include "folly/experimental/symbolizer/LineReader.h"
32
33 namespace folly {
34 namespace symbolizer {
35
36 namespace {
37
38 /**
39  * Read a hex value.
40  */
41 uintptr_t readHex(StringPiece& sp) {
42   uintptr_t val = 0;
43   const char* p = sp.begin();
44   for (; p != sp.end(); ++p) {
45     unsigned int v;
46     if (*p >= '0' && *p <= '9') {
47       v = (*p - '0');
48     } else if (*p >= 'a' && *p <= 'f') {
49       v = (*p - 'a') + 10;
50     } else if (*p >= 'A' && *p <= 'F') {
51       v = (*p - 'A') + 10;
52     } else {
53       break;
54     }
55     val = (val << 4) + v;
56   }
57   sp.assign(p, sp.end());
58   return val;
59 }
60
61 /**
62  * Skip over non-space characters.
63  */
64 void skipNS(StringPiece& sp) {
65   const char* p = sp.begin();
66   for (; p != sp.end() && (*p != ' ' && *p != '\t'); ++p) { }
67   sp.assign(p, sp.end());
68 }
69
70 /**
71  * Skip over space and tab characters.
72  */
73 void skipWS(StringPiece& sp) {
74   const char* p = sp.begin();
75   for (; p != sp.end() && (*p == ' ' || *p == '\t'); ++p) { }
76   sp.assign(p, sp.end());
77 }
78
79 /**
80  * Parse a line from /proc/self/maps
81  */
82 bool parseProcMapsLine(StringPiece line,
83                        uintptr_t& from, uintptr_t& to,
84                        StringPiece& fileName) {
85   // from     to       perm offset   dev   inode             path
86   // 00400000-00405000 r-xp 00000000 08:03 35291182          /bin/cat
87   if (line.empty()) {
88     return false;
89   }
90
91   // Remove trailing newline, if any
92   if (line.back() == '\n') {
93     line.pop_back();
94   }
95
96   // from
97   from = readHex(line);
98   if (line.empty() || line.front() != '-') {
99     return false;
100   }
101   line.pop_front();
102
103   // to
104   to = readHex(line);
105   if (line.empty() || line.front() != ' ') {
106     return false;
107   }
108   line.pop_front();
109
110   // perms
111   skipNS(line);
112   if (line.empty() || line.front() != ' ') {
113     return false;
114   }
115   line.pop_front();
116
117   uintptr_t fileOffset = readHex(line);
118   if (line.empty() || line.front() != ' ') {
119     return false;
120   }
121   line.pop_front();
122   if (fileOffset != 0) {
123     return false;  // main mapping starts at 0
124   }
125
126   // dev
127   skipNS(line);
128   if (line.empty() || line.front() != ' ') {
129     return false;
130   }
131   line.pop_front();
132
133   // inode
134   skipNS(line);
135   if (line.empty() || line.front() != ' ') {
136     return false;
137   }
138
139   skipWS(line);
140   if (line.empty()) {
141     fileName.clear();
142     return true;
143   }
144
145   fileName = line;
146   return true;
147 }
148
149 }  // namespace
150
151 ssize_t getStackTrace(FrameInfo* addresses,
152                       size_t maxAddresses,
153                       size_t skip) {
154   unw_context_t uctx;
155   int r = unw_getcontext(&uctx);
156   if (r < 0) {
157     return -1;
158   }
159
160   unw_cursor_t cursor;
161   size_t idx = 0;
162   bool first = true;
163   for (;;) {
164     if (first) {
165       first = false;
166       r = unw_init_local(&cursor, &uctx);
167     } else {
168       r = unw_step(&cursor);
169       if (r == 0) {
170         break;
171       }
172     }
173     if (r < 0) {
174       return -1;
175     }
176
177     if (skip != 0) {
178       --skip;
179       continue;
180     }
181
182     if (idx < maxAddresses) {
183       unw_word_t ip;
184       int rr = unw_get_reg(&cursor, UNW_REG_IP, &ip);
185       if (rr < 0) {
186         return -1;
187       }
188
189       // If error, assume not a signal frame
190       rr = unw_is_signal_frame(&cursor);
191       addresses[idx] = FrameInfo(ip, (rr > 0));
192     }
193     ++idx;
194   }
195
196   return idx;
197 }
198
199 void Symbolizer::symbolize(FrameInfo* addresses, size_t addressCount) {
200   size_t remaining = 0;
201   for (size_t i = 0; i < addressCount; ++i) {
202     auto& ainfo = addresses[i];
203     if (!ainfo.found) {
204       ++remaining;
205       ainfo.name.clear();
206       ainfo.location = Dwarf::LocationInfo();
207     }
208   }
209
210   if (remaining == 0) {  // we're done
211     return;
212   }
213
214   int fd = openNoInt("/proc/self/maps", O_RDONLY);
215   if (fd == -1) {
216     return;
217   }
218
219   char buf[PATH_MAX + 100];  // Long enough for any line
220   LineReader reader(fd, buf, sizeof(buf));
221
222   char fileNameBuf[PATH_MAX];
223
224   while (remaining != 0) {
225     StringPiece line;
226     if (reader.readLine(line) != LineReader::kReading) {
227       break;
228     }
229
230     // Parse line
231     uintptr_t from;
232     uintptr_t to;
233     StringPiece fileName;
234     if (!parseProcMapsLine(line, from, to, fileName)) {
235       continue;
236     }
237
238     bool first = true;
239     ElfFile* elfFile = nullptr;
240
241     // See if any addresses are here
242     for (size_t i = 0; i < addressCount; ++i) {
243       auto& ainfo = addresses[i];
244       if (ainfo.found) {
245         continue;
246       }
247
248       uintptr_t address = ainfo.address;
249
250       // If the next address (closer to the top of the stack) was a signal
251       // frame, then this is the *resume* address, which is the address
252       // after the location where the signal was caught. This might be in
253       // the next function, so subtract 1 before symbolizing.
254       if (i != 0 && addresses[i-1].isSignalFrame) {
255         --address;
256       }
257
258       if (from > address || address >= to) {
259         continue;
260       }
261
262       // Found
263       ainfo.found = true;
264       --remaining;
265
266       // Open the file on first use
267       if (first) {
268         first = false;
269         if (fileCount_ < kMaxFiles &&
270             !fileName.empty() &&
271             fileName.size() < sizeof(fileNameBuf)) {
272           memcpy(fileNameBuf, fileName.data(), fileName.size());
273           fileNameBuf[fileName.size()] = '\0';
274           auto& f = files_[fileCount_++];
275           if (f.openNoThrow(fileNameBuf) != -1) {
276             elfFile = &f;
277           }
278         }
279       }
280
281       if (!elfFile) {
282         continue;
283       }
284
285       // Undo relocation
286       uintptr_t fileAddress = address - from + elfFile->getBaseAddress();
287       auto sym = elfFile->getDefinitionByAddress(fileAddress);
288       if (!sym.first) {
289         continue;
290       }
291       auto name = elfFile->getSymbolName(sym);
292       if (name) {
293         ainfo.name = name;
294       }
295
296       Dwarf(elfFile).findAddress(fileAddress, ainfo.location);
297     }
298   }
299
300   closeNoInt(fd);
301 }
302
303 namespace {
304 const char kHexChars[] = "0123456789abcdef";
305 }  // namespace
306
307 void SymbolizePrinter::print(const FrameInfo& ainfo) {
308   uintptr_t address = ainfo.address;
309   // Can't use sprintf, not async-signal-safe
310   static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
311   char buf[] = "    @ 0000000000000000";
312   char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
313   const char padBuf[] = "                       ";
314   folly::StringPiece pad(padBuf,
315                          sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
316   char* p = end;
317   *p-- = '\0';
318   while (address != 0) {
319     *p-- = kHexChars[address & 0xf];
320     address >>= 4;
321   }
322   doPrint(folly::StringPiece(buf, end));
323
324   char mangledBuf[1024];
325   if (!ainfo.found) {
326     doPrint(" (not found)\n");
327     return;
328   }
329
330   if (ainfo.name.empty()) {
331     doPrint(" (unknown)\n");
332   } else if (ainfo.name.size() >= sizeof(mangledBuf)) {
333     doPrint(" ");
334     doPrint(ainfo.name);
335     doPrint("\n");
336   } else {
337     memcpy(mangledBuf, ainfo.name.data(), ainfo.name.size());
338     mangledBuf[ainfo.name.size()] = '\0';
339
340     char demangledBuf[1024];
341     demangle(mangledBuf, demangledBuf, sizeof(demangledBuf));
342     doPrint(" ");
343     doPrint(demangledBuf);
344     doPrint("\n");
345   }
346
347   char fileBuf[PATH_MAX];
348   fileBuf[0] = '\0';
349   if (ainfo.location.hasFileAndLine) {
350     ainfo.location.file.toBuffer(fileBuf, sizeof(fileBuf));
351     doPrint(pad);
352     doPrint(fileBuf);
353
354     char buf[22];
355     uint32_t n = uint64ToBufferUnsafe(ainfo.location.line, buf);
356     doPrint(":");
357     doPrint(StringPiece(buf, n));
358     doPrint("\n");
359   }
360
361   if (ainfo.location.hasMainFile) {
362     char mainFileBuf[PATH_MAX];
363     mainFileBuf[0] = '\0';
364     ainfo.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
365     if (!ainfo.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
366       doPrint(pad);
367       doPrint("-> ");
368       doPrint(mainFileBuf);
369       doPrint("\n");
370     }
371   }
372 }
373
374 void SymbolizePrinter::print(const FrameInfo* addresses,
375                              size_t addressesSize,
376                              size_t frameCount) {
377   for (size_t i = 0; i < std::min(addressesSize, frameCount); ++i) {
378     auto& ainfo = addresses[i];
379     print(ainfo);
380   }
381
382   // Indicate the number of frames that we couldn't log due to space
383   if (frameCount > addressesSize) {
384     char buf[22];
385     uint32_t n = uint64ToBufferUnsafe(frameCount - addressesSize, buf);
386     doPrint("    (");
387     doPrint(StringPiece(buf, n));
388     doPrint(" omitted, max buffer size reached)\n");
389   }
390 }
391
392 void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
393   out_ << sp;
394 }
395
396 void FDSymbolizePrinter::doPrint(StringPiece sp) {
397   writeFull(fd_, sp.data(), sp.size());
398 }
399
400 std::ostream& operator<<(std::ostream& out, const FrameInfo& ainfo) {
401   OStreamSymbolizePrinter osp(out);
402   osp.print(ainfo);
403   return out;
404 }
405
406 namespace {
407
408 struct Init {
409   Init();
410 };
411
412 Init::Init() {
413   // Don't use global caching -- it's slow and leads to lock contention.  (And
414   // it's made signal-safe using sigprocmask to block all signals while the
415   // lock is being held, and sigprocmask contends on a lock inside the kernel,
416   // too, ugh.)
417   unw_set_caching_policy(unw_local_addr_space, UNW_CACHE_PER_THREAD);
418 }
419
420 Init initializer;
421
422 }  // namespace
423
424 }  // namespace symbolizer
425 }  // namespace folly