#pragma once
#define FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_
#include <fcntl.h>
#include <sys/types.h>
#include <cstdio>
#include <initializer_list>
#include <stdexcept>
#include <system_error>
#include <unordered_map>
#include <folly/Conv.h>
#include <folly/Likely.h>
#include <folly/Range.h>
#include <folly/lang/SafeAssert.h>
#include <folly/portability/Config.h>
#if FOLLY_HAVE_ELF
#include <elf.h>
#include <link.h>
namespace folly {
namespace symbolizer {
#if defined(ElfW)
#define FOLLY_ELF_ELFW …
#elif defined(__FreeBSD__)
#define FOLLY_ELF_ELFW …
#endif
using ElfAddr = FOLLY_ELF_ELFW(Addr);
using ElfEhdr = FOLLY_ELF_ELFW(Ehdr);
using ElfOff = FOLLY_ELF_ELFW(Off);
using ElfPhdr = FOLLY_ELF_ELFW(Phdr);
using ElfShdr = FOLLY_ELF_ELFW(Shdr);
using ElfSym = FOLLY_ELF_ELFW(Sym);
using ElfRel = FOLLY_ELF_ELFW(Rel);
using ElfRela = FOLLY_ELF_ELFW(Rela);
struct ElfFileId {
dev_t dev;
ino_t inode;
off_t size;
uint64_t mtime;
};
inline bool operator==(const ElfFileId& lhs, const ElfFileId& rhs) {
return lhs.dev == rhs.dev && lhs.inode == rhs.inode && lhs.size == rhs.size &&
lhs.mtime == rhs.mtime;
}
class ElfFile {
public:
class Options {
public:
constexpr Options() noexcept {}
constexpr bool writable() const noexcept { return writable_; }
constexpr Options& writable(bool const value) noexcept {
writable_ = value;
return *this;
}
private:
bool writable_ = false;
};
ElfFile() noexcept;
explicit ElfFile(const char* name, Options const& options = Options());
enum OpenResultCode : int {
kSuccess = 0,
kSystemError = -1,
kInvalidElfFile = -2,
};
struct OpenResult {
OpenResultCode code{};
char const* msg{};
constexpr operator OpenResultCode() const noexcept {
return code;
}
};
OpenResult openNoThrow(
const char* name, Options const& options = Options()) noexcept;
OpenResult openAndFollow(
const char* name, Options const& options = Options()) noexcept;
void open(const char* name, Options const& options = Options());
~ElfFile();
ElfFile(ElfFile&& other) noexcept;
ElfFile& operator=(ElfFile&& other) noexcept;
const ElfEhdr& elfHeader() const noexcept { return at<ElfEhdr>(0); }
uintptr_t getBaseAddress() const noexcept { return baseAddress_; }
const ElfShdr* getSectionByName(const char* name) const noexcept;
const ElfShdr* getSectionByIndex(size_t idx) const noexcept;
const char* getSectionName(const ElfShdr& section) const noexcept;
folly::StringPiece getSectionBody(const ElfShdr& section) const noexcept;
const char* getString(
const ElfShdr& stringTable, size_t offset) const noexcept;
template <class Fn>
const char* iterateStrings(const ElfShdr& stringTable, Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, const char*>);
template <class Fn>
const ElfPhdr* iterateProgramHeaders(Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfPhdr const&>);
template <class Fn>
const ElfShdr* iterateSections(Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
template <class Fn>
const ElfShdr* iterateSectionsWithType(uint32_t type, Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
template <class Fn>
const ElfShdr* iterateSectionsWithTypes(
std::initializer_list<uint32_t> types, Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfShdr const&>);
template <class Fn>
const ElfSym* iterateSymbols(const ElfShdr& section, Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
template <class Fn>
const ElfSym* iterateSymbolsWithType(
const ElfShdr& section, uint32_t type, Fn fn) const
noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
template <class Fn>
const ElfSym* iterateSymbolsWithTypes(
const ElfShdr& section,
std::initializer_list<uint32_t> types,
Fn fn) const noexcept(is_nothrow_invocable_v<Fn, ElfSym const&>);
template <typename E, class Fn>
const E* iterateSectionEntries(const ElfShdr& section, Fn&& fn) const
noexcept(is_nothrow_invocable_v<E const&>);
typedef std::pair<const ElfShdr*, const ElfSym*> Symbol;
Symbol getDefinitionByAddress(uintptr_t address) const noexcept;
Symbol getSymbolByName(
const char* name,
std::initializer_list<uint32_t> types = {
STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept;
template <typename C, typename T = typename C::value_type>
std::unordered_map<std::string, Symbol> getSymbolsByName(
const C& names,
std::initializer_list<uint32_t> types = {
STT_OBJECT, STT_FUNC, STT_GNU_IFUNC}) const noexcept {
std::unordered_map<std::string, Symbol> result(names.size());
if (names.empty()) {
return result;
}
for (const std::string& name : names) {
result[name] = {nullptr, nullptr};
}
size_t seenCount = 0;
auto findSymbol = [&](const folly::symbolizer::ElfShdr& section,
const folly::symbolizer::ElfSym& sym) -> bool {
auto symbol = folly::symbolizer::ElfFile::Symbol(§ion, &sym);
auto name = getSymbolName(symbol);
if (name == nullptr) {
return false;
}
auto itr = result.find(name);
if (itr != result.end() && itr->second.first == nullptr &&
itr->second.second == nullptr) {
itr->second = symbol;
++seenCount;
}
return seenCount == result.size();
};
auto iterSection = [&](const folly::symbolizer::ElfShdr& section) -> bool {
iterateSymbolsWithTypes(section, types, [&](const auto& sym) -> bool {
return findSymbol(section, sym);
});
return false;
};
iterateSectionsWithType(SHT_DYNSYM, iterSection) ||
iterateSectionsWithType(SHT_SYMTAB, iterSection);
return result;
}
template <class T>
const T* getSymbolValue(const ElfSym* symbol) const noexcept {
const ElfShdr* section = getSectionByIndex(symbol->st_shndx);
if (section == nullptr) {
return nullptr;
}
return valueAt<T>(*section, symbol->st_value);
}
template <class T>
const T* getAddressValue(const ElfAddr addr) const noexcept {
const ElfShdr* section = getSectionContainingAddress(addr);
if (section == nullptr) {
return nullptr;
}
return valueAt<T>(*section, addr);
}
const char* getSymbolName(const Symbol& symbol) const noexcept;
const ElfShdr* getSectionContainingAddress(ElfAddr addr) const noexcept;
const char* filepath() const { return filepath_; }
const ElfFileId& getFileId() const { return fileId_; }
std::pair<const int, char const*> posixFadvise(
off_t offset, off_t len, int const advice) const noexcept;
std::pair<const int, char const*> posixFadvise(
int const advice) const noexcept;
private:
OpenResult init() noexcept;
void reset() noexcept;
ElfFile(const ElfFile&) = delete;
ElfFile& operator=(const ElfFile&) = delete;
void validateStringTable(const ElfShdr& stringTable) const noexcept;
template <class T>
const T& at(ElfOff offset) const noexcept {
static_assert(
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
"non-pod");
FOLLY_SAFE_CHECK(
offset + sizeof(T) <= length_,
"Offset (",
static_cast<size_t>(offset),
" + ",
sizeof(T),
") is not contained within our mapped file (",
filepath_,
") of length ",
length_);
return *reinterpret_cast<T*>(file_ + offset);
}
template <class T>
const T* valueAt(const ElfShdr& section, const ElfAddr addr) const noexcept {
if (!(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN ||
elfHeader().e_type == ET_CORE)) {
return nullptr;
}
if (!(addr >= section.sh_addr &&
(addr + sizeof(T)) <= (section.sh_addr + section.sh_size))) {
return nullptr;
}
if (section.sh_type == SHT_NOBITS) {
static T t = {};
return &t;
}
ElfOff offset = section.sh_offset + (addr - section.sh_addr);
return (offset + sizeof(T) <= length_) ? &at<T>(offset) : nullptr;
}
static constexpr size_t kFilepathMaxLen = 512;
char filepath_[kFilepathMaxLen] = {};
int fd_;
char* file_;
size_t length_;
ElfFileId fileId_;
uintptr_t baseAddress_;
};
}
}
namespace std {
template <>
struct hash<folly::symbolizer::ElfFileId> {
size_t operator()(const folly::symbolizer::ElfFileId fileId) const {
return folly::hash::hash_combine(
fileId.dev, fileId.inode, fileId.size, fileId.mtime);
}
};
}
#include <folly/debugging/symbolizer/Elf-inl.h>
#endif