Move exception tracer library to folly/experimental
[folly.git] / folly / experimental / symbolizer / Elf.cpp
1 /*
2  * Copyright 2012 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
32 namespace facebook {
33 namespace symbolizer {
34
35 ElfFile::ElfFile()
36   : fd_(-1),
37     file_(static_cast<char*>(MAP_FAILED)),
38     length_(0),
39     baseAddress_(0) {
40 }
41
42 ElfFile::ElfFile(const char* name)
43   : fd_(open(name, O_RDONLY)),
44     file_(static_cast<char*>(MAP_FAILED)),
45     length_(0),
46     baseAddress_(0) {
47   if (fd_ == -1) {
48     systemError("open ", name);
49   }
50
51   struct stat st;
52   int r = fstat(fd_, &st);
53   if (r == -1) {
54     systemError("fstat");
55   }
56
57   length_ = st.st_size;
58   file_ = static_cast<char*>(
59       mmap(nullptr, length_, PROT_READ, MAP_SHARED, fd_, 0));
60   if (file_ == MAP_FAILED) {
61     systemError("mmap");
62   }
63   init();
64 }
65
66 ElfFile::~ElfFile() {
67   destroy();
68 }
69
70 ElfFile::ElfFile(ElfFile&& other)
71   : fd_(other.fd_),
72     file_(other.file_),
73     length_(other.length_),
74     baseAddress_(other.baseAddress_) {
75   other.fd_ = -1;
76   other.file_ = static_cast<char*>(MAP_FAILED);
77   other.length_ = 0;
78   other.baseAddress_ = 0;
79 }
80
81 ElfFile& ElfFile::operator=(ElfFile&& other) {
82   assert(this != &other);
83   destroy();
84
85   fd_ = other.fd_;
86   file_ = other.file_;
87   length_ = other.length_;
88   baseAddress_ = other.baseAddress_;
89
90   other.fd_ = -1;
91   other.file_ = static_cast<char*>(MAP_FAILED);
92   other.length_ = 0;
93   other.baseAddress_ = 0;
94
95   return *this;
96 }
97
98 void ElfFile::destroy() {
99   if (file_ != MAP_FAILED) {
100     munmap(file_, length_);
101   }
102
103   if (fd_ != -1) {
104     close(fd_);
105   }
106 }
107
108 void ElfFile::init() {
109   auto& elfHeader = this->elfHeader();
110
111   // Validate ELF magic numbers
112   enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
113           elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
114           elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
115           elfHeader.e_ident[EI_MAG3] == ELFMAG3,
116           "invalid ELF magic");
117
118   // Validate ELF class (32/64 bits)
119 #define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS)
120 #define P1(a, b) P2(a, b)
121 #define P2(a, b) a ## b
122   enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
123           "invalid ELF class");
124 #undef P1
125 #undef P2
126 #undef EXPECTED_CLASS
127
128   // Validate ELF data encoding (LSB/MSB)
129 #if __BYTE_ORDER == __LITTLE_ENDIAN
130 # define EXPECTED_ENCODING ELFDATA2LSB
131 #elif __BYTE_ORDER == __BIG_ENDIAN
132 # define EXPECTED_ENCODING ELFDATA2MSB
133 #else
134 # error Unsupported byte order
135 #endif
136   enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
137           "invalid ELF encoding");
138 #undef EXPECTED_ENCODING
139
140   // Validate ELF version (1)
141   enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
142           elfHeader.e_version == EV_CURRENT,
143           "invalid ELF version");
144
145   // We only support executable and shared object files
146   enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
147           "invalid ELF file type");
148
149   enforce(elfHeader.e_phnum != 0, "no program header!");
150   enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
151           "invalid program header entry size");
152   enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
153           "invalid section header entry size");
154
155   const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
156   bool foundBase = false;
157   for (size_t i = 0; i < elfHeader.e_phnum; programHeader++, i++) {
158     // Program headers are sorted by load address, so the first PT_LOAD
159     // header gives us the base address.
160     if (programHeader->p_type == PT_LOAD) {
161       baseAddress_ = programHeader->p_vaddr;
162       foundBase = true;
163       break;
164     }
165   }
166
167   enforce(foundBase, "could not find base address");
168 }
169
170 const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
171   enforce(idx < elfHeader().e_shnum, "invalid section index");
172   return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
173 }
174
175 folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
176   return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
177 }
178
179 void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
180   enforce(stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
181
182   const char* start = file_ + stringTable.sh_offset;
183   // First and last bytes must be 0
184   enforce(stringTable.sh_size == 0 ||
185           (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
186           "invalid string table");
187 }
188
189 const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
190   const {
191   validateStringTable(stringTable);
192   enforce(offset < stringTable.sh_size, "invalid offset in string table");
193
194   return file_ + stringTable.sh_offset + offset;
195 }
196
197 const char* ElfFile::getSectionName(const ElfW(Shdr)& section) const {
198   if (elfHeader().e_shstrndx == SHN_UNDEF) {
199     return nullptr;  // no section name string table
200   }
201
202   const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
203   return getString(sectionNames, section.sh_name);
204 }
205
206 const ElfW(Shdr)* ElfFile::getSectionByName(const char* name) const {
207   if (elfHeader().e_shstrndx == SHN_UNDEF) {
208     return nullptr;  // no section name string table
209   }
210
211   // Find offset in the section name string table of the requested name
212   const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
213   const char* foundName = iterateStrings(
214       sectionNames,
215       [&] (const char* s) { return !strcmp(name, s); });
216   if (foundName == nullptr) {
217     return nullptr;
218   }
219
220   size_t offset = foundName - (file_ + sectionNames.sh_offset);
221
222   // Find section with the appropriate sh_name offset
223   const ElfW(Shdr)* foundSection = iterateSections(
224     [&](const ElfW(Shdr)& sh) {
225       if (sh.sh_name == offset) {
226         return true;
227       }
228       return false;
229     });
230   return foundSection;
231 }
232
233 ElfFile::Symbol ElfFile::getDefinitionByAddress(uintptr_t address) const {
234   Symbol foundSymbol {nullptr, nullptr};
235
236   auto find = [&] (const ElfW(Shdr)& section) {
237     enforce(section.sh_entsize == sizeof(ElfW(Sym)),
238             "invalid entry size in symbol table");
239
240     const ElfW(Sym)* sym = &at<ElfW(Sym)>(section.sh_offset);
241     const ElfW(Sym)* end = &at<ElfW(Sym)>(section.sh_offset + section.sh_size);
242     for (; sym != end; ++sym) {
243       // st_info has the same representation on 32- and 64-bit platforms
244       auto type = ELF32_ST_TYPE(sym->st_info);
245
246       // TODO(tudorb): Handle STT_TLS, but then we'd have to understand
247       // thread-local relocations.  If all we're looking up is functions
248       // (instruction pointers), it doesn't matter, though.
249       if (type != STT_OBJECT && type != STT_FUNC) {
250         continue;
251       }
252       if (sym->st_shndx == SHN_UNDEF) {
253         continue;  // not a definition
254       }
255       if (address >= sym->st_value && address < sym->st_value + sym->st_size) {
256         foundSymbol.first = &section;
257         foundSymbol.second = sym;
258         return true;
259       }
260     }
261
262     return false;
263   };
264
265   // Try the .dynsym section first if it exists, it's smaller.
266   (iterateSectionsWithType(SHT_DYNSYM, find) ||
267    iterateSectionsWithType(SHT_SYMTAB, find));
268
269   return foundSymbol;
270 }
271
272 const char* ElfFile::getSymbolName(Symbol symbol) const {
273   if (!symbol.first || !symbol.second) {
274     return nullptr;
275   }
276
277   if (symbol.second->st_name == 0) {
278     return nullptr;  // symbol has no name
279   }
280
281   if (symbol.first->sh_link == SHN_UNDEF) {
282     return nullptr;  // symbol table has no strings
283   }
284
285   return getString(*getSectionByIndex(symbol.first->sh_link),
286                    symbol.second->st_name);
287 }
288
289 }  // namespace symbolizer
290 }  // namespace facebook
291