/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
#include <folly/experimental/symbolizer/Elf.h>
-#include <sys/mman.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <fcntl.h>
+#include <folly/portability/SysMman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <string>
#include <folly/Conv.h>
#include <folly/Exception.h>
+#include <folly/ScopeGuard.h>
namespace folly {
namespace symbolizer {
}
}
-int ElfFile::openNoThrow(const char* name, bool readOnly, const char** msg)
- noexcept {
+int ElfFile::openNoThrow(const char* name,
+ bool readOnly,
+ const char** msg) noexcept {
FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
fd_ = ::open(name, readOnly ? O_RDONLY : O_RDWR);
if (fd_ == -1) {
if (msg) *msg = "open";
return kSystemError;
}
-
+ // 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
+ // like it is inside SignalSafeElfCache
+ ScopeGuard guard = makeGuard([&]{ reset(); });
struct stat st;
int r = fstat(fd_, &st);
if (r == -1) {
errno = EINVAL;
return kInvalidElfFile;
}
-
+ guard.dismiss();
return kSuccess;
}
+int ElfFile::openAndFollow(const char* name,
+ bool readOnly,
+ const char** msg) noexcept {
+ auto result = openNoThrow(name, readOnly, msg);
+ if (!readOnly || 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, readOnly, msg);
+ if (result == kSuccess) return result;
+ return openNoThrow(name, readOnly, msg);
+}
+
ElfFile::~ElfFile() {
- destroy();
+ reset();
}
ElfFile::ElfFile(ElfFile&& other) noexcept
ElfFile& ElfFile::operator=(ElfFile&& other) {
assert(this != &other);
- destroy();
+ reset();
fd_ = other.fd_;
file_ = other.file_;
return *this;
}
-void ElfFile::destroy() {
+void ElfFile::reset() {
if (file_ != MAP_FAILED) {
munmap(file_, length_);
+ file_ = static_cast<char*>(MAP_FAILED);
}
if (fd_ != -1) {
close(fd_);
+ fd_ = -1;
}
}
#undef EXPECTED_CLASS
// Validate ELF data encoding (LSB/MSB)
-#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
-# define EXPECTED_ENCODING ELFDATA2LSB
-#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
-# define EXPECTED_ENCODING ELFDATA2MSB
-#else
-# error Unsupported byte order
-#endif
- if (elfHeader.e_ident[EI_DATA] != EXPECTED_ENCODING) {
+ static constexpr auto kExpectedEncoding =
+ kIsLittleEndian ? ELFDATA2LSB : ELFDATA2MSB;
+ if (elfHeader.e_ident[EI_DATA] != kExpectedEncoding) {
if (msg) *msg = "invalid ELF encoding";
return false;
}
-#undef EXPECTED_ENCODING
// Validate ELF version (1)
if (elfHeader.e_ident[EI_VERSION] != EV_CURRENT ||