//===- DLL.cpp ------------------------------------------------------------===//
//
//                             The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines various types of chunks for the DLL import or export
// descriptor tables. They are inherently Windows-specific.
// You need to read Microsoft PE/COFF spec to understand details
// about the data structures.
//
// If you are not particularly interested in linking against Windows
// DLL, you can skip this file, and you should still be able to
// understand the rest of the linker.
//
//===----------------------------------------------------------------------===//

#include "Chunks.h"
#include "DLL.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Path.h"

using namespace llvm;
using namespace llvm::object;
using namespace llvm::support::endian;
using namespace llvm::COFF;

namespace lld {
namespace coff {
namespace {

// Import table

static int ptrSize() { return Config->is64() ? 8 : 4; }

// A chunk for the import descriptor table.
class HintNameChunk : public Chunk {
public:
  HintNameChunk(StringRef N, uint16_t H) : Name(N), Hint(H) {}

  size_t getSize() const override {
    // Starts with 2 byte Hint field, followed by a null-terminated string,
    // ends with 0 or 1 byte padding.
    return alignTo(Name.size() + 3, 2);
  }

  void writeTo(uint8_t *Buf) const override {
    write16le(Buf + OutputSectionOff, Hint);
    memcpy(Buf + OutputSectionOff + 2, Name.data(), Name.size());
  }

private:
  StringRef Name;
  uint16_t Hint;
};

// A chunk for the import descriptor table.
class LookupChunk : public Chunk {
public:
  explicit LookupChunk(Chunk *C) : HintName(C) { Alignment = ptrSize(); }
  size_t getSize() const override { return ptrSize(); }

  void writeTo(uint8_t *Buf) const override {
    write32le(Buf + OutputSectionOff, HintName->getRVA());
  }

  Chunk *HintName;
};

// A chunk for the import descriptor table.
// This chunk represent import-by-ordinal symbols.
// See Microsoft PE/COFF spec 7.1. Import Header for details.
class OrdinalOnlyChunk : public Chunk {
public:
  explicit OrdinalOnlyChunk(uint16_t V) : Ordinal(V) { Alignment = ptrSize(); }
  size_t getSize() const override { return ptrSize(); }

  void writeTo(uint8_t *Buf) const override {
    // An import-by-ordinal slot has MSB 1 to indicate that
    // this is import-by-ordinal (and not import-by-name).
    if (Config->is64()) {
      write64le(Buf + OutputSectionOff, (1ULL << 63) | Ordinal);
    } else {
      write32le(Buf + OutputSectionOff, (1ULL << 31) | Ordinal);
    }
  }

  uint16_t Ordinal;
};

// A chunk for the import descriptor table.
class ImportDirectoryChunk : public Chunk {
public:
  explicit ImportDirectoryChunk(Chunk *N) : DLLName(N) {}
  size_t getSize() const override { return sizeof(ImportDirectoryTableEntry); }

  void writeTo(uint8_t *Buf) const override {
    auto *E = (coff_import_directory_table_entry *)(Buf + OutputSectionOff);
    E->ImportLookupTableRVA = LookupTab->getRVA();
    E->NameRVA = DLLName->getRVA();
    E->ImportAddressTableRVA = AddressTab->getRVA();
  }

  Chunk *DLLName;
  Chunk *LookupTab;
  Chunk *AddressTab;
};

// A chunk representing null terminator in the import table.
// Contents of this chunk is always null bytes.
class NullChunk : public Chunk {
public:
  explicit NullChunk(size_t N) : Size(N) {}
  bool hasData() const override { return false; }
  size_t getSize() const override { return Size; }

private:
  size_t Size;
};

static std::vector<std::vector<DefinedImportData *>>
binImports(const std::vector<DefinedImportData *> &Imports) {
  // Group DLL-imported symbols by DLL name because that's how
  // symbols are layed out in the import descriptor table.
  auto Less = [](const std::string &A, const std::string &B) {
    return Config->DLLOrder[A] < Config->DLLOrder[B];
  };
  std::map<std::string, std::vector<DefinedImportData *>,
           bool(*)(const std::string &, const std::string &)> M(Less);
  for (DefinedImportData *Sym : Imports)
    M[Sym->getDLLName().lower()].push_back(Sym);

  std::vector<std::vector<DefinedImportData *>> V;
  for (auto &KV : M) {
    // Sort symbols by name for each group.
    std::vector<DefinedImportData *> &Syms = KV.second;
    std::sort(Syms.begin(), Syms.end(),
              [](DefinedImportData *A, DefinedImportData *B) {
                return A->getName() < B->getName();
              });
    V.push_back(std::move(Syms));
  }
  return V;
}

// Export table
// See Microsoft PE/COFF spec 4.3 for details.

// A chunk for the delay import descriptor table etnry.
class DelayDirectoryChunk : public Chunk {
public:
  explicit DelayDirectoryChunk(Chunk *N) : DLLName(N) {}

  size_t getSize() const override {
    return sizeof(delay_import_directory_table_entry);
  }

  void writeTo(uint8_t *Buf) const override {
    auto *E = (delay_import_directory_table_entry *)(Buf + OutputSectionOff);
    E->Attributes = 1;
    E->Name = DLLName->getRVA();
    E->ModuleHandle = ModuleHandle->getRVA();
    E->DelayImportAddressTable = AddressTab->getRVA();
    E->DelayImportNameTable = NameTab->getRVA();
  }

  Chunk *DLLName;
  Chunk *ModuleHandle;
  Chunk *AddressTab;
  Chunk *NameTab;
};

// Initial contents for delay-loaded functions.
// This code calls __delayLoadHelper2 function to resolve a symbol
// and then overwrites its jump table slot with the result
// for subsequent function calls.
static const uint8_t ThunkX64[] = {
    0x51,                               // push    rcx
    0x52,                               // push    rdx
    0x41, 0x50,                         // push    r8
    0x41, 0x51,                         // push    r9
    0x48, 0x83, 0xEC, 0x48,             // sub     rsp, 48h
    0x66, 0x0F, 0x7F, 0x04, 0x24,       // movdqa  xmmword ptr [rsp], xmm0
    0x66, 0x0F, 0x7F, 0x4C, 0x24, 0x10, // movdqa  xmmword ptr [rsp+10h], xmm1
    0x66, 0x0F, 0x7F, 0x54, 0x24, 0x20, // movdqa  xmmword ptr [rsp+20h], xmm2
    0x66, 0x0F, 0x7F, 0x5C, 0x24, 0x30, // movdqa  xmmword ptr [rsp+30h], xmm3
    0x48, 0x8D, 0x15, 0, 0, 0, 0,       // lea     rdx, [__imp_<FUNCNAME>]
    0x48, 0x8D, 0x0D, 0, 0, 0, 0,       // lea     rcx, [___DELAY_IMPORT_...]
    0xE8, 0, 0, 0, 0,                   // call    __delayLoadHelper2
    0x66, 0x0F, 0x6F, 0x04, 0x24,       // movdqa  xmm0, xmmword ptr [rsp]
    0x66, 0x0F, 0x6F, 0x4C, 0x24, 0x10, // movdqa  xmm1, xmmword ptr [rsp+10h]
    0x66, 0x0F, 0x6F, 0x54, 0x24, 0x20, // movdqa  xmm2, xmmword ptr [rsp+20h]
    0x66, 0x0F, 0x6F, 0x5C, 0x24, 0x30, // movdqa  xmm3, xmmword ptr [rsp+30h]
    0x48, 0x83, 0xC4, 0x48,             // add     rsp, 48h
    0x41, 0x59,                         // pop     r9
    0x41, 0x58,                         // pop     r8
    0x5A,                               // pop     rdx
    0x59,                               // pop     rcx
    0xFF, 0xE0,                         // jmp     rax
};

static const uint8_t ThunkX86[] = {
    0x51,              // push  ecx
    0x52,              // push  edx
    0x68, 0, 0, 0, 0,  // push  offset ___imp__<FUNCNAME>
    0x68, 0, 0, 0, 0,  // push  offset ___DELAY_IMPORT_DESCRIPTOR_<DLLNAME>_dll
    0xE8, 0, 0, 0, 0,  // call  ___delayLoadHelper2@8
    0x5A,              // pop   edx
    0x59,              // pop   ecx
    0xFF, 0xE0,        // jmp   eax
};

static const uint8_t ThunkARM[] = {
    0x40, 0xf2, 0x00, 0x0c, // mov.w   ip, #0 __imp_<FUNCNAME>
    0xc0, 0xf2, 0x00, 0x0c, // mov.t   ip, #0 __imp_<FUNCNAME>
    0x2d, 0xe9, 0x0f, 0x48, // push.w  {r0, r1, r2, r3, r11, lr}
    0x0d, 0xf2, 0x10, 0x0b, // addw    r11, sp, #16
    0x2d, 0xed, 0x10, 0x0b, // vpush   {d0, d1, d2, d3, d4, d5, d6, d7}
    0x61, 0x46,             // mov     r1, ip
    0x40, 0xf2, 0x00, 0x00, // mov.w   r0, #0 DELAY_IMPORT_DESCRIPTOR
    0xc0, 0xf2, 0x00, 0x00, // mov.t   r0, #0 DELAY_IMPORT_DESCRIPTOR
    0x00, 0xf0, 0x00, 0xd0, // bl      #0 __delayLoadHelper2
    0x84, 0x46,             // mov     ip, r0
    0xbd, 0xec, 0x10, 0x0b, // vpop    {d0, d1, d2, d3, d4, d5, d6, d7}
    0xbd, 0xe8, 0x0f, 0x48, // pop.w   {r0, r1, r2, r3, r11, lr}
    0x60, 0x47,             // bx      ip
};

// A chunk for the delay import thunk.
class ThunkChunkX64 : public Chunk {
public:
  ThunkChunkX64(Defined *I, Chunk *D, Defined *H)
      : Imp(I), Desc(D), Helper(H) {}

  size_t getSize() const override { return sizeof(ThunkX64); }

  void writeTo(uint8_t *Buf) const override {
    memcpy(Buf + OutputSectionOff, ThunkX64, sizeof(ThunkX64));
    write32le(Buf + OutputSectionOff + 36, Imp->getRVA() - RVA - 40);
    write32le(Buf + OutputSectionOff + 43, Desc->getRVA() - RVA - 47);
    write32le(Buf + OutputSectionOff + 48, Helper->getRVA() - RVA - 52);
  }

  Defined *Imp = nullptr;
  Chunk *Desc = nullptr;
  Defined *Helper = nullptr;
};

class ThunkChunkX86 : public Chunk {
public:
  ThunkChunkX86(Defined *I, Chunk *D, Defined *H)
      : Imp(I), Desc(D), Helper(H) {}

  size_t getSize() const override { return sizeof(ThunkX86); }

  void writeTo(uint8_t *Buf) const override {
    memcpy(Buf + OutputSectionOff, ThunkX86, sizeof(ThunkX86));
    write32le(Buf + OutputSectionOff + 3, Imp->getRVA() + Config->ImageBase);
    write32le(Buf + OutputSectionOff + 8, Desc->getRVA() + Config->ImageBase);
    write32le(Buf + OutputSectionOff + 13, Helper->getRVA() - RVA - 17);
  }

  void getBaserels(std::vector<Baserel> *Res) override {
    Res->emplace_back(RVA + 3);
    Res->emplace_back(RVA + 8);
  }

  Defined *Imp = nullptr;
  Chunk *Desc = nullptr;
  Defined *Helper = nullptr;
};

class ThunkChunkARM : public Chunk {
public:
  ThunkChunkARM(Defined *I, Chunk *D, Defined *H)
      : Imp(I), Desc(D), Helper(H) {}

  size_t getSize() const override { return sizeof(ThunkARM); }

  void writeTo(uint8_t *Buf) const override {
    memcpy(Buf + OutputSectionOff, ThunkARM, sizeof(ThunkARM));
    applyMOV32T(Buf + OutputSectionOff + 0, Imp->getRVA() + Config->ImageBase);
    applyMOV32T(Buf + OutputSectionOff + 22, Desc->getRVA() + Config->ImageBase);
    applyBranch24T(Buf + OutputSectionOff + 30, Helper->getRVA() - RVA - 34);
  }

  void getBaserels(std::vector<Baserel> *Res) override {
    Res->emplace_back(RVA + 0, IMAGE_REL_BASED_ARM_MOV32T);
    Res->emplace_back(RVA + 22, IMAGE_REL_BASED_ARM_MOV32T);
  }

  Defined *Imp = nullptr;
  Chunk *Desc = nullptr;
  Defined *Helper = nullptr;
};

// A chunk for the import descriptor table.
class DelayAddressChunk : public Chunk {
public:
  explicit DelayAddressChunk(Chunk *C) : Thunk(C) { Alignment = ptrSize(); }
  size_t getSize() const override { return ptrSize(); }

  void writeTo(uint8_t *Buf) const override {
    if (Config->is64()) {
      write64le(Buf + OutputSectionOff, Thunk->getRVA() + Config->ImageBase);
    } else {
      uint32_t Bit = 0;
      // Pointer to thumb code must have the LSB set, so adjust it.
      if (Config->Machine == ARMNT)
        Bit = 1;
      write32le(Buf + OutputSectionOff, (Thunk->getRVA() + Config->ImageBase) | Bit);
    }
  }

  void getBaserels(std::vector<Baserel> *Res) override {
    Res->emplace_back(RVA);
  }

  Chunk *Thunk;
};

// Export table
// Read Microsoft PE/COFF spec 5.3 for details.

// A chunk for the export descriptor table.
class ExportDirectoryChunk : public Chunk {
public:
  ExportDirectoryChunk(int I, int J, Chunk *D, Chunk *A, Chunk *N, Chunk *O)
      : MaxOrdinal(I), NameTabSize(J), DLLName(D), AddressTab(A), NameTab(N),
        OrdinalTab(O) {}

  size_t getSize() const override {
    return sizeof(export_directory_table_entry);
  }

  void writeTo(uint8_t *Buf) const override {
    auto *E = (export_directory_table_entry *)(Buf + OutputSectionOff);
    E->NameRVA = DLLName->getRVA();
    E->OrdinalBase = 0;
    E->AddressTableEntries = MaxOrdinal + 1;
    E->NumberOfNamePointers = NameTabSize;
    E->ExportAddressTableRVA = AddressTab->getRVA();
    E->NamePointerRVA = NameTab->getRVA();
    E->OrdinalTableRVA = OrdinalTab->getRVA();
  }

  uint16_t MaxOrdinal;
  uint16_t NameTabSize;
  Chunk *DLLName;
  Chunk *AddressTab;
  Chunk *NameTab;
  Chunk *OrdinalTab;
};

class AddressTableChunk : public Chunk {
public:
  explicit AddressTableChunk(size_t MaxOrdinal) : Size(MaxOrdinal + 1) {}
  size_t getSize() const override { return Size * 4; }

  void writeTo(uint8_t *Buf) const override {
    uint32_t Bit = 0;
    // Pointer to thumb code must have the LSB set, so adjust it.
    if (Config->Machine == ARMNT)
      Bit = 1;
    for (Export &E : Config->Exports) {
      uint8_t *P = Buf + OutputSectionOff + E.Ordinal * 4;
      if (E.ForwardChunk) {
        write32le(P, E.ForwardChunk->getRVA() | Bit);
      } else {
        write32le(P, cast<Defined>(E.Sym)->getRVA() | Bit);
      }
    }
  }

private:
  size_t Size;
};

class NamePointersChunk : public Chunk {
public:
  explicit NamePointersChunk(std::vector<Chunk *> &V) : Chunks(V) {}
  size_t getSize() const override { return Chunks.size() * 4; }

  void writeTo(uint8_t *Buf) const override {
    uint8_t *P = Buf + OutputSectionOff;
    for (Chunk *C : Chunks) {
      write32le(P, C->getRVA());
      P += 4;
    }
  }

private:
  std::vector<Chunk *> Chunks;
};

class ExportOrdinalChunk : public Chunk {
public:
  explicit ExportOrdinalChunk(size_t I) : Size(I) {}
  size_t getSize() const override { return Size * 2; }

  void writeTo(uint8_t *Buf) const override {
    uint8_t *P = Buf + OutputSectionOff;
    for (Export &E : Config->Exports) {
      if (E.Noname)
        continue;
      write16le(P, E.Ordinal);
      P += 2;
    }
  }

private:
  size_t Size;
};

} // anonymous namespace

uint64_t IdataContents::getDirSize() {
  return Dirs.size() * sizeof(ImportDirectoryTableEntry);
}

uint64_t IdataContents::getIATSize() {
  return Addresses.size() * ptrSize();
}

// Returns a list of .idata contents.
// See Microsoft PE/COFF spec 5.4 for details.
std::vector<Chunk *> IdataContents::getChunks() {
  create();

  // The loader assumes a specific order of data.
  // Add each type in the correct order.
  std::vector<Chunk *> V;
  V.insert(V.end(), Dirs.begin(), Dirs.end());
  V.insert(V.end(), Lookups.begin(), Lookups.end());
  V.insert(V.end(), Addresses.begin(), Addresses.end());
  V.insert(V.end(), Hints.begin(), Hints.end());
  V.insert(V.end(), DLLNames.begin(), DLLNames.end());
  return V;
}

void IdataContents::create() {
  std::vector<std::vector<DefinedImportData *>> V = binImports(Imports);

  // Create .idata contents for each DLL.
  for (std::vector<DefinedImportData *> &Syms : V) {
    // Create lookup and address tables. If they have external names,
    // we need to create HintName chunks to store the names.
    // If they don't (if they are import-by-ordinals), we store only
    // ordinal values to the table.
    size_t Base = Lookups.size();
    for (DefinedImportData *S : Syms) {
      uint16_t Ord = S->getOrdinal();
      if (S->getExternalName().empty()) {
        Lookups.push_back(make<OrdinalOnlyChunk>(Ord));
        Addresses.push_back(make<OrdinalOnlyChunk>(Ord));
        continue;
      }
      auto *C = make<HintNameChunk>(S->getExternalName(), Ord);
      Lookups.push_back(make<LookupChunk>(C));
      Addresses.push_back(make<LookupChunk>(C));
      Hints.push_back(C);
    }
    // Terminate with null values.
    Lookups.push_back(make<NullChunk>(ptrSize()));
    Addresses.push_back(make<NullChunk>(ptrSize()));

    for (int I = 0, E = Syms.size(); I < E; ++I)
      Syms[I]->setLocation(Addresses[Base + I]);

    // Create the import table header.
    DLLNames.push_back(make<StringChunk>(Syms[0]->getDLLName()));
    auto *Dir = make<ImportDirectoryChunk>(DLLNames.back());
    Dir->LookupTab = Lookups[Base];
    Dir->AddressTab = Addresses[Base];
    Dirs.push_back(Dir);
  }
  // Add null terminator.
  Dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry)));
}

std::vector<Chunk *> DelayLoadContents::getChunks() {
  std::vector<Chunk *> V;
  V.insert(V.end(), Dirs.begin(), Dirs.end());
  V.insert(V.end(), Names.begin(), Names.end());
  V.insert(V.end(), HintNames.begin(), HintNames.end());
  V.insert(V.end(), DLLNames.begin(), DLLNames.end());
  return V;
}

std::vector<Chunk *> DelayLoadContents::getDataChunks() {
  std::vector<Chunk *> V;
  V.insert(V.end(), ModuleHandles.begin(), ModuleHandles.end());
  V.insert(V.end(), Addresses.begin(), Addresses.end());
  return V;
}

uint64_t DelayLoadContents::getDirSize() {
  return Dirs.size() * sizeof(delay_import_directory_table_entry);
}

void DelayLoadContents::create(Defined *H) {
  Helper = H;
  std::vector<std::vector<DefinedImportData *>> V = binImports(Imports);

  // Create .didat contents for each DLL.
  for (std::vector<DefinedImportData *> &Syms : V) {
    // Create the delay import table header.
    DLLNames.push_back(make<StringChunk>(Syms[0]->getDLLName()));
    auto *Dir = make<DelayDirectoryChunk>(DLLNames.back());

    size_t Base = Addresses.size();
    for (DefinedImportData *S : Syms) {
      Chunk *T = newThunkChunk(S, Dir);
      auto *A = make<DelayAddressChunk>(T);
      Addresses.push_back(A);
      Thunks.push_back(T);
      StringRef ExtName = S->getExternalName();
      if (ExtName.empty()) {
        Names.push_back(make<OrdinalOnlyChunk>(S->getOrdinal()));
      } else {
        auto *C = make<HintNameChunk>(ExtName, 0);
        Names.push_back(make<LookupChunk>(C));
        HintNames.push_back(C);
      }
    }
    // Terminate with null values.
    Addresses.push_back(make<NullChunk>(8));
    Names.push_back(make<NullChunk>(8));

    for (int I = 0, E = Syms.size(); I < E; ++I)
      Syms[I]->setLocation(Addresses[Base + I]);
    auto *MH = make<NullChunk>(8);
    MH->Alignment = 8;
    ModuleHandles.push_back(MH);

    // Fill the delay import table header fields.
    Dir->ModuleHandle = MH;
    Dir->AddressTab = Addresses[Base];
    Dir->NameTab = Names[Base];
    Dirs.push_back(Dir);
  }
  // Add null terminator.
  Dirs.push_back(make<NullChunk>(sizeof(delay_import_directory_table_entry)));
}

Chunk *DelayLoadContents::newThunkChunk(DefinedImportData *S, Chunk *Dir) {
  switch (Config->Machine) {
  case AMD64:
    return make<ThunkChunkX64>(S, Dir, Helper);
  case I386:
    return make<ThunkChunkX86>(S, Dir, Helper);
  case ARMNT:
    return make<ThunkChunkARM>(S, Dir, Helper);
  default:
    llvm_unreachable("unsupported machine type");
  }
}

EdataContents::EdataContents() {
  uint16_t MaxOrdinal = 0;
  for (Export &E : Config->Exports)
    MaxOrdinal = std::max(MaxOrdinal, E.Ordinal);

  auto *DLLName = make<StringChunk>(sys::path::filename(Config->OutputFile));
  auto *AddressTab = make<AddressTableChunk>(MaxOrdinal);
  std::vector<Chunk *> Names;
  for (Export &E : Config->Exports)
    if (!E.Noname)
      Names.push_back(make<StringChunk>(E.ExportName));

  std::vector<Chunk *> Forwards;
  for (Export &E : Config->Exports) {
    if (E.ForwardTo.empty())
      continue;
    E.ForwardChunk = make<StringChunk>(E.ForwardTo);
    Forwards.push_back(E.ForwardChunk);
  }

  auto *NameTab = make<NamePointersChunk>(Names);
  auto *OrdinalTab = make<ExportOrdinalChunk>(Names.size());
  auto *Dir = make<ExportDirectoryChunk>(MaxOrdinal, Names.size(), DLLName,
                                         AddressTab, NameTab, OrdinalTab);
  Chunks.push_back(Dir);
  Chunks.push_back(DLLName);
  Chunks.push_back(AddressTab);
  Chunks.push_back(NameTab);
  Chunks.push_back(OrdinalTab);
  Chunks.insert(Chunks.end(), Names.begin(), Names.end());
  Chunks.insert(Chunks.end(), Forwards.begin(), Forwards.end());
}

} // namespace coff
} // namespace lld
