-
Notifications
You must be signed in to change notification settings - Fork 5.5k
/
Elf.cpp
484 lines (408 loc) · 13.7 KB
/
Elf.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/symbolizer/Elf.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cstring>
#include <string>
#include <glog/logging.h>
#include <folly/Conv.h>
#include <folly/Exception.h>
#include <folly/ScopeGuard.h>
#include <folly/lang/CString.h>
#include <folly/portability/Config.h>
#include <folly/portability/SysMman.h>
#if FOLLY_HAVE_ELF
#ifndef STT_GNU_IFUNC
#define STT_GNU_IFUNC 10
#endif
#if defined(__ELF_NATIVE_CLASS)
#define FOLLY_ELF_NATIVE_CLASS __ELF_NATIVE_CLASS
#elif defined(__FreeBSD__)
#if defined(__LP64__)
#define FOLLY_ELF_NATIVE_CLASS 64
#else
#define FOLLY_ELF_NATIVE_CLASS 32
#endif
#endif // __ELF_NATIVE_CLASS
namespace folly {
namespace symbolizer {
ElfFile::ElfFile() noexcept
: fd_(-1),
file_(static_cast<char*>(MAP_FAILED)),
length_(0),
fileId_(),
baseAddress_(0) {}
ElfFile::ElfFile(const char* name, Options const& options)
: fd_(-1),
file_(static_cast<char*>(MAP_FAILED)),
length_(0),
fileId_(),
baseAddress_(0) {
open(name, options);
}
void ElfFile::open(const char* name, Options const& options) {
auto r = openNoThrow(name, options);
if (r == kSystemError) {
throwSystemError(r.msg);
} else {
CHECK_EQ(r, kSuccess) << r.msg;
}
}
ElfFile::OpenResult ElfFile::openNoThrow(
const char* name, Options const& options) noexcept {
FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
// Always close fd and unmap in case of failure along the way to avoid
// check failure above if we leave fd != -1 and the object is recycled
auto guard = makeGuard([&] { reset(); });
strlcpy(filepath_, name, kFilepathMaxLen - 1);
fd_ = ::open(name, options.writable() ? O_RDWR : O_RDONLY);
if (fd_ == -1) {
return {kSystemError, "open"};
}
struct stat st;
int r = fstat(fd_, &st);
if (r == -1) {
return {kSystemError, "fstat"};
}
fileId_ = std::make_tuple(
st.st_dev,
st.st_ino,
st.st_size,
st.st_mtim.tv_sec * 1000'000'000LL + st.st_mtim.tv_nsec);
length_ = st.st_size;
int prot = PROT_READ;
if (options.writable()) {
prot |= PROT_WRITE;
}
file_ = static_cast<char*>(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0));
if (file_ == MAP_FAILED) {
return {kSystemError, "mmap"};
}
auto const initOpenResult = init();
if (initOpenResult != kSuccess) {
reset();
errno = EINVAL;
return initOpenResult;
}
guard.dismiss();
return {kSuccess, nullptr};
}
ElfFile::OpenResult ElfFile::openAndFollow(
const char* name, Options const& options) noexcept {
auto result = openNoThrow(name, options);
if (options.writable() || result != kSuccess) {
return result;
}
/* NOTE .gnu_debuglink specifies only the name of the debugging info file
* (with no directory components). GDB checks 3 different directories, but
* ElfFile only supports the first version:
* - dirname(name)
* - dirname(name) + /.debug/
* - X/dirname(name)/ - where X is set in gdb's `debug-file-directory`.
*/
auto dirend = strrchr(name, '/');
// include ending '/' if any.
auto dirlen = dirend != nullptr ? dirend + 1 - name : 0;
auto debuginfo = getSectionByName(".gnu_debuglink");
if (!debuginfo) {
return result;
}
// The section starts with the filename, with any leading directory
// components removed, followed by a zero byte.
auto debugFileName = getSectionBody(*debuginfo);
auto debugFileLen = strlen(debugFileName.begin());
if (dirlen + debugFileLen >= PATH_MAX) {
return result;
}
char linkname[PATH_MAX];
memcpy(linkname, name, dirlen);
memcpy(linkname + dirlen, debugFileName.begin(), debugFileLen + 1);
reset();
result = openNoThrow(linkname, options);
if (result == kSuccess) {
return result;
}
return openNoThrow(name, options);
}
ElfFile::~ElfFile() {
reset();
}
ElfFile::ElfFile(ElfFile&& other) noexcept
: fd_(other.fd_),
file_(other.file_),
length_(other.length_),
fileId_(other.fileId_),
baseAddress_(other.baseAddress_) {
// copy other.filepath_, leaving filepath_ zero-terminated, always.
strlcpy(filepath_, other.filepath_, kFilepathMaxLen - 1);
other.filepath_[0] = 0;
other.fd_ = -1;
other.file_ = static_cast<char*>(MAP_FAILED);
other.length_ = 0;
other.fileId_ = {};
other.baseAddress_ = 0;
}
ElfFile& ElfFile::operator=(ElfFile&& other) noexcept {
assert(this != &other);
reset();
// copy other.filepath_, leaving filepath_ zero-terminated, always.
strlcpy(filepath_, other.filepath_, kFilepathMaxLen - 1);
fd_ = other.fd_;
file_ = other.file_;
length_ = other.length_;
fileId_ = other.fileId_;
baseAddress_ = other.baseAddress_;
other.filepath_[0] = 0;
other.fd_ = -1;
other.file_ = static_cast<char*>(MAP_FAILED);
other.length_ = 0;
other.fileId_ = {};
other.baseAddress_ = 0;
return *this;
}
void ElfFile::reset() noexcept {
filepath_[0] = 0;
if (file_ != MAP_FAILED) {
munmap(file_, length_);
file_ = static_cast<char*>(MAP_FAILED);
}
if (fd_ != -1) {
close(fd_);
fd_ = -1;
}
fileId_ = {};
}
ElfFile::OpenResult ElfFile::init() noexcept {
if (length_ < 4) {
return {kInvalidElfFile, "not an ELF file (too short)"};
}
std::array<char, 5> elfMagBuf = {{0, 0, 0, 0, 0}};
if (::lseek(fd_, 0, SEEK_SET) != 0 || ::read(fd_, elfMagBuf.data(), 4) != 4) {
return {kInvalidElfFile, "unable to read ELF file for magic number"};
}
if (std::strncmp(elfMagBuf.data(), ELFMAG, sizeof(ELFMAG)) != 0) {
return {kInvalidElfFile, "invalid ELF magic"};
}
char c;
if (::pread(fd_, &c, 1, length_ - 1) != 1) {
auto msg =
"The last bit of the mmaped memory is no longer valid. This may be "
"caused by the original file being resized, "
"deleted or otherwise modified.";
return {kInvalidElfFile, msg};
}
if (::lseek(fd_, 0, SEEK_SET) != 0) {
return {
kInvalidElfFile,
"unable to reset file descriptor after reading ELF magic number"};
}
auto& elfHeader = this->elfHeader();
#define EXPECTED_CLASS P1(ELFCLASS, FOLLY_ELF_NATIVE_CLASS)
#define P1(a, b) P2(a, b)
#define P2(a, b) a##b
// Validate ELF class (32/64 bits)
if (elfHeader.e_ident[EI_CLASS] != EXPECTED_CLASS) {
return {kInvalidElfFile, "invalid ELF class"};
}
#undef P1
#undef P2
#undef EXPECTED_CLASS
// Validate ELF data encoding (LSB/MSB)
static constexpr auto kExpectedEncoding =
kIsLittleEndian ? ELFDATA2LSB : ELFDATA2MSB;
if (elfHeader.e_ident[EI_DATA] != kExpectedEncoding) {
return {kInvalidElfFile, "invalid ELF encoding"};
}
// Validate ELF version (1)
if (elfHeader.e_ident[EI_VERSION] != EV_CURRENT ||
elfHeader.e_version != EV_CURRENT) {
return {kInvalidElfFile, "invalid ELF version"};
}
// We only support executable and shared object files
if (elfHeader.e_type != ET_REL && elfHeader.e_type != ET_EXEC &&
elfHeader.e_type != ET_DYN && elfHeader.e_type != ET_CORE) {
return {kInvalidElfFile, "invalid ELF file type"};
}
// We support executable and shared object files and extracting debug info
// from relocatable objects (.dwo sections in .o/.dwo files). The e_phnum and
// e_phentsize header fileds are not required for relocatable files.
// https://docs.oracle.com/cd/E19620-01/805-4693/6j4emccrq/index.html
if (elfHeader.e_type != ET_REL) {
if (elfHeader.e_phnum == 0) {
return {kInvalidElfFile, "no program header!"};
}
if (elfHeader.e_phentsize != sizeof(ElfPhdr)) {
return {kInvalidElfFile, "invalid program header entry size"};
}
}
if (elfHeader.e_shentsize != sizeof(ElfShdr)) {
if (elfHeader.e_shentsize != 0 || elfHeader.e_type != ET_CORE) {
return {kInvalidElfFile, "invalid section header entry size"};
}
}
// Program headers are sorted by load address, so the first PT_LOAD
// header gives us the base address.
if (elfHeader.e_type != ET_REL) {
const ElfPhdr* programHeader =
iterateProgramHeaders([](auto& h) { return h.p_type == PT_LOAD; });
if (!programHeader) {
return {kInvalidElfFile, "could not find base address"};
}
baseAddress_ = programHeader->p_vaddr;
}
return {kSuccess, nullptr};
}
const ElfShdr* ElfFile::getSectionByIndex(size_t idx) const noexcept {
FOLLY_SAFE_CHECK(idx < elfHeader().e_shnum, "invalid section index");
return &at<ElfShdr>(elfHeader().e_shoff + idx * sizeof(ElfShdr));
}
folly::StringPiece ElfFile::getSectionBody(
const ElfShdr& section) const noexcept {
return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
}
void ElfFile::validateStringTable(const ElfShdr& stringTable) const noexcept {
FOLLY_SAFE_CHECK(
stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
const char* start = file_ + stringTable.sh_offset;
// First and last bytes must be 0
FOLLY_SAFE_CHECK(
stringTable.sh_size == 0 ||
(start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
"invalid string table");
}
const char* ElfFile::getString(
const ElfShdr& stringTable, size_t offset) const noexcept {
validateStringTable(stringTable);
FOLLY_SAFE_CHECK(
offset < stringTable.sh_size, "invalid offset in string table");
return file_ + stringTable.sh_offset + offset;
}
const char* ElfFile::getSectionName(const ElfShdr& section) const noexcept {
if (elfHeader().e_shstrndx == SHN_UNDEF) {
return nullptr; // no section name string table
}
const ElfShdr& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
return getString(sectionNames, section.sh_name);
}
const ElfShdr* ElfFile::getSectionByName(const char* name) const noexcept {
if (elfHeader().e_shstrndx == SHN_UNDEF) {
return nullptr; // no section name string table
}
const ElfShdr& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
const char* start = file_ + sectionNames.sh_offset;
// Find section with the appropriate sh_name offset
const ElfShdr* foundSection = iterateSections([&](const ElfShdr& sh) {
if (sh.sh_name >= sectionNames.sh_size) {
return false;
}
return !strcmp(start + sh.sh_name, name);
});
return foundSection;
}
ElfFile::Symbol ElfFile::getDefinitionByAddress(
uintptr_t address) const noexcept {
Symbol foundSymbol{nullptr, nullptr};
auto findSection = [&](const ElfShdr& section) {
auto findSymbols = [&](const ElfSym& sym) {
if (sym.st_shndx == SHN_UNDEF) {
return false; // not a definition
}
if (address >= sym.st_value && address < sym.st_value + sym.st_size) {
foundSymbol.first = §ion;
foundSymbol.second = &sym;
return true;
}
return false;
};
return iterateSymbolsWithTypes(
section, {STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}, findSymbols);
};
// Try the .dynsym section first if it exists, it's smaller.
(iterateSectionsWithType(SHT_DYNSYM, findSection) ||
iterateSectionsWithType(SHT_SYMTAB, findSection));
return foundSymbol;
}
ElfFile::Symbol ElfFile::getSymbolByName(const char* name) const noexcept {
Symbol foundSymbol{nullptr, nullptr};
auto findSection = [&](const ElfShdr& section) -> bool {
// This section has no string table associated w/ its symbols; hence we
// can't get names for them
if (section.sh_link == SHN_UNDEF) {
return false;
}
auto findSymbols = [&](const ElfSym& sym) -> bool {
if (sym.st_shndx == SHN_UNDEF) {
return false; // not a definition
}
if (sym.st_name == 0) {
return false; // no name for this symbol
}
const char* sym_name =
getString(*getSectionByIndex(section.sh_link), sym.st_name);
if (strcmp(sym_name, name) == 0) {
foundSymbol.first = §ion;
foundSymbol.second = &sym;
return true;
}
return false;
};
return iterateSymbolsWithTypes(
section, {STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}, findSymbols);
};
// Try the .dynsym section first if it exists, it's smaller.
iterateSectionsWithType(SHT_DYNSYM, findSection) ||
iterateSectionsWithType(SHT_SYMTAB, findSection);
return foundSymbol;
}
const ElfShdr* ElfFile::getSectionContainingAddress(
ElfAddr addr) const noexcept {
return iterateSections([&](const ElfShdr& sh) -> bool {
return (addr >= sh.sh_addr) && (addr < (sh.sh_addr + sh.sh_size));
});
}
const char* ElfFile::getSymbolName(Symbol symbol) const noexcept {
if (!symbol.first || !symbol.second) {
return nullptr;
}
if (symbol.second->st_name == 0) {
return nullptr; // symbol has no name
}
if (symbol.first->sh_link == SHN_UNDEF) {
return nullptr; // symbol table has no strings
}
return getString(
*getSectionByIndex(symbol.first->sh_link), symbol.second->st_name);
}
std::pair<const int, char const*> ElfFile::posixFadvise(
off_t offset, off_t len, int const advice) const noexcept {
if (fd_ == -1) {
return {1, "file not open"};
}
int res = posix_fadvise(fd_, offset, len, advice);
if (res != 0) {
return {res, "posix_fadvise failed for file"};
}
return {res, ""};
}
std::pair<const int, char const*> ElfFile::posixFadvise(
int const advice) const noexcept {
return posixFadvise(0, 0, advice);
}
} // namespace symbolizer
} // namespace folly
#endif // FOLLY_HAVE_ELF