[ELF] Add support for IFUNC.
authorMichael J. Spencer <bigcheesegs@gmail.com>
Tue, 29 Jan 2013 16:38:03 +0000 (16:38 +0000)
committerMichael J. Spencer <bigcheesegs@gmail.com>
Tue, 29 Jan 2013 16:38:03 +0000 (16:38 +0000)
This sadly doesn't have a test for the final
output because llvm-objdump can't dump relocations
that don't belong to a section :(

llvm-svn: 173808

12 files changed:
lld/include/lld/ReaderWriter/ELFTargetInfo.h
lld/include/lld/ReaderWriter/Simple.h
lld/lib/ReaderWriter/ELF/DefaultELFLayout.h
lld/lib/ReaderWriter/ELF/ELFSectionChunks.h
lld/lib/ReaderWriter/ELF/FileELF.h
lld/lib/ReaderWriter/ELF/WriterELF.cpp
lld/lib/ReaderWriter/ELF/X86_64/X86_64ELFTargetInfo.h
lld/lib/ReaderWriter/ELF/X86_64/X86_64TargetInfo.cpp
lld/lib/ReaderWriter/ELF/X86_64Reference.cpp
lld/test/elf/Inputs/ifunc.cpp [new file with mode: 0644]
lld/test/elf/Inputs/ifunc.cpp.x86-64 [new file with mode: 0644]
lld/test/elf/ifunc.test

index 7914083..ee1631b 100644 (file)
@@ -18,6 +18,8 @@
 #include <memory>
 
 namespace lld {
+class DefinedAtom;
+class Reference;
 
 namespace elf { template <typename ELFT> class ELFTargetHandler; }
 
@@ -37,6 +39,11 @@ public:
   virtual StringRef getEntry() const;
   virtual uint64_t getBaseAddress() const { return _options._baseAddress; }
 
+  virtual bool isRuntimeRelocation(const DefinedAtom &,
+                                   const Reference &) const {
+    return false;
+  }
+
   static std::unique_ptr<ELFTargetInfo> create(const LinkerOptions &lo);
 
   template <typename ELFT>
index be611d6..e149f5e 100644 (file)
 namespace lld {
 class SimpleFile : public MutableFile {
 public:
-  SimpleFile(const TargetInfo &ti, StringRef path) : MutableFile(ti, path) {}
+  SimpleFile(const TargetInfo &ti, StringRef path) : MutableFile(ti, path) {
+    static uint32_t lastOrdinal = 0;
+    _ordinal = lastOrdinal++;
+  }
 
   virtual void addAtom(const Atom &atom) {
     if (const DefinedAtom *defAtom = dyn_cast<DefinedAtom>(&atom)) {
index 1a73aaa..eb0418c 100644 (file)
@@ -61,6 +61,7 @@ public:
     ORDER_TEXT = 70,
     ORDER_PLT = 80,
     ORDER_FINI = 90,
+    ORDER_REL = 95,
     ORDER_RODATA = 100,
     ORDER_EH_FRAME = 110,
     ORDER_EH_FRAMEHDR = 120,
@@ -251,6 +252,16 @@ public:
     return _programHeader;
   }
 
+  ELFRelocationTable<ELFT> *getRelocationTable() {
+    // Only create the relocation table if it is needed.
+    if (!_relocationTable) {
+      _relocationTable = new (_allocator)
+          ELFRelocationTable<ELFT>(_targetInfo, ".rela.plt", ORDER_REL);
+      addSection(_relocationTable);
+    }
+    return _relocationTable;
+  }
+
 private:
   SectionMapT _sectionMap;
   MergedSectionMapT _mergedSectionMap;
@@ -260,6 +271,7 @@ private:
   std::vector<MergedSections<ELFT> *> _mergedSections;
   ELFHeader<ELFT> *_elfHeader;
   ELFProgramHeader<ELFT> *_programHeader;
+  ELFRelocationTable<ELFT> *_relocationTable;
   std::vector<AbsoluteAtomPair> _absoluteAtoms;
   llvm::BumpPtrAllocator _allocator;
   const ELFTargetInfo &_targetInfo;
@@ -272,6 +284,7 @@ DefaultELFLayout<ELFT>::getSectionOrder(const StringRef name,
                                         int32_t contentPermissions)
 {
   switch (contentType) {
+  case DefinedAtom::typeResolver:
   case DefinedAtom::typeCode:
     return llvm::StringSwitch<Reference::Kind>(name)
       .StartsWith(".eh_frame_hdr", ORDER_EH_FRAMEHDR)
@@ -291,6 +304,11 @@ DefaultELFLayout<ELFT>::getSectionOrder(const StringRef name,
   
   case DefinedAtom::typeZeroFill:
     return ORDER_BSS;
+
+  case DefinedAtom::typeGOT:
+    return ORDER_GOT;
+  case DefinedAtom::typeStub:
+    return ORDER_PLT;
   
   default:
     // If we get passed in a section push it to OTHER
@@ -327,6 +345,7 @@ DefaultELFLayout<ELFT>::getSegmentType(Section<ELFT> *section) const {
   case ORDER_HASH:
   case ORDER_DYNAMIC_SYMBOLS:
   case ORDER_DYNAMIC_STRINGS:
+  case ORDER_REL:
   case ORDER_INIT:
   case ORDER_PLT:
   case ORDER_FINI:
@@ -343,9 +362,9 @@ DefaultELFLayout<ELFT>::getSegmentType(Section<ELFT> *section) const {
 
   case ORDER_CTORS:
   case ORDER_DTORS:
-  case ORDER_GOT:
     return llvm::ELF::PT_GNU_RELRO;
 
+  case ORDER_GOT:
   case ORDER_GOT_PLT:
   case ORDER_DATA:
   case ORDER_BSS:
@@ -366,6 +385,7 @@ DefaultELFLayout<ELFT>::hasOutputSegment(Section<ELFT> *section) {
   case ORDER_HASH:
   case ORDER_DYNAMIC_SYMBOLS:
   case ORDER_DYNAMIC_STRINGS:
+  case ORDER_REL:
   case ORDER_INIT:
   case ORDER_PLT:
   case ORDER_TEXT:
@@ -415,6 +435,10 @@ DefaultELFLayout<ELFT>::addAtom(const Atom *atom) {
       section = _sectionMap[sectionKey];
     }
     section->appendAtom(atom);
+    // Add runtime relocations to the .rela section.
+    for (const auto &reloc : *definedAtom)
+      if (_targetInfo.isRuntimeRelocation(*definedAtom, *reloc))
+        getRelocationTable()->addRelocation(*definedAtom, *reloc);
   } else if (const AbsoluteAtom *absoluteAtom = dyn_cast<AbsoluteAtom>(atom)) {
     // Absolute atoms are not part of any section, they are global for the whole
     // link
index 2dea2c6..9253938 100644 (file)
@@ -227,9 +227,16 @@ Section<ELFT>::appendAtom(const Atom *atom) {
     case  DefinedAtom::typeCode:
     case  DefinedAtom::typeData:
     case  DefinedAtom::typeConstant:
+    case DefinedAtom::typeGOT:
+    case DefinedAtom::typeStub:
+    case DefinedAtom::typeResolver:
       _atoms.push_back(AtomLayout(atom, fOffset, 0));
       this->_fsize = fOffset + definedAtom->size();
       this->_msize = mOffset + definedAtom->size();
+      DEBUG_WITH_TYPE("Section",
+                      llvm::dbgs() << "[" << this->name() << " " << this << "] "
+                                   << "Adding atom: " << atom->name() << "@"
+                                   << fOffset << "\n");
       break;
     case  DefinedAtom::typeZeroFill:
       _atoms.push_back(AtomLayout(atom, mOffset, 0));
@@ -286,10 +293,16 @@ Section<ELFT>::flags() {
 template<class ELFT>
 int 
 Section<ELFT>::type() {
+  if (_sectionKind == K_SymbolTable)
+    return llvm::ELF::SHT_SYMTAB;
+
   switch (_contentType) {
   case DefinedAtom::typeCode:
   case DefinedAtom::typeData:
   case DefinedAtom::typeConstant:
+  case DefinedAtom::typeGOT:
+  case DefinedAtom::typeStub:
+  case DefinedAtom::typeResolver:
     return llvm::ELF::SHT_PROGBITS;
 
   case DefinedAtom::typeZeroFill:
@@ -332,6 +345,9 @@ template <class ELFT>
 void Section<ELFT>::write(ELFWriter *writer, llvm::FileOutputBuffer &buffer) {
   uint8_t *chunkBuffer = buffer.getBufferStart();
   for (auto &ai : _atoms) {
+    DEBUG_WITH_TYPE("Section",
+                    llvm::dbgs() << "Writing atom: " << ai._atom->name()
+                                 << " | " << ai._fileOffset << "\n");
     const DefinedAtom *definedAtom = cast<DefinedAtom>(ai._atom);
     if (definedAtom->contentType() == DefinedAtom::typeZeroFill)
       continue;
@@ -603,11 +619,17 @@ ELFSymbolTable<ELFT>::addSymbol(const Atom *atom,
     lld::DefinedAtom::ContentType ct;
     switch (ct = da->contentType()){
     case  DefinedAtom::typeCode:
+    case DefinedAtom::typeStub:
       symbol->st_value = addr;
       type = llvm::ELF::STT_FUNC;
       break;
+    case DefinedAtom::typeResolver:
+      symbol->st_value = addr;
+      type = llvm::ELF::STT_GNU_IFUNC;
+      break;
     case  DefinedAtom::typeData:
     case  DefinedAtom::typeConstant:
+    case DefinedAtom::typeGOT:
       symbol->st_value = addr;
       type = llvm::ELF::STT_OBJECT;
       break;
@@ -678,6 +700,46 @@ void ELFSymbolTable<ELFT>::write(ELFWriter *writer,
   }
 }
 
+template <class ELFT> class ELFRelocationTable : public Section<ELFT> {
+public:
+  typedef llvm::object::Elf_Rel_Impl<ELFT, true> Elf_Rela;
+
+  ELFRelocationTable(const ELFTargetInfo &ti, StringRef str, int32_t order)
+      : Section<ELFT>(ti, str, llvm::ELF::SHT_RELA, DefinedAtom::permR__, order,
+                      Section<ELFT>::K_Default) {
+    this->setOrder(order);
+    this->_entSize = sizeof(Elf_Rela);
+    this->_align2 = llvm::alignOf<Elf_Rela>();
+  }
+
+  void addRelocation(const DefinedAtom &da, const Reference &r) {
+    _relocs.emplace_back(da, r);
+    this->_fsize = _relocs.size() * sizeof(Elf_Rela);
+    this->_msize = this->_fsize;
+  }
+
+  void write(ELFWriter *writer, llvm::FileOutputBuffer &buffer) {
+    uint8_t *chunkBuffer = buffer.getBufferStart();
+    uint8_t *dest = chunkBuffer + this->fileOffset();
+    for (const auto &rel : _relocs) {
+      Elf_Rela *r = reinterpret_cast<Elf_Rela *>(dest);
+      r->setSymbolAndType(0, rel.second.kind());
+      r->r_offset =
+          writer->addressOfAtom(&rel.first) + rel.second.offsetInAtom();
+      r->r_addend =
+          writer->addressOfAtom(rel.second.target()) + rel.second.addend();
+      dest += sizeof(Elf_Rela);
+      DEBUG_WITH_TYPE("ELFRelocationTable", llvm::dbgs()
+                      << "IRELATIVE relocation at " << rel.first.name() << "@"
+                      << r->r_offset << " to " << rel.second.target()->name()
+                      << "@" << r->r_addend << "\n");
+    }
+  }
+
+private:
+  std::vector<std::pair<const DefinedAtom &, const Reference &>> _relocs;
+};
+
 } // elf
 } // lld
 
index 0b81684..9eecb70 100644 (file)
@@ -52,6 +52,9 @@ public:
   FileELF(const ELFTargetInfo &ti, std::unique_ptr<llvm::MemoryBuffer> MB,
           llvm::error_code &EC)
       : File(MB->getBufferIdentifier()), _elfTargetInfo(ti) {
+    static uint32_t lastOrdinal = 0;
+    _ordinal = lastOrdinal++;
+
     llvm::OwningPtr<llvm::object::Binary> binaryFile;
     EC = createBinary(MB.release(), binaryFile);
     if (EC)
index d43ebfb..fa744ca 100644 (file)
@@ -156,6 +156,8 @@ void ELFExecutableWriter<ELFT>::addDefaultAtoms() {
   _runtimeFile.addAbsoluteAtom("end");
   _runtimeFile.addAbsoluteAtom("__init_array_start");
   _runtimeFile.addAbsoluteAtom("__init_array_end");
+  _runtimeFile.addAbsoluteAtom("__rela_iplt_start");
+  _runtimeFile.addAbsoluteAtom("__rela_iplt_end");
 }
 
 /// \brief Hook in lld to add CRuntime file 
@@ -169,41 +171,47 @@ void ELFExecutableWriter<ELFT>::addFiles(InputFiles &inputFiles) {
 /// created
 template<class ELFT>
 void ELFExecutableWriter<ELFT>::finalizeDefaultAtomValues() {
- auto bssStartAtomIter = _layout->findAbsoluteAtom("__bss_start");
- auto bssEndAtomIter = _layout->findAbsoluteAtom("__bss_end");
- auto underScoreEndAtomIter = _layout->findAbsoluteAtom("_end");
- auto endAtomIter = _layout->findAbsoluteAtom("end");
- auto initArrayStartIter = _layout->findAbsoluteAtom("__init_array_start");
- auto initArrayEndIter = _layout->findAbsoluteAtom("__init_array_end");
-
- auto section = _layout->findOutputSection(".init_array");
- if (section) {
-   initArrayStartIter->setValue(section->virtualAddr());
-   initArrayEndIter->setValue(section->virtualAddr() +
-                              section->memSize());
- } else {
-   initArrayStartIter->setValue(0);
-   initArrayEndIter->setValue(0);
- }
-
- assert(!(bssStartAtomIter == _layout->absoluteAtoms().end() ||
-         bssEndAtomIter == _layout->absoluteAtoms().end() ||
-         underScoreEndAtomIter == _layout->absoluteAtoms().end() ||
-         endAtomIter == _layout->absoluteAtoms().end()) &&
-        "Unable to find the absolute atoms that have been added by lld");
-
- auto phe = _programHeader->findProgramHeader(
-                                 llvm::ELF::PT_LOAD,
-                                 llvm::ELF::PF_W,
-                                 llvm::ELF::PF_X);
-
- assert(!(phe == _programHeader->end()) &&
-       "Can't find a data segment in the program header!");
-
- bssStartAtomIter->setValue((*phe)->p_vaddr+(*phe)->p_filesz);
- bssEndAtomIter->setValue((*phe)->p_vaddr+(*phe)->p_memsz);
- underScoreEndAtomIter->setValue((*phe)->p_vaddr+(*phe)->p_memsz);
- endAtomIter->setValue((*phe)->p_vaddr+(*phe)->p_memsz);
+  auto bssStartAtomIter = _layout->findAbsoluteAtom("__bss_start");
+  auto bssEndAtomIter = _layout->findAbsoluteAtom("__bss_end");
+  auto underScoreEndAtomIter = _layout->findAbsoluteAtom("_end");
+  auto endAtomIter = _layout->findAbsoluteAtom("end");
+  auto initArrayStartIter = _layout->findAbsoluteAtom("__init_array_start");
+  auto initArrayEndIter = _layout->findAbsoluteAtom("__init_array_end");
+  auto realIpltStartIter = _layout->findAbsoluteAtom("__rela_iplt_start");
+  auto realIpltEndIter = _layout->findAbsoluteAtom("__rela_iplt_end");
+
+  auto startEnd = [&](typename DefaultELFLayout<ELFT>::AbsoluteAtomIterT start,
+                      typename DefaultELFLayout<ELFT>::AbsoluteAtomIterT end,
+                      StringRef sec) -> void {
+    auto section = _layout->findOutputSection(sec);
+    if (section) {
+      start->setValue(section->virtualAddr());
+      end->setValue(section->virtualAddr() + section->memSize());
+    } else {
+      start->setValue(0);
+      end->setValue(0);
+    }
+  };
+
+  startEnd(initArrayStartIter, initArrayEndIter, ".init_array");
+  startEnd(realIpltStartIter, realIpltEndIter, ".rela.plt");
+
+  assert(!(bssStartAtomIter == _layout->absoluteAtoms().end() ||
+           bssEndAtomIter == _layout->absoluteAtoms().end() ||
+           underScoreEndAtomIter == _layout->absoluteAtoms().end() ||
+           endAtomIter == _layout->absoluteAtoms().end()) &&
+         "Unable to find the absolute atoms that have been added by lld");
+
+  auto phe = _programHeader->findProgramHeader(
+      llvm::ELF::PT_LOAD, llvm::ELF::PF_W, llvm::ELF::PF_X);
+
+  assert(!(phe == _programHeader->end()) &&
+         "Can't find a data segment in the program header!");
+
+  bssStartAtomIter->setValue((*phe)->p_vaddr + (*phe)->p_filesz);
+  bssEndAtomIter->setValue((*phe)->p_vaddr + (*phe)->p_memsz);
+  underScoreEndAtomIter->setValue((*phe)->p_vaddr + (*phe)->p_memsz);
+  endAtomIter->setValue((*phe)->p_vaddr + (*phe)->p_memsz);
 }
 
 template<class ELFT>
index 7d6d97b..a7380e8 100644 (file)
@@ -30,12 +30,19 @@ public:
 
   virtual uint64_t getPageSize() const { return 0x1000; }
 
+  virtual void addPasses(PassManager &) const;
+
   virtual uint64_t getBaseAddress() const {
     if (_options._baseAddress == 0)
       return 0x400000;
     return _options._baseAddress;
   }
 
+  virtual bool isRuntimeRelocation(const DefinedAtom &,
+                                   const Reference &r) const {
+    return r.kind() == llvm::ELF::R_X86_64_IRELATIVE;
+  }
+
   virtual ErrorOr<int32_t> relocKindFromString(StringRef str) const;
   virtual ErrorOr<std::string> stringFromRelocKind(int32_t kind) const;
 
index 0841e33..c3d5856 100644 (file)
 
 #include "X86_64ELFTargetInfo.h"
 
+#include "lld/Core/File.h"
+#include "lld/Core/Pass.h"
+#include "lld/Core/PassManager.h"
+#include "lld/ReaderWriter/Simple.h"
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringSwitch.h"
 
 using namespace lld;
 
+namespace {
+class GOTAtom : public SimpleDefinedAtom {
+  static const uint8_t _defaultContent[8];
+
+public:
+  GOTAtom(const File &f, const DefinedAtom *target) : SimpleDefinedAtom(f) {
+    if (target->contentType() == typeResolver) {
+      DEBUG_WITH_TYPE("GOTAtom", llvm::dbgs() << "IRELATIVE relocation to "
+                                              << target->name());
+      addReference(llvm::ELF::R_X86_64_IRELATIVE, 0, target, 0);
+    }
+  }
+
+  virtual StringRef name() const { return "ELF-GOTAtom"; }
+
+  virtual SectionChoice sectionChoice() const { return sectionCustomRequired; }
+
+  virtual StringRef customSectionName() const { return ".got.plt"; }
+
+  virtual ContentType contentType() const { return typeGOT; }
+
+  virtual uint64_t size() const { return rawContent().size(); }
+
+  virtual ContentPermissions permissions() const { return permRW_; }
+
+  virtual ArrayRef<uint8_t> rawContent() const {
+    return ArrayRef<uint8_t>(_defaultContent, 8);
+  }
+};
+
+const uint8_t GOTAtom::_defaultContent[8] = { 0 };
+
+class PLTAtom : public SimpleDefinedAtom {
+  static const uint8_t _defaultContent[16];
+
+public:
+  PLTAtom(const File &f, GOTAtom *ga) : SimpleDefinedAtom(f) {
+    addReference(llvm::ELF::R_X86_64_PC32, 2, ga, -4);
+  }
+
+  virtual StringRef name() const { return "ELF-PLTAtom"; }
+
+  virtual SectionChoice sectionChoice() const { return sectionCustomRequired; }
+
+  virtual StringRef customSectionName() const { return ".plt"; }
+
+  virtual ContentType contentType() const { return typeStub; }
+
+  virtual uint64_t size() const { return rawContent().size(); }
+
+  virtual ContentPermissions permissions() const { return permR_X; }
+
+  virtual ArrayRef<uint8_t> rawContent() const {
+    return ArrayRef<uint8_t>(_defaultContent, 16);
+  }
+};
+
+const uint8_t PLTAtom::_defaultContent[16] = {
+  0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmpq *gotatom(%rip)
+  0x68, 0x00, 0x00, 0x00, 0x00,       // pushq pltentry
+  0xe9, 0x00, 0x00, 0x00, 0x00        // jmpq plt[-1]
+};
+
+class ELFPassFile : public SimpleFile {
+public:
+  ELFPassFile(const ELFTargetInfo &eti) : SimpleFile(eti, "ELFPassFile") {}
+
+  llvm::BumpPtrAllocator _alloc;
+};
+
+class PLTPass : public Pass {
+public:
+  PLTPass(const ELFTargetInfo &ti) : _file(ti) {}
+
+  virtual void perform(MutableFile &mf) {
+    for (const auto &atom : mf.defined())
+      for (const auto &ref : *atom) {
+        if (ref->kind() != llvm::ELF::R_X86_64_PC32)
+          continue;
+        if (const DefinedAtom *da =
+                dyn_cast<const DefinedAtom>(ref->target())) {
+          if (da->contentType() != DefinedAtom::typeResolver)
+            continue;
+          // We have a PC32 call to a IFUNC. Create a plt and got entry.
+          // Look it up first.
+          const PLTAtom *pa;
+          auto plt = _pltMap.find(da);
+          if (plt == _pltMap.end()) {
+            // Add an entry.
+            auto ga = new (_file._alloc) GOTAtom(_file, da);
+            mf.addAtom(*ga);
+            pa = new (_file._alloc) PLTAtom(_file, ga);
+            mf.addAtom(*pa);
+            _pltMap[da] = pa;
+          } else
+            pa = plt->second;
+          // This is dirty.
+          const_cast<Reference *>(ref)->setTarget(pa);
+        }
+      }
+  }
+
+private:
+  llvm::DenseMap<const DefinedAtom *, const PLTAtom *> _pltMap;
+  ELFPassFile _file;
+};
+} // end anon namespace
+
+void elf::X86_64ELFTargetInfo::addPasses(PassManager &pm) const {
+  pm.add(std::unique_ptr<Pass>(new PLTPass(*this)));
+}
+
 #define LLD_CASE(name) .Case(#name, llvm::ELF::name)
 
 ErrorOr<int32_t> elf::X86_64ELFTargetInfo::relocKindFromString(StringRef str) const {
index fde00bd..c60cc5f 100644 (file)
@@ -112,6 +112,8 @@ bool X86_64KindHandler::isLazyTarget(Kind kind) {
 void X86_64KindHandler::applyFixup(int32_t reloc, uint64_t addend,
                                    uint8_t *location, uint64_t fixupAddress,
                                    uint64_t targetAddress) {
+  if (reloc == llvm::ELF::R_X86_64_IRELATIVE)
+    return;
   if (_fixupHandler[reloc])
     _fixupHandler[reloc](location, fixupAddress, targetAddress, addend);
   else {
diff --git a/lld/test/elf/Inputs/ifunc.cpp b/lld/test/elf/Inputs/ifunc.cpp
new file mode 100644 (file)
index 0000000..2e52027
--- /dev/null
@@ -0,0 +1,3 @@
+extern "C" int hey();
+
+int main() { return hey(); }
diff --git a/lld/test/elf/Inputs/ifunc.cpp.x86-64 b/lld/test/elf/Inputs/ifunc.cpp.x86-64
new file mode 100644 (file)
index 0000000..20c812c
Binary files /dev/null and b/lld/test/elf/Inputs/ifunc.cpp.x86-64 differ
index 0e636a4..43bdc9a 100644 (file)
@@ -1,6 +1,27 @@
 RUN: lld -core -target x86_64-linux -emit-yaml -output=- %p/Inputs/ifunc.x86-64 \
 RUN: | FileCheck %s
+RUN: lld -core -target x86_64-linux -emit-yaml -output=- %p/Inputs/ifunc.x86-64 \
+RUN: %p/Inputs/ifunc.cpp.x86-64 | FileCheck %s --check-prefix=PLT
 
 CHECK: name: hey
 CHECK: scope: global
 CHECK: type: resolver
+
+// Get the target that main references.
+PLT: name: main
+PLT: scope: global
+PLT: references
+PLT: kind: R_X86_64_PC32
+PLT: target: [[PLTNAME:[a-zA-Z0-9-]+]]
+
+// Make sure there's a got entry with a IRELATIVE relocation.
+PLT: type: got
+PLT: kind: R_X86_64_IRELATIVE
+
+// Make sure the target of main's relocation is a stub with a PC32 relocation.
+// This relocation is to the got atom, but you can't really write that check in
+// FileCheck.
+PLT: name: [[PLTNAME]]
+PLT: type: stub
+PLT: references
+PLT: kind: R_X86_64_PC32