//===-- MinidumpTypesTest.cpp -----------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// Project includes
#include "Plugins/Process/Utility/RegisterContextLinux_i386.h"
#include "Plugins/Process/Utility/RegisterContextLinux_x86_64.h"
#include "Plugins/Process/minidump/MinidumpParser.h"
#include "Plugins/Process/minidump/MinidumpTypes.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_x86_32.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_x86_64.h"

// Other libraries and framework includes
#include "gtest/gtest.h"

#include "lldb/Core/ArchSpec.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Utility/DataBufferLLVM.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/FileSpec.h"
#include "unittests/Utility/Helpers/TestUtilities.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"

// C includes

// C++ includes
#include <memory>

using namespace lldb_private;
using namespace minidump;

class MinidumpParserTest : public testing::Test {
public:
  void SetUpData(const char *minidump_filename,
                 uint64_t load_size = UINT64_MAX) {
    std::string filename = GetInputFilePath(minidump_filename);
    auto BufferPtr = DataBufferLLVM::CreateSliceFromPath(filename, load_size, 0);

    llvm::Optional<MinidumpParser> optional_parser =
        MinidumpParser::Create(BufferPtr);
    ASSERT_TRUE(optional_parser.hasValue());
    parser.reset(new MinidumpParser(optional_parser.getValue()));
    ASSERT_GT(parser->GetData().size(), 0UL);
  }

  std::unique_ptr<MinidumpParser> parser;
};

TEST_F(MinidumpParserTest, GetThreadsAndGetThreadContext) {
  SetUpData("linux-x86_64.dmp");
  llvm::ArrayRef<MinidumpThread> thread_list;

  thread_list = parser->GetThreads();
  ASSERT_EQ(1UL, thread_list.size());

  const MinidumpThread thread = thread_list[0];

  EXPECT_EQ(16001UL, thread.thread_id);

  llvm::ArrayRef<uint8_t> context = parser->GetThreadContext(thread);
  EXPECT_EQ(1232UL, context.size());
}

TEST_F(MinidumpParserTest, GetThreadsTruncatedFile) {
  SetUpData("linux-x86_64.dmp", 200);
  llvm::ArrayRef<MinidumpThread> thread_list;

  thread_list = parser->GetThreads();
  ASSERT_EQ(0UL, thread_list.size());
}

TEST_F(MinidumpParserTest, GetArchitecture) {
  SetUpData("linux-x86_64.dmp");
  ASSERT_EQ(llvm::Triple::ArchType::x86_64,
            parser->GetArchitecture().GetMachine());
  ASSERT_EQ(llvm::Triple::OSType::Linux,
            parser->GetArchitecture().GetTriple().getOS());
}

TEST_F(MinidumpParserTest, GetMiscInfo) {
  SetUpData("linux-x86_64.dmp");
  const MinidumpMiscInfo *misc_info = parser->GetMiscInfo();
  ASSERT_EQ(nullptr, misc_info);
}

TEST_F(MinidumpParserTest, GetLinuxProcStatus) {
  SetUpData("linux-x86_64.dmp");
  llvm::Optional<LinuxProcStatus> proc_status = parser->GetLinuxProcStatus();
  ASSERT_TRUE(proc_status.hasValue());
  lldb::pid_t pid = proc_status->GetPid();
  ASSERT_EQ(16001UL, pid);
}

TEST_F(MinidumpParserTest, GetPid) {
  SetUpData("linux-x86_64.dmp");
  llvm::Optional<lldb::pid_t> pid = parser->GetPid();
  ASSERT_TRUE(pid.hasValue());
  ASSERT_EQ(16001UL, pid.getValue());
}

TEST_F(MinidumpParserTest, GetModuleList) {
  SetUpData("linux-x86_64.dmp");
  llvm::ArrayRef<MinidumpModule> modules = parser->GetModuleList();
  ASSERT_EQ(8UL, modules.size());
  std::string module_names[8] = {
      "/usr/local/google/home/dvlahovski/projects/test_breakpad/a.out",
      "/lib/x86_64-linux-gnu/libm-2.19.so",
      "/lib/x86_64-linux-gnu/libc-2.19.so",
      "/lib/x86_64-linux-gnu/libgcc_s.so.1",
      "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19",
      "/lib/x86_64-linux-gnu/libpthread-2.19.so",
      "/lib/x86_64-linux-gnu/ld-2.19.so",
      "linux-gate.so",
  };

  for (int i = 0; i < 8; ++i) {
    llvm::Optional<std::string> name =
        parser->GetMinidumpString(modules[i].module_name_rva);
    ASSERT_TRUE(name.hasValue());
    EXPECT_EQ(module_names[i], name.getValue());
  }
}

TEST_F(MinidumpParserTest, GetFilteredModuleList) {
  SetUpData("linux-x86_64_not_crashed.dmp");
  llvm::ArrayRef<MinidumpModule> modules = parser->GetModuleList();
  std::vector<const MinidumpModule *> filtered_modules =
      parser->GetFilteredModuleList();
  EXPECT_EQ(10UL, modules.size());
  EXPECT_EQ(9UL, filtered_modules.size());
  // EXPECT_GT(modules.size(), filtered_modules.size());
  bool found = false;
  for (size_t i = 0; i < filtered_modules.size(); ++i) {
    llvm::Optional<std::string> name =
        parser->GetMinidumpString(filtered_modules[i]->module_name_rva);
    ASSERT_TRUE(name.hasValue());
    if (name.getValue() == "/tmp/test/linux-x86_64_not_crashed") {
      ASSERT_FALSE(found) << "There should be only one module with this name "
                             "in the filtered module list";
      found = true;
      ASSERT_EQ(0x400000UL, filtered_modules[i]->base_of_image);
    }
  }
}

TEST_F(MinidumpParserTest, GetExceptionStream) {
  SetUpData("linux-x86_64.dmp");
  const MinidumpExceptionStream *exception_stream =
      parser->GetExceptionStream();
  ASSERT_NE(nullptr, exception_stream);
  ASSERT_EQ(11UL, exception_stream->exception_record.exception_code);
}

void check_mem_range_exists(std::unique_ptr<MinidumpParser> &parser,
                            const uint64_t range_start,
                            const uint64_t range_size) {
  llvm::Optional<minidump::Range> range = parser->FindMemoryRange(range_start);
  ASSERT_TRUE(range.hasValue()) << "There is no range containing this address";
  EXPECT_EQ(range_start, range->start);
  EXPECT_EQ(range_start + range_size, range->start + range->range_ref.size());
}

TEST_F(MinidumpParserTest, FindMemoryRange) {
  SetUpData("linux-x86_64.dmp");
  // There are two memory ranges in the file (size is in bytes, decimal):
  // 1) 0x401d46 256
  // 2) 0x7ffceb34a000 12288
  EXPECT_FALSE(parser->FindMemoryRange(0x00).hasValue());
  EXPECT_FALSE(parser->FindMemoryRange(0x2a).hasValue());

  check_mem_range_exists(parser, 0x401d46, 256);
  EXPECT_FALSE(parser->FindMemoryRange(0x401d46 + 256).hasValue());

  check_mem_range_exists(parser, 0x7ffceb34a000, 12288);
  EXPECT_FALSE(parser->FindMemoryRange(0x7ffceb34a000 + 12288).hasValue());
}

TEST_F(MinidumpParserTest, GetMemory) {
  SetUpData("linux-x86_64.dmp");

  EXPECT_EQ(128UL, parser->GetMemory(0x401d46, 128).size());
  EXPECT_EQ(256UL, parser->GetMemory(0x401d46, 512).size());

  EXPECT_EQ(12288UL, parser->GetMemory(0x7ffceb34a000, 12288).size());
  EXPECT_EQ(1024UL, parser->GetMemory(0x7ffceb34a000, 1024).size());

  EXPECT_TRUE(parser->GetMemory(0x500000, 512).empty());
}

TEST_F(MinidumpParserTest, FindMemoryRangeWithFullMemoryMinidump) {
  SetUpData("fizzbuzz_wow64.dmp");

  // There are a lot of ranges in the file, just testing with some of them
  EXPECT_FALSE(parser->FindMemoryRange(0x00).hasValue());
  EXPECT_FALSE(parser->FindMemoryRange(0x2a).hasValue());
  check_mem_range_exists(parser, 0x10000, 65536); // first range
  check_mem_range_exists(parser, 0x40000, 4096);
  EXPECT_FALSE(parser->FindMemoryRange(0x40000 + 4096).hasValue());
  check_mem_range_exists(parser, 0x77c12000, 8192);
  check_mem_range_exists(parser, 0x7ffe0000, 4096); // last range
  EXPECT_FALSE(parser->FindMemoryRange(0x7ffe0000 + 4096).hasValue());
}

void check_region_info(std::unique_ptr<MinidumpParser> &parser,
                       const uint64_t addr, MemoryRegionInfo::OptionalBool read,
                       MemoryRegionInfo::OptionalBool write,
                       MemoryRegionInfo::OptionalBool exec) {
  auto range_info = parser->GetMemoryRegionInfo(addr);
  ASSERT_TRUE(range_info.hasValue());
  EXPECT_EQ(read, range_info->GetReadable());
  EXPECT_EQ(write, range_info->GetWritable());
  EXPECT_EQ(exec, range_info->GetExecutable());
}

TEST_F(MinidumpParserTest, GetMemoryRegionInfo) {
  SetUpData("fizzbuzz_wow64.dmp");

  const auto yes = MemoryRegionInfo::eYes;
  const auto no = MemoryRegionInfo::eNo;

  check_region_info(parser, 0x00000, no, no, no);
  check_region_info(parser, 0x10000, yes, yes, no);
  check_region_info(parser, 0x20000, yes, yes, no);
  check_region_info(parser, 0x30000, yes, yes, no);
  check_region_info(parser, 0x31000, no, no, no);
  check_region_info(parser, 0x40000, yes, no, no);
}

// Windows Minidump tests
// fizzbuzz_no_heap.dmp is copied from the WinMiniDump tests
TEST_F(MinidumpParserTest, GetArchitectureWindows) {
  SetUpData("fizzbuzz_no_heap.dmp");
  ASSERT_EQ(llvm::Triple::ArchType::x86,
            parser->GetArchitecture().GetMachine());
  ASSERT_EQ(llvm::Triple::OSType::Win32,
            parser->GetArchitecture().GetTriple().getOS());
}

TEST_F(MinidumpParserTest, GetLinuxProcStatusWindows) {
  SetUpData("fizzbuzz_no_heap.dmp");
  llvm::Optional<LinuxProcStatus> proc_status = parser->GetLinuxProcStatus();
  ASSERT_FALSE(proc_status.hasValue());
}

TEST_F(MinidumpParserTest, GetMiscInfoWindows) {
  SetUpData("fizzbuzz_no_heap.dmp");
  const MinidumpMiscInfo *misc_info = parser->GetMiscInfo();
  ASSERT_NE(nullptr, misc_info);
  llvm::Optional<lldb::pid_t> pid = misc_info->GetPid();
  ASSERT_TRUE(pid.hasValue());
  ASSERT_EQ(4440UL, pid.getValue());
}

TEST_F(MinidumpParserTest, GetPidWindows) {
  SetUpData("fizzbuzz_no_heap.dmp");
  llvm::Optional<lldb::pid_t> pid = parser->GetPid();
  ASSERT_TRUE(pid.hasValue());
  ASSERT_EQ(4440UL, pid.getValue());
}

// wow64
TEST_F(MinidumpParserTest, GetPidWow64) {
  SetUpData("fizzbuzz_wow64.dmp");
  llvm::Optional<lldb::pid_t> pid = parser->GetPid();
  ASSERT_TRUE(pid.hasValue());
  ASSERT_EQ(7836UL, pid.getValue());
}

TEST_F(MinidumpParserTest, GetModuleListWow64) {
  SetUpData("fizzbuzz_wow64.dmp");
  llvm::ArrayRef<MinidumpModule> modules = parser->GetModuleList();
  ASSERT_EQ(16UL, modules.size());
  std::string module_names[16] = {
      R"(D:\src\llvm\llvm\tools\lldb\packages\Python\lldbsuite\test\functionalities\postmortem\wow64_minidump\fizzbuzz.exe)",
      R"(C:\Windows\System32\ntdll.dll)",
      R"(C:\Windows\System32\wow64.dll)",
      R"(C:\Windows\System32\wow64win.dll)",
      R"(C:\Windows\System32\wow64cpu.dll)",
      R"(D:\src\llvm\llvm\tools\lldb\packages\Python\lldbsuite\test\functionalities\postmortem\wow64_minidump\fizzbuzz.exe)",
      R"(C:\Windows\SysWOW64\ntdll.dll)",
      R"(C:\Windows\SysWOW64\kernel32.dll)",
      R"(C:\Windows\SysWOW64\KERNELBASE.dll)",
      R"(C:\Windows\SysWOW64\advapi32.dll)",
      R"(C:\Windows\SysWOW64\msvcrt.dll)",
      R"(C:\Windows\SysWOW64\sechost.dll)",
      R"(C:\Windows\SysWOW64\rpcrt4.dll)",
      R"(C:\Windows\SysWOW64\sspicli.dll)",
      R"(C:\Windows\SysWOW64\CRYPTBASE.dll)",
      R"(C:\Windows\System32\api-ms-win-core-synch-l1-2-0.DLL)",
  };

  for (int i = 0; i < 16; ++i) {
    llvm::Optional<std::string> name =
        parser->GetMinidumpString(modules[i].module_name_rva);
    ASSERT_TRUE(name.hasValue());
    EXPECT_EQ(module_names[i], name.getValue());
  }
}

// Register tests
#define REG_VAL32(x) *(reinterpret_cast<uint32_t *>(x))
#define REG_VAL64(x) *(reinterpret_cast<uint64_t *>(x))

TEST_F(MinidumpParserTest, ConvertMinidumpContext_x86_32) {
  SetUpData("linux-i386.dmp");
  llvm::ArrayRef<MinidumpThread> thread_list = parser->GetThreads();
  const MinidumpThread thread = thread_list[0];
  llvm::ArrayRef<uint8_t> registers(parser->GetThreadContext(thread));

  ArchSpec arch = parser->GetArchitecture();
  RegisterInfoInterface *reg_interface = new RegisterContextLinux_i386(arch);
  lldb::DataBufferSP buf =
      ConvertMinidumpContext_x86_32(registers, reg_interface);
  ASSERT_EQ(reg_interface->GetGPRSize(), buf->GetByteSize());

  const RegisterInfo *reg_info = reg_interface->GetRegisterInfo();

  std::map<uint64_t, uint32_t> reg_values;

  reg_values[lldb_eax_i386] = 0x00000000;
  reg_values[lldb_ebx_i386] = 0xf7778000;
  reg_values[lldb_ecx_i386] = 0x00000001;
  reg_values[lldb_edx_i386] = 0xff9dd4a3;
  reg_values[lldb_edi_i386] = 0x080482a8;
  reg_values[lldb_esi_i386] = 0xff9dd55c;
  reg_values[lldb_ebp_i386] = 0xff9dd53c;
  reg_values[lldb_esp_i386] = 0xff9dd52c;
  reg_values[lldb_eip_i386] = 0x080482a0;
  reg_values[lldb_eflags_i386] = 0x00010282;
  reg_values[lldb_cs_i386] = 0x00000023;
  reg_values[lldb_fs_i386] = 0x00000000;
  reg_values[lldb_gs_i386] = 0x00000063;
  reg_values[lldb_ss_i386] = 0x0000002b;
  reg_values[lldb_ds_i386] = 0x0000002b;
  reg_values[lldb_es_i386] = 0x0000002b;

  for (uint32_t reg_index = 0; reg_index < reg_interface->GetRegisterCount();
       ++reg_index) {
    if (reg_values.find(reg_index) != reg_values.end()) {
      EXPECT_EQ(reg_values[reg_index],
                REG_VAL32(buf->GetBytes() + reg_info[reg_index].byte_offset));
    }
  }
}

TEST_F(MinidumpParserTest, ConvertMinidumpContext_x86_64) {
  SetUpData("linux-x86_64.dmp");
  llvm::ArrayRef<MinidumpThread> thread_list = parser->GetThreads();
  const MinidumpThread thread = thread_list[0];
  llvm::ArrayRef<uint8_t> registers(parser->GetThreadContext(thread));

  ArchSpec arch = parser->GetArchitecture();
  RegisterInfoInterface *reg_interface = new RegisterContextLinux_x86_64(arch);
  lldb::DataBufferSP buf =
      ConvertMinidumpContext_x86_64(registers, reg_interface);
  ASSERT_EQ(reg_interface->GetGPRSize(), buf->GetByteSize());

  const RegisterInfo *reg_info = reg_interface->GetRegisterInfo();

  std::map<uint64_t, uint64_t> reg_values;

  reg_values[lldb_rax_x86_64] = 0x0000000000000000;
  reg_values[lldb_rbx_x86_64] = 0x0000000000000000;
  reg_values[lldb_rcx_x86_64] = 0x0000000000000010;
  reg_values[lldb_rdx_x86_64] = 0x0000000000000000;
  reg_values[lldb_rdi_x86_64] = 0x00007ffceb349cf0;
  reg_values[lldb_rsi_x86_64] = 0x0000000000000000;
  reg_values[lldb_rbp_x86_64] = 0x00007ffceb34a210;
  reg_values[lldb_rsp_x86_64] = 0x00007ffceb34a210;
  reg_values[lldb_r8_x86_64] = 0x00007fe9bc1aa9c0;
  reg_values[lldb_r9_x86_64] = 0x0000000000000000;
  reg_values[lldb_r10_x86_64] = 0x00007fe9bc3f16a0;
  reg_values[lldb_r11_x86_64] = 0x0000000000000246;
  reg_values[lldb_r12_x86_64] = 0x0000000000401c92;
  reg_values[lldb_r13_x86_64] = 0x00007ffceb34a430;
  reg_values[lldb_r14_x86_64] = 0x0000000000000000;
  reg_values[lldb_r15_x86_64] = 0x0000000000000000;
  reg_values[lldb_rip_x86_64] = 0x0000000000401dc6;
  reg_values[lldb_rflags_x86_64] = 0x0000000000010206;
  reg_values[lldb_cs_x86_64] = 0x0000000000000033;
  reg_values[lldb_fs_x86_64] = 0x0000000000000000;
  reg_values[lldb_gs_x86_64] = 0x0000000000000000;
  reg_values[lldb_ss_x86_64] = 0x0000000000000000;
  reg_values[lldb_ds_x86_64] = 0x0000000000000000;
  reg_values[lldb_es_x86_64] = 0x0000000000000000;

  for (uint32_t reg_index = 0; reg_index < reg_interface->GetRegisterCount();
       ++reg_index) {
    if (reg_values.find(reg_index) != reg_values.end()) {
      EXPECT_EQ(reg_values[reg_index],
                REG_VAL64(buf->GetBytes() + reg_info[reg_index].byte_offset));
    }
  }
}

TEST_F(MinidumpParserTest, ConvertMinidumpContext_x86_32_wow64) {
  SetUpData("fizzbuzz_wow64.dmp");
  llvm::ArrayRef<MinidumpThread> thread_list = parser->GetThreads();
  const MinidumpThread thread = thread_list[0];
  llvm::ArrayRef<uint8_t> registers(parser->GetThreadContextWow64(thread));

  ArchSpec arch = parser->GetArchitecture();
  RegisterInfoInterface *reg_interface = new RegisterContextLinux_i386(arch);
  lldb::DataBufferSP buf =
      ConvertMinidumpContext_x86_32(registers, reg_interface);
  ASSERT_EQ(reg_interface->GetGPRSize(), buf->GetByteSize());

  const RegisterInfo *reg_info = reg_interface->GetRegisterInfo();

  std::map<uint64_t, uint32_t> reg_values;

  reg_values[lldb_eax_i386] = 0x00000000;
  reg_values[lldb_ebx_i386] = 0x0037f608;
  reg_values[lldb_ecx_i386] = 0x00e61578;
  reg_values[lldb_edx_i386] = 0x00000008;
  reg_values[lldb_edi_i386] = 0x00000000;
  reg_values[lldb_esi_i386] = 0x00000002;
  reg_values[lldb_ebp_i386] = 0x0037f654;
  reg_values[lldb_esp_i386] = 0x0037f5b8;
  reg_values[lldb_eip_i386] = 0x77ce01fd;
  reg_values[lldb_eflags_i386] = 0x00000246;
  reg_values[lldb_cs_i386] = 0x00000023;
  reg_values[lldb_fs_i386] = 0x00000053;
  reg_values[lldb_gs_i386] = 0x0000002b;
  reg_values[lldb_ss_i386] = 0x0000002b;
  reg_values[lldb_ds_i386] = 0x0000002b;
  reg_values[lldb_es_i386] = 0x0000002b;

  for (uint32_t reg_index = 0; reg_index < reg_interface->GetRegisterCount();
       ++reg_index) {
    if (reg_values.find(reg_index) != reg_values.end()) {
      EXPECT_EQ(reg_values[reg_index],
                REG_VAL32(buf->GetBytes() + reg_info[reg_index].byte_offset));
    }
  }
}
