//===-- TestPECallFrameInfo.cpp -------------------------------------------===//
//
//
// 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 "gtest/gtest.h"

#include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h"
#include "Plugins/Process/Utility/lldb-x86-register-enums.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/TestUtilities.h"

#include "lldb/Core/Module.h"
#include "lldb/Symbol/CallFrameInfo.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "llvm/Testing/Support/Error.h"

using namespace lldb_private;
using namespace lldb;

class PECallFrameInfoTest : public testing::Test {
  SubsystemRAII<FileSystem, ObjectFilePECOFF> subsystems;

protected:
  void GetUnwindPlan(addr_t file_addr, UnwindPlan &plan) const;
};

void PECallFrameInfoTest::GetUnwindPlan(addr_t file_addr, UnwindPlan &plan) const {
  llvm::Expected<TestFile> ExpectedFile = TestFile::fromYaml(
      R"(
--- !COFF
OptionalHeader:  
  AddressOfEntryPoint: 0
  ImageBase:       16777216
  SectionAlignment: 4096
  FileAlignment:   512
  MajorOperatingSystemVersion: 6
  MinorOperatingSystemVersion: 0
  MajorImageVersion: 0
  MinorImageVersion: 0
  MajorSubsystemVersion: 6
  MinorSubsystemVersion: 0
  Subsystem:       IMAGE_SUBSYSTEM_WINDOWS_CUI
  DLLCharacteristics: [ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA, IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLL_CHARACTERISTICS_NX_COMPAT ]
  SizeOfStackReserve: 1048576
  SizeOfStackCommit: 4096
  SizeOfHeapReserve: 1048576
  SizeOfHeapCommit: 4096
  ExportTable:     
    RelativeVirtualAddress: 0
    Size:            0
  ImportTable:     
    RelativeVirtualAddress: 0
    Size:            0
  ResourceTable:   
    RelativeVirtualAddress: 0
    Size:            0
  ExceptionTable:  
    RelativeVirtualAddress: 12288
    Size:            60
  CertificateTable: 
    RelativeVirtualAddress: 0
    Size:            0
  BaseRelocationTable: 
    RelativeVirtualAddress: 0
    Size:            0
  Debug:           
    RelativeVirtualAddress: 0
    Size:            0
  Architecture:    
    RelativeVirtualAddress: 0
    Size:            0
  GlobalPtr:       
    RelativeVirtualAddress: 0
    Size:            0
  TlsTable:        
    RelativeVirtualAddress: 0
    Size:            0
  LoadConfigTable: 
    RelativeVirtualAddress: 0
    Size:            0
  BoundImport:     
    RelativeVirtualAddress: 0
    Size:            0
  IAT:             
    RelativeVirtualAddress: 0
    Size:            0
  DelayImportDescriptor: 
    RelativeVirtualAddress: 0
    Size:            0
  ClrRuntimeHeader: 
    RelativeVirtualAddress: 0
    Size:            0
header:          
  Machine:         IMAGE_FILE_MACHINE_AMD64
  Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
sections:        
  - Name:            .text
    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
    VirtualAddress:  4096
    VirtualSize:     4096
  - Name:            .rdata
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
    VirtualAddress:  8192
    VirtualSize:     68
    SectionData:     010C06000C3208F006E00470036002302105020005540D0000100000001100000020000019400E352F74670028646600213465001A3315015E000EF00CE00AD008C00650


# Unwind info at 0x2000:
# 01 0C 06 00    No chained info, prolog size = 0xC, unwind codes size is 6 words, no frame register
# 0C 32          UOP_AllocSmall(2) 3 * 8 + 8 bytes, offset in prolog is 0xC
# 08 F0          UOP_PushNonVol(0) R15(0xF), offset in prolog is 8
# 06 E0          UOP_PushNonVol(0) R14(0xE), offset in prolog is 6
# 04 70          UOP_PushNonVol(0) RDI(7), offset in prolog is 4
# 03 60          UOP_PushNonVol(0) RSI(6), offset in prolog is 3
# 02 30          UOP_PushNonVol(0) RBX(3), offset in prolog is 2
# Corresponding prolog:
# 00    push    rbx
# 02    push    rsi
# 03    push    rdi
# 04    push    r14
# 06    push    r15
# 08    sub     rsp, 20h

# Unwind info at 0x2010:
# 21 05 02 00    Has chained info, prolog size = 5, unwind codes size is 2 words, no frame register
# 05 54 0D 00    UOP_SaveNonVol(4) RBP(5) to RSP + 0xD * 8, offset in prolog is 5
# Chained runtime function:
# 00 10 00 00    Start address is 0x1000
# 00 11 00 00    End address is 0x1100
# 00 20 00 00    Unwind info RVA is 0x2000
# Corresponding prolog:
# 00    mov     [rsp+68h], rbp

# Unwind info at 0x2024:
# 19 40 0E 35    No chained info, prolog size = 0x40, unwind codes size is 0xE words, frame register is RBP, frame register offset is RSP + 3 * 16
# 2F 74 67 00    UOP_SaveNonVol(4) RDI(7) to RSP + 0x67 * 8, offset in prolog is 0x2F
# 28 64 66 00    UOP_SaveNonVol(4) RSI(6) to RSP + 0x66 * 8, offset in prolog is 0x28
# 21 34 65 00    UOP_SaveNonVol(4) RBX(3) to RSP + 0x65 * 8, offset in prolog is 0x21
# 1A 33          UOP_SetFPReg(3), offset in prolog is 0x1A
# 15 01 5E 00    UOP_AllocLarge(1) 0x5E * 8 bytes, offset in prolog is 0x15
# 0E F0          UOP_PushNonVol(0) R15(0xF), offset in prolog is 0xE
# 0C E0          UOP_PushNonVol(0) R14(0xE), offset in prolog is 0xC
# 0A D0          UOP_PushNonVol(0) R13(0xD), offset in prolog is 0xA
# 08 C0          UOP_PushNonVol(0) R12(0xC), offset in prolog is 8
# 06 50          UOP_PushNonVol(0) RBP(5), offset in prolog is 6
# Corresponding prolog:
# 00    mov     [rsp+8], rcx
# 05    push    rbp
# 06    push    r12
# 08    push    r13
# 0A    push    r14
# 0C    push    r15
# 0E    sub     rsp, 2F0h
# 15    lea     rbp, [rsp+30h]
# 1A    mov     [rbp+2F8h], rbx
# 21    mov     [rbp+300h], rsi
# 28    mov     [rbp+308h], rdi

  - Name:            .pdata
    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
    VirtualAddress:  12288
    VirtualSize:     60
    SectionData:     000000000000000000000000000000000000000000000000001000000011000000200000001100000012000010200000001200000013000024200000

# 00 00 00 00
# 00 00 00 00    Test correct processing of empty runtime functions at begin
# 00 00 00 00

# 00 00 00 00
# 00 00 00 00    Test correct processing of empty runtime functions at begin
# 00 00 00 00

# 00 10 00 00    Start address is 0x1000
# 00 11 00 00    End address is 0x1100
# 00 20 00 00    Unwind info RVA is 0x2000

# 00 11 00 00    Start address is 0x1100
# 00 12 00 00    End address is 0x1200
# 10 20 00 00    Unwind info RVA is 0x2010

# 00 12 00 00    Start address is 0x1200
# 00 13 00 00    End address is 0x1300
# 24 20 00 00    Unwind info RVA is 0x2024

symbols:         []
...
)");
  ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded());

  ModuleSP module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec());
  ObjectFile *object_file = module_sp->GetObjectFile();
  ASSERT_NE(object_file, nullptr);

  std::unique_ptr<CallFrameInfo> cfi = object_file->CreateCallFrameInfo();
  ASSERT_NE(cfi.get(), nullptr);

  SectionList *sect_list = object_file->GetSectionList();
  ASSERT_NE(sect_list, nullptr);

  EXPECT_TRUE(cfi->GetUnwindPlan(Address(file_addr, sect_list), plan));
}

TEST_F(PECallFrameInfoTest, Basic_eh) {
  UnwindPlan plan(eRegisterKindLLDB);
  GetUnwindPlan(0x1001080, plan);
  EXPECT_EQ(plan.GetRowCount(), 7);

  UnwindPlan::Row row;
  row.SetOffset(0);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 8);
  row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true);
  EXPECT_EQ(*plan.GetRowAtIndex(0), row);

  row.SetOffset(2);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x10);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, -0x10, true);
  EXPECT_EQ(*plan.GetRowAtIndex(1), row);

  row.SetOffset(3);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x18);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, -0x18, true);
  EXPECT_EQ(*plan.GetRowAtIndex(2), row);

  row.SetOffset(4);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x20);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, -0x20, true);
  EXPECT_EQ(*plan.GetRowAtIndex(3), row);

  row.SetOffset(6);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x28);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true);
  EXPECT_EQ(*plan.GetRowAtIndex(4), row);

  row.SetOffset(8);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x30);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true);
  EXPECT_EQ(*plan.GetRowAtIndex(5), row);

  row.SetOffset(0xC);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x50);
  EXPECT_EQ(*plan.GetRowAtIndex(6), row);
}

TEST_F(PECallFrameInfoTest, Chained_eh) {
  UnwindPlan plan(eRegisterKindLLDB);
  GetUnwindPlan(0x1001180, plan);
  EXPECT_EQ(plan.GetRowCount(), 2);

  UnwindPlan::Row row;
  row.SetOffset(0);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x50);
  row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, -0x10, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, -0x18, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, -0x20, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true);
  EXPECT_EQ(*plan.GetRowAtIndex(0), row);

  row.SetOffset(5);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbp_x86_64, 0x18, true);
  EXPECT_EQ(*plan.GetRowAtIndex(1), row);
}

TEST_F(PECallFrameInfoTest, Frame_reg_eh) {
  UnwindPlan plan(eRegisterKindLLDB);
  GetUnwindPlan(0x1001280, plan);
  EXPECT_EQ(plan.GetRowCount(), 11);

  UnwindPlan::Row row;
  row.SetOffset(0);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 8);
  row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true);
  EXPECT_EQ(*plan.GetRowAtIndex(0), row);

  row.SetOffset(6);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x10);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbp_x86_64, -0x10, true);
  EXPECT_EQ(*plan.GetRowAtIndex(1), row);

  row.SetOffset(8);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x18);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r12_x86_64, -0x18, true);
  EXPECT_EQ(*plan.GetRowAtIndex(2), row);

  row.SetOffset(0xA);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x20);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r13_x86_64, -0x20, true);
  EXPECT_EQ(*plan.GetRowAtIndex(3), row);

  row.SetOffset(0xC);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x28);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true);
  EXPECT_EQ(*plan.GetRowAtIndex(4), row);

  row.SetOffset(0xE);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x30);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true);
  EXPECT_EQ(*plan.GetRowAtIndex(5), row);

  row.SetOffset(0x15);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x320);
  EXPECT_EQ(*plan.GetRowAtIndex(6), row);

  row.SetOffset(0x1A);
  row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rbp_x86_64, 0x2F0);
  EXPECT_EQ(*plan.GetRowAtIndex(7), row);

  row.SetOffset(0x21);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, 8, true);
  EXPECT_EQ(*plan.GetRowAtIndex(8), row);

  row.SetOffset(0x28);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, 0x10, true);
  EXPECT_EQ(*plan.GetRowAtIndex(9), row);

  row.SetOffset(0x2F);
  row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, 0x18, true);
  EXPECT_EQ(*plan.GetRowAtIndex(10), row);
}
