[lld] r186336 - [PECOFF][Writer] Emit .reloc section.

Rui Ueyama ruiu at google.com
Mon Jul 15 11:43:01 PDT 2013


Author: ruiu
Date: Mon Jul 15 13:43:01 2013
New Revision: 186336

URL: http://llvm.org/viewvc/llvm-project?rev=186336&view=rev
Log:
[PECOFF][Writer] Emit .reloc section.

Emit .reloc section. This is the first step to support DLL creation. The
executable doesn't need .reloc section, but the DLL does.

Reviewers: Bigcheese

CC: llvm-commits

Differential Revision: http://llvm-reviews.chandlerc.com/D1126

Added:
    lld/trunk/test/pecoff/base-reloc.test
Modified:
    lld/trunk/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
    lld/trunk/test/pecoff/trivial.test

Modified: lld/trunk/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp?rev=186336&r1=186335&r2=186336&view=diff
==============================================================================
--- lld/trunk/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp (original)
+++ lld/trunk/lib/ReaderWriter/PECOFF/WriterPECOFF.cpp Mon Jul 15 13:43:01 2013
@@ -26,6 +26,8 @@
 #include <time.h>
 #include <vector>
 
+#include "Atoms.h"
+
 #include "lld/Core/DefinedAtom.h"
 #include "lld/Core/File.h"
 #include "lld/Core/InputFiles.h"
@@ -145,14 +147,9 @@ public:
     // The size of PE header including optional data directory is always 224.
     _coffHeader.SizeOfOptionalHeader = 224;
 
-    // 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.
+    // Attributes of the executable.
     _coffHeader.Characteristics = llvm::COFF::IMAGE_FILE_32BIT_MACHINE |
-                                  llvm::COFF::IMAGE_FILE_EXECUTABLE_IMAGE |
-                                  llvm::COFF::IMAGE_FILE_RELOCS_STRIPPED;
+                                  llvm::COFF::IMAGE_FILE_EXECUTABLE_IMAGE;
 
     // 0x10b indicates a normal PE32 executable. For PE32+ it should be 0x20b.
     _peHeader.Magic = 0x10b;
@@ -270,20 +267,19 @@ public:
   }
 
   /// Add all atoms to the given map. This data will be used to do relocation.
-  void
-  buildAtomToVirtualAddr(std::map<const Atom *, uint64_t> &atomToVirtualAddr) {
+  void buildAtomToVirtualAddr(std::map<const Atom *, uint64_t> &atomRva) {
     for (const auto *layout : _atomLayouts)
-      atomToVirtualAddr[layout->_atom] = layout->_virtualAddr;
+      atomRva[layout->_atom] = layout->_virtualAddr;
   }
 
   void applyRelocations(uint8_t *fileBuffer,
-                        std::map<const Atom *, uint64_t> &atomToVirtualAddr) {
+                        std::map<const Atom *, uint64_t> &atomRva) {
     for (const auto *layout : _atomLayouts) {
       const DefinedAtom *atom = dyn_cast<const DefinedAtom>(layout->_atom);
       for (const Reference *ref : *atom) {
         auto relocSite = reinterpret_cast<llvm::support::ulittle32_t *>(
             fileBuffer + layout->_fileOffset + ref->offsetInAtom());
-        uint64_t targetAddr = atomToVirtualAddr[ref->target()];
+        uint64_t targetAddr = atomRva[ref->target()];
 
         // Skip if this reference is not for relocation.
         if (ref->kind() < lld::Reference::kindTargetLow)
@@ -304,7 +300,7 @@ public:
         case llvm::COFF::IMAGE_REL_I386_REL32: {
           // Set 32-bit relative address of the target. This relocation is
           // usually used for relative branch or call instruction.
-          uint32_t disp = atomToVirtualAddr[atom] + ref->offsetInAtom() + 4;
+          uint32_t disp = atomRva[atom] + ref->offsetInAtom() + 4;
           *relocSite = targetAddr - disp;
           break;
         }
@@ -315,6 +311,19 @@ public:
     }
   }
 
+  /// List all the relocation sites that need to be fixed up if image base is
+  /// relocated. Such relocation types are DIR32 and DIR32NB on i386. REL32 does
+  /// not be (and should not be) fixed up because it's PC-relative.
+  void addBaseRelocations(std::vector<uint64_t> &relocSites) {
+    for (const auto *layout : _atomLayouts) {
+      const DefinedAtom *atom = dyn_cast<const DefinedAtom>(layout->_atom);
+      for (const Reference *ref : *atom)
+        if (ref->kind() == llvm::COFF::IMAGE_REL_I386_DIR32 ||
+            ref->kind() == llvm::COFF::IMAGE_REL_I386_DIR32NB)
+          relocSites.push_back(layout->_virtualAddr + ref->offsetInAtom());
+    }
+  }
+
   // Set the file offset of the beginning of this section.
   virtual void setFileOffset(uint64_t fileOffset) {
     Chunk::setFileOffset(fileOffset);
@@ -322,6 +331,11 @@ public:
       layout->_fileOffset += fileOffset;
   }
 
+  uint64_t getSectionRva() {
+    assert(_atomLayouts.size() > 0);
+    return _atomLayouts[0]->_virtualAddr;
+  }
+
   virtual void setVirtualAddress(uint32_t rva) {
     for (AtomLayout *layout : _atomLayouts)
       layout->_virtualAddr += rva;
@@ -344,12 +358,12 @@ protected:
 class DataDirectoryChunk : public AtomChunk {
 public:
   DataDirectoryChunk(const File &linkedFile)
-      : AtomChunk(kindDataDirectory) {
+      : AtomChunk(kindDataDirectory), _file(linkedFile) {
     // Extract atoms from the linked file and append them to this section.
     for (const DefinedAtom *atom : linkedFile.defined()) {
       if (atom->contentType() == DefinedAtom::typeDataDirectoryEntry) {
-        uint64_t size = atom->ordinal() * sizeof(llvm::object::data_directory);
-        _atomLayouts.push_back(new (_alloc) AtomLayout(atom, size, size));
+        uint64_t offset = atom->ordinal() * sizeof(llvm::object::data_directory);
+        _atomLayouts.push_back(new (_alloc) AtomLayout(atom, offset, offset));
       }
     }
   }
@@ -358,6 +372,12 @@ public:
     return sizeof(llvm::object::data_directory) * 16;
   }
 
+  void setBaseRelocField(uint32_t addr, uint32_t size) {
+    auto *atom = new (_alloc) coff::COFFDataDirectoryAtom(_file, 5);
+    uint64_t offset = atom->ordinal() * sizeof(llvm::object::data_directory);
+    _atomLayouts.push_back(new (_alloc) AtomLayout(atom, offset, offset));
+  }
+
   virtual void write(uint8_t *fileBuffer) {
     fileBuffer += fileOffset();
     for (const AtomLayout *layout : _atomLayouts) {
@@ -369,6 +389,7 @@ public:
   }
 
 private:
+  const File &_file;
   mutable llvm::BumpPtrAllocator _alloc;
 };
 
@@ -384,6 +405,10 @@ public:
     return llvm::RoundUpToAlignment(_size, _align);
   }
 
+  virtual uint64_t rawSize() const {
+    return _size;
+  }
+
   // Set the file offset of the beginning of this section.
   virtual void setFileOffset(uint64_t fileOffset) {
     AtomChunk::setFileOffset(fileOffset);
@@ -398,9 +423,20 @@ public:
   virtual uint32_t getVirtualAddress() { return _sectionHeader.VirtualAddress; }
 
   const llvm::object::coff_section &getSectionHeader() {
+    // Fix up section size before returning it. VirtualSize should be the size
+    // of the actual content, and SizeOfRawData should be aligned to the section
+    // alignment.
+    _sectionHeader.VirtualSize = _size;
+    _sectionHeader.SizeOfRawData = size();
     return _sectionHeader;
   }
 
+  void appendAtom(const DefinedAtom *atom) {
+    auto *layout = new (_alloc) AtomLayout(atom, _size, _size);
+    _atomLayouts.push_back(layout);
+    _size += atom->rawContent().size();
+  }
+
   static bool classof(const Chunk *c) { return c->getKind() == kindSection; }
 
 protected:
@@ -456,12 +492,6 @@ private:
     return header;
   }
 
-  void appendAtom(const DefinedAtom *atom) {
-    auto *layout = new (_alloc) AtomLayout(atom, _size, _size);
-    _atomLayouts.push_back(layout);
-    _size += atom->rawContent().size();
-  }
-
   llvm::object::coff_section _sectionHeader;
   mutable llvm::BumpPtrAllocator _alloc;
 };
@@ -538,6 +568,108 @@ private:
       llvm::COFF::IMAGE_SCN_MEM_WRITE;
 };
 
+/// A BaseRelocAtom represents a base relocation block in ".reloc" section.
+class BaseRelocAtom : public coff::COFFLinkerInternalAtom {
+public:
+  BaseRelocAtom(const File &file, std::vector<uint8_t> data)
+      : COFFLinkerInternalAtom(file, std::move(data)) {}
+
+  virtual ContentType contentType() const { return typeData; }
+};
+
+/// A BaseRelocChunk represents ".reloc" section.
+///
+/// .reloc section contains a list of addresses. If the PE/COFF loader decides
+/// to load the binary at a memory address different from its preferred base
+/// address, which is specified by ImageBase field in the COFF header, the
+/// loader needs to relocate the binary, so that all the addresses in the binary
+/// point to new locations. The loader will do that by fixing up the addresses
+/// specified by .reloc section.
+///
+/// The executable is almost always loaded at the preferred base address because
+/// it's loaded into an empty address space. The DLL is however an subject of
+/// load-time relocation because it may conflict with other DLLs or the
+/// executable.
+class BaseRelocChunk : public SectionChunk {
+  typedef std::vector<std::unique_ptr<Chunk>> ChunkVectorT;
+  typedef std::map<uint64_t, std::vector<uint16_t>> PageOffsetT;
+
+public:
+  BaseRelocChunk(const File &linkedFile)
+      : SectionChunk(".reloc", characteristics), _file(linkedFile) {}
+
+  /// Creates .reloc section content from the other sections. The content of
+  /// .reloc is basically a list of relocation sites. The relocation sites are
+  /// divided into blocks. Each block represents the base relocation for a 4K
+  /// page.
+  ///
+  /// By dividing 32 bit RVAs into blocks, COFF saves disk and memory space for
+  /// the base relocation. A block consists with a 32 bit page RVA and 16 bit
+  /// relocation entries which represent offsets in the page. That is a compact
+  /// represetation than a simple vector of 32 bit RVAs.
+  void setContents(ChunkVectorT &chunks) {
+    std::vector<uint64_t> relocSites = listRelocSites(chunks);
+    PageOffsetT blocks = groupByPage(relocSites);
+    for (auto &i : blocks) {
+      uint64_t pageAddr = i.first;
+      std::vector<uint16_t> offsetsInPage = i.second;
+      appendAtom(createBaseRelocBlock(_file, pageAddr, offsetsInPage));
+    }
+  }
+
+private:
+  // 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;
+
+  // Returns a list of RVAs that needs to be relocated if the binary is loaded
+  // at an address different from its preferred one.
+  std::vector<uint64_t> listRelocSites(ChunkVectorT &chunks) {
+    std::vector<uint64_t> ret;
+    for (auto &cp : chunks)
+      if (SectionChunk *chunk = dyn_cast<SectionChunk>(&*cp))
+        chunk->addBaseRelocations(ret);
+    return std::move(ret);
+  }
+
+  // Divide the given RVAs into blocks.
+  PageOffsetT groupByPage(std::vector<uint64_t> relocSites) {
+    PageOffsetT blocks;
+    uint64_t mask = static_cast<uint64_t>(PAGE_SIZE) - 1;
+    for (uint64_t addr : relocSites)
+      blocks[addr & ~mask].push_back(addr & mask);
+    return std::move(blocks);
+  }
+
+  // Create the content of a relocation block.
+  DefinedAtom *createBaseRelocBlock(const File &file, uint64_t pageAddr,
+                                    std::vector<uint16_t> &offsets) {
+    uint32_t size = 8 + offsets.size() * 2;
+    std::vector<uint8_t> contents(size);
+
+    // The first four bytes is the page RVA.
+    *reinterpret_cast<llvm::support::ulittle32_t *>(&contents[0]) = pageAddr;
+
+    // The second four bytes is the size of the block, including the the page
+    // RVA and this size field.
+    *reinterpret_cast<llvm::support::ulittle32_t *>(&contents[4]) = size;
+
+    // The rest of the block consists of offsets in the page.
+    size_t i = 8;
+    for (uint16_t offset : offsets) {
+      assert(offset < PAGE_SIZE);
+      uint16_t val = (llvm::COFF::IMAGE_REL_BASED_HIGHLOW << 12) | offset;
+      *reinterpret_cast<llvm::support::ulittle16_t *>(&contents[i]) = val;
+      i += 2;
+    }
+    return new (_alloc) BaseRelocAtom(file, std::move(contents));
+  }
+
+  mutable llvm::BumpPtrAllocator _alloc;
+  const File &_file;
+};
+
 }  // end anonymous namespace
 
 class ExecutableWriter : public Writer {
@@ -546,40 +678,41 @@ private:
   /// pass, we visit all atoms to create a map from atom to its virtual
   /// address. In the second pass, we visit all relocation references to fix
   /// up addresses in the buffer.
-  void applyRelocations(uint8_t *bufferStart) {
-    std::map<const Atom *, uint64_t> atomToVirtualAddr;
-    for (auto &cp : _chunks)
-      if (AtomChunk *chunk = dyn_cast<AtomChunk>(&*cp))
-        chunk->buildAtomToVirtualAddr(atomToVirtualAddr);
+  void applyAllRelocations(uint8_t *bufferStart) {
     for (auto &cp : _chunks)
       if (AtomChunk *chunk = dyn_cast<AtomChunk>(&*cp))
-        chunk->applyRelocations(bufferStart, atomToVirtualAddr);
+        chunk->applyRelocations(bufferStart, atomRva);
   }
 
   void addChunk(Chunk *chunk) {
     _chunks.push_back(std::unique_ptr<Chunk>(chunk));
-
-    // Compute and set the offset of the chunk in the output file.
-    _imageSizeOnDisk = llvm::RoundUpToAlignment(_imageSizeOnDisk,
-                                                chunk->align());
-    chunk->setFileOffset(_imageSizeOnDisk);
-    _imageSizeOnDisk += chunk->size();
   }
 
-  void maybeAddSectionChunk(SectionChunk *chunk,
-                            SectionHeaderTableChunk *table) {
-    // Skip the empty section. Windows loader does not like a section of size
-    // zero and rejects such executable.
-    if (chunk->size() == 0)
-      return;
-    addChunk(chunk);
+  void addSectionChunk(SectionChunk *chunk,
+                       SectionHeaderTableChunk *table) {
+    _chunks.push_back(std::unique_ptr<Chunk>(chunk));
     table->addSection(chunk);
     _numSections++;
+
+    // Compute and set 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.
     chunk->setVirtualAddress(_imageSizeInMemory);
+    chunk->buildAtomToVirtualAddr(atomRva);
     _imageSizeInMemory = llvm::RoundUpToAlignment(
         _imageSizeInMemory + chunk->size(), PAGE_SIZE);
   }
 
+  void setImageSizeOnDisk() {
+    for (auto &chunk : _chunks) {
+      // Compute and set the offset of the chunk in the output file.
+      _imageSizeOnDisk = llvm::RoundUpToAlignment(_imageSizeOnDisk,
+                                                  chunk->align());
+      chunk->setFileOffset(_imageSizeOnDisk);
+      _imageSizeOnDisk += chunk->size();
+    }
+  }
+
 public:
   explicit ExecutableWriter(const PECOFFTargetInfo &targetInfo)
     : _PECOFFTargetInfo(targetInfo), _numSections(0),
@@ -595,24 +728,43 @@ public:
     auto *text = new TextSectionChunk(linkedFile);
     auto *rdata = new RDataSectionChunk(linkedFile);
     auto *data = new DataSectionChunk(linkedFile);
+    auto *baseReloc = new BaseRelocChunk(linkedFile);
 
     addChunk(dosStub);
     addChunk(peHeader);
     addChunk(dataDirectory);
     addChunk(sectionTable);
-    maybeAddSectionChunk(text, sectionTable);
-    maybeAddSectionChunk(rdata, sectionTable);
-    maybeAddSectionChunk(data, sectionTable);
+
+    // Do not add the empty section. Windows loader does not like a section of
+    // size zero and rejects such executable.
+    if (text->size())
+      addSectionChunk(text, sectionTable);
+    if (rdata->size())
+      addSectionChunk(rdata, sectionTable);
+    if (data->size())
+      addSectionChunk(data, sectionTable);
+
+    // Now that we know the addresses of all defined atoms that needs to be
+    // relocated. So we can create the ".reloc" section which contains all the
+    // relocation sites.
+    baseReloc->setContents(_chunks);
+    if (baseReloc->size()) {
+      dataDirectory->setBaseRelocField(baseReloc->getSectionRva(),
+                                       baseReloc->rawSize());
+      addSectionChunk(baseReloc, sectionTable);
+    }
+
+    setImageSizeOnDisk();
 
     // Now that we know the size and file offset of sections. Set the file
     // header accordingly.
     peHeader->setSizeOfCode(text->size());
-    if (text->size() > 0) {
+    if (text->size()) {
       peHeader->setBaseOfCode(text->getVirtualAddress());
     }
-    if (rdata->size() > 0) {
+    if (rdata->size()) {
       peHeader->setBaseOfData(rdata->getVirtualAddress());
-    } else if (data->size() > 0) {
+    } else if (data->size()) {
       peHeader->setBaseOfData(data->getVirtualAddress());
     }
     peHeader->setSizeOfInitializedData(rdata->size() + data->size());
@@ -632,7 +784,7 @@ public:
 
     for (const auto &chunk : _chunks)
       chunk->write(buffer->getBufferStart());
-    applyRelocations(buffer->getBufferStart());
+    applyAllRelocations(buffer->getBufferStart());
     return buffer->commit();
   }
 
@@ -650,6 +802,9 @@ private:
   // The size of the image on disk. This is basically the sum of all chunks in
   // the output file with paddings between them.
   uint32_t _imageSizeOnDisk;
+
+  // The map from defined atoms to its RVAs. Will be used for relocation.
+  std::map<const Atom *, uint64_t> atomRva;
 };
 
 } // end namespace pecoff

Added: lld/trunk/test/pecoff/base-reloc.test
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/pecoff/base-reloc.test?rev=186336&view=auto
==============================================================================
--- lld/trunk/test/pecoff/base-reloc.test (added)
+++ lld/trunk/test/pecoff/base-reloc.test Mon Jul 15 13:43:01 2013
@@ -0,0 +1,13 @@
+# RUN: yaml2obj %p/Inputs/hello.obj.yaml > %t.obj
+#
+# RUN: lld -flavor link -out %t1 -subsystem console -force -- %t.obj \
+# RUN:   && llvm-objdump -s %t1 | FileCheck %s
+
+# Because llvm-objdump cannot pretty-print the contents of .reloc section, we
+# have no choice other than comparing the result with this binary blob.
+#
+# TODO: Improve llvm-objdump to pretty print .reloc section as GNU binutil
+# objdump does.
+
+CHECK: Contents of section .reloc:
+CHECK-NEXT:  3000 00100000 0c000000 07300c30 00000000  .........0.0....

Modified: lld/trunk/test/pecoff/trivial.test
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/test/pecoff/trivial.test?rev=186336&r1=186335&r2=186336&view=diff
==============================================================================
--- lld/trunk/test/pecoff/trivial.test (original)
+++ lld/trunk/test/pecoff/trivial.test Mon Jul 15 13:43:01 2013
@@ -20,10 +20,9 @@ FILE:   TimeDateStamp:
 FILE:   PointerToSymbolTable: 0x0
 FILE:   SymbolCount: 0
 FILE:   OptionalHeaderSize: 224
-FILE:   Characteristics [ (0x103)
+FILE:   Characteristics [ (0x102)
 FILE:     IMAGE_FILE_32BIT_MACHINE (0x100)
 FILE:     IMAGE_FILE_EXECUTABLE_IMAGE (0x2)
-FILE:     IMAGE_FILE_RELOCS_STRIPPED (0x1)
 FILE:   ]
 FILE: }
 FILE: ImageOptionalHeader {





More information about the llvm-commits mailing list