summaryrefslogtreecommitdiffstats
path: root/lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp')
-rw-r--r--lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp329
1 files changed, 237 insertions, 92 deletions
diff --git a/lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp b/lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
index e0a053e4367..4b57491c213 100644
--- a/lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
+++ b/lld/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
@@ -22,6 +22,7 @@
#define DEBUG_TYPE "WriterPECOFF"
+#include <map>
#include <time.h>
#include <vector>
@@ -43,6 +44,7 @@ namespace lld {
namespace pecoff {
namespace {
+class SectionChunk;
// Page size of x86 processor. Some data needs to be aligned at page boundary
// when loaded into memory.
@@ -55,7 +57,12 @@ const int SECTOR_SIZE = 512;
/// A Chunk is an abstrace contiguous range in an output file.
class Chunk {
public:
- Chunk() : _size(0), _align(1) {}
+ enum Kind {
+ kindHeader,
+ kindSection
+ };
+
+ Chunk(Kind kind) : _kind(kind), _size(0), _align(1) {}
virtual ~Chunk() {};
virtual void write(uint8_t *fileBuffer) = 0;
@@ -67,17 +74,31 @@ public:
_fileOffset = fileOffset;
}
+ Kind getKind() const { return _kind; }
+
protected:
+ Kind _kind;
uint64_t _size;
uint64_t _fileOffset;
uint64_t _align;
};
+/// A HeaderChunk is an abstract class to represent a file header for
+/// PE/COFF. The data in the header chunk is metadata about program and will
+/// be consumed by the windows loader. HeaderChunks are not mapped to memory
+/// when executed.
+class HeaderChunk : public Chunk {
+public:
+ HeaderChunk() : Chunk(kindHeader) {}
+
+ static bool classof(const Chunk *c) { return c->getKind() == kindHeader; }
+};
+
/// A DOSStubChunk represents the DOS compatible header at the beginning
/// of PE/COFF files.
-class DOSStubChunk : public Chunk {
+class DOSStubChunk : public HeaderChunk {
public:
- DOSStubChunk() : Chunk() {
+ DOSStubChunk() : HeaderChunk() {
// Make the DOS stub occupy the first 128 bytes of an exe. Technically
// this can be as small as 64 bytes, but GNU binutil's objdump cannot
// parse such irregular header.
@@ -102,36 +123,37 @@ private:
llvm::object::dos_header _dosHeader;
};
-/// A PEHeaderChunk represents PE header.
-class PEHeaderChunk : public Chunk {
+/// A PEHeaderChunk represents PE header including COFF header.
+class PEHeaderChunk : public HeaderChunk {
public:
- PEHeaderChunk(const PECOFFTargetInfo &targetInfo) : Chunk() {
+ PEHeaderChunk(const PECOFFTargetInfo &targetInfo) : HeaderChunk() {
// Set the size of the chunk and initialize the header with null bytes.
_size = sizeof(llvm::COFF::PEMagic) + sizeof(_coffHeader)
+ sizeof(_peHeader);
std::memset(&_coffHeader, 0, sizeof(_coffHeader));
std::memset(&_peHeader, 0, sizeof(_peHeader));
-
_coffHeader.Machine = llvm::COFF::IMAGE_FILE_MACHINE_I386;
- _coffHeader.NumberOfSections = 1; // [FIXME]
_coffHeader.TimeDateStamp = time(NULL);
// The size of PE header including optional data directory is always 224.
_coffHeader.SizeOfOptionalHeader = 224;
- _coffHeader.Characteristics = llvm::COFF::IMAGE_FILE_32BIT_MACHINE
- | llvm::COFF::IMAGE_FILE_EXECUTABLE_IMAGE;
- // 0x10b indicates a normal executable. For PE32+ it should be 0x20b.
+ // Attributes of the executable. We set IMAGE_FILE_RELOCS_STRIPPED flag
+ // because we do not support ".reloc" section. That means that the
+ // executable will have to be loaded at the preferred address as specified
+ // by ImageBase (which the Windows loader usually do), or fail to start
+ // because of lack of relocation info.
+ _coffHeader.Characteristics = llvm::COFF::IMAGE_FILE_32BIT_MACHINE |
+ llvm::COFF::IMAGE_FILE_EXECUTABLE_IMAGE |
+ llvm::COFF::IMAGE_FILE_RELOCS_STRIPPED;
+
+ // 0x10b indicates a normal PE32 executable. For PE32+ it should be 0x20b.
_peHeader.Magic = 0x10b;
// The address of entry point relative to ImageBase. Windows executable
// usually starts at address 0x401000.
_peHeader.AddressOfEntryPoint = 0x1000;
- _peHeader.BaseOfCode = 0x1000;
-
- // [FIXME] The address of data section relative to ImageBase.
- _peHeader.BaseOfData = 0x2000;
// The address of the executable when loaded into memory. The default for
// DLLs is 0x10000000. The default for executables is 0x400000.
@@ -153,9 +175,6 @@ public:
_peHeader.MajorSubsystemVersion = minOSVersion.majorVersion;
_peHeader.MinorSubsystemVersion = minOSVersion.minorVersion;
- // [FIXME] The size of the image when loaded into memory
- _peHeader.SizeOfImage = 0x2000;
-
// The combined size of the DOS, PE and section headers including garbage
// between the end of the header and the beginning of the first section.
// Must be multiple of FileAlignment.
@@ -187,6 +206,16 @@ public:
_peHeader.SizeOfCode = size;
}
+ virtual void setNumberOfSections(uint32_t num) {
+ _coffHeader.NumberOfSections = num;
+ }
+
+ virtual void setBaseOfCode(uint32_t rva) { _peHeader.BaseOfCode = rva; }
+
+ virtual void setBaseOfData(uint32_t rva) { _peHeader.BaseOfData = rva; }
+
+ virtual void setSizeOfImage(uint32_t size) { _peHeader.SizeOfImage = size; }
+
private:
llvm::object::coff_file_header _coffHeader;
llvm::object::pe32_header _peHeader;
@@ -196,9 +225,9 @@ private:
/// header in the output file. An entry consists of an 8 byte field that
/// indicates a relative virtual address (the starting address of the entry data
/// in memory) and 8 byte entry data size.
-class DataDirectoryChunk : public Chunk {
+class DataDirectoryChunk : public HeaderChunk {
public:
- DataDirectoryChunk() : Chunk() {
+ DataDirectoryChunk() : HeaderChunk() {
// [FIXME] Currently all entries are filled with zero.
_size = sizeof(_dirs);
std::memset(&_dirs, 0, sizeof(_dirs));
@@ -212,12 +241,63 @@ private:
llvm::object::data_directory _dirs[16];
};
+/// A SectionHeaderTableChunk represents Section Table Header of PE/COFF
+/// format, which is a list of section headers.
+class SectionHeaderTableChunk : public HeaderChunk {
+public:
+ SectionHeaderTableChunk() : HeaderChunk() {}
+ void addSection(SectionChunk *chunk);
+ virtual uint64_t size() const;
+ virtual void write(uint8_t *fileBuffer);
+
+private:
+ std::vector<SectionChunk *> _sections;
+};
+
/// A SectionChunk represents a section in the output file. It consists of a
/// section header and atoms which to be output as the content of the section.
class SectionChunk : public Chunk {
+private:
+ llvm::object::coff_section
+ createSectionHeader(StringRef sectionName, uint32_t characteristics) const {
+ llvm::object::coff_section header;
+
+ // Section name equal to or shorter than 8 byte fits in the section
+ // header. Longer names should be stored to string table, which is not
+ // implemented yet.
+ if (sizeof(header.Name) < sectionName.size())
+ llvm_unreachable("Cannot handle section name longer than 8 byte");
+
+ // Name field must be NUL-padded. If the name is exactly 8 byte long,
+ // there's no terminating NUL.
+ std::memset(header.Name, 0, sizeof(header.Name));
+ std::strncpy(header.Name, sectionName.data(), sizeof(header.Name));
+
+ header.VirtualSize = 0;
+ header.VirtualAddress = 0;
+ header.SizeOfRawData = 0;
+ header.PointerToRawData = 0;
+ header.PointerToRelocations = 0;
+ header.PointerToLinenumbers = 0;
+ header.NumberOfRelocations = 0;
+ header.NumberOfLinenumbers = 0;
+ header.Characteristics = characteristics;
+ return header;
+ }
+
public:
- SectionChunk(llvm::object::coff_section sectionHeader)
- : _sectionHeader(sectionHeader) {}
+ SectionChunk(SectionHeaderTableChunk *table, StringRef sectionName,
+ uint32_t characteristics)
+ : Chunk(kindSection),
+ _sectionHeader(createSectionHeader(sectionName, characteristics)) {
+ table->addSection(this);
+ }
+
+ virtual uint64_t size() const {
+ // Round up to the nearest alignment border, so that the text segment ends
+ // at a border.
+ return llvm::RoundUpToAlignment(_size, _align);
+ }
void appendAtom(const DefinedAtom *atom) {
_atoms.push_back(atom);
@@ -233,10 +313,26 @@ public:
}
}
+ const std::vector<const DefinedAtom *> getAtoms() { return _atoms; }
+
+ // Set the file offset of the beginning of this section.
+ virtual void setFileOffset(uint64_t fileOffset) {
+ Chunk::setFileOffset(fileOffset);
+ _sectionHeader.PointerToRawData = fileOffset;
+ }
+
+ virtual void setVirtualAddress(uint32_t rva) {
+ _sectionHeader.VirtualAddress = rva;
+ }
+
+ virtual uint32_t getVirtualAddress() { return _sectionHeader.VirtualAddress; }
+
const llvm::object::coff_section &getSectionHeader() {
return _sectionHeader;
}
+ static bool classof(const Chunk *c) { return c->getKind() == kindSection; }
+
protected:
llvm::object::coff_section _sectionHeader;
@@ -244,69 +340,42 @@ private:
std::vector<const DefinedAtom *> _atoms;
};
-/// A SectionHeaderTableChunk is a list of section headers. The number of
-/// section headers is in the PE header. A section header has metadata about the
-/// section and a file offset to its content. Each section header is 40 byte and
-/// contiguous in the output file.
-class SectionHeaderTableChunk : public Chunk {
-public:
- SectionHeaderTableChunk() : Chunk() {}
-
- void addSection(SectionChunk *chunk) {
- _sections.push_back(chunk);
- }
+void SectionHeaderTableChunk::addSection(SectionChunk *chunk) {
+ _sections.push_back(chunk);
+}
- virtual uint64_t size() const {
- return _sections.size() * sizeof(llvm::object::coff_section);
- }
+size_t SectionHeaderTableChunk::size() const {
+ return _sections.size() * sizeof(llvm::object::coff_section);
+}
- virtual void write(uint8_t *fileBuffer) {
- uint64_t offset = 0;
- for (const auto &chunk : _sections) {
- const llvm::object::coff_section &header = chunk->getSectionHeader();
- std::memcpy(fileBuffer + offset, &header, sizeof(header));
- offset += sizeof(header);
- }
+void SectionHeaderTableChunk::write(uint8_t *fileBuffer) {
+ uint64_t offset = 0;
+ for (const auto &chunk : _sections) {
+ const llvm::object::coff_section &header = chunk->getSectionHeader();
+ std::memcpy(fileBuffer + offset, &header, sizeof(header));
+ offset += sizeof(header);
}
-
-private:
- std::vector<SectionChunk*> _sections;
-};
+}
// \brief A TextSectionChunk represents a .text section.
class TextSectionChunk : public SectionChunk {
-private:
- llvm::object::coff_section createSectionHeader() {
- llvm::object::coff_section header;
- std::memcpy(&header.Name, ".text\0\0\0\0", 8);
- header.VirtualSize = 0;
- header.VirtualAddress = 0x1000;
- header.SizeOfRawData = 0;
- header.PointerToRawData = 0;
- header.PointerToRelocations = 0;
- header.PointerToLinenumbers = 0;
- header.NumberOfRelocations = 0;
- header.NumberOfLinenumbers = 0;
- header.Characteristics = llvm::COFF::IMAGE_SCN_CNT_CODE
- | llvm::COFF::IMAGE_SCN_MEM_EXECUTE
- | llvm::COFF::IMAGE_SCN_MEM_READ;
- return header;
- }
+ // When loaded into memory, text section should be readable and executable.
+ static const uint32_t characteristics =
+ llvm::COFF::IMAGE_SCN_CNT_CODE | llvm::COFF::IMAGE_SCN_MEM_EXECUTE |
+ llvm::COFF::IMAGE_SCN_MEM_READ;
public:
- TextSectionChunk(const File &linkedFile)
- : SectionChunk(createSectionHeader()) {
+ TextSectionChunk(const File &linkedFile, SectionHeaderTableChunk *table)
+ : SectionChunk(table, ".text", characteristics) {
// The text section should be aligned to disk sector.
_align = SECTOR_SIZE;
// Extract executable atoms from the linked file and append them to this
// section.
- for (const DefinedAtom* atom : linkedFile.defined()) {
+ for (const DefinedAtom *atom : linkedFile.defined()) {
assert(atom->sectionChoice() == DefinedAtom::sectionBasedOnContent);
- DefinedAtom::ContentType type = atom->contentType();
- if (type != DefinedAtom::typeCode)
- continue;
- appendAtom(atom);
+ if (atom->contentType() == DefinedAtom::typeCode)
+ appendAtom(atom);
}
// Now that we have a list of atoms that to be written in this section, and
@@ -314,17 +383,65 @@ public:
_sectionHeader.VirtualSize = _size;
_sectionHeader.SizeOfRawData = _size;
}
+};
- virtual uint64_t size() const {
- // Round up to the nearest alignment border, so that the text segment ends
- // at a border.
- return (_size + _align - 1) & -_align;
+// \brief A RDataSectionChunk represents a .rdata section.
+class RDataSectionChunk : public SectionChunk {
+ // When loaded into memory, rdata section should be readable.
+ static const uint32_t characteristics =
+ llvm::COFF::IMAGE_SCN_MEM_READ |
+ llvm::COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
+
+public:
+ RDataSectionChunk(const File &linkedFile, SectionHeaderTableChunk *table)
+ : SectionChunk(table, ".rdata", characteristics) {
+ // The data section should be aligned to disk sector.
+ _align = 512;
+
+ // Extract executable atoms from the linked file and append them to this
+ // section.
+ for (const DefinedAtom *atom : linkedFile.defined()) {
+ assert(atom->sectionChoice() == DefinedAtom::sectionBasedOnContent);
+ if (atom->contentType() == DefinedAtom::typeData &&
+ atom->permissions() == DefinedAtom::permRW_)
+ appendAtom(atom);
+ }
+
+ // Now that we have a list of atoms that to be written in this section, and
+ // we know the size of the section.
+ _sectionHeader.VirtualSize = _size;
+ _sectionHeader.SizeOfRawData = _size;
}
+};
- // Set the file offset of the beginning of this section.
- virtual void setFileOffset(uint64_t fileOffset) {
- SectionChunk::setFileOffset(fileOffset);
- _sectionHeader.PointerToRawData = fileOffset;
+// \brief A DataSectionChunk represents a .data section.
+class DataSectionChunk : public SectionChunk {
+ // When loaded into memory, data section should be readable and writable.
+ static const uint32_t characteristics =
+ llvm::COFF::IMAGE_SCN_MEM_READ |
+ llvm::COFF::IMAGE_SCN_CNT_INITIALIZED_DATA |
+ llvm::COFF::IMAGE_SCN_MEM_WRITE;
+
+public:
+ DataSectionChunk(const File &linkedFile, SectionHeaderTableChunk *table)
+ : SectionChunk(table, ".data", characteristics) {
+ // The data section should be aligned to disk sector.
+ _align = 512;
+
+ // Extract executable atoms from the linked file and append them to this
+ // section.
+ for (const DefinedAtom *atom : linkedFile.defined()) {
+ assert(atom->sectionChoice() == DefinedAtom::sectionBasedOnContent);
+
+ if (atom->contentType() == DefinedAtom::typeData &&
+ atom->permissions() == DefinedAtom::permR__)
+ appendAtom(atom);
+ }
+
+ // Now that we have a list of atoms that to be written in this section, and
+ // we know the size of the section.
+ _sectionHeader.VirtualSize = _size;
+ _sectionHeader.SizeOfRawData = _size;
}
};
@@ -333,16 +450,36 @@ public:
class ExecutableWriter : public Writer {
private:
// Compute and set the offset of each chunk in the output file.
- void computeChunkSize() {
+ void computeChunkSizeOnDisk() {
uint64_t offset = 0;
for (auto &chunk : _chunks) {
// Round up to the nearest alignment boundary.
- offset = (offset + chunk->align() - 1) & -chunk->align();
+ offset = llvm::RoundUpToAlignment(offset, chunk->align());
chunk->setFileOffset(offset);
offset += chunk->size();
}
}
+ // Compute the starting address of sections when loaded in memory. They are
+ // different from positions on disk because sections need to be
+ // sector-aligned on disk but page-aligned in memory.
+ void computeChunkSizeInMemory(uint32_t &numSections, uint32_t &imageSize) {
+ // The first page starting at ImageBase is usually left unmapped. IIUC
+ // there's no technical reason to do so, but we'll follow that convention
+ // so that we don't produce odd-looking binary. We should update the code
+ // (or this comment) once we figure the reason out.
+ uint32_t offset = PAGE_SIZE;
+ uint32_t va = offset;
+ for (auto &cp : _chunks) {
+ if (SectionChunk *chunk = dyn_cast<SectionChunk>(&*cp)) {
+ numSections++;
+ chunk->setVirtualAddress(va);
+ va = llvm::RoundUpToAlignment(va + chunk->size(), PAGE_SIZE);
+ }
+ }
+ imageSize = va - offset;
+ }
+
void addChunk(Chunk *chunk) {
_chunks.push_back(std::unique_ptr<Chunk>(chunk));
}
@@ -354,27 +491,35 @@ public:
// Create all chunks that consist of the output file.
void build(const File &linkedFile) {
// Create file chunks and add them to the list.
- Chunk *dosStub(new DOSStubChunk());
- PEHeaderChunk *peHeader(new PEHeaderChunk(_PECOFFTargetInfo));
- Chunk *dataDirectoryHeader(new DataDirectoryChunk());
- SectionHeaderTableChunk *sectionTable(new SectionHeaderTableChunk());
+ auto *dosStub = new DOSStubChunk();
+ auto *peHeader = new PEHeaderChunk(_PECOFFTargetInfo);
+ auto *dataDirectory = new DataDirectoryChunk();
+ auto *sectionTable = new SectionHeaderTableChunk();
+ auto *text = new TextSectionChunk(linkedFile, sectionTable);
+ auto *rdata = new RDataSectionChunk(linkedFile, sectionTable);
+ auto *data = new DataSectionChunk(linkedFile, sectionTable);
+
addChunk(dosStub);
addChunk(peHeader);
- addChunk(dataDirectoryHeader);
+ addChunk(dataDirectory);
addChunk(sectionTable);
-
- // Create text section.
- // [FIXME] Handle data and bss sections.
- SectionChunk *text = new TextSectionChunk(linkedFile);
- sectionTable->addSection(text);
addChunk(text);
+ addChunk(rdata);
+ addChunk(data);
// Compute and assign file offset to each chunk.
- computeChunkSize();
+ uint32_t numSections = 0;
+ uint32_t imageSize = 0;
+ computeChunkSizeOnDisk();
+ computeChunkSizeInMemory(numSections, imageSize);
// Now that we know the size and file offset of sections. Set the file
// header accordingly.
peHeader->setSizeOfCode(text->size());
+ peHeader->setBaseOfCode(text->getVirtualAddress());
+ peHeader->setBaseOfData(rdata->getVirtualAddress());
+ peHeader->setNumberOfSections(numSections);
+ peHeader->setSizeOfImage(imageSize);
}
virtual error_code writeFile(const File &linkedFile, StringRef path) {
OpenPOWER on IntegriCloud