//===-- NativeProcessELFTest.cpp --------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TestingSupport/Host/NativeProcessTestUtils.h"

#include "Plugins/Process/POSIX/NativeProcessELF.h"
#include "Plugins/Process/Utility/AuxVector.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataEncoder.h"
#include "lldb/Utility/DataExtractor.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/MemoryBuffer.h"

#include "gmock/gmock.h"

using namespace lldb_private;
using namespace lldb;
using namespace testing;

namespace {
class MockProcessELF : public MockProcess<NativeProcessELF> {
public:
  using MockProcess::MockProcess;
  using NativeProcessELF::GetAuxValue;
  using NativeProcessELF::GetELFImageInfoAddress;
};

std::unique_ptr<llvm::MemoryBuffer> CreateAuxvData(
    MockProcessELF &process,
    llvm::ArrayRef<std::pair<AuxVector::EntryType, uint32_t>> auxv_data) {
  auto addr_size = process.GetAddressByteSize();
  DataBufferSP buffer_sp(
      new DataBufferHeap(auxv_data.size() * addr_size * 2, 0));
  DataEncoder encoder(buffer_sp, process.GetByteOrder(), addr_size);
  uint32_t offset = 0;
  for (auto &pair : auxv_data) {
    offset = encoder.PutAddress(offset, pair.first);
    offset = encoder.PutAddress(offset, pair.second);
  }
  return llvm::MemoryBuffer::getMemBufferCopy(
      llvm::toStringRef(buffer_sp->GetData()), "");
}

} // namespace

TEST(NativeProcessELFTest, GetAuxValue) {
  NiceMock<MockDelegate> DummyDelegate;
  MockProcessELF process(DummyDelegate, ArchSpec("i386-pc-linux"));

  uint64_t phdr_addr = 0x42;
  auto auxv_buffer = CreateAuxvData(
      process, {std::make_pair(AuxVector::AUXV_AT_PHDR, phdr_addr)});
  EXPECT_CALL(process, GetAuxvData())
      .WillOnce(Return(ByMove(std::move(auxv_buffer))));

  ASSERT_EQ(phdr_addr, process.GetAuxValue(AuxVector::AUXV_AT_PHDR));
}

TEST(NativeProcessELFTest, GetELFImageInfoAddress) {
  NiceMock<MockDelegate> DummyDelegate;
  MockProcessELF process(DummyDelegate, ArchSpec("i386-pc-linux"));

  uint32_t load_base = 0x1000;
  uint32_t info_addr = 0x3741;
  uint32_t phdr_addr = load_base + sizeof(llvm::ELF::Elf32_Ehdr);

  auto auxv_buffer = CreateAuxvData(
      process,
      {std::make_pair(AuxVector::AUXV_AT_PHDR, phdr_addr),
       std::make_pair(AuxVector::AUXV_AT_PHENT, sizeof(llvm::ELF::Elf32_Phdr)),
       std::make_pair(AuxVector::AUXV_AT_PHNUM, 2)});
  EXPECT_CALL(process, GetAuxvData())
      .WillOnce(Return(ByMove(std::move(auxv_buffer))));

  // We're going to set up a fake memory with 2 program headers and 1 entry in
  // the dynamic section. For simplicity sake they will be contiguous in memory.
  struct MemoryContents {
    llvm::ELF::Elf32_Phdr phdr_load;
    llvm::ELF::Elf32_Phdr phdr_dynamic;
    llvm::ELF::Elf32_Dyn dyn_debug;
  } MC;
  // Setup the 2 program header entries
  MC.phdr_load.p_type = llvm::ELF::PT_PHDR;
  MC.phdr_load.p_vaddr = phdr_addr - load_base;

  MC.phdr_dynamic.p_type = llvm::ELF::PT_DYNAMIC;
  MC.phdr_dynamic.p_vaddr =
      (phdr_addr + 2 * sizeof(llvm::ELF::Elf32_Phdr)) - load_base;
  MC.phdr_dynamic.p_memsz = sizeof(llvm::ELF::Elf32_Dyn);

  // Setup the single entry in the .dynamic section
  MC.dyn_debug.d_tag = llvm::ELF::DT_DEBUG;
  MC.dyn_debug.d_un.d_ptr = info_addr;

  FakeMemory M(&MC, sizeof(MC), phdr_addr);
  EXPECT_CALL(process, ReadMemory(_, _))
      .WillRepeatedly(Invoke(&M, &FakeMemory::Read));

  lldb::addr_t elf_info_addr = process.GetELFImageInfoAddress<
      llvm::ELF::Elf32_Ehdr, llvm::ELF::Elf32_Phdr, llvm::ELF::Elf32_Dyn>();

  // Read the address at the elf_info_addr location to make sure we're reading
  // the correct one.
  lldb::offset_t info_addr_offset = elf_info_addr - phdr_addr;
  DataExtractor mem_extractor(&MC, sizeof(MC), process.GetByteOrder(),
                              process.GetAddressByteSize());
  ASSERT_EQ(mem_extractor.GetAddress(&info_addr_offset), info_addr);
}

TEST(NativeProcessELFTest, GetELFImageInfoAddress_NoDebugEntry) {
  NiceMock<MockDelegate> DummyDelegate;
  MockProcessELF process(DummyDelegate, ArchSpec("i386-pc-linux"));

  uint32_t phdr_addr = sizeof(llvm::ELF::Elf32_Ehdr);

  auto auxv_buffer = CreateAuxvData(
      process,
      {std::make_pair(AuxVector::AUXV_AT_PHDR, phdr_addr),
       std::make_pair(AuxVector::AUXV_AT_PHENT, sizeof(llvm::ELF::Elf32_Phdr)),
       std::make_pair(AuxVector::AUXV_AT_PHNUM, 2)});
  EXPECT_CALL(process, GetAuxvData())
      .WillOnce(Return(ByMove(std::move(auxv_buffer))));

  // We're going to set up a fake memory with 2 program headers and 1 entry in
  // the dynamic section. For simplicity sake they will be contiguous in memory.
  struct MemoryContents {
    llvm::ELF::Elf32_Phdr phdr_load;
    llvm::ELF::Elf32_Phdr phdr_dynamic;
    llvm::ELF::Elf32_Dyn dyn_notdebug;
  } MC;
  // Setup the 2 program header entries
  MC.phdr_load.p_type = llvm::ELF::PT_PHDR;
  MC.phdr_load.p_vaddr = phdr_addr;

  MC.phdr_dynamic.p_type = llvm::ELF::PT_DYNAMIC;
  MC.phdr_dynamic.p_vaddr = (phdr_addr + 2 * sizeof(llvm::ELF::Elf32_Phdr));
  MC.phdr_dynamic.p_memsz = sizeof(llvm::ELF::Elf32_Dyn);

  // Setup the single entry in the .dynamic section
  MC.dyn_notdebug.d_tag = llvm::ELF::DT_NULL;

  FakeMemory M(&MC, sizeof(MC), phdr_addr);
  EXPECT_CALL(process, ReadMemory(_, _))
      .WillRepeatedly(Invoke(&M, &FakeMemory::Read));

  lldb::addr_t elf_info_addr = process.GetELFImageInfoAddress<
      llvm::ELF::Elf32_Ehdr, llvm::ELF::Elf32_Phdr, llvm::ELF::Elf32_Dyn>();

  ASSERT_EQ(elf_info_addr, LLDB_INVALID_ADDRESS);
}
