//===--- StaticBinaryELF.cpp ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Parse a static ELF binary to implement lookupSymbol() address lookup.
//
//===----------------------------------------------------------------------===//

#if defined(__ELF__) && defined(__linux__)

#include "ImageInspection.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
#include "swift/Basic/Lazy.h"
#include <cassert>
#include <string>
#include <vector>
#include <elf.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <linux/limits.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

using namespace std;
using namespace llvm;

#ifdef __LP64__
#define ELFCLASS ELFCLASS64
typedef Elf64_Ehdr Elf_Ehdr;
typedef Elf64_Shdr Elf_Shdr;
typedef Elf64_Phdr Elf_Phdr;
typedef Elf64_Addr Elf_Addr;
typedef Elf64_Word Elf_Word;
typedef Elf64_Sym Elf_Sym;
typedef Elf64_Section Elf_Section;
#define ELF_ST_TYPE(x) ELF64_ST_TYPE(x)
#else
#define ELFCLASS ELFCLASS32
typedef Elf32_Ehdr Elf_Ehdr;
typedef Elf32_Shdr Elf_Shdr;
typedef Elf32_Phdr Elf_Phdr;
typedef Elf32_Addr Elf_Addr;
typedef Elf32_Word Elf_Word;
typedef Elf32_Sym Elf_Sym;
typedef Elf32_Section Elf_Section;
#define ELF_ST_TYPE(x) ELF32_ST_TYPE(x)
#endif

extern const Elf_Ehdr elfHeader asm("__ehdr_start");

class StaticBinaryELF {

private:
  // mmap a section of a file that might not be page aligned.
  class Mapping {
  public:
    void *mapping;
    size_t mapLength;
    off_t diff;

    Mapping(int fd, size_t fileSize, off_t offset, size_t length) {
      if (fd < 0 || offset + length > fileSize) {
        mapping = nullptr;
        mapLength = 0;
        diff = 0;
        return;
      }
      long pageSize = sysconf(_SC_PAGESIZE);
      long pageMask = ~(pageSize - 1);

      off_t alignedOffset = offset & pageMask;
      diff = (offset - alignedOffset);
      mapLength = diff + length;
      mapping = mmap(nullptr, mapLength, PROT_READ, MAP_PRIVATE, fd,
                     alignedOffset);
      if (mapping == MAP_FAILED) {
        mapping = nullptr;
        mapLength = 0;
        diff = 0;
      }
    }

    template<typename T>
    ArrayRef<T> data() {
      size_t elements = (mapLength - diff) / sizeof(T);
      const T *data = reinterpret_cast<T *>(reinterpret_cast<char *>(mapping)
                                            + diff);
      return ArrayRef<T>(data, elements);
    }

    ~Mapping() {
      if (mapping && mapLength > 0) {
        munmap(mapping, mapLength);
      }
    }
  };

  string fullPathName;
  const Elf_Phdr *programHeaders = nullptr;
  Optional<Mapping> symbolTable;
  Optional<Mapping> stringTable;

public:
  StaticBinaryELF() {
    getExecutablePathName();

    programHeaders = reinterpret_cast<const Elf_Phdr *>(getauxval(AT_PHDR));
    if (programHeaders == nullptr) {
      return;
    }
    // If an interpreter is set in the program headers then this is a
    // dynamic executable and therefore not valid.
    for (size_t idx = 0; idx < elfHeader.e_phnum; idx++) {
      if (programHeaders[idx].p_type == PT_INTERP) {
        programHeaders = nullptr;
        return;
      }
    }

    if (!fullPathName.empty()) {
      mmapExecutable();
    }
  }

  const char *getPathName() {
    return fullPathName.empty() ? nullptr : fullPathName.c_str();
  }

  void *getSectionLoadAddress(const void *addr) {
    if (programHeaders) {
      auto searchAddr = reinterpret_cast<Elf_Addr>(addr);

      for (size_t idx = 0; idx < elfHeader.e_phnum; idx++) {
        auto header = &programHeaders[idx];
        if (header->p_type == PT_LOAD && searchAddr >= header->p_vaddr
            && searchAddr <= (header->p_vaddr + header->p_memsz)) {
          return reinterpret_cast<void *>(header->p_vaddr);
        }
      }
    }
    return nullptr;
  }

  // Lookup a function symbol by address.
  const Elf_Sym *findSymbol(const void *addr) {
    if (symbolTable.hasValue()) {
      auto searchAddr = reinterpret_cast<Elf_Addr>(addr);
      auto symbols = symbolTable->data<Elf_Sym>();

      for (size_t idx = 0; idx < symbols.size(); idx++) {
        auto symbol = &symbols[idx];
        if (ELF_ST_TYPE(symbol->st_info) == STT_FUNC
            && searchAddr >= symbol->st_value
            && searchAddr < (symbol->st_value + symbol->st_size)) {
          return symbol;
        }
      }
    }
    return nullptr;
  }

  const char *symbolName(const Elf_Sym *symbol) {
    if (stringTable.hasValue()) {
      auto strings = stringTable->data<char>();
      if (symbol->st_name < strings.size()) {
        return &strings[symbol->st_name];
      }
    }
    return nullptr;
  }

private:
  // This is Linux specific - find the full path of the executable
  // by looking in /proc/self/maps for a mapping holding the current
  // address space. For a static binary it should only be mapping one
  // file anyway. Don't use /proc/self/exe as the symlink will be removed
  // if the main thread terminates - see proc(5).
  void getExecutablePathName() {
    uintptr_t address = (uintptr_t)&elfHeader;

    FILE *fp = fopen("/proc/self/maps", "r");
    if (!fp) {
      perror("Unable to open /proc/self/maps");
    } else {
      char *line = nullptr;
      size_t size = 0;
      // Format is: addrLo-addrHi perms offset dev inode pathname.
      // If the executable has been deleted the last column will be '(deleted)'.
      StringRef deleted = StringRef("(deleted)");

      while (getdelim(&line, &size, '\n', fp) != -1) {
        StringRef entry = StringRef(line).rsplit('\n').first;
        auto addrRange = entry.split(' ').first.split('-');
        unsigned long long low = strtoull(addrRange.first.str().c_str(),
                                          nullptr, 16);
        if (low == 0 || low > UINTPTR_MAX || address < (uintptr_t)low) {
          continue;
        }

        unsigned long long high = strtoull(addrRange.second.str().c_str(),
                                           nullptr, 16);
        if (high == 0 || high > UINTPTR_MAX || address > (uintptr_t)high) {
          continue;
        }

        auto fname = entry.split('/').second;
        if (fname.empty() || fname.endswith(deleted)) {
          continue;
        }

        fullPathName = "/" + fname.str();
        break;
      }
      if (line) {
        free(line);
      }
      fclose(fp);
    }
  }


  // Parse the ELF binary using mmap for the section headers, symbol table and
  // string table.
  void mmapExecutable() {
    struct stat buf;
    int fd = open(fullPathName.c_str(), O_RDONLY);
    if (fd < 0) {
      return;
    }

    if (fstat(fd, &buf) != 0) {
      close(fd);
      return;
    }

    // Map in the section headers.
    size_t sectionHeadersSize = (elfHeader.e_shentsize * elfHeader.e_shnum);
    Mapping sectionHeaders = Mapping(fd, buf.st_size, elfHeader.e_shoff,
                                     sectionHeadersSize);
    if (sectionHeaders.mapping) {
      auto headers = sectionHeaders.data<Elf_Shdr>();
      auto section = findSectionHeader(headers, SHT_SYMTAB);
      if (section) {
        assert(section->sh_entsize == sizeof(Elf_Sym));
        symbolTable.emplace(fd, buf.st_size, section->sh_offset,
                            section->sh_size);
        if (symbolTable->mapping == nullptr) {
          symbolTable.reset();
        }
      }
      section = findSectionHeader(headers, SHT_STRTAB);
      if (section) {
        stringTable.emplace(fd, buf.st_size, section->sh_offset,
                            section->sh_size);
        if (stringTable->mapping == nullptr) {
          stringTable.reset();
        }
      }
    }
    close(fd);
    return;
  }

  // Find the section header of a specified type in the section headers table.
  const Elf_Shdr *findSectionHeader(ArrayRef<Elf_Shdr> headers,
                                    Elf_Word sectionType) {
    assert(elfHeader.e_shnum == headers.size());
    for (size_t idx = 0; idx < headers.size(); idx++) {
      if (idx == elfHeader.e_shstrndx) {
        continue;
      }
      auto header = &headers[idx];
      if (header->sh_type == sectionType) {
        if (header->sh_entsize > 0 && header->sh_size % header->sh_entsize) {
          fprintf(stderr,
                  "section size is not a multiple of entrysize (%ld/%ld)\n",
                  header->sh_size, header->sh_entsize);
          return nullptr;
        }
        return header;
      }
    }
    return nullptr;
  }
};


static swift::Lazy<StaticBinaryELF> TheBinary;

int
swift::lookupSymbol(const void *address, SymbolInfo *info) {
  // The pointers returned point into the mmap()'d binary so keep the
  // object once instantiated.
  auto &binary = TheBinary.get();

  info->fileName = binary.getPathName();
  info->baseAddress = binary.getSectionLoadAddress(address);

  auto symbol = binary.findSymbol(address);
  if (symbol != nullptr) {
    info->symbolAddress = reinterpret_cast<void *>(symbol->st_value);
    info->symbolName = binary.symbolName(symbol);
  } else {
    info->symbolAddress = nullptr;
    info->symbolName = nullptr;
  }

  return 1;
}

#endif // defined(__ELF__) && defined(__linux__)
