mirror of
https://github.com/intel/llvm.git
synced 2026-01-26 21:53:12 +08:00
This is an initial patch for a section-based COFF linker. The patch has 2300 lines of code including comments and blank lines. Before diving into details, you want to start from reading README because it should give you an overview of the design. All important things are written in the README file, so I write summary here. - The linker is already able to self-link on Windows. - It's significantly faster than the existing implementation. The existing one takes 5 seconds to link LLD on my machine, while the new one only takes 1.2 seconds, even though the new one is not multi-threaded yet. (And a proof-of-concept multi- threaded version was able to link it in 0.5 seconds.) - It uses much less memory (250MB vs. 2GB virtual memory space to self-host). - IMHO the new code is much simpler and easier to read than the existing PE/COFF port. http://reviews.llvm.org/D10036 llvm-svn: 238458
382 lines
13 KiB
C++
382 lines
13 KiB
C++
//===- Writer.cpp ---------------------------------------------------------===//
|
|
//
|
|
// The LLVM Linker
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Config.h"
|
|
#include "Writer.h"
|
|
#include "lld/Core/Error.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/FileOutputBuffer.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <utility>
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::object;
|
|
using namespace llvm::COFF;
|
|
|
|
static const int PageSize = 4096;
|
|
static const int FileAlignment = 512;
|
|
static const int SectionAlignment = 4096;
|
|
static const int DOSStubSize = 64;
|
|
static const int NumberfOfDataDirectory = 16;
|
|
static const int HeaderSize =
|
|
DOSStubSize + sizeof(PEMagic) + sizeof(coff_file_header) +
|
|
sizeof(pe32plus_header) + sizeof(data_directory) * NumberfOfDataDirectory;
|
|
|
|
namespace lld {
|
|
namespace coff {
|
|
|
|
OutputSection::OutputSection(StringRef N, uint32_t SI)
|
|
: Name(N), SectionIndex(SI) {
|
|
memset(&Header, 0, sizeof(Header));
|
|
strncpy(Header.Name, Name.data(), std::min(Name.size(), size_t(8)));
|
|
}
|
|
|
|
void OutputSection::setRVA(uint64_t RVA) {
|
|
Header.VirtualAddress = RVA;
|
|
for (Chunk *C : Chunks)
|
|
C->setRVA(C->getRVA() + RVA);
|
|
}
|
|
|
|
void OutputSection::setFileOffset(uint64_t Off) {
|
|
// If a section has no actual data (i.e. BSS section), we want to
|
|
// set 0 to its PointerToRawData. Otherwise the output is rejected
|
|
// by the loader.
|
|
if (Header.SizeOfRawData == 0)
|
|
return;
|
|
Header.PointerToRawData = Off;
|
|
for (Chunk *C : Chunks)
|
|
C->setFileOff(C->getFileOff() + Off);
|
|
}
|
|
|
|
void OutputSection::addChunk(Chunk *C) {
|
|
Chunks.push_back(C);
|
|
uint64_t Off = Header.VirtualSize;
|
|
Off = RoundUpToAlignment(Off, C->getAlign());
|
|
C->setRVA(Off);
|
|
C->setFileOff(Off);
|
|
Off += C->getSize();
|
|
Header.VirtualSize = Off;
|
|
if (C->hasData())
|
|
Header.SizeOfRawData = RoundUpToAlignment(Off, FileAlignment);
|
|
}
|
|
|
|
void OutputSection::addPermissions(uint32_t C) {
|
|
Header.Characteristics = Header.Characteristics | (C & PermMask);
|
|
}
|
|
|
|
void Writer::markLive() {
|
|
Entry = cast<Defined>(Symtab->find(Config->EntryName));
|
|
Entry->markLive();
|
|
for (Chunk *C : Symtab->getChunks())
|
|
if (C->isRoot())
|
|
C->markLive();
|
|
}
|
|
|
|
void Writer::createSections() {
|
|
std::map<StringRef, std::vector<Chunk *>> Map;
|
|
for (Chunk *C : Symtab->getChunks()) {
|
|
if (!C->isLive()) {
|
|
if (Config->Verbose)
|
|
C->printDiscardedMessage();
|
|
continue;
|
|
}
|
|
// '$' and all following characters in input section names are
|
|
// discarded when determining output section. So, .text$foo
|
|
// contributes to .text, for example. See PE/COFF spec 3.2.
|
|
Map[C->getSectionName().split('$').first].push_back(C);
|
|
}
|
|
|
|
// Input sections are ordered by their names including '$' parts,
|
|
// which gives you some control over the output layout.
|
|
auto Comp = [](Chunk *A, Chunk *B) {
|
|
return A->getSectionName() < B->getSectionName();
|
|
};
|
|
for (auto &P : Map) {
|
|
StringRef SectionName = P.first;
|
|
std::vector<Chunk *> &Chunks = P.second;
|
|
std::stable_sort(Chunks.begin(), Chunks.end(), Comp);
|
|
auto Sec =
|
|
llvm::make_unique<OutputSection>(SectionName, OutputSections.size());
|
|
for (Chunk *C : Chunks) {
|
|
C->setOutputSection(Sec.get());
|
|
Sec->addChunk(C);
|
|
Sec->addPermissions(C->getPermissions());
|
|
}
|
|
OutputSections.push_back(std::move(Sec));
|
|
}
|
|
}
|
|
|
|
std::map<StringRef, std::vector<DefinedImportData *>> Writer::binImports() {
|
|
// Group DLL-imported symbols by DLL name because that's how symbols
|
|
// are layed out in the import descriptor table.
|
|
std::map<StringRef, std::vector<DefinedImportData *>> Res;
|
|
OutputSection *Text = createSection(".text");
|
|
for (std::unique_ptr<ImportFile> &P : Symtab->ImportFiles) {
|
|
for (SymbolBody *B : P->getSymbols()) {
|
|
if (auto *Import = dyn_cast<DefinedImportData>(B)) {
|
|
Res[Import->getDLLName()].push_back(Import);
|
|
continue;
|
|
}
|
|
// Linker-created function thunks for DLL symbols are added to
|
|
// .text section.
|
|
Text->addChunk(cast<DefinedImportThunk>(B)->getChunk());
|
|
}
|
|
}
|
|
|
|
// Sort symbols by name for each group.
|
|
auto Comp = [](DefinedImportData *A, DefinedImportData *B) {
|
|
return A->getName() < B->getName();
|
|
};
|
|
for (auto &P : Res) {
|
|
std::vector<DefinedImportData *> &V = P.second;
|
|
std::sort(V.begin(), V.end(), Comp);
|
|
}
|
|
return Res;
|
|
}
|
|
|
|
// Create .idata section contents.
|
|
void Writer::createImportTables() {
|
|
if (Symtab->ImportFiles.empty())
|
|
return;
|
|
|
|
std::vector<ImportTable> Tabs;
|
|
for (auto &P : binImports()) {
|
|
StringRef DLLName = P.first;
|
|
std::vector<DefinedImportData *> &Imports = P.second;
|
|
Tabs.emplace_back(DLLName, Imports);
|
|
}
|
|
OutputSection *Idata = createSection(".idata");
|
|
size_t NumChunks = Idata->getChunks().size();
|
|
|
|
// Add the directory tables.
|
|
for (ImportTable &T : Tabs)
|
|
Idata->addChunk(T.DirTab);
|
|
Idata->addChunk(new NullChunk(sizeof(ImportDirectoryTableEntry)));
|
|
ImportDirectoryTableSize = (Tabs.size() + 1) * sizeof(ImportDirectoryTableEntry);
|
|
|
|
// Add the import lookup tables.
|
|
for (ImportTable &T : Tabs) {
|
|
for (LookupChunk *C : T.LookupTables)
|
|
Idata->addChunk(C);
|
|
Idata->addChunk(new NullChunk(sizeof(uint64_t)));
|
|
}
|
|
|
|
// Add the import address tables. Their contents are the same as the
|
|
// lookup tables.
|
|
for (ImportTable &T : Tabs) {
|
|
for (LookupChunk *C : T.AddressTables)
|
|
Idata->addChunk(C);
|
|
Idata->addChunk(new NullChunk(sizeof(uint64_t)));
|
|
ImportAddressTableSize += (T.AddressTables.size() + 1) * sizeof(uint64_t);
|
|
}
|
|
ImportAddressTable = Tabs[0].AddressTables[0];
|
|
|
|
// Add the hint name table.
|
|
for (ImportTable &T : Tabs)
|
|
for (HintNameChunk *C : T.HintNameTables)
|
|
Idata->addChunk(C);
|
|
|
|
// Add DLL names.
|
|
for (ImportTable &T : Tabs)
|
|
Idata->addChunk(T.DLLName);
|
|
|
|
// Claim ownership of all chunks in the .idata section.
|
|
for (size_t I = NumChunks, E = Idata->getChunks().size(); I < E; ++I)
|
|
Chunks.push_back(std::unique_ptr<Chunk>(Idata->getChunks()[I]));
|
|
}
|
|
|
|
// The Windows loader doesn't seem to like empty sections,
|
|
// so we remove them if any.
|
|
void Writer::removeEmptySections() {
|
|
auto IsEmpty = [](const std::unique_ptr<OutputSection> &S) {
|
|
return S->getVirtualSize() == 0;
|
|
};
|
|
OutputSections.erase(
|
|
std::remove_if(OutputSections.begin(), OutputSections.end(), IsEmpty),
|
|
OutputSections.end());
|
|
}
|
|
|
|
// Visits all sections to assign incremental, non-overlapping RVAs and
|
|
// file offsets.
|
|
void Writer::assignAddresses() {
|
|
SizeOfHeaders = RoundUpToAlignment(
|
|
HeaderSize + sizeof(coff_section) * OutputSections.size(), PageSize);
|
|
uint64_t RVA = 0x1000; // The first page is kept unmapped.
|
|
uint64_t FileOff = SizeOfHeaders;
|
|
for (std::unique_ptr<OutputSection> &Sec : OutputSections) {
|
|
Sec->setRVA(RVA);
|
|
Sec->setFileOffset(FileOff);
|
|
RVA += RoundUpToAlignment(Sec->getVirtualSize(), PageSize);
|
|
FileOff += RoundUpToAlignment(Sec->getRawSize(), FileAlignment);
|
|
}
|
|
SizeOfImage = SizeOfHeaders + RoundUpToAlignment(RVA - 0x1000, PageSize);
|
|
FileSize = SizeOfHeaders +
|
|
RoundUpToAlignment(FileOff - SizeOfHeaders, FileAlignment);
|
|
}
|
|
|
|
void Writer::writeHeader() {
|
|
// Write DOS stub
|
|
uint8_t *Buf = Buffer->getBufferStart();
|
|
auto *DOS = reinterpret_cast<dos_header *>(Buf);
|
|
Buf += DOSStubSize;
|
|
DOS->Magic[0] = 'M';
|
|
DOS->Magic[1] = 'Z';
|
|
DOS->AddressOfRelocationTable = sizeof(dos_header);
|
|
DOS->AddressOfNewExeHeader = DOSStubSize;
|
|
|
|
// Write PE magic
|
|
memcpy(Buf, PEMagic, sizeof(PEMagic));
|
|
Buf += sizeof(PEMagic);
|
|
|
|
// Write COFF header
|
|
auto *COFF = reinterpret_cast<coff_file_header *>(Buf);
|
|
Buf += sizeof(*COFF);
|
|
COFF->Machine = IMAGE_FILE_MACHINE_AMD64;
|
|
COFF->NumberOfSections = OutputSections.size();
|
|
COFF->Characteristics =
|
|
(IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_RELOCS_STRIPPED |
|
|
IMAGE_FILE_LARGE_ADDRESS_AWARE);
|
|
COFF->SizeOfOptionalHeader =
|
|
sizeof(pe32plus_header) + sizeof(data_directory) * NumberfOfDataDirectory;
|
|
|
|
// Write PE header
|
|
auto *PE = reinterpret_cast<pe32plus_header *>(Buf);
|
|
Buf += sizeof(*PE);
|
|
PE->Magic = PE32Header::PE32_PLUS;
|
|
PE->ImageBase = Config->ImageBase;
|
|
PE->SectionAlignment = SectionAlignment;
|
|
PE->FileAlignment = FileAlignment;
|
|
PE->MajorOperatingSystemVersion = 6;
|
|
PE->MajorSubsystemVersion = 6;
|
|
PE->Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI;
|
|
PE->SizeOfImage = SizeOfImage;
|
|
PE->SizeOfHeaders = SizeOfHeaders;
|
|
PE->AddressOfEntryPoint = Entry->getRVA();
|
|
PE->SizeOfStackReserve = 1024 * 1024;
|
|
PE->SizeOfStackCommit = 4096;
|
|
PE->SizeOfHeapReserve = 1024 * 1024;
|
|
PE->SizeOfHeapCommit = 4096;
|
|
PE->NumberOfRvaAndSize = NumberfOfDataDirectory;
|
|
if (OutputSection *Text = findSection(".text")) {
|
|
PE->BaseOfCode = Text->getRVA();
|
|
PE->SizeOfCode = Text->getRawSize();
|
|
}
|
|
PE->SizeOfInitializedData = getSizeOfInitializedData();
|
|
|
|
// Write data directory
|
|
auto *DataDirectory = reinterpret_cast<data_directory *>(Buf);
|
|
Buf += sizeof(*DataDirectory) * NumberfOfDataDirectory;
|
|
if (OutputSection *Idata = findSection(".idata")) {
|
|
using namespace llvm::COFF;
|
|
DataDirectory[IMPORT_TABLE].RelativeVirtualAddress = Idata->getRVA();
|
|
DataDirectory[IMPORT_TABLE].Size = ImportDirectoryTableSize;
|
|
DataDirectory[IAT].RelativeVirtualAddress = ImportAddressTable->getRVA();
|
|
DataDirectory[IAT].Size = ImportAddressTableSize;
|
|
}
|
|
|
|
// Write section table
|
|
coff_section *SectionTable = reinterpret_cast<coff_section *>(Buf);
|
|
int Idx = 0;
|
|
for (std::unique_ptr<OutputSection> &Sec : OutputSections)
|
|
SectionTable[Idx++] = Sec->getHeader();
|
|
}
|
|
|
|
std::error_code Writer::openFile(StringRef Path) {
|
|
if (auto EC = FileOutputBuffer::create(Path, FileSize, Buffer,
|
|
FileOutputBuffer::F_executable))
|
|
return make_dynamic_error_code(Twine("Failed to open ") + Path + ": " +
|
|
EC.message());
|
|
return std::error_code();
|
|
}
|
|
|
|
// Write section contents to a mmap'ed file.
|
|
void Writer::writeSections() {
|
|
uint8_t *Buf = Buffer->getBufferStart();
|
|
for (std::unique_ptr<OutputSection> &Sec : OutputSections) {
|
|
// Fill gaps between functions in .text with INT3 instructions
|
|
// instead of leaving as NUL bytes (which can be interpreted as
|
|
// ADD instructions).
|
|
if (Sec->getPermissions() & IMAGE_SCN_CNT_CODE)
|
|
memset(Buf + Sec->getFileOff(), 0xCC, Sec->getRawSize());
|
|
for (Chunk *C : Sec->getChunks())
|
|
if (C->hasData())
|
|
memcpy(Buf + C->getFileOff(), C->getData(), C->getSize());
|
|
}
|
|
}
|
|
|
|
OutputSection *Writer::findSection(StringRef Name) {
|
|
for (std::unique_ptr<OutputSection> &Sec : OutputSections)
|
|
if (Sec->getName() == Name)
|
|
return Sec.get();
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t Writer::getSizeOfInitializedData() {
|
|
uint32_t Res = 0;
|
|
for (std::unique_ptr<OutputSection> &S : OutputSections)
|
|
if (S->getPermissions() & IMAGE_SCN_CNT_INITIALIZED_DATA)
|
|
Res += S->getRawSize();
|
|
return Res;
|
|
}
|
|
|
|
// Returns an existing section or create a new one if not found.
|
|
OutputSection *Writer::createSection(StringRef Name) {
|
|
if (auto *Sec = findSection(Name))
|
|
return Sec;
|
|
const auto R = IMAGE_SCN_MEM_READ;
|
|
const auto W = IMAGE_SCN_MEM_WRITE;
|
|
const auto E = IMAGE_SCN_MEM_EXECUTE;
|
|
uint32_t Perm = StringSwitch<uint32_t>(Name)
|
|
.Case(".bss", IMAGE_SCN_CNT_UNINITIALIZED_DATA | R | W)
|
|
.Case(".data", IMAGE_SCN_CNT_INITIALIZED_DATA | R | W)
|
|
.Case(".idata", IMAGE_SCN_CNT_INITIALIZED_DATA | R)
|
|
.Case(".rdata", IMAGE_SCN_CNT_INITIALIZED_DATA | R)
|
|
.Case(".text", IMAGE_SCN_CNT_CODE | R | E)
|
|
.Default(0);
|
|
if (!Perm)
|
|
llvm_unreachable("unknown section name");
|
|
auto Sec = new OutputSection(Name, OutputSections.size());
|
|
Sec->addPermissions(Perm);
|
|
OutputSections.push_back(std::unique_ptr<OutputSection>(Sec));
|
|
return Sec;
|
|
}
|
|
|
|
void Writer::applyRelocations() {
|
|
uint8_t *Buf = Buffer->getBufferStart();
|
|
for (std::unique_ptr<OutputSection> &Sec : OutputSections)
|
|
for (Chunk *C : Sec->getChunks())
|
|
C->applyRelocations(Buf);
|
|
}
|
|
|
|
std::error_code Writer::write(StringRef OutputPath) {
|
|
markLive();
|
|
createSections();
|
|
createImportTables();
|
|
assignAddresses();
|
|
removeEmptySections();
|
|
if (auto EC = openFile(OutputPath))
|
|
return EC;
|
|
writeHeader();
|
|
writeSections();
|
|
applyRelocations();
|
|
if (auto EC = Buffer->commit())
|
|
return EC;
|
|
return std::error_code();
|
|
}
|
|
|
|
} // namespace coff
|
|
} // namespace lld
|