//===- lib/ReaderWriter/ELF/HeaderChunks.cpp ------------------------------===//
//
//                             The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "HeaderChunks.h"
#include "TargetLayout.h"
#include "llvm/ADT/STLExtras.h"

namespace lld {
namespace elf {

template <class ELFT> void ELFHeader<ELFT>::finalize() {
  _eh.e_ident[llvm::ELF::EI_CLASS] =
      (ELFT::Is64Bits) ? llvm::ELF::ELFCLASS64 : llvm::ELF::ELFCLASS32;
  _eh.e_ident[llvm::ELF::EI_DATA] =
      (ELFT::TargetEndianness == llvm::support::little)
          ? llvm::ELF::ELFDATA2LSB
          : llvm::ELF::ELFDATA2MSB;
  _eh.e_type = this->_ctx.getOutputELFType();
  _eh.e_machine = this->_ctx.getOutputMachine();
}

template <class ELFT>
ELFHeader<ELFT>::ELFHeader(const ELFLinkingContext &ctx)
    : Chunk<ELFT>("elfhdr", Chunk<ELFT>::Kind::ELFHeader, ctx) {
  this->_alignment = ELFT::Is64Bits ? 8 : 4;
  this->_fsize = sizeof(Elf_Ehdr);
  this->_msize = sizeof(Elf_Ehdr);
  memset(_eh.e_ident, 0, llvm::ELF::EI_NIDENT);
  e_ident(llvm::ELF::EI_MAG0, 0x7f);
  e_ident(llvm::ELF::EI_MAG1, 'E');
  e_ident(llvm::ELF::EI_MAG2, 'L');
  e_ident(llvm::ELF::EI_MAG3, 'F');
  e_ehsize(sizeof(Elf_Ehdr));
  e_flags(0);
}

template <class ELFT>
void ELFHeader<ELFT>::write(ELFWriter *writer, TargetLayout<ELFT> &layout,
                            llvm::FileOutputBuffer &buffer) {
  uint8_t *chunkBuffer = buffer.getBufferStart();
  uint8_t *atomContent = chunkBuffer + this->fileOffset();
  memcpy(atomContent, &_eh, fileSize());
}

template <class ELFT>
bool ProgramHeader<ELFT>::addSegment(Segment<ELFT> *segment) {
  bool allocatedNew = false;
  ELFLinkingContext::OutputMagic outputMagic = this->_ctx.getOutputMagic();
  // For segments that are not a loadable segment, we
  // just pick the values directly from the segment as there
  // wouldnt be any slices within that
  if (segment->segmentType() != llvm::ELF::PT_LOAD) {
    Elf_Phdr *phdr = allocateProgramHeader(allocatedNew);
    phdr->p_type = segment->segmentType();
    phdr->p_offset = segment->fileOffset();
    phdr->p_vaddr = segment->virtualAddr();
    phdr->p_paddr = segment->virtualAddr();
    phdr->p_filesz = segment->fileSize();
    phdr->p_memsz = segment->memSize();
    phdr->p_flags = segment->flags();
    phdr->p_align = segment->alignment();
    this->_fsize = fileSize();
    this->_msize = this->_fsize;
    return allocatedNew;
  }
  // For all other segments, use the slice
  // to derive program headers
  for (auto slice : segment->slices()) {
    Elf_Phdr *phdr = allocateProgramHeader(allocatedNew);
    phdr->p_type = segment->segmentType();
    phdr->p_offset = slice->fileOffset();
    phdr->p_vaddr = slice->virtualAddr();
    phdr->p_paddr = slice->virtualAddr();
    phdr->p_filesz = slice->fileSize();
    phdr->p_memsz = slice->memSize();
    phdr->p_flags = segment->flags();
    phdr->p_align = slice->alignment();
    uint64_t segPageSize = segment->pageSize();
    uint64_t sliceAlign = slice->alignment();
    // Alignment of PT_LOAD segments are set to the page size, but if the
    // alignment of the slice is greater than the page size, set the alignment
    // of the segment appropriately.
    if (outputMagic != ELFLinkingContext::OutputMagic::NMAGIC &&
        outputMagic != ELFLinkingContext::OutputMagic::OMAGIC) {
      phdr->p_align =
          (phdr->p_type == llvm::ELF::PT_LOAD)
              ? (segPageSize < sliceAlign) ? sliceAlign : segPageSize
              : sliceAlign;
    } else
      phdr->p_align = slice->alignment();
  }
  this->_fsize = fileSize();
  this->_msize = this->_fsize;

  return allocatedNew;
}

template <class ELFT>
void ProgramHeader<ELFT>::write(ELFWriter *writer, TargetLayout<ELFT> &layout,
                                llvm::FileOutputBuffer &buffer) {
  uint8_t *chunkBuffer = buffer.getBufferStart();
  uint8_t *dest = chunkBuffer + this->fileOffset();
  for (auto phi : _ph) {
    memcpy(dest, phi, sizeof(Elf_Phdr));
    dest += sizeof(Elf_Phdr);
  }
}

template <class ELFT>
typename ProgramHeader<ELFT>::Elf_Phdr *
ProgramHeader<ELFT>::allocateProgramHeader(bool &allocatedNew) {
  Elf_Phdr *phdr;
  if (_phi == _ph.end()) {
    phdr = new (_allocator) Elf_Phdr;
    _ph.push_back(phdr);
    _phi = _ph.end();
    allocatedNew = true;
  } else {
    phdr = (*_phi);
    ++_phi;
  }
  return phdr;
}

template <class ELFT>
SectionHeader<ELFT>::SectionHeader(const ELFLinkingContext &ctx, int32_t order)
    : Chunk<ELFT>("shdr", Chunk<ELFT>::Kind::SectionHeader, ctx) {
  this->_fsize = 0;
  this->_alignment = 8;
  this->setOrder(order);
  // The first element in the list is always NULL
  Elf_Shdr *nullshdr = new (_sectionAllocate.Allocate<Elf_Shdr>()) Elf_Shdr;
  ::memset(nullshdr, 0, sizeof(Elf_Shdr));
  _sectionInfo.push_back(nullshdr);
  this->_fsize += sizeof(Elf_Shdr);
}

template <class ELFT>
void SectionHeader<ELFT>::appendSection(OutputSection<ELFT> *section) {
  Elf_Shdr *shdr = new (_sectionAllocate.Allocate<Elf_Shdr>()) Elf_Shdr;
  shdr->sh_name = _stringSection->addString(section->name());
  shdr->sh_type = section->type();
  shdr->sh_flags = section->flags();
  shdr->sh_offset = section->fileOffset();
  shdr->sh_addr = section->virtualAddr();
  if (section->isLoadableSection())
    shdr->sh_size = section->memSize();
  else
    shdr->sh_size = section->fileSize();
  shdr->sh_link = section->link();
  shdr->sh_info = section->shinfo();
  shdr->sh_addralign = section->alignment();
  shdr->sh_entsize = section->entsize();
  _sectionInfo.push_back(shdr);
}

template <class ELFT>
void SectionHeader<ELFT>::updateSection(Section<ELFT> *section) {
  Elf_Shdr *shdr = _sectionInfo[section->ordinal()];
  shdr->sh_type = section->getType();
  shdr->sh_flags = section->getFlags();
  shdr->sh_offset = section->fileOffset();
  shdr->sh_addr = section->virtualAddr();
  shdr->sh_size = section->fileSize();
  shdr->sh_link = section->getLink();
  shdr->sh_info = section->getInfo();
  shdr->sh_addralign = section->alignment();
  shdr->sh_entsize = section->getEntSize();
}

template <class ELFT>
void SectionHeader<ELFT>::write(ELFWriter *writer, TargetLayout<ELFT> &layout,
                                llvm::FileOutputBuffer &buffer) {
  uint8_t *chunkBuffer = buffer.getBufferStart();
  uint8_t *dest = chunkBuffer + this->fileOffset();
  for (auto shi : _sectionInfo) {
    memcpy(dest, shi, sizeof(Elf_Shdr));
    dest += sizeof(Elf_Shdr);
  }
  _stringSection->write(writer, layout, buffer);
}

template class ELFHeader<ELF32LE>;
template class ELFHeader<ELF32BE>;
template class ELFHeader<ELF64LE>;
template class ELFHeader<ELF64BE>;

template class ProgramHeader<ELF32LE>;
template class ProgramHeader<ELF32BE>;
template class ProgramHeader<ELF64LE>;
template class ProgramHeader<ELF64BE>;

template class SectionHeader<ELF32LE>;
template class SectionHeader<ELF32BE>;
template class SectionHeader<ELF64LE>;
template class SectionHeader<ELF64BE>;

} // end namespace elf
} // end namespace lld
