// Copyright (c) Meta Platforms, Inc. and affiliates. // All rights reserved. // // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace torch::unwind { struct Section { char* data = nullptr; size_t size = 0; const char* string(size_t offset) { return lexer(offset).readCString(); } CheckedLexer lexer(size_t offset) { return CheckedLexer(data + offset, data, data + size); } }; /// Memory maps a file into the address space read-only, and manages the /// lifetime of the mapping. Here are a few use cases: /// 1. Used in the loader to read in initial image, and to inspect // ELF files for dependencies before calling dlopen. /// /// 2. Used in unity to load the elf file. struct MemFile { explicit MemFile(const char* filename_) : fd_(open(filename_, O_RDONLY)), name_(filename_) { UNWIND_CHECK( fd_ != -1, "failed to open {}: {}", filename_, c10::utils::str_error(errno)); struct stat s{}; if (-1 == fstat(fd_, &s)) { close(fd_); // destructors don't run during exceptions UNWIND_CHECK( false, "failed to stat {}: {}", filename_, c10::utils::str_error(errno)); } n_bytes_ = s.st_size; UNWIND_CHECK( n_bytes_ > sizeof(Elf64_Ehdr), "empty shared library: {}", filename_); mem_ = (char*)mmap(nullptr, n_bytes_, PROT_READ, MAP_SHARED, fd_, 0); if (MAP_FAILED == mem_) { close(fd_); UNWIND_CHECK( false, "failed to mmap {}: {}", filename_, c10::utils::str_error(errno)); } ehdr_ = (Elf64_Ehdr*)mem_; #define ELF_CHECK(cond) UNWIND_CHECK(cond, "not an ELF file: {}", filename_) ELF_CHECK(ehdr_->e_ident[EI_MAG0] == ELFMAG0); ELF_CHECK(ehdr_->e_ident[EI_MAG1] == ELFMAG1); ELF_CHECK(ehdr_->e_ident[EI_MAG2] == ELFMAG2); ELF_CHECK(ehdr_->e_ident[EI_MAG3] == ELFMAG3); ELF_CHECK(ehdr_->e_ident[EI_CLASS] == ELFCLASS64); ELF_CHECK(ehdr_->e_ident[EI_VERSION] == EV_CURRENT); ELF_CHECK(ehdr_->e_version == EV_CURRENT); ELF_CHECK(ehdr_->e_machine == EM_X86_64); #undef ELF_CHECK UNWIND_CHECK( ehdr_->e_shoff + sizeof(Elf64_Shdr) * ehdr_->e_shnum <= n_bytes_, "invalid section header table {} {} {}", ehdr_->e_shoff + sizeof(Elf64_Shdr) * ehdr_->e_shnum, n_bytes_, ehdr_->e_shnum); shdr_ = (Elf64_Shdr*)(mem_ + ehdr_->e_shoff); UNWIND_CHECK( ehdr_->e_shstrndx < ehdr_->e_shnum, "invalid strtab section offset"); auto& strtab_hdr = shdr_[ehdr_->e_shstrndx]; strtab_ = getSection(strtab_hdr); } MemFile(const MemFile&) = delete; MemFile(MemFile&&) = delete; MemFile& operator=(const MemFile&) = delete; MemFile& operator=(MemFile&&) = delete; [[nodiscard]] const char* data() const { return (const char*)mem_; } /// Returns whether or not the file descriptor /// of the underlying file is valid. int valid() { return fcntl(fd_, F_GETFD) != -1 || errno != EBADF; } ~MemFile() { if (mem_) { munmap((void*)mem_, n_bytes_); } if (fd_ >= 0) { close(fd_); } } /// Returns the size of the underlying file defined by the `MemFile` size_t size() { return n_bytes_; } [[nodiscard]] int fd() const { return fd_; } Section getSection(const Elf64_Shdr& shdr) { UNWIND_CHECK(shdr.sh_offset + shdr.sh_size <= n_bytes_, "invalid section"); return Section{mem_ + shdr.sh_offset, shdr.sh_size}; } Section getSection(const char* name, bool optional) { for (int i = 0; i < ehdr_->e_shnum; i++) { if (strcmp(strtab_.string(shdr_[i].sh_name), name) == 0) { return getSection(shdr_[i]); } } UNWIND_CHECK(optional, "{} has no section {}", name_, name); return Section{nullptr, 0}; } Section strtab() { return strtab_; } private: template T* load(size_t offset) { UNWIND_CHECK(offset < n_bytes_, "out of range"); return (T*)(mem_ + offset); } int fd_; char* mem_{nullptr}; size_t n_bytes_{0}; std::string name_; Elf64_Ehdr* ehdr_; Elf64_Shdr* shdr_; Section strtab_ = {nullptr, 0}; }; } // namespace torch::unwind