diff --git a/lld/ELF/DWARF.cpp b/lld/ELF/DWARF.cpp index 5d58e0c60a952..517d26810a378 100644 --- a/lld/ELF/DWARF.cpp +++ b/lld/ELF/DWARF.cpp @@ -136,7 +136,8 @@ template std::optional LLDDwarfObj::find(const llvm::DWARFSection &s, uint64_t pos) const { auto &sec = static_cast(s); - const RelsOrRelas rels = sec.sec->template relsOrRelas(); + const RelsOrRelas rels = + sec.sec->template relsOrRelas(/*supportsCrel=*/false); if (rels.areRelocsRel()) return findAux(*sec.sec, pos, rels.rels); return findAux(*sec.sec, pos, rels.relas); diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp index a6b52d78fa806..44e8a71cc6286 100644 --- a/lld/ELF/ICF.cpp +++ b/lld/ELF/ICF.cpp @@ -324,6 +324,8 @@ bool ICF::equalsConstant(const InputSection *a, const InputSection *b) { const RelsOrRelas ra = a->template relsOrRelas(); const RelsOrRelas rb = b->template relsOrRelas(); + if (ra.areRelocsCrel()) + return constantEq(a, ra.crels, b, rb.crels); return ra.areRelocsRel() || rb.areRelocsRel() ? constantEq(a, ra.rels, b, rb.rels) : constantEq(a, ra.relas, b, rb.relas); @@ -374,6 +376,8 @@ template bool ICF::equalsVariable(const InputSection *a, const InputSection *b) { const RelsOrRelas ra = a->template relsOrRelas(); const RelsOrRelas rb = b->template relsOrRelas(); + if (ra.areRelocsCrel()) + return variableEq(a, ra.crels, b, rb.crels); return ra.areRelocsRel() || rb.areRelocsRel() ? variableEq(a, ra.rels, b, rb.rels) : variableEq(a, ra.relas, b, rb.relas); @@ -505,7 +509,9 @@ template void ICF::run() { for (unsigned cnt = 0; cnt != 2; ++cnt) { parallelForEach(sections, [&](InputSection *s) { const RelsOrRelas rels = s->template relsOrRelas(); - if (rels.areRelocsRel()) + if (rels.areRelocsCrel()) + combineRelocHashes(cnt, s, rels.crels); + else if (rels.areRelocsRel()) combineRelocHashes(cnt, s, rels.rels); else combineRelocHashes(cnt, s, rels.relas); diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp index 3bc39848d6035..0e4ba06e9b780 100644 --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -831,6 +831,7 @@ void ObjFile::initializeSections(bool ignoreComdats, case SHT_STRTAB: case SHT_REL: case SHT_RELA: + case SHT_CREL: case SHT_NULL: break; case SHT_PROGBITS: diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h index 755b30de12bc7..b0a74877d0aae 100644 --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -81,6 +81,7 @@ class InputFile { assert(fileKind == ObjKind || fileKind == BinaryKind); return sections; } + void cacheDecodedCrel(size_t i, InputSectionBase *s) { sections[i] = s; } // Returns object file symbols. It is a runtime error to call this // function on files of other types. diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp index 7857d857488c0..570e485455bad 100644 --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -133,21 +133,56 @@ void InputSectionBase::decompress() const { compressed = false; } -template RelsOrRelas InputSectionBase::relsOrRelas() const { +template +RelsOrRelas InputSectionBase::relsOrRelas(bool supportsCrel) const { if (relSecIdx == 0) return {}; RelsOrRelas ret; - typename ELFT::Shdr shdr = - cast(file)->getELFShdrs()[relSecIdx]; + auto *f = cast>(file); + typename ELFT::Shdr shdr = f->template getELFShdrs()[relSecIdx]; + if (shdr.sh_type == SHT_CREL) { + // Return an iterator if supported by caller. + if (supportsCrel) { + ret.crels = Relocs( + (const uint8_t *)f->mb.getBufferStart() + shdr.sh_offset); + return ret; + } + InputSectionBase *const &relSec = f->getSections()[relSecIdx]; + // Otherwise, allocate a buffer to hold the decoded RELA relocations. When + // called for the first time, relSec is null (without --emit-relocs) or an + // InputSection with zero eqClass[0]. + if (!relSec || !cast(relSec)->eqClass[0]) { + auto *sec = makeThreadLocal(*f, shdr, name); + f->cacheDecodedCrel(relSecIdx, sec); + sec->type = SHT_RELA; + sec->eqClass[0] = SHT_RELA; + + RelocsCrel entries(sec->content_); + sec->size = entries.size() * sizeof(typename ELFT::Rela); + auto *relas = makeThreadLocalN(entries.size()); + sec->content_ = reinterpret_cast(relas); + for (auto [i, r] : llvm::enumerate(entries)) { + relas[i].r_offset = r.r_offset; + relas[i].setSymbolAndType(r.r_symidx, r.r_type, false); + relas[i].r_addend = r.r_addend; + } + } + ret.relas = {ArrayRef( + reinterpret_cast(relSec->content_), + relSec->size / sizeof(typename ELFT::Rela))}; + return ret; + } + + const void *content = f->mb.getBufferStart() + shdr.sh_offset; + size_t size = shdr.sh_size; if (shdr.sh_type == SHT_REL) { - ret.rels = ArrayRef(reinterpret_cast( - file->mb.getBufferStart() + shdr.sh_offset), - shdr.sh_size / sizeof(typename ELFT::Rel)); + ret.rels = {ArrayRef(reinterpret_cast(content), + size / sizeof(typename ELFT::Rel))}; } else { assert(shdr.sh_type == SHT_RELA); - ret.relas = ArrayRef(reinterpret_cast( - file->mb.getBufferStart() + shdr.sh_offset), - shdr.sh_size / sizeof(typename ELFT::Rela)); + ret.relas = { + ArrayRef(reinterpret_cast(content), + size / sizeof(typename ELFT::Rela))}; } return ret; } @@ -1248,7 +1283,7 @@ SyntheticSection *EhInputSection::getParent() const { // .eh_frame is a sequence of CIE or FDE records. // This function splits an input section into records and returns them. template void EhInputSection::split() { - const RelsOrRelas rels = relsOrRelas(); + const RelsOrRelas rels = relsOrRelas(/*supportsCrel=*/false); // getReloc expects the relocations to be sorted by r_offset. See the comment // in scanRelocs. if (rels.areRelocsRel()) { @@ -1414,10 +1449,14 @@ template void InputSection::writeTo(uint8_t *); template void InputSection::writeTo(uint8_t *); template void InputSection::writeTo(uint8_t *); -template RelsOrRelas InputSectionBase::relsOrRelas() const; -template RelsOrRelas InputSectionBase::relsOrRelas() const; -template RelsOrRelas InputSectionBase::relsOrRelas() const; -template RelsOrRelas InputSectionBase::relsOrRelas() const; +template RelsOrRelas +InputSectionBase::relsOrRelas(bool) const; +template RelsOrRelas +InputSectionBase::relsOrRelas(bool) const; +template RelsOrRelas +InputSectionBase::relsOrRelas(bool) const; +template RelsOrRelas +InputSectionBase::relsOrRelas(bool) const; template MergeInputSection::MergeInputSection(ObjFile &, const ELF32LE::Shdr &, StringRef); diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h index c89a545e1543f..6659530a9c9c2 100644 --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -35,17 +35,21 @@ class OutputSection; LLVM_LIBRARY_VISIBILITY extern std::vector partitions; -// Returned by InputSectionBase::relsOrRelas. At least one member is empty. +// Returned by InputSectionBase::relsOrRelas. At most one member is empty. template struct RelsOrRelas { Relocs rels; Relocs relas; + Relocs crels; bool areRelocsRel() const { return rels.size(); } + bool areRelocsCrel() const { return crels.size(); } }; #define invokeOnRelocs(sec, f, ...) \ { \ const RelsOrRelas rs = (sec).template relsOrRelas(); \ - if (rs.areRelocsRel()) \ + if (rs.areRelocsCrel()) \ + f(__VA_ARGS__, rs.crels); \ + else if (rs.areRelocsRel()) \ f(__VA_ARGS__, rs.rels); \ else \ f(__VA_ARGS__, rs.relas); \ @@ -209,7 +213,8 @@ class InputSectionBase : public SectionBase { // used by --gc-sections. InputSectionBase *nextInSectionGroup = nullptr; - template RelsOrRelas relsOrRelas() const; + template + RelsOrRelas relsOrRelas(bool supportsCrel = true) const; // InputSections that are dependent on us (reverse dependency for GC) llvm::TinyPtrVector dependentSections; @@ -483,7 +488,8 @@ class SyntheticSection : public InputSection { }; inline bool isStaticRelSecType(uint32_t type) { - return type == llvm::ELF::SHT_RELA || type == llvm::ELF::SHT_REL; + return type == llvm::ELF::SHT_RELA || type == llvm::ELF::SHT_CREL || + type == llvm::ELF::SHT_REL; } inline bool isDebugSection(const InputSectionBase &sec) { diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp index e2208da18dce0..055fa21d44ca6 100644 --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -61,6 +61,8 @@ static StringRef getOutputSectionName(const InputSectionBase *s) { assert(config->relocatable && (rel->flags & SHF_LINK_ORDER)); return s->name; } + if (s->type == SHT_CREL) + return saver().save(".crel" + out->name); if (s->type == SHT_RELA) return saver().save(".rela" + out->name); return saver().save(".rel" + out->name); diff --git a/lld/ELF/MarkLive.cpp b/lld/ELF/MarkLive.cpp index 45431e44a6c8c..16e5883c2002c 100644 --- a/lld/ELF/MarkLive.cpp +++ b/lld/ELF/MarkLive.cpp @@ -85,6 +85,13 @@ static uint64_t getAddend(InputSectionBase &sec, return rel.r_addend; } +// Currently, we assume all input CREL relocations have an explicit addend. +template +static uint64_t getAddend(InputSectionBase &sec, + const typename ELFT::Crel &rel) { + return rel.r_addend; +} + template template void MarkLive::resolveReloc(InputSectionBase &sec, RelTy &rel, @@ -239,7 +246,8 @@ template void MarkLive::run() { // all of them. We also want to preserve personality routines and LSDA // referenced by .eh_frame sections, so we scan them for that here. for (EhInputSection *eh : ctx.ehInputSections) { - const RelsOrRelas rels = eh->template relsOrRelas(); + const RelsOrRelas rels = + eh->template relsOrRelas(/*supportsCrel=*/false); if (rels.areRelocsRel()) scanEhFrameSection(*eh, rels.rels); else if (rels.relas.size()) @@ -310,6 +318,8 @@ template void MarkLive::mark() { resolveReloc(sec, rel, false); for (const typename ELFT::Rela &rel : rels.relas) resolveReloc(sec, rel, false); + for (const typename ELFT::Crel &rel : rels.crels) + resolveReloc(sec, rel, false); for (InputSectionBase *isec : sec.dependentSections) enqueue(isec, 0); diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp index 60de10061c53d..29f18f89274f3 100644 --- a/lld/ELF/OutputSections.cpp +++ b/lld/ELF/OutputSections.cpp @@ -18,6 +18,7 @@ #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/Config/llvm-config.h" // LLVM_ENABLE_ZLIB #include "llvm/Support/Compression.h" +#include "llvm/Support/LEB128.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/TimeProfiler.h" @@ -115,7 +116,19 @@ void OutputSection::recordSection(InputSectionBase *isec) { // other InputSections. void OutputSection::commitSection(InputSection *isec) { if (LLVM_UNLIKELY(type != isec->type)) { - if (hasInputSections || typeIsSet) { + if (!hasInputSections && !typeIsSet) { + type = isec->type; + } else if (isStaticRelSecType(type) && isStaticRelSecType(isec->type) && + (type == SHT_CREL) != (isec->type == SHT_CREL)) { + // Combine mixed SHT_REL[A] and SHT_CREL to SHT_CREL. + type = SHT_CREL; + if (type == SHT_REL) { + if (name.consume_front(".rel")) + name = saver().save(".crel" + name); + } else if (name.consume_front(".rela")) { + name = saver().save(".crel" + name); + } + } else { if (typeIsSet || !canMergeToProgbits(type) || !canMergeToProgbits(isec->type)) { // The (NOLOAD) changes the section type to SHT_NOBITS, the intention is @@ -133,8 +146,6 @@ void OutputSection::commitSection(InputSection *isec) { } if (!typeIsSet) type = SHT_PROGBITS; - } else { - type = isec->type; } } if (!hasInputSections) { @@ -470,6 +481,11 @@ void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) { llvm::TimeTraceScope timeScope("Write sections", name); if (type == SHT_NOBITS) return; + if (type == SHT_CREL && !(flags & SHF_ALLOC)) { + buf += encodeULEB128(crelHeader, buf); + memcpy(buf, crelBody.data(), crelBody.size()); + return; + } // If the section is compressed due to // --compress-debug-section/--compress-sections, the content is already known. @@ -505,6 +521,12 @@ void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) { if (nonZeroFiller) fill(buf, sections.empty() ? size : sections[0]->outSecOff, filler); + if (type == SHT_CREL && !(flags & SHF_ALLOC)) { + buf += encodeULEB128(crelHeader, buf); + memcpy(buf, crelBody.data(), crelBody.size()); + return; + } + auto fn = [=](size_t begin, size_t end) { size_t numSections = sections.size(); for (size_t i = begin; i != end; ++i) { @@ -592,6 +614,103 @@ static void finalizeShtGroup(OutputSection *os, InputSection *section) { os->size = (1 + seen.size()) * sizeof(uint32_t); } +template +LLVM_ATTRIBUTE_ALWAYS_INLINE static void +encodeOneCrel(raw_svector_ostream &os, Elf_Crel &out, + uint offset, const Symbol &sym, uint32_t type, uint addend) { + const auto deltaOffset = static_cast(offset - out.r_offset); + out.r_offset = offset; + int64_t symidx = in.symTab->getSymbolIndex(sym); + if (sym.type == STT_SECTION) { + auto *d = dyn_cast(&sym); + if (d) { + SectionBase *section = d->section; + assert(section->isLive()); + addend = sym.getVA(addend) - section->getOutputSection()->addr; + } else { + // Encode R_*_NONE(symidx=0). + symidx = type = addend = 0; + } + } + + // Similar to llvm::ELF::encodeCrel. + uint8_t b = deltaOffset * 8 + (out.r_symidx != symidx) + + (out.r_type != type ? 2 : 0) + + (uint(out.r_addend) != addend ? 4 : 0); + if (deltaOffset < 0x10) { + os << char(b); + } else { + os << char(b | 0x80); + encodeULEB128(deltaOffset >> 4, os); + } + if (b & 1) { + encodeSLEB128(static_cast(symidx - out.r_symidx), os); + out.r_symidx = symidx; + } + if (b & 2) { + encodeSLEB128(static_cast(type - out.r_type), os); + out.r_type = type; + } + if (b & 4) { + encodeSLEB128(std::make_signed_t(addend - out.r_addend), os); + out.r_addend = addend; + } +} + +template +static size_t relToCrel(raw_svector_ostream &os, Elf_Crel &out, + InputSection *relSec, InputSectionBase *sec) { + const auto &file = *cast(relSec->file); + if (relSec->type == SHT_REL) { + // REL conversion is complex and unsupported yet. + errorOrWarn(toString(relSec) + ": REL cannot be converted to CREL"); + return 0; + } + auto rels = relSec->getDataAs(); + for (auto rel : rels) { + encodeOneCrel( + os, out, sec->getVA(rel.r_offset), file.getRelocTargetSym(rel), + rel.getType(config->isMips64EL), getAddend(rel)); + } + return rels.size(); +} + +// Compute the content of a non-alloc CREL section due to -r or --emit-relocs. +// Input CREL sections are decoded while REL[A] need to be converted. +template void OutputSection::finalizeNonAllocCrel() { + using uint = typename Elf_Crel_Impl::uint; + raw_svector_ostream os(crelBody); + uint64_t totalCount = 0; + Elf_Crel out{}; + assert(commands.size() == 1); + auto *isd = cast(commands[0]); + for (InputSection *relSec : isd->sections) { + const auto &file = *cast(relSec->file); + InputSectionBase *sec = relSec->getRelocatedSection(); + if (relSec->type == SHT_CREL) { + RelocsCrel entries(relSec->content_); + totalCount += entries.size(); + for (Elf_Crel_Impl r : entries) { + encodeOneCrel(os, out, uint(sec->getVA(r.r_offset)), + file.getSymbol(r.r_symidx), r.r_type, r.r_addend); + } + continue; + } + + // Convert REL[A] to CREL. + if constexpr (is64) { + totalCount += config->isLE ? relToCrel(os, out, relSec, sec) + : relToCrel(os, out, relSec, sec); + } else { + totalCount += config->isLE ? relToCrel(os, out, relSec, sec) + : relToCrel(os, out, relSec, sec); + } + } + + crelHeader = totalCount * 8 + 4; + size = getULEB128Size(crelHeader) + crelBody.size(); +} + void OutputSection::finalize() { InputSection *first = getFirstInputSection(this); @@ -628,6 +747,13 @@ void OutputSection::finalize() { InputSectionBase *s = first->getRelocatedSection(); info = s->getOutputSection()->sectionIndex; flags |= SHF_INFO_LINK; + // Finalize the content of non-alloc CREL. + if (type == SHT_CREL) { + if (config->is64) + finalizeNonAllocCrel(); + else + finalizeNonAllocCrel(); + } } // Returns true if S is in one of the many forms the compiler driver may pass diff --git a/lld/ELF/OutputSections.h b/lld/ELF/OutputSections.h index 78fede48a23f2..8c0c52f34ac9f 100644 --- a/lld/ELF/OutputSections.h +++ b/lld/ELF/OutputSections.h @@ -84,6 +84,11 @@ class OutputSection final : public SectionBase { Expr alignExpr; Expr lmaExpr; Expr subalignExpr; + + // Used by non-alloc SHT_CREL to hold the header and content byte stream. + uint64_t crelHeader = 0; + SmallVector crelBody; + SmallVector commands; SmallVector phdrs; std::optional> filler; @@ -106,6 +111,7 @@ class OutputSection final : public SectionBase { // DATA_RELRO_END. bool relro = false; + template void finalizeNonAllocCrel(); void finalize(); template void writeTo(uint8_t *buf, llvm::parallel::TaskGroup &tg); diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp index 94ad7b3570820..1c0dd97ed3910 100644 --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -1441,10 +1441,11 @@ void RelocationScanner::scanOne(typename Relocs::const_iterator &i) { uint32_t symIndex = rel.getSymbol(config->isMips64EL); Symbol &sym = sec->getFile()->getSymbol(symIndex); RelType type; - if constexpr (ELFT::Is64Bits) { + if constexpr (ELFT::Is64Bits || RelTy::IsCrel) { type = rel.getType(config->isMips64EL); ++i; } else { + // CREL is unsupported for MIPS N32. if (config->mipsN32Abi) { type = getMipsN32RelType(i); } else { @@ -1497,15 +1498,18 @@ void RelocationScanner::scanOne(typename Relocs::const_iterator &i) { if ((type == R_PPC64_TLSGD && expr == R_TLSDESC_CALL) || (type == R_PPC64_TLSLD && expr == R_TLSLD_HINT)) { - if (i == end) { - errorOrWarn("R_PPC64_TLSGD/R_PPC64_TLSLD may not be the last " - "relocation" + - getLocation(*sec, sym, offset)); - return; + // Skip the error check for CREL, which does not set `end`. + if constexpr (!RelTy::IsCrel) { + if (i == end) { + errorOrWarn("R_PPC64_TLSGD/R_PPC64_TLSLD may not be the last " + "relocation" + + getLocation(*sec, sym, offset)); + return; + } } - // Offset the 4-byte aligned R_PPC64_TLSGD by one byte in the NOTOC case, - // so we can discern it later from the toc-case. + // Offset the 4-byte aligned R_PPC64_TLSGD by one byte in the NOTOC + // case, so we can discern it later from the toc-case. if (i->getType(/*isMips64EL=*/false) == R_PPC64_REL24_NOTOC) ++offset; } @@ -1545,7 +1549,7 @@ void RelocationScanner::scanOne(typename Relocs::const_iterator &i) { // instructions are generated by very old IBM XL compilers. Work around the // issue by disabling GD/LD to IE/LE relaxation. template -static void checkPPC64TLSRelax(InputSectionBase &sec, ArrayRef rels) { +static void checkPPC64TLSRelax(InputSectionBase &sec, Relocs rels) { // Skip if sec is synthetic (sec.file is null) or if sec has been marked. if (!sec.file || sec.file->ppc64DisableTLSRelax) return; @@ -1593,9 +1597,15 @@ void RelocationScanner::scan(Relocs rels) { if (isa(sec) || config->emachine == EM_S390) rels = sortRels(rels, storage); - end = static_cast(rels.end()); - for (auto i = rels.begin(); i != end;) - scanOne(i); + if constexpr (RelTy::IsCrel) { + for (auto i = rels.begin(); i != rels.end();) + scanOne(i); + } else { + // The non-CREL code path has additional check for PPC64 TLS. + end = static_cast(rels.end()); + for (auto i = rels.begin(); i != end;) + scanOne(i); + } // Sort relocations by offset for more efficient searching for // R_RISCV_PCREL_HI20 and R_PPC64_ADDR64. @@ -1611,7 +1621,9 @@ template void RelocationScanner::scanSection(InputSectionBase &s) { sec = &s; getter = OffsetGetter(s); const RelsOrRelas rels = s.template relsOrRelas(); - if (rels.areRelocsRel()) + if (rels.areRelocsCrel()) + scan(rels.crels); + else if (rels.areRelocsRel()) scan(rels.rels); else scan(rels.relas); diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h index 77d8d52ca3d3f..aaa4581490a28 100644 --- a/lld/ELF/Relocations.h +++ b/lld/ELF/Relocations.h @@ -12,6 +12,7 @@ #include "lld/Common/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Object/ELFTypes.h" #include namespace lld::elf { @@ -205,11 +206,91 @@ class ThunkCreator { uint32_t pass = 0; }; +// Decode LEB128 without error checking. Only used by performance critical code +// like RelocsCrel. +inline uint64_t readLEB128(const uint8_t *&p, uint64_t leb) { + uint64_t acc = 0, shift = 0, byte; + do { + byte = *p++; + acc |= (byte - 128 * (byte >= leb)) << shift; + shift += 7; + } while (byte >= 128); + return acc; +} +inline uint64_t readULEB128(const uint8_t *&p) { return readLEB128(p, 128); } +inline int64_t readSLEB128(const uint8_t *&p) { return readLEB128(p, 64); } + +// This class implements a CREL iterator that does not allocate extra memory. +template struct RelocsCrel { + using uint = std::conditional_t; + struct const_iterator { + using iterator_category = std::forward_iterator_tag; + using value_type = llvm::object::Elf_Crel_Impl; + using difference_type = ptrdiff_t; + using pointer = value_type *; + using reference = const value_type &; + uint32_t count; + uint8_t flagBits, shift; + const uint8_t *p; + llvm::object::Elf_Crel_Impl crel{}; + const_iterator(size_t hdr, const uint8_t *p) + : count(hdr / 8), flagBits(hdr & 4 ? 3 : 2), shift(hdr % 4), p(p) { + if (count) + step(); + } + void step() { + // See object::decodeCrel. + const uint8_t b = *p++; + crel.r_offset += b >> flagBits << shift; + if (b >= 0x80) + crel.r_offset += + ((readULEB128(p) << (7 - flagBits)) - (0x80 >> flagBits)) << shift; + if (b & 1) + crel.r_symidx += readSLEB128(p); + if (b & 2) + crel.r_type += readSLEB128(p); + if (b & 4 && flagBits == 3) + crel.r_addend += static_cast(readSLEB128(p)); + } + llvm::object::Elf_Crel_Impl operator*() const { return crel; }; + const llvm::object::Elf_Crel_Impl *operator->() const { + return &crel; + } + // For llvm::enumerate. + bool operator==(const const_iterator &r) const { return count == r.count; } + bool operator!=(const const_iterator &r) const { return count != r.count; } + const_iterator &operator++() { + if (--count) + step(); + return *this; + } + // For RelocationScanner::scanOne. + void operator+=(size_t n) { + for (; n; --n) + operator++(); + } + }; + + size_t hdr = 0; + const uint8_t *p = nullptr; + + constexpr RelocsCrel() = default; + RelocsCrel(const uint8_t *p) : hdr(readULEB128(p)) { this->p = p; } + size_t size() const { return hdr / 8; } + const_iterator begin() const { return {hdr, p}; } + const_iterator end() const { return {0, nullptr}; } +}; + template struct Relocs : ArrayRef { Relocs() = default; Relocs(ArrayRef a) : ArrayRef(a) {} }; +template +struct Relocs> : RelocsCrel { + using RelocsCrel::RelocsCrel; +}; + // Return a int64_t to make sure we get the sign extension out of the way as // early as possible. template @@ -220,6 +301,10 @@ template static inline int64_t getAddend(const typename ELFT::Rela &rel) { return rel.r_addend; } +template +static inline int64_t getAddend(const typename ELFT::Crel &rel) { + return rel.r_addend; +} template inline Relocs sortRels(Relocs rels, @@ -235,6 +320,13 @@ inline Relocs sortRels(Relocs rels, return rels; } +template +inline Relocs> +sortRels(Relocs> rels, + SmallVector, 0> &storage) { + return {}; +} + // Returns true if Expr refers a GOT entry. Note that this function returns // false for TLS variables even though they need GOT, because TLS variables uses // GOT differently than the regular variables. diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp index d0b1933121fa2..8ffa917635120 100644 --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -455,7 +455,8 @@ template void EhFrameSection::addSectionAux(EhInputSection *sec) { if (!sec->isLive()) return; - const RelsOrRelas rels = sec->template relsOrRelas(); + const RelsOrRelas rels = + sec->template relsOrRelas(/*supportsCrel=*/false); if (rels.areRelocsRel()) addRecords(sec, rels.rels); else @@ -489,7 +490,8 @@ void EhFrameSection::iterateFDEWithLSDA( DenseSet ciesWithLSDA; for (EhInputSection *sec : sections) { ciesWithLSDA.clear(); - const RelsOrRelas rels = sec->template relsOrRelas(); + const RelsOrRelas rels = + sec->template relsOrRelas(/*supportsCrel=*/false); if (rels.areRelocsRel()) iterateFDEWithLSDAAux(*sec, rels.rels, ciesWithLSDA, fn); else diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp index 515ebb7453ad5..2ceae215ae278 100644 --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -401,10 +401,19 @@ template static void markUsedLocalSymbols() { InputSection *isec = dyn_cast_or_null(s); if (!isec) continue; - if (isec->type == SHT_REL) + if (isec->type == SHT_REL) { markUsedLocalSymbolsImpl(f, isec->getDataAs()); - else if (isec->type == SHT_RELA) + } else if (isec->type == SHT_RELA) { markUsedLocalSymbolsImpl(f, isec->getDataAs()); + } else if (isec->type == SHT_CREL) { + // The is64=true variant also works with ELF32 since only the r_symidx + // member is used. + for (Elf_Crel_Impl r : RelocsCrel(isec->content_)) { + Symbol &sym = file->getSymbol(r.r_symidx); + if (sym.isLocal()) + sym.used = true; + } + } } } } diff --git a/lld/test/ELF/crel-rel-mixed.s b/lld/test/ELF/crel-rel-mixed.s new file mode 100644 index 0000000000000..a69fa1c09b436 --- /dev/null +++ b/lld/test/ELF/crel-rel-mixed.s @@ -0,0 +1,22 @@ +# REQUIRES: arm +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=armv7a -crel a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=armv7a b.s -o b.o +# RUN: not ld.lld -r a.o b.o 2>&1 | FileCheck %s --check-prefix=ERR + +# ERR: error: b.o:(.rel.text): REL cannot be converted to CREL + +#--- a.s +.global _start, foo +_start: + bl foo + bl .text.foo + +.section .text.foo,"ax" +foo: + nop + +#--- b.s +.globl fb +fb: + bl fb diff --git a/lld/test/ELF/crel.s b/lld/test/ELF/crel.s new file mode 100644 index 0000000000000..d7c87be9a5402 --- /dev/null +++ b/lld/test/ELF/crel.s @@ -0,0 +1,90 @@ +# REQUIRES: x86 +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 -crel a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 -crel b.s -o b.o +# RUN: ld.lld -pie a.o b.o -o out +# RUN: llvm-objdump -d out | FileCheck %s +# RUN: llvm-readelf -Srs out | FileCheck %s --check-prefix=RELOC + +# CHECK: <_start>: +# CHECK-NEXT: callq {{.*}} +# CHECK-NEXT: callq {{.*}} +# CHECK-EMPTY: +# CHECK-NEXT: : +# CHECK-NEXT: leaq {{.*}} # 0x27c +# CHECK-NEXT: leaq {{.*}} # 0x278 + +# RELOC: .data PROGBITS {{0*}}[[#%x,DATA:]] + +# RELOC: {{0*}}[[#DATA+8]] 0000000000000008 R_X86_64_RELATIVE [[#%x,DATA+0x8000000000000000]] + +# RUN: ld.lld -pie --emit-relocs a.o b.o -o out1 +# RUN: llvm-objdump -dr out1 | FileCheck %s --check-prefix=CHECKE +# RUN: llvm-readelf -Sr out1 | FileCheck %s --check-prefix=RELOCE + +# CHECKE: <_start>: +# CHECKE-NEXT: callq {{.*}} +# CHECKE-NEXT: R_X86_64_PLT32 foo-0x4 +# CHECKE-NEXT: callq {{.*}} +# CHECKE-NEXT: R_X86_64_PLT32 .text+0x6 +# CHECKE-EMPTY: +# CHECKE-NEXT: : +# CHECKE-NEXT: leaq {{.*}} +# CHECKE-NEXT: R_X86_64_PC32 .L.str-0x4 +# CHECKE-NEXT: leaq {{.*}} +# CHECKE-NEXT: R_X86_64_PC32 .L.str1-0x4 + +# RELOCE: .rodata PROGBITS {{0*}}[[#%x,RO:]] +# RELOCE: .eh_frame PROGBITS {{0*}}[[#%x,EHFRAME:]] +# RELOCE: .data PROGBITS {{0*}}[[#%x,DATA:]] + +# RELOCE: Relocation section '.crel.data' at offset {{.*}} contains 2 entries: +# RELOCE-NEXT: Offset Info Type Symbol's Value Symbol's Name + Addend +# RELOCE-NEXT: {{0*}}[[#DATA+8]] {{.*}} R_X86_64_64 {{.*}} .data - 8000000000000000 +# RELOCE-NEXT: {{0*}}[[#DATA+24]]{{.*}} R_X86_64_64 {{.*}} .data - 1 +# RELOCE: Relocation section '.crel.eh_frame' at offset {{.*}} contains 2 entries: +# RELOCE-NEXT: Offset Info Type Symbol's Value Symbol's Name + Addend +# RELOCE-NEXT: {{0*}}[[#EHFRAME+32]] {{.*}} R_X86_64_PC32 {{.*}} .text + 0 +# RELOCE-NEXT: {{0*}}[[#EHFRAME+52]] {{.*}} R_X86_64_PC32 {{.*}} .text + a +# RELOCE: Relocation section '.crel.rodata' at offset {{.*}} contains 4 entries: +# RELOCE-NEXT: Offset Info Type Symbol's Value Symbol's Name + Addend +# RELOCE-NEXT: {{0*}}[[#RO+8]] {{.*}} R_X86_64_PC32 {{.*}} foo + 0 +# RELOCE-NEXT: {{0*}}[[#RO+23]] {{.*}} R_X86_64_PC32 {{.*}} foo + 3f +# RELOCE-NEXT: {{0*}}[[#RO+39]] {{.*}} R_X86_64_PC64 {{.*}} foo + 7f +# RELOCE-NEXT: {{0*}}[[#RO+47]] {{.*}} R_X86_64_PC32 {{.*}} _start - 1f81 + +#--- a.s +.global _start, foo +_start: + .cfi_startproc # Test .eh_frame + call foo + call .text.foo + .cfi_endproc + +.section .text.foo,"ax" +foo: + .cfi_startproc + leaq .L.str(%rip), %rsi + leaq .L.str1(%rip), %rsi + .cfi_endproc + +.section .rodata.str1.1,"aMS",@progbits,1 +.L.str: + .asciz "abc" +.L.str1: + .asciz "def" + +.data +.quad 0 +.quad .data - 0x8000000000000000 +.quad 0 +.quad .data - 1 + +#--- b.s +.section .rodata,"a" +.long foo - . +.space 15-4 +.long foo - . + 63 # offset+=15 +.space 16-4 +.quad foo - . + 127 # offset+=16 +.long _start - . - 8065 diff --git a/lld/test/ELF/debug-names.s b/lld/test/ELF/debug-names.s index 888dd9007ed12..1bbb07b065e33 100644 --- a/lld/test/ELF/debug-names.s +++ b/lld/test/ELF/debug-names.s @@ -10,7 +10,7 @@ # REQUIRES: x86 # RUN: rm -rf %t && split-file %s %t && cd %t -# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 --crel a.s -o a.o # RUN: llvm-mc -filetype=obj -triple=x86_64 b.s -o b.o # RUN: ld.lld --debug-names --no-debug-names a.o b.o -o out0 diff --git a/lld/test/ELF/gc-sections.s b/lld/test/ELF/gc-sections.s index 94adc8210b4bc..31e00d495146a 100644 --- a/lld/test/ELF/gc-sections.s +++ b/lld/test/ELF/gc-sections.s @@ -8,6 +8,10 @@ # RUN: ld.lld --export-dynamic --gc-sections %t -o %t2 # RUN: llvm-readobj --sections --symbols %t2 | FileCheck -check-prefix=GC2 %s +# RUN: llvm-mc -filetype=obj -triple=x86_64 --crel %s -o %t.o +# RUN: ld.lld --gc-sections --print-gc-sections %t.o -o %t2 | FileCheck --check-prefix=GC1-DISCARD %s +# RUN: llvm-readobj --sections --symbols %t2 | FileCheck -check-prefix=GC1 %s + # NOGC: Name: .eh_frame # NOGC: Name: .text # NOGC: Name: .init diff --git a/lld/test/ELF/icf1.s b/lld/test/ELF/icf1.s index 5c6e667d53c78..9682b06f4606f 100644 --- a/lld/test/ELF/icf1.s +++ b/lld/test/ELF/icf1.s @@ -3,6 +3,9 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t # RUN: ld.lld %t -o /dev/null --icf=all --print-icf-sections | FileCheck %s +# RUN: llvm-mc -filetype=obj -triple=x86_64 --crel %s -o %t +# RUN: ld.lld %t -o /dev/null --icf=all --print-icf-sections | FileCheck %s + # CHECK: selected section {{.*}}:(.text.f1) # CHECK: removing identical section {{.*}}:(.text.f2) diff --git a/lld/test/ELF/icf4.s b/lld/test/ELF/icf4.s index ff13a7ebff3da..310577a55c0d8 100644 --- a/lld/test/ELF/icf4.s +++ b/lld/test/ELF/icf4.s @@ -1,6 +1,6 @@ # REQUIRES: x86 -# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t +# RUN: llvm-mc --crel -filetype=obj -triple=x86_64-unknown-linux %s -o %t # RUN: ld.lld %t -o /dev/null --icf=all --print-icf-sections | count 0 .globl _start, f1, f2 diff --git a/lld/test/ELF/linkerscript/nocrossrefs.test b/lld/test/ELF/linkerscript/nocrossrefs.test index f13d50a03be87..5eb56190fe63b 100644 --- a/lld/test/ELF/linkerscript/nocrossrefs.test +++ b/lld/test/ELF/linkerscript/nocrossrefs.test @@ -2,6 +2,7 @@ # RUN: rm -rf %t && split-file %s %t && cd %t # RUN: llvm-mc --triple=x86_64 -filetype=obj a.s -o a.o +# RUN: llvm-mc --triple=x86_64 -filetype=obj -crel a.s -o ac.o # RUN: llvm-mc --triple=x86_64 -filetype=obj data.s -o data.o # RUN: ld.lld a.o data.o -T 0.t 2>&1 | FileCheck %s --check-prefix=CHECK0 --implicit-check-not=warning: @@ -9,7 +10,8 @@ # CHECK0-NEXT: warning: 0.t:4: ignored with fewer than 2 output sections # RUN: not ld.lld a.o data.o -T 1.t 2>&1 | FileCheck %s --check-prefix=CHECK1 --implicit-check-not=error: -# CHECK1: error: a.o:(.text.start+0x11): prohibited cross reference from '.text' to 'data' in '.data' +# RUN: not ld.lld ac.o data.o -T 1.t 2>&1 | FileCheck %s --check-prefix=CHECK1 --implicit-check-not=error: +# CHECK1: error: a{{.?}}.o:(.text.start+0x11): prohibited cross reference from '.text' to 'data' in '.data' ## .text and .text1 are in two NOCROSSREFS commands. Violations are reported twice. # RUN: not ld.lld --threads=1 a.o data.o -T 2.t 2>&1 | FileCheck %s --check-prefix=CHECK2 --implicit-check-not=error: diff --git a/lld/test/ELF/relocatable-crel-32.s b/lld/test/ELF/relocatable-crel-32.s new file mode 100644 index 0000000000000..8fbf236d77452 --- /dev/null +++ b/lld/test/ELF/relocatable-crel-32.s @@ -0,0 +1,71 @@ +# REQUIRES: ppc +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=powerpc -crel a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=powerpc -crel b.s -o b.o +# RUN: ld.lld -r b.o a.o -o out +# RUN: llvm-readobj -r out | FileCheck %s --check-prefixes=CHECK,CRELFOO + +# RUN: llvm-mc -filetype=obj -triple=powerpc a.s -o a1.o +# RUN: ld.lld -r b.o a1.o -o out1 +# RUN: llvm-readobj -r out1 | FileCheck %s --check-prefixes=CHECK,RELAFOO +# RUN: ld.lld -r a1.o b.o -o out2 +# RUN: llvm-readobj -r out2 | FileCheck %s --check-prefixes=CHECK2 + +# CHECK: Relocations [ +# CHECK-NEXT: Section (2) .crel.text { +# CHECK-NEXT: 0x0 R_PPC_REL24 fb 0x0 +# CHECK-NEXT: 0x4 R_PPC_REL24 foo 0x0 +# CHECK-NEXT: 0x8 R_PPC_REL24 .text.foo 0x0 +# CHECK-NEXT: 0xE R_PPC_ADDR16_HA .rodata.str1.1 0x4 +# CHECK-NEXT: 0x12 R_PPC_ADDR16_LO .rodata.str1.1 0x4 +# CHECK-NEXT: 0x16 R_PPC_ADDR16_HA .rodata.str1.1 0x0 +# CHECK-NEXT: 0x1A R_PPC_ADDR16_LO .rodata.str1.1 0x0 +# CHECK-NEXT: } +# CRELFOO-NEXT: Section (4) .crel.text.foo { +# RELAFOO-NEXT: Section (4) .rela.text.foo { +# CHECK-NEXT: 0x0 R_PPC_REL24 g 0x0 +# CHECK-NEXT: 0x4 R_PPC_REL24 g 0x0 +# CHECK-NEXT: } +# CHECK-NEXT: ] + +# CHECK2: Relocations [ +# CHECK2-NEXT: Section (2) .crel.text { +# CHECK2-NEXT: 0x0 R_PPC_REL24 foo 0x0 +# CHECK2-NEXT: 0x4 R_PPC_REL24 .text.foo 0x0 +# CHECK2-NEXT: 0xA R_PPC_ADDR16_HA .rodata.str1.1 0x4 +# CHECK2-NEXT: 0xE R_PPC_ADDR16_LO .rodata.str1.1 0x4 +# CHECK2-NEXT: 0x12 R_PPC_ADDR16_HA .rodata.str1.1 0x0 +# CHECK2-NEXT: 0x16 R_PPC_ADDR16_LO .rodata.str1.1 0x0 +# CHECK2-NEXT: 0x18 R_PPC_REL24 fb 0x0 +# CHECK2-NEXT: } +# CHECK2-NEXT: Section (4) .rela.text.foo { +# CHECK2-NEXT: 0x0 R_PPC_REL24 g 0x0 +# CHECK2-NEXT: 0x4 R_PPC_REL24 g 0x0 +# CHECK2-NEXT: } +# CHECK2-NEXT: ] + +#--- a.s +.global _start, foo +_start: + bl foo + bl .text.foo + lis 3, .L.str@ha + la 3, .L.str@l(3) + lis 3, .L.str1@ha + la 3, .L.str1@l(3) + +.section .text.foo,"ax" +foo: + bl g + bl g + +.section .rodata.str1.1,"aMS",@progbits,1 +.L.str: + .asciz "abc" +.L.str1: + .asciz "def" + +#--- b.s +.globl fb +fb: + bl fb diff --git a/lld/test/ELF/relocatable-crel.s b/lld/test/ELF/relocatable-crel.s new file mode 100644 index 0000000000000..6e97c3e24d66c --- /dev/null +++ b/lld/test/ELF/relocatable-crel.s @@ -0,0 +1,107 @@ +# REQUIRES: x86 +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=x86_64 -crel a.s -o a.o +# RUN: llvm-mc -filetype=obj -triple=x86_64 -crel b.s -o b.o +# RUN: ld.lld -r b.o a.o -o out +# RUN: llvm-readobj -r out | FileCheck %s --check-prefixes=CHECK,CRELFOO + +# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a1.o +# RUN: ld.lld -r b.o a1.o -o out1 +# RUN: llvm-readobj -r out1 | FileCheck %s --check-prefixes=CHECK,RELAFOO +# RUN: ld.lld -r a1.o b.o -o out2 +# RUN: llvm-readobj -r out2 | FileCheck %s --check-prefixes=CHECK2 + +# CHECK: Relocations [ +# CHECK-NEXT: .crel.text { +# CHECK-NEXT: 0x1 R_X86_64_PLT32 fb 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0x9 R_X86_64_PLT32 foo 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0xE R_X86_64_PLT32 .text.foo 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: } +# CHECK-NEXT: .crel.rodata { +# CHECK-NEXT: 0x0 R_X86_64_PC32 foo 0x0 +# CHECK-NEXT: 0xF R_X86_64_PC32 foo 0x3F +# CHECK-NEXT: 0x1F R_X86_64_PC64 foo 0x7F +# CHECK-NEXT: 0x27 R_X86_64_PC32 _start 0xFFFFFFFFFFFFE07F +# CHECK-COUNT-12: R_X86_64_32 _start 0x0 +# CHECK-NEXT: } +# CRELFOO-NEXT: .crel.text.foo { +# RELAFOO-NEXT: .rela.text.foo { +# CHECK-NEXT: 0x3 R_X86_64_PC32 .L.str 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0xA R_X86_64_PC32 .L.str1 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0xF R_X86_64_PLT32 g 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: 0x14 R_X86_64_PLT32 g 0xFFFFFFFFFFFFFFFC +# CHECK-NEXT: } +# CRELFOO-NEXT: .crel.data { +# RELAFOO-NEXT: .rela.data { +# CHECK-NEXT: 0x8 R_X86_64_64 _start 0x8000000000000000 +# CHECK-NEXT: 0x18 R_X86_64_64 _start 0xFFFFFFFFFFFFFFFF +# CHECK-NEXT: } +# CHECK-NEXT: ] + +# CHECK2: Relocations [ +# CHECK2-NEXT: .crel.text { +# CHECK2-NEXT: 0x1 R_X86_64_PLT32 foo 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: 0x6 R_X86_64_PLT32 .text.foo 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: 0xD R_X86_64_PLT32 fb 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: } +# CHECK2-NEXT: .rela.text.foo { +# CHECK2-NEXT: 0x3 R_X86_64_PC32 .L.str 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: 0xA R_X86_64_PC32 .L.str1 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: 0xF R_X86_64_PLT32 g 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: 0x14 R_X86_64_PLT32 g 0xFFFFFFFFFFFFFFFC +# CHECK2-NEXT: } +# CHECK2-NEXT: .rela.data { +# CHECK2-NEXT: 0x8 R_X86_64_64 _start 0x8000000000000000 +# CHECK2-NEXT: 0x18 R_X86_64_64 _start 0xFFFFFFFFFFFFFFFF +# CHECK2-NEXT: } +# CHECK2-NEXT: .crel.rodata { +# CHECK2-NEXT: 0x0 R_X86_64_PC32 foo 0x0 +# CHECK2-NEXT: 0xF R_X86_64_PC32 foo 0x3F +# CHECK2-NEXT: 0x1F R_X86_64_PC64 foo 0x7F +# CHECK2-NEXT: 0x27 R_X86_64_PC32 _start 0xFFFFFFFFFFFFE07F +# CHECK2-COUNT-12: R_X86_64_32 _start 0x0 +# CHECK2-NEXT: } +# CHECK2-NEXT: ] + +#--- a.s +.global _start, foo +_start: + call foo + call .text.foo + +.section .text.foo,"ax" +foo: + leaq .L.str(%rip), %rsi + leaq .L.str1(%rip), %rsi + call g + call g + +.section .rodata.str1.1,"aMS",@progbits,1 +.L.str: + .asciz "abc" +.L.str1: + .asciz "def" + +.data +.quad 0 +.quad _start - 0x8000000000000000 +.quad 0 +.quad _start - 1 + +#--- b.s +.globl fb +fb: + call fb + +.section .rodata,"a" +.long foo - . +.space 15-4 +.long foo - . + 63 # offset+=15 +.space 16-4 +.quad foo - . + 127 # offset+=16 +.long _start - . - 8065 + +## Ensure .crel.rodata contains 16 relocations so that getULEB128Size(crelHeader) > 1. +.rept 12 +.long _start +.endr