Add symbol name resolution and value retrieval
[folly.git] / folly / experimental / symbolizer / Elf.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
18 #include "folly/experimental/symbolizer/Elf.h"
19
20 #include <sys/mman.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <endian.h>
24 #include <fcntl.h>
25
26 #include <string>
27
28 #include <glog/logging.h>
29
30 #include "folly/Conv.h"
31 #include "folly/Exception.h"
32
33 namespace folly {
34 namespace symbolizer {
35
36 ElfFile::ElfFile()
37   : fd_(-1),
38     file_(static_cast<char*>(MAP_FAILED)),
39     length_(0),
40     baseAddress_(0) {
41 }
42
43 ElfFile::ElfFile(const char* name)
44   : fd_(open(name, O_RDONLY)),
45     file_(static_cast<char*>(MAP_FAILED)),
46     length_(0),
47     baseAddress_(0) {
48   if (fd_ == -1) {
49     folly::throwSystemError("open ", name);
50   }
51
52   struct stat st;
53   int r = fstat(fd_, &st);
54   if (r == -1) {
55     folly::throwSystemError("fstat");
56   }
57
58   length_ = st.st_size;
59   file_ = static_cast<char*>(
60       mmap(nullptr, length_, PROT_READ, MAP_SHARED, fd_, 0));
61   if (file_ == MAP_FAILED) {
62     folly::throwSystemError("mmap");
63   }
64   init();
65 }
66
67 ElfFile::~ElfFile() {
68   destroy();
69 }
70
71 ElfFile::ElfFile(ElfFile&& other)
72   : fd_(other.fd_),
73     file_(other.file_),
74     length_(other.length_),
75     baseAddress_(other.baseAddress_) {
76   other.fd_ = -1;
77   other.file_ = static_cast<char*>(MAP_FAILED);
78   other.length_ = 0;
79   other.baseAddress_ = 0;
80 }
81
82 ElfFile& ElfFile::operator=(ElfFile&& other) {
83   assert(this != &other);
84   destroy();
85
86   fd_ = other.fd_;
87   file_ = other.file_;
88   length_ = other.length_;
89   baseAddress_ = other.baseAddress_;
90
91   other.fd_ = -1;
92   other.file_ = static_cast<char*>(MAP_FAILED);
93   other.length_ = 0;
94   other.baseAddress_ = 0;
95
96   return *this;
97 }
98
99 void ElfFile::destroy() {
100   if (file_ != MAP_FAILED) {
101     munmap(file_, length_);
102   }
103
104   if (fd_ != -1) {
105     close(fd_);
106   }
107 }
108
109 void ElfFile::init() {
110   auto& elfHeader = this->elfHeader();
111
112   // Validate ELF magic numbers
113   enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
114           elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
115           elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
116           elfHeader.e_ident[EI_MAG3] == ELFMAG3,
117           "invalid ELF magic");
118
119   // Validate ELF class (32/64 bits)
120 #define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS)
121 #define P1(a, b) P2(a, b)
122 #define P2(a, b) a ## b
123   enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
124           "invalid ELF class");
125 #undef P1
126 #undef P2
127 #undef EXPECTED_CLASS
128
129   // Validate ELF data encoding (LSB/MSB)
130 #if __BYTE_ORDER == __LITTLE_ENDIAN
131 # define EXPECTED_ENCODING ELFDATA2LSB
132 #elif __BYTE_ORDER == __BIG_ENDIAN
133 # define EXPECTED_ENCODING ELFDATA2MSB
134 #else
135 # error Unsupported byte order
136 #endif
137   enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
138           "invalid ELF encoding");
139 #undef EXPECTED_ENCODING
140
141   // Validate ELF version (1)
142   enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
143           elfHeader.e_version == EV_CURRENT,
144           "invalid ELF version");
145
146   // We only support executable and shared object files
147   enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
148           "invalid ELF file type");
149
150   enforce(elfHeader.e_phnum != 0, "no program header!");
151   enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
152           "invalid program header entry size");
153   enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
154           "invalid section header entry size");
155
156   const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
157   bool foundBase = false;
158   for (size_t i = 0; i < elfHeader.e_phnum; programHeader++, i++) {
159     // Program headers are sorted by load address, so the first PT_LOAD
160     // header gives us the base address.
161     if (programHeader->p_type == PT_LOAD) {
162       baseAddress_ = programHeader->p_vaddr;
163       foundBase = true;
164       break;
165     }
166   }
167
168   enforce(foundBase, "could not find base address");
169 }
170
171 const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
172   enforce(idx < elfHeader().e_shnum, "invalid section index");
173   return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
174 }
175
176 folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
177   return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
178 }
179
180 void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
181   enforce(stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
182
183   const char* start = file_ + stringTable.sh_offset;
184   // First and last bytes must be 0
185   enforce(stringTable.sh_size == 0 ||
186           (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
187           "invalid string table");
188 }
189
190 const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
191   const {
192   validateStringTable(stringTable);
193   enforce(offset < stringTable.sh_size, "invalid offset in string table");
194
195   return file_ + stringTable.sh_offset + offset;
196 }
197
198 const char* ElfFile::getSectionName(const ElfW(Shdr)& section) const {
199   if (elfHeader().e_shstrndx == SHN_UNDEF) {
200     return nullptr;  // no section name string table
201   }
202
203   const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
204   return getString(sectionNames, section.sh_name);
205 }
206
207 const ElfW(Shdr)* ElfFile::getSectionByName(const char* name) const {
208   if (elfHeader().e_shstrndx == SHN_UNDEF) {
209     return nullptr;  // no section name string table
210   }
211
212   // Find offset in the section name string table of the requested name
213   const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
214   const char* foundName = iterateStrings(
215       sectionNames,
216       [&] (const char* s) { return !strcmp(name, s); });
217   if (foundName == nullptr) {
218     return nullptr;
219   }
220
221   size_t offset = foundName - (file_ + sectionNames.sh_offset);
222
223   // Find section with the appropriate sh_name offset
224   const ElfW(Shdr)* foundSection = iterateSections(
225     [&](const ElfW(Shdr)& sh) {
226       if (sh.sh_name == offset) {
227         return true;
228       }
229       return false;
230     });
231   return foundSection;
232 }
233
234 ElfFile::Symbol ElfFile::getDefinitionByAddress(uintptr_t address) const {
235   Symbol foundSymbol {nullptr, nullptr};
236
237   auto findSection = [&](const ElfW(Shdr)& section) {
238     auto findSymbols = [&](const ElfW(Sym)& sym) {
239       if (sym.st_shndx == SHN_UNDEF) {
240         return false;  // not a definition
241       }
242       if (address >= sym.st_value && address < sym.st_value + sym.st_size) {
243         foundSymbol.first = &section;
244         foundSymbol.second = &sym;
245         return true;
246       }
247
248       return false;
249     };
250
251     return iterateSymbolsWithType(section, STT_OBJECT, findSymbols) ||
252       iterateSymbolsWithType(section, STT_FUNC, findSymbols);
253   };
254
255   // Try the .dynsym section first if it exists, it's smaller.
256   (iterateSectionsWithType(SHT_DYNSYM, findSection) ||
257    iterateSectionsWithType(SHT_SYMTAB, findSection));
258
259   return foundSymbol;
260 }
261
262 ElfFile::Symbol ElfFile::getSymbolByName(const char* name) const {
263   Symbol foundSymbol{nullptr, nullptr};
264
265   auto findSection = [&](const ElfW(Shdr)& section) -> bool {
266     // This section has no string table associated w/ its symbols; hence we
267     // can't get names for them
268     if (section.sh_link == SHN_UNDEF) {
269       return false;
270     }
271
272     auto findSymbols = [&](const ElfW(Sym)& sym) -> bool {
273       if (sym.st_shndx == SHN_UNDEF) {
274         return false;  // not a definition
275       }
276       if (sym.st_name == 0) {
277         return false;  // no name for this symbol
278       }
279       const char* sym_name = getString(
280         *getSectionByIndex(section.sh_link), sym.st_name);
281       if (strcmp(sym_name, name) == 0) {
282         foundSymbol.first = &section;
283         foundSymbol.second = &sym;
284         return true;
285       }
286
287       return false;
288     };
289
290     return iterateSymbolsWithType(section, STT_OBJECT, findSymbols) ||
291       iterateSymbolsWithType(section, STT_FUNC, findSymbols);
292   };
293
294   // Try the .dynsym section first if it exists, it's smaller.
295   iterateSectionsWithType(SHT_DYNSYM, findSection) ||
296     iterateSectionsWithType(SHT_SYMTAB, findSection);
297
298   return foundSymbol;
299 }
300
301 const ElfW(Shdr)* ElfFile::getSectionContainingAddress(ElfW(Addr) addr) const {
302   return iterateSections([&](const ElfW(Shdr)& sh) -> bool {
303     return (addr >= sh.sh_addr) && (addr < (sh.sh_addr + sh.sh_size));
304   });
305 }
306
307 const char* ElfFile::getSymbolName(Symbol symbol) const {
308   if (!symbol.first || !symbol.second) {
309     return nullptr;
310   }
311
312   if (symbol.second->st_name == 0) {
313     return nullptr;  // symbol has no name
314   }
315
316   if (symbol.first->sh_link == SHN_UNDEF) {
317     return nullptr;  // symbol table has no strings
318   }
319
320   return getString(*getSectionByIndex(symbol.first->sh_link),
321                    symbol.second->st_name);
322 }
323
324 }  // namespace symbolizer
325 }  // namespace folly
326