-
Notifications
You must be signed in to change notification settings - Fork 15.7k
[lld] Add support for eh_frame_hdr sdata8 #174486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary: The eh_frame_hdr supports variable encodings (https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html). Even with large code-model, we can run into relocation overflows since the encoding for the eh_frame_ptr was fixed to sdata4 (32bit). This change autodetects when we need to upgrade the encoding to 8 bytes. fixes llvm#172777 The eh_frame_hdr supports variable encodings (https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html). Even with large code-model, we can run into relocation overflows since the encoding for the eh_frame_ptr was fixed to sdata4 (32bit). This change autodetects when we need to upgrade the encoding to 8 bytes. fixes llvm#172777
|
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
|
@llvm/pr-subscribers-lld-elf @llvm/pr-subscribers-lld Author: Farid Zakaria (fzakaria) ChangesThe eh_frame_hdr supports variable encodings (https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html). Even with large code-model, we can run into relocation overflows since the encoding for the eh_frame_ptr was fixed to sdata4 (32bit). This change autodetects when we need to upgrade the encoding to 8 bytes. fixes #172777 Full diff: https://github.com/llvm/llvm-project/pull/174486.diff 5 Files Affected:
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index c81f649861a73..8fa3e80290aa7 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -613,12 +613,9 @@ void EhFrameSection::writeTo(uint8_t *buf) {
for (EhSectionPiece *fde : rec->fdes) {
uint64_t pc = getFdePc(buf, fde->outputOff, enc);
uint64_t fdeVA = getParent()->addr + fde->outputOff;
- if (!isInt<32>(pc - va)) {
- Err(ctx) << fde->sec << ": PC offset is too large: 0x"
- << Twine::utohexstr(pc - va);
- continue;
- }
- fdes.push_back({uint32_t(pc - va), uint32_t(fdeVA - va)});
+ int64_t pcRel = pc - va;
+ int64_t fdeVARel = fdeVA - va;
+ fdes.push_back({pcRel, fdeVARel});
}
}
@@ -632,23 +629,33 @@ void EhFrameSection::writeTo(uint8_t *buf) {
llvm::unique(fdes, [](auto &a, auto &b) { return a.pcRel == b.pcRel; }),
fdes.end());
- // Write header.
+ // Determine encodings. Use 64-bit encodings if any entry or offset exceeds
+ // 32-bit range, which can happen with very large binaries (e.g., large code
+ // model).
+ int64_t ehFramePtrOff = getParent()->addr - hdr->getVA() - 4;
+ bool large =
+ !isInt<32>(ehFramePtrOff) || llvm::any_of(fdes, [](const FdeData &fde) {
+ return !isInt<32>(fde.pcRel) || !isInt<32>(fde.fdeVARel);
+ });
+ bool largeCount = fdes.size() > UINT32_MAX;
+ auto write = [&](uint8_t *buf, uint64_t val, bool large) {
+ large ? write64(ctx, buf, val) : write32(ctx, buf, val);
+ };
+
uint8_t *hdrBuf = ctx.bufferStart + hdr->getParent()->offset + hdr->outSecOff;
- hdrBuf[0] = 1; // version
- hdrBuf[1] = DW_EH_PE_pcrel | DW_EH_PE_sdata4; // eh_frame_ptr_enc
- hdrBuf[2] = DW_EH_PE_udata4; // fde_count_enc
- hdrBuf[3] = DW_EH_PE_datarel | DW_EH_PE_sdata4; // table_enc
- write32(ctx, hdrBuf + 4,
- getParent()->addr - hdr->getVA() - 4); // eh_frame_ptr
- write32(ctx, hdrBuf + 8, fdes.size()); // fde_count
- hdrBuf += 12;
-
- // Write binary search table. Each entry describes the starting PC and the FDE
- // address.
+ hdrBuf[0] = 1;
+ hdrBuf[1] = DW_EH_PE_pcrel | (large ? DW_EH_PE_sdata8 : DW_EH_PE_sdata4);
+ hdrBuf[2] = largeCount ? DW_EH_PE_udata8 : DW_EH_PE_udata4;
+ hdrBuf[3] = DW_EH_PE_datarel | (large ? DW_EH_PE_sdata8 : DW_EH_PE_sdata4);
+ hdrBuf += 4;
+ write(hdrBuf, ehFramePtrOff, large);
+ hdrBuf += large ? 8 : 4;
+ write(hdrBuf, fdes.size(), largeCount);
+ hdrBuf += largeCount ? 8 : 4;
for (FdeData &fde : fdes) {
- write32(ctx, hdrBuf, fde.pcRel);
- write32(ctx, hdrBuf + 4, fde.fdeVARel);
- hdrBuf += 8;
+ write(hdrBuf, fde.pcRel, large);
+ write(hdrBuf + (large ? 8 : 4), fde.fdeVARel, large);
+ hdrBuf += large ? 16 : 8;
}
}
@@ -659,15 +666,68 @@ void EhFrameHeader::writeTo(uint8_t *buf) {
// The section content is written during EhFrameSection::writeTo.
}
-size_t EhFrameHeader::getSize() const {
- // .eh_frame_hdr has a 12 bytes header followed by an array of FDEs.
- return 12 + getPartition(ctx).ehFrame->numFdes * 8;
-}
-
bool EhFrameHeader::isNeeded() const {
return isLive() && getPartition(ctx).ehFrame->isNeeded();
}
+bool EhFrameHeader::updateAllocSize(Ctx &) {
+ // Size is computed by EhFrameSection::updateAllocSize().
+ return false;
+}
+
+bool EhFrameSection::updateAllocSize(Ctx &ctx) {
+ if (!isLive())
+ return false;
+ EhFrameHeader *hdr = getPartition(ctx).ehFrameHdr.get();
+ if (!hdr || !hdr->getParent() || !getParent())
+ return false;
+
+ // Compute exact size by checking all FDE entries for 32-bit overflow.
+ // This mirrors the layout written in writeTo():
+ // - 4 bytes: header
+ // - 4 or 8 bytes: eh_frame_ptr (based on large)
+ // - 4 or 8 bytes: fde_count (based on numFdes > UINT32_MAX)
+ // - numFdes * (8 or 16) bytes: FDE table entries (based on large)
+ size_t oldSize = hdr->size;
+ int64_t ehFramePtrOff = getParent()->addr - hdr->getVA() - 4;
+ bool large = !isInt<32>(ehFramePtrOff);
+
+ // Check if any FDE entry values exceed 32-bit range.
+ if (!large) {
+ uint64_t hdrVA = hdr->getVA();
+ for (CieRecord *rec : cieRecords) {
+ for (EhSectionPiece *fde : rec->fdes) {
+ // Get the function symbol from the FDE's first relocation.
+ if (fde->firstRelocation == (unsigned)-1)
+ continue;
+ auto *isec = cast<EhInputSection>(fde->sec);
+ const Relocation &rel = isec->rels[fde->firstRelocation];
+ auto *d = dyn_cast<Defined>(rel.sym);
+ if (!d || d->folded || !d->section ||
+ d->section->partition != partition)
+ continue;
+
+ // Compute pcRel and fdeVARel as done in writeTo().
+ uint64_t pc = d->getVA(ctx);
+ uint64_t fdeVA = getParent()->addr + fde->outputOff;
+ int64_t pcRel = pc - hdrVA;
+ int64_t fdeVARel = fdeVA - hdrVA;
+ if (!isInt<32>(pcRel) || !isInt<32>(fdeVARel)) {
+ large = true;
+ break;
+ }
+ }
+ if (large)
+ break;
+ }
+ }
+
+ bool largeCount = numFdes > UINT32_MAX;
+ hdr->size =
+ 4 + (large ? 8 : 4) + (largeCount ? 8 : 4) + numFdes * (large ? 16 : 8);
+ return hdr->size != oldSize;
+}
+
GotSection::GotSection(Ctx &ctx)
: SyntheticSection(ctx, ".got", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE,
ctx.target->gotEntrySize) {
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index f9862b07b1ed7..62dce96e5b4f2 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -55,6 +55,7 @@ class EhFrameSection final : public SyntheticSection {
void finalizeContents() override;
bool isNeeded() const override { return !sections.empty(); }
size_t getSize() const override { return size; }
+ bool updateAllocSize(Ctx &) override;
static bool classof(const SectionBase *d) {
return SyntheticSection::classof(d) && d->name == ".eh_frame";
@@ -64,8 +65,8 @@ class EhFrameSection final : public SyntheticSection {
size_t numFdes = 0;
struct FdeData {
- uint32_t pcRel;
- uint32_t fdeVARel;
+ int64_t pcRel;
+ int64_t fdeVARel;
};
ArrayRef<CieRecord *> getCieRecords() const { return cieRecords; }
@@ -101,8 +102,12 @@ class EhFrameHeader final : public SyntheticSection {
public:
EhFrameHeader(Ctx &);
void writeTo(uint8_t *buf) override;
- size_t getSize() const override;
+ size_t getSize() const override { return size; }
bool isNeeded() const override;
+ bool updateAllocSize(Ctx &) override;
+
+ // EhFrameSection computes and sets the size.
+ size_t size = 0;
};
class GotSection final : public SyntheticSection {
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 57202f42cce5b..2b635d75fd78a 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -1603,6 +1603,8 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
changed |= part.relrAuthDyn->updateAllocSize(ctx);
if (part.memtagGlobalDescriptors)
changed |= part.memtagGlobalDescriptors->updateAllocSize(ctx);
+ if (part.ehFrame)
+ changed |= part.ehFrame->updateAllocSize(ctx);
}
std::pair<const OutputSection *, const Defined *> changes =
diff --git a/lld/test/ELF/eh-frame-hdr-large.s b/lld/test/ELF/eh-frame-hdr-large.s
new file mode 100644
index 0000000000000..a77b7c98ff3bb
--- /dev/null
+++ b/lld/test/ELF/eh-frame-hdr-large.s
@@ -0,0 +1,33 @@
+# REQUIRES: x86
+
+# Test that .eh_frame_hdr uses 64-bit encodings (sdata8) when offsets exceed
+# 32-bit range. This can happen with very large binaries even with the large code model.
+# https://github.com/llvm/llvm-project/issues/172777
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s --large-code-model -o %t.o
+
+## Test 1: Place .text at a high address (>4GB from .eh_frame_hdr) using linker script.
+## The eh_frame_hdr should use sdata8 encoding (0x0C = DW_EH_PE_sdata8).
+# RUN: echo "SECTIONS { \
+# RUN: .eh_frame_hdr : { *(.eh_frame_hdr) } \
+# RUN: .eh_frame : { *(.eh_frame) } \
+# RUN: .text 0x100000000 : { *(.text) } \
+# RUN: }" > %t-large.lds
+# RUN: ld.lld --eh-frame-hdr --script %t-large.lds %t.o -o %t-large
+# RUN: llvm-readobj -S --section-data %t-large | FileCheck %s
+
+# CHECK: Section {
+# CHECK: Name: .eh_frame_hdr
+# CHECK-NEXT: Type: SHT_PROGBITS
+# CHECK-NEXT: Flags [
+# CHECK-NEXT: SHF_ALLOC
+# CHECK-NEXT: ]
+# CHECK: Size: 32
+# ^^ The size should be: 4 + 8 (large eh_frame_ptr) + 4 (small fde_count) + 16 (one large entry)
+# = 4 + 8 + 4 + 16 = 32
+.text
+.global _start
+_start:
+ .cfi_startproc
+ nop
+ .cfi_endproc
diff --git a/lld/test/ELF/eh-frame-pcrel-overflow.s b/lld/test/ELF/eh-frame-pcrel-overflow.s
deleted file mode 100644
index 3dfcf9ee1a7f9..0000000000000
--- a/lld/test/ELF/eh-frame-pcrel-overflow.s
+++ /dev/null
@@ -1,35 +0,0 @@
-# REQUIRES: x86
-
-# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
-# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %p/Inputs/eh-frame-pcrel-overflow.s -o %t1.o
-# RUN: ld.lld --eh-frame-hdr -Ttext=0x90000000 %t.o -o /dev/null
-# RUN: not ld.lld --eh-frame-hdr %t.o %t1.o -o /dev/null 2>&1 | FileCheck %s
-# RUN: ld.lld --eh-frame-hdr %t.o %t1.o -o /dev/null --noinhibit-exec 2>&1 | FileCheck %s --check-prefix=WARN
-# CHECK: error: {{.*}}.o:(.eh_frame): PC offset is too large: 0x90001054
-# WARN: warning: {{.*}}.o:(.eh_frame): PC offset is too large: 0x90001054
-
-.text
-.global _start
-_start:
- ret
-
-.section .eh_frame,"a",@unwind
- .long 12 # Size
- .long 0x00 # ID
- .byte 0x01 # Version.
-
- .byte 0x52 # Augmentation string: 'R','\0'
- .byte 0x00
-
- .byte 0x01
-
- .byte 0x01 # LEB128
- .byte 0x01 # LEB128
-
- .byte 0x00 # DW_EH_PE_absptr
-
- .byte 0xFF
-
- .long 12 # Size
- .long 0x14 # ID
- .quad _start + 0x70000000
|
Broader motivation: I am working to support large binaries with
mcmodel=mediumand I ran into this reported issue (+ others).The eh_frame_hdr supports variable encodings (https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html).
Even with large code-model, we can run into relocation overflows since the encoding for the eh_frame_ptr was fixed to sdata4 (32bit).
This change autodetects when we need to upgrade the encoding to 8 bytes.
fixes #172777