// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_
#define COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_

#include <stdint.h>

#include <algorithm>
#include <deque>
#include <memory>
#include <string>
#include <vector>

#include "components/zucchini/address_translator.h"
#include "components/zucchini/buffer_view.h"
#include "components/zucchini/disassembler.h"
#include "components/zucchini/image_utils.h"
#include "components/zucchini/rel32_finder.h"
#include "components/zucchini/rel32_utils.h"
#include "components/zucchini/reloc_elf.h"
#include "components/zucchini/type_elf.h"

namespace zucchini {

struct ArmReferencePool {
  enum : uint8_t {
    kPoolReloc,
    kPoolAbs32,
    kPoolRel32,
  };
};

struct AArch32ReferenceType {
  enum : uint8_t {
    kReloc,  // kPoolReloc

    kAbs32,  // kPoolAbs32

    kRel32_A24,  // kPoolRel32
    kRel32_T8,
    kRel32_T11,
    kRel32_T20,
    kRel32_T24,

    kTypeCount
  };
};

struct AArch64ReferenceType {
  enum : uint8_t {
    kReloc,  // kPoolReloc

    kAbs32,  // kPoolAbs32

    kRel32_Immd14,  // kPoolRel32
    kRel32_Immd19,
    kRel32_Immd26,

    kTypeCount
  };
};

struct Elf32Traits {
  static constexpr uint16_t kVersion = 1;
  static constexpr Bitness kBitness = kBit32;
  static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS32;
  using Elf_Shdr = elf::Elf32_Shdr;
  using Elf_Phdr = elf::Elf32_Phdr;
  using Elf_Ehdr = elf::Elf32_Ehdr;
  using Elf_Rel = elf::Elf32_Rel;
  using Elf_Rela = elf::Elf32_Rela;
};

// Architecture-specific definitions.

struct Elf32IntelTraits : public Elf32Traits {
  static constexpr ExecutableType kExeType = kExeTypeElfX86;
  static const char kExeTypeString[];
  static constexpr elf::MachineArchitecture kMachineValue = elf::EM_386;
  static constexpr uint32_t kRelType = elf::R_386_RELATIVE;
  enum : uint32_t { kVAWidth = 4 };
  using Rel32FinderUse = Rel32FinderX86;
};

struct ElfAArch32Traits : public Elf32Traits {
  static constexpr ExecutableType kExeType = kExeTypeElfAArch32;
  static const char kExeTypeString[];
  static constexpr elf::MachineArchitecture kMachineValue = elf::EM_ARM;
  static constexpr uint32_t kRelType = elf::R_ARM_RELATIVE;
  enum : uint32_t { kVAWidth = 4 };
  using ArmReferenceType = AArch32ReferenceType;
  using Rel32FinderUse = Rel32FinderAArch32;
};

struct Elf64Traits {
  static constexpr uint16_t kVersion = 1;
  static constexpr Bitness kBitness = kBit64;
  static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS64;
  using Elf_Shdr = elf::Elf64_Shdr;
  using Elf_Phdr = elf::Elf64_Phdr;
  using Elf_Ehdr = elf::Elf64_Ehdr;
  using Elf_Rel = elf::Elf64_Rel;
  using Elf_Rela = elf::Elf64_Rela;
};

// Architecture-specific definitions.
struct Elf64IntelTraits : public Elf64Traits {
  static constexpr ExecutableType kExeType = kExeTypeElfX64;
  static const char kExeTypeString[];
  static constexpr elf::MachineArchitecture kMachineValue = elf::EM_X86_64;
  static constexpr uint32_t kRelType = elf::R_X86_64_RELATIVE;
  enum : uint32_t { kVAWidth = 8 };
  using Rel32FinderUse = Rel32FinderX64;
};

struct ElfAArch64Traits : public Elf64Traits {
  static constexpr ExecutableType kExeType = kExeTypeElfAArch64;
  static const char kExeTypeString[];
  static constexpr elf::MachineArchitecture kMachineValue = elf::EM_AARCH64;
  // TODO(huangs): See if R_AARCH64_GLOB_DAT and R_AARCH64_JUMP_SLOT should be
  // used.
  static constexpr uint32_t kRelType = elf::R_AARCH64_RELATIVE;
  enum : uint32_t { kVAWidth = 8 };
  using ArmReferenceType = AArch64ReferenceType;
  using Rel32FinderUse = Rel32FinderAArch64;
};

// Decides whether target |offset| is covered by a section in |sorted_headers|.
template <class ELF_SHDR>
bool IsTargetOffsetInElfSectionList(
    const std::vector<const ELF_SHDR*>& sorted_headers,
    offset_t offset) {
  // Use binary search to search in a list of intervals, in a fashion similar to
  // AddressTranslator::OffsetToUnit().
  auto comp = [](offset_t offset, const ELF_SHDR* header) -> bool {
    return offset < header->sh_offset;
  };
  auto it = std::upper_bound(sorted_headers.begin(), sorted_headers.end(),
                             offset, comp);
  if (it == sorted_headers.begin())
    return false;
  --it;
  // Just check offset without worrying about width, since this is a target.
  // Not using RangeCovers() because |sh_offset| and |sh_size| can be 64-bit.
  return offset >= (*it)->sh_offset &&
         offset - (*it)->sh_offset < (*it)->sh_size;
}

// Disassembler for ELF.
template <class TRAITS>
class DisassemblerElf : public Disassembler {
 public:
  using Traits = TRAITS;
  static constexpr uint16_t kVersion = Traits::kVersion;
  // Applies quick checks to determine whether |image| *may* point to the start
  // of an executable. Returns true iff the check passes.
  static bool QuickDetect(ConstBufferView image);

  DisassemblerElf(const DisassemblerElf&) = delete;
  const DisassemblerElf& operator=(const DisassemblerElf&) = delete;
  ~DisassemblerElf() override;

  // Disassembler:
  ExecutableType GetExeType() const override;
  std::string GetExeTypeString() const override;
  std::vector<ReferenceGroup> MakeReferenceGroups() const override = 0;

  // Read/Write functions that are common among different architectures.
  std::unique_ptr<ReferenceReader> MakeReadRelocs(offset_t lo, offset_t hi);
  std::unique_ptr<ReferenceWriter> MakeWriteRelocs(MutableBufferView image);

  const AddressTranslator& translator() const { return translator_; }

 protected:
  friend Disassembler;

  DisassemblerElf();

  bool Parse(ConstBufferView image) override;

  // Returns the supported Elf_Ehdr::e_machine enum.
  static constexpr elf::MachineArchitecture supported_architecture() {
    return Traits::kMachineValue;
  }

  // Returns the type to look for in the reloc section.
  static constexpr uint32_t supported_relocation_type() {
    return Traits::kRelType;
  }

  // Performs architecture-specific parsing of an executable section, to extract
  // rel32 references.
  virtual void ParseExecSection(const typename Traits::Elf_Shdr& section) = 0;

  // Processes rel32 data after they are extracted from executable sections.
  virtual void PostProcessRel32() = 0;

  // Parses ELF header and section headers, and performs basic validation.
  // Returns whether parsing was successful.
  bool ParseHeader();

  // Extracts and stores section headers that we need.
  void ExtractInterestingSectionHeaders();

  // Parsing functions that extract references from various sections.
  void GetAbs32FromRelocSections();
  void GetRel32FromCodeSections();
  void ParseSections();

  // Main ELF header.
  const typename Traits::Elf_Ehdr* header_ = nullptr;

  // Section header table, ordered by section id.
  elf::Elf32_Half sections_count_ = 0;
  const typename Traits::Elf_Shdr* sections_ = nullptr;

  // Program header table.
  elf::Elf32_Half segments_count_ = 0;
  const typename Traits::Elf_Phdr* segments_ = nullptr;

  // Bit fields to store the role each section may play.
  std::vector<int> section_judgements_;

  // Translator between offsets and RVAs.
  AddressTranslator translator_;

  // Identity translator for abs32 translation.
  AddressTranslator identity_translator_;

  // Extracted relocation section dimensions data, sorted by file offsets.
  std::vector<SectionDimensionsElf> reloc_section_dims_;

  // Headers of executable sections, sorted by file offsets of the data each
  // header points to.
  std::vector<const typename Traits::Elf_Shdr*> exec_headers_;

  // Sorted file offsets of abs32 locations.
  std::vector<offset_t> abs32_locations_;
};

// Disassembler for ELF with Intel architectures.
template <class TRAITS>
class DisassemblerElfIntel : public DisassemblerElf<TRAITS> {
 public:
  using Traits = TRAITS;
  enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount };

  DisassemblerElfIntel();
  DisassemblerElfIntel(const DisassemblerElfIntel&) = delete;
  const DisassemblerElfIntel& operator=(const DisassemblerElfIntel&) = delete;
  ~DisassemblerElfIntel() override;

  // Disassembler:
  std::vector<ReferenceGroup> MakeReferenceGroups() const override;

  // DisassemblerElf:
  void ParseExecSection(const typename Traits::Elf_Shdr& section) override;
  void PostProcessRel32() override;

  // Specialized Read/Write functions.
  std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi);
  std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image);
  std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lo, offset_t hi);
  std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image);

 private:
  // Sorted file offsets of rel32 locations.
  // Using std::deque to reduce peak memory footprint.
  std::deque<offset_t> rel32_locations_;
};

using DisassemblerElfX86 = DisassemblerElfIntel<Elf32IntelTraits>;
using DisassemblerElfX64 = DisassemblerElfIntel<Elf64IntelTraits>;

// Disassembler for ELF with ARM architectures.
template <class TRAITS>
class DisassemblerElfArm : public DisassemblerElf<TRAITS> {
 public:
  using Traits = TRAITS;
  DisassemblerElfArm();
  DisassemblerElfArm(const DisassemblerElfArm&) = delete;
  const DisassemblerElfArm& operator=(const DisassemblerElfArm&) = delete;
  ~DisassemblerElfArm() override;

  // Determines whether target |offset| is in an executable section.
  bool IsTargetOffsetInExecSection(offset_t offset) const;

  // Creates an architecture-specific Rel32Finder for ParseExecSection.
  virtual std::unique_ptr<typename Traits::Rel32FinderUse> MakeRel32Finder(
      const typename Traits::Elf_Shdr& section) = 0;

  // DisassemblerElf:
  void ParseExecSection(const typename Traits::Elf_Shdr& section) override;
  void PostProcessRel32() override;

  // Specialized Read/Write functions.
  std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi);
  std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image);

  // Specialized Read/Write functions for different rel32 address types.
  template <class ADDR_TRAITS>
  std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lower,
                                                 offset_t upper);
  template <class ADDR_TRAITS>
  std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image);

 protected:
  // Sorted file offsets of rel32 locations for each rel32 address type.
  std::deque<offset_t>
      rel32_locations_table_[Traits::ArmReferenceType::kTypeCount];
};

// Disassembler for ELF with AArch32 (AKA ARM32).
class DisassemblerElfAArch32 : public DisassemblerElfArm<ElfAArch32Traits> {
 public:
  DisassemblerElfAArch32();
  DisassemblerElfAArch32(const DisassemblerElfAArch32&) = delete;
  const DisassemblerElfAArch32& operator=(const DisassemblerElfAArch32&) =
      delete;
  ~DisassemblerElfAArch32() override;

  // Disassembler:
  std::vector<ReferenceGroup> MakeReferenceGroups() const override;

  // DisassemblerElfArm:
  std::unique_ptr<typename Traits::Rel32FinderUse> MakeRel32Finder(
      const typename Traits::Elf_Shdr& section) override;

  // Under the naive assumption that an executable section is entirely ARM mode
  // or THUMB2 mode, this function implements heuristics to distinguish between
  // the two. Returns true if section is THUMB2 mode; otherwise return false.
  bool IsExecSectionThumb2(const typename Traits::Elf_Shdr& section) const;
};

// Disassembler for ELF with AArch64 (AKA ARM64).
class DisassemblerElfAArch64 : public DisassemblerElfArm<ElfAArch64Traits> {
 public:
  DisassemblerElfAArch64();
  DisassemblerElfAArch64(const DisassemblerElfAArch64&) = delete;
  const DisassemblerElfAArch64& operator=(const DisassemblerElfAArch64&) =
      delete;
  ~DisassemblerElfAArch64() override;

  // Disassembler:
  std::vector<ReferenceGroup> MakeReferenceGroups() const override;

  // DisassemblerElfArm:
  std::unique_ptr<typename Traits::Rel32FinderUse> MakeRel32Finder(
      const typename Traits::Elf_Shdr& section) override;
};

}  // namespace zucchini

#endif  // COMPONENTS_ZUCCHINI_DISASSEMBLER_ELF_H_
