2 * Copyright 2016 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <folly/experimental/symbolizer/Symbolizer.h>
26 #include <ext/stdio_filebuf.h>
27 #include <ext/stdio_sync_filebuf.h>
30 #include <folly/Conv.h>
31 #include <folly/FileUtil.h>
32 #include <folly/ScopeGuard.h>
33 #include <folly/String.h>
35 #include <folly/experimental/symbolizer/Elf.h>
36 #include <folly/experimental/symbolizer/Dwarf.h>
37 #include <folly/experimental/symbolizer/LineReader.h>
41 namespace symbolizer {
48 uintptr_t readHex(StringPiece& sp) {
50 const char* p = sp.begin();
51 for (; p != sp.end(); ++p) {
53 if (*p >= '0' && *p <= '9') {
55 } else if (*p >= 'a' && *p <= 'f') {
57 } else if (*p >= 'A' && *p <= 'F') {
64 sp.assign(p, sp.end());
69 * Skip over non-space characters.
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());
78 * Skip over space and tab characters.
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());
87 * Parse a line from /proc/self/maps
89 bool parseProcMapsLine(StringPiece line,
94 StringPiece& fileName) {
96 // from to perm offset dev inode path
97 // 00400000-00405000 r-xp 00000000 08:03 35291182 /bin/cat
102 // Remove trailing newline, if any
103 if (line.back() == '\n') {
108 from = readHex(line);
109 if (line.empty() || line.front() != '-') {
116 if (line.empty() || line.front() != ' ') {
123 if (line.empty() || line.front() != ' ') {
128 uintptr_t fileOffset = readHex(line);
129 if (line.empty() || line.front() != ' ') {
133 // main mapping starts at 0 but there can be multi-segment binary
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;
145 if (line.empty() || line.front() != ' ') {
152 if (line.empty() || line.front() != ' ') {
156 // if inode is 0, such as in case of ANON pages, there should be atleast
157 // one white space before EOL
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.
172 ElfCache* defaultElfCache() {
173 static constexpr size_t defaultCapacity = 500;
174 static auto cache = new ElfCache(defaultCapacity);
180 void SymbolizedFrame::set(const std::shared_ptr<ElfFile>& file,
185 address += file->getBaseAddress();
186 auto sym = file->getDefinitionByAddress(address);
192 name = file->getSymbolName(sym);
194 Dwarf(file.get()).findAddress(address, location);
198 Symbolizer::Symbolizer(ElfCacheBase* cache)
199 : cache_(cache ?: defaultElfCache()) {
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];
214 if (remaining == 0) { // we're done
218 int fd = openNoInt("/proc/self/maps", O_RDONLY);
223 char selfFile[PATH_MAX + 8];
225 if ((selfSize = readlink("/proc/self/exe", selfFile, PATH_MAX + 1)) == -1) {
226 // something terribly wrong
229 selfFile[selfSize] = '\0';
231 char buf[PATH_MAX + 100]; // Long enough for any line
232 LineReader reader(fd, buf, sizeof(buf));
234 while (remaining != 0) {
236 if (reader.readLine(line) != LineReader::kReading) {
245 bool isSelf = false; // fileName can potentially be '/proc/self/exe'
246 StringPiece fileName;
247 if (!parseProcMapsLine(line, from, to, fileOff, isSelf, fileName)) {
253 std::shared_ptr<ElfFile> elfFile;
255 // case of text on ANON?
256 // Recompute from/to/base from the executable
257 if (isSelf && fileName.empty()) {
258 elfFile = cache_->getFile(selfFile);
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;
266 first = false; // no need to get this file again from the cache
270 // See if any addresses are here
271 for (size_t i = 0; i < addressCount; ++i) {
272 auto& frame = frames[i];
277 uintptr_t address = addresses[i];
279 if (from > address || address >= to) {
287 // Open the file on first use
290 elfFile = cache_->getFile(fileName);
292 // Need to get the correct base address as from
294 if (fileOff && elfFile != nullptr) {
295 base = elfFile->getBaseAddress();
304 frame.set(elfFile, address - base);
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;
318 constexpr char AddressFormatter::bufTemplate[];
319 constexpr std::array<const char*, SymbolizePrinter::Color::NUM>
320 SymbolizePrinter::kColorMap;
322 AddressFormatter::AddressFormatter() {
323 memcpy(buf_, bufTemplate, sizeof(buf_));
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));
332 while (address != 0) {
333 *p-- = kHexChars[address & 0xf];
337 return folly::StringPiece(buf_, end);
340 void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
341 if (options_ & TERSE) {
342 printTerse(address, frame);
346 SCOPE_EXIT { color(Color::DEFAULT); };
348 if (!(options_ & NO_FRAME_ADDRESS)) {
349 color(kAddressColor);
351 AddressFormatter formatter;
352 doPrint(formatter.format(address));
355 const char padBuf[] = " ";
356 folly::StringPiece pad(padBuf,
357 sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
359 color(kFunctionColor);
361 doPrint(" (not found)");
365 if (!frame.name || frame.name[0] == '\0') {
366 doPrint(" (unknown)");
368 char demangledBuf[2048];
369 demangle(frame.name, demangledBuf, sizeof(demangledBuf));
371 doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
374 if (!(options_ & NO_FILE_AND_LINE)) {
376 char fileBuf[PATH_MAX];
378 if (frame.location.hasFileAndLine) {
379 frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
385 uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
387 doPrint(StringPiece(buf, n));
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)) {
398 doPrint(mainFileBuf);
404 void SymbolizePrinter::color(SymbolizePrinter::Color color) {
405 if ((options_ & COLOR) == 0 &&
406 ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
409 if (color < 0 || color >= kColorMap.size()) {
412 doPrint(kColorMap[color]);
415 void SymbolizePrinter::println(uintptr_t address,
416 const SymbolizedFrame& frame) {
417 print(address, frame);
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);
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));
434 while (address != 0) {
435 *p-- = kHexChars[address & 0xf];
438 doPrint(StringPiece(buf, end));
442 void SymbolizePrinter::println(const uintptr_t* addresses,
443 const SymbolizedFrame* frames,
445 for (size_t i = 0; i < frameCount; ++i) {
446 println(addresses[i], frames[i]);
452 int getFD(const std::ios& stream) {
454 std::streambuf* buf = stream.rdbuf();
455 using namespace __gnu_cxx;
458 auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf);
460 return fileno(sbuf->file());
464 auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf);
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)) {
479 auto term = ::getenv("TERM");
480 return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0);
483 } // anonymous namespace
485 OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
486 : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
490 void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
494 FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize)
495 : SymbolizePrinter(options, isColorfulTty(options, fd)),
497 buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {
500 FDSymbolizePrinter::~FDSymbolizePrinter() {
504 void FDSymbolizePrinter::doPrint(StringPiece sp) {
506 if (sp.size() > buffer_->tailroom()) {
508 writeFull(fd_, sp.data(), sp.size());
510 memcpy(buffer_->writableTail(), sp.data(), sp.size());
511 buffer_->append(sp.size());
514 writeFull(fd_, sp.data(), sp.size());
518 void FDSymbolizePrinter::flush() {
519 if (buffer_ && !buffer_->empty()) {
520 writeFull(fd_, buffer_->data(), buffer_->length());
525 FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
526 : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
530 void FILESymbolizePrinter::doPrint(StringPiece sp) {
531 fwrite(sp.data(), 1, sp.size(), file_);
534 void StringSymbolizePrinter::doPrint(StringPiece sp) {
535 buf_.append(sp.data(), sp.size());
538 } // namespace symbolizer