// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CRASHPAD_SNAPSHOT_ELF_ELF_IMAGE_READER_H_
#define CRASHPAD_SNAPSHOT_ELF_ELF_IMAGE_READER_H_

#include <elf.h>
#include <stdint.h>
#include <sys/types.h>

#include <memory>
#include <string>

#include "snapshot/elf/elf_dynamic_array_reader.h"
#include "snapshot/elf/elf_symbol_table_reader.h"
#include "util/misc/address_types.h"
#include "util/misc/initialization_state.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/process/process_memory_range.h"

namespace crashpad {

//! \brief A reader for ELF images mapped into another process.
//!
//! This class is capable of reading both 32-bit and 64-bit images.
class ElfImageReader {
 private:
  class ProgramHeaderTable;

 public:
  //! \brief This class enables reading note segments from an ELF image.
  //!
  //! Objects of this class should be created by calling
  //! ElfImageReader::Notes() or ElfImageReader::NotesWithNameAndType().
  class NoteReader {
   public:
    NoteReader(const NoteReader&) = delete;
    NoteReader& operator=(const NoteReader&) = delete;

    ~NoteReader();

    //! \brief The return value for NextNote().
    enum class Result {
      //! \brief An error occurred. The NoteReader is invalidated and message is
      //!     logged.
      kError,

      //! \brief A note was found.
      kSuccess,

      //! \brief No more notes were found.
      kNoMoreNotes,
    };

    //! \brief A type large enough to hold a note type, potentially across
    //!     bitness.
    using NoteType = decltype(Elf64_Nhdr::n_type);

    //! \brief Searches for the next note in the image.
    //!
    //! \param[out] name The name of the note owner, if not `nullptr`.
    //! \param[out] type A type for the note, if not `nullptr`.
    //! \param[out] desc The note descriptor.
    //! \param[out] desc_addr The address in the remote process' address space
    //!     \a desc was read from.
    //! \return a #Result value. \a name, \a type, \a desc, and \a desc_addr are
    //!     only valid if this method returns Result::kSuccess.
    Result NextNote(std::string* name,
                    NoteType* type,
                    std::string* desc,
                    VMAddress* desc_addr);

    // private
    NoteReader(const ElfImageReader* elf_reader_,
               const ProcessMemoryRange* range,
               const ProgramHeaderTable* phdr_table,
               size_t max_note_size,
               const std::string& name_filter = std::string(),
               NoteType type_filter = 0,
               bool use_filter = false);

   private:
    // Reads the next note at the current segment address. Sets retry_ to true
    // and returns kError if use_filter_ is true and the note's name and type do
    // not match name_filter_ and type_filter_.
    template <typename T>
    Result ReadNote(std::string* name,
                    NoteType* type,
                    std::string* desc,
                    VMAddress* desc_addr);

    VMAddress current_address_;
    VMAddress segment_end_address_;
    const ElfImageReader* elf_reader_;  // weak
    const ProcessMemoryRange* range_;  // weak
    const ProgramHeaderTable* phdr_table_;  // weak
    std::unique_ptr<ProcessMemoryRange> segment_range_;
    size_t phdr_index_;
    size_t max_note_size_;
    std::string name_filter_;
    NoteType type_filter_;
    bool use_filter_;
    bool is_valid_;
    bool retry_;
  };

  ElfImageReader();

  ElfImageReader(const ElfImageReader&) = delete;
  ElfImageReader& operator=(const ElfImageReader&) = delete;

  ~ElfImageReader();

  //! \brief Initializes the reader.
  //!
  //! This method must be called once on an object and must be successfully
  //! called before any other method in this class may be called.
  //!
  //! \param[in] memory A memory reader for the remote process.
  //! \param[in] address The address in the remote process' address space where
  //!     the ELF image is loaded.
  //! \param[in] verbose `true` if this method should log error messages during
  //!     initialization. Setting this value to `false` will reduce the error
  //!     messages relating to verifying the ELF image, but may not suppress
  //!     logging entirely.
  bool Initialize(const ProcessMemoryRange& memory,
                  VMAddress address,
                  bool verbose = true);

  //! \brief Returns the base address of the image's memory range.
  //!
  //! This may differ from the address passed to Initialize() if the ELF header
  //! is not loaded at the start of the first `PT_LOAD` segment.
  VMAddress Address() const { return memory_.Base(); }

  //! \brief Returns the size of the range containing all loaded segments for
  //!     this image.
  //!
  //! The size may include memory that is unmapped or mapped to other objects if
  //! this image's `PT_LOAD` segments are not contiguous.
  VMSize Size() const { return memory_.Size(); }

  //! \brief Returns the file type for the image.
  //!
  //! Possible values include `ET_EXEC` or `ET_DYN` from `<elf.h>`.
  uint16_t FileType() const;

  //! \brief Returns the load bias for the image.
  //!
  //! The load bias is the actual load address minus the preferred load address.
  VMOffset GetLoadBias() const { return load_bias_; }

  //! \brief Determines the name of this object using `DT_SONAME`, if present.
  //!
  //! \param[out] name The name of this object, only valid if this method
  //!     returns `true`.
  //! \return `true` if a name was found for this object.
  bool SoName(std::string* name);

  //! \brief Reads information from the dynamic symbol table about the symbol
  //!     identified by \a name.
  //!
  //! \param[in] name The name of the symbol to search for.
  //! \param[out] address The address of the symbol in the target process'
  //!     address space, if found.
  //! \param[out] size The size of the symbol, if found.
  //! \return `true` if the symbol was found.
  bool GetDynamicSymbol(const std::string& name,
                        VMAddress* address,
                        VMSize* size);

  //! \brief Reads a `NUL`-terminated C string from this image's dynamic string
  //!     table.
  //!
  //! \param[in] offset the byte offset in the string table to start reading.
  //! \param[out] string the string read.
  //! \return `true` on success. Otherwise `false` with a message logged.
  bool ReadDynamicStringTableAtOffset(VMSize offset, std::string* string);

  //! \brief Determine the debug address.
  //!
  //! The debug address is a pointer to an `r_debug` struct defined in
  //! `<link.h>`.
  //!
  //! \param[out] debug the debug address, if found.
  //! \return `true` if the debug address was found.
  bool GetDebugAddress(VMAddress* debug);

  //! \brief Determine the address of `PT_DYNAMIC` segment.
  //!
  //! \param[out] address The address of the array, valid if this method returns
  //!     `true`.
  //! \return `true` on success. Otherwise `false` with a message logged.
  bool GetDynamicArrayAddress(VMAddress* address);

  //! \brief Return the address of the program header table.
  VMAddress GetProgramHeaderTableAddress();

  //! \brief Return a NoteReader for this image, which scans all PT_NOTE
  //!     segments in the image.
  //!
  //! The returned NoteReader is only valid for the lifetime of the
  //! ElfImageReader that created it.
  //!
  //! \param[in] max_note_size The maximum note size to read. Notes whose
  //!     combined name, descriptor, and padding size are greater than
  //!     \a max_note_size will be silently skipped.
  //! \return A NoteReader object capable of reading notes in this image.
  std::unique_ptr<NoteReader> Notes(size_t max_note_size);

  //! \brief Return a NoteReader for this image, which scans all PT_NOTE
  //!     segments in the image, filtering by name and type.
  //!
  //! The returned NoteReader is only valid for the lifetime of the
  //! ElfImageReader that created it.
  //!
  //! \param[in] name The note name to match.
  //! \param[in] type The note type to match.
  //! \param[in] max_note_size The maximum note size to read. Notes whose
  //!     combined name, descriptor, and padding size are greater than
  //!     \a max_note_size will be silently skipped.
  //! \return A NoteReader object capable of reading notes in this image.
  std::unique_ptr<NoteReader> NotesWithNameAndType(const std::string& name,
                                                   NoteReader::NoteType type,
                                                   size_t max_note_size);

  //! \brief Return a ProcessMemoryRange restricted to the range of this image.
  //!
  //! The caller does not take ownership of the returned object.
  const ProcessMemoryRange* Memory() const;

  //! \brief Retrieves the number of symbol table entries in `DT_SYMTAB`
  //!     according to the data in the `DT_HASH` section.
  //!
  //! \note Exposed for testing, not normally otherwise useful.
  //!
  //! \param[out] number_of_symbol_table_entries The number of entries expected
  //!     in `DT_SYMTAB`.
  //! \return `true` if a `DT_HASH` section was found, and was read
  //!     successfully, otherwise `false` with an error logged.
  bool GetNumberOfSymbolEntriesFromDtHash(
      VMSize* number_of_symbol_table_entries);

  //! \brief Retrieves the number of symbol table entries in `DT_SYMTAB`
  //!     according to the data in the `DT_GNU_HASH` section.
  //!
  //! \note Exposed for testing, not normally otherwise useful.
  //!
  //! \note Depending on the linker that generated the `DT_GNU_HASH` section,
  //!     this value may not be as expected if there are zero exported symbols.
  //!
  //! \param[out] number_of_symbol_table_entries The number of entries expected
  //!     in `DT_SYMTAB`.
  //! \return `true` if a `DT_GNU_HASH` section was found, and was read
  //!     successfully, otherwise `false` with an error logged.
  bool GetNumberOfSymbolEntriesFromDtGnuHash(
      VMSize* number_of_symbol_table_entries);

 private:
  template <typename PhdrType>
  class ProgramHeaderTableSpecific;

  bool InitializeProgramHeaders(bool verbose);
  bool InitializeDynamicArray();
  bool InitializeDynamicSymbolTable();
  bool GetAddressFromDynamicArray(uint64_t tag, bool log, VMAddress* address);

  union {
    Elf32_Ehdr header_32_;
    Elf64_Ehdr header_64_;
  };
  VMAddress ehdr_address_;
  VMOffset load_bias_;
  ProcessMemoryRange memory_;
  std::unique_ptr<ProgramHeaderTable> program_headers_;
  std::unique_ptr<ElfDynamicArrayReader> dynamic_array_;
  std::unique_ptr<ElfSymbolTableReader> symbol_table_;
  InitializationStateDcheck initialized_;
  InitializationState dynamic_array_initialized_;
  InitializationState symbol_table_initialized_;
};

}  // namespace crashpad

#endif  // CRASHPAD_SNAPSHOT_ELF_ELF_IMAGE_READER_H_
