[lld] [lld][ELF] Prefer non-zero-range FDEs when building .eh_frame_hdr (PR #218127)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 22 05:25:45 PDT 2026
https://github.com/8051Enthusiast created https://github.com/llvm/llvm-project/pull/218127
FDEs whose PC range includes zero bytes, as produced by functions containing a single `unreachable` instruction, may land on the same address as a regular function. This can cause the former function to land in the .eh_frame_hdr while the latter function does not, so that a PC in that range is not found to lie in any function.
This patch adds a preference for non-zero-sized functions to be picked for the .eh_frame_hdr. It also fixes some tests whose hand-encoded FDEs seem to be malformed.
Fixes #218124.
>From 5ac308a7dcf86dcacc075726f75c37ef43b542fa Mon Sep 17 00:00:00 2001
From: 8051enthusiast <8051enthusiast at protonmail.com>
Date: Sat, 22 Aug 2026 13:53:31 +0200
Subject: [PATCH] [lld][ELF] Prefer non-zero-range FDEs when building
.eh_frame_hdr
FDEs whose PC range includes zero bytes, as produced by functions containing
a single `unreachable` instruction, may land on the same address as a regular
function. This can cause the former function to land in the .eh_frame_hdr
while the latter function does not, so that a PC in that range is not found
to lie in any function.
This patch adds a preference for non-zero-sized functions to be picked for
the .eh_frame_hdr. It also fixes some tests whose hand-encoded FDEs seem to
be malformed.
Fixes #218124.
---
lld/ELF/EhFrame.cpp | 92 ++++++++++++++++---
lld/ELF/EhFrame.h | 2 +
lld/ELF/SyntheticSections.cpp | 16 +++-
lld/ELF/SyntheticSections.h | 1 +
lld/test/ELF/eh-frame-fde-encoding.s | 13 ++-
lld/test/ELF/eh-frame-hdr-zerosized.s | 27 ++++++
lld/test/ELF/eh-frame-invalid-cie.s | 4 +-
lld/test/ELF/eh-frame-invalid-fde-encoding.s | 2 +-
lld/test/ELF/eh-frame-negative-pcrel-sdata2.s | 8 +-
lld/test/ELF/eh-frame-negative-pcrel-sdata8.s | 22 ++---
10 files changed, 149 insertions(+), 38 deletions(-)
create mode 100644 lld/test/ELF/eh-frame-hdr-zerosized.s
diff --git a/lld/ELF/EhFrame.cpp b/lld/ELF/EhFrame.cpp
index aade9291d1fba..ebfc4b6e49ce2 100644
--- a/lld/ELF/EhFrame.cpp
+++ b/lld/ELF/EhFrame.cpp
@@ -36,6 +36,7 @@ class EhReader {
EhReader(InputSectionBase *s, ArrayRef<uint8_t> d) : isec(s), d(d) {}
uint8_t getFdeEncoding();
bool hasLSDA();
+ std::optional<uint64_t> getPcRangeSize(uint8_t encoding);
private:
template <class P> void errOn(const P *loc, const Twine &msg) {
@@ -48,6 +49,8 @@ class EhReader {
void skipBytes(size_t count);
StringRef readString();
void skipLeb128();
+ void skipEncoded(uint8_t encoding);
+ std::optional<uint64_t> readEncodedLength(uint8_t encoding);
void skipAugP();
StringRef getAugmentation();
@@ -59,7 +62,7 @@ class EhReader {
// Read a byte and advance D by one byte.
uint8_t EhReader::readByte() {
if (d.empty()) {
- errOn(d.data(), "unexpected end of CIE");
+ errOn(d.data(), "unexpected end of CIE/FDE");
return 0;
}
uint8_t b = d.front();
@@ -69,7 +72,7 @@ uint8_t EhReader::readByte() {
void EhReader::skipBytes(size_t count) {
if (d.size() < count)
- errOn(d.data(), "CIE is too small");
+ errOn(d.data(), "CIE/FDE is too small");
else
d = d.slice(count);
}
@@ -101,7 +104,7 @@ void EhReader::skipLeb128() {
errOn(errPos, "corrupted CIE (failed to read LEB128)");
}
-static size_t getAugPSize(Ctx &ctx, unsigned enc) {
+static size_t getEncodedSize(Ctx &ctx, unsigned enc) {
switch (enc & 0x0f) {
case DW_EH_PE_absptr:
case DW_EH_PE_signed:
@@ -119,18 +122,70 @@ static size_t getAugPSize(Ctx &ctx, unsigned enc) {
return 0;
}
-void EhReader::skipAugP() {
- uint8_t enc = readByte();
- if ((enc & 0xf0) == DW_EH_PE_aligned)
- return errOn(d.data() - 1, "DW_EH_PE_aligned encoding is not supported");
- size_t size = getAugPSize(isec->getCtx(), enc);
+void EhReader::skipEncoded(uint8_t encoding) {
+ if ((encoding & 0xf0) == DW_EH_PE_aligned)
+ return errOn(d.data(), "DW_EH_PE_aligned encoding is not supported");
+
+ size_t size = getEncodedSize(isec->getCtx(), encoding);
if (size == 0)
- return errOn(d.data() - 1, "unknown FDE encoding");
- if (size >= d.size())
- return errOn(d.data() - 1, "corrupted CIE");
+ return errOn(d.data(), "unknown FDE encoding");
+ if (size > d.size())
+ return errOn(d.data(), "corrupted CIE/FDE (failed to read encoded value)");
d = d.slice(size);
}
+void EhReader::skipAugP() {
+ uint8_t enc = readByte();
+ skipEncoded(enc);
+}
+
+std::optional<uint64_t> EhReader::readEncodedLength(uint8_t encoding) {
+ using support::endian::read;
+
+ const auto *value = d.data();
+ skipEncoded(encoding);
+
+ auto &ctx = isec->getCtx();
+ const auto endian = ctx.arg.endianness;
+ int64_t signedValue = -1;
+ switch (encoding & 0x0f) {
+ case DW_EH_PE_absptr:
+ if (ctx.arg.wordsize == 4)
+ return read<uint32_t>(value, endian);
+ else
+ return read<uint64_t>(value, endian);
+ case DW_EH_PE_signed:
+ if (ctx.arg.wordsize == 4)
+ signedValue = read<int32_t>(value, endian);
+ else
+ signedValue = read<int64_t>(value, endian);
+ break;
+ case DW_EH_PE_udata2:
+ return read<uint16_t>(value, endian);
+ case DW_EH_PE_sdata2:
+ signedValue = read<int16_t>(value, endian);
+ break;
+ case DW_EH_PE_udata4:
+ return read<uint32_t>(value, endian);
+ case DW_EH_PE_sdata4:
+ signedValue = read<int32_t>(value, endian);
+ break;
+ case DW_EH_PE_udata8:
+ return read<uint64_t>(value, endian);
+ case DW_EH_PE_sdata8:
+ signedValue = read<int64_t>(value, endian);
+ break;
+ default:
+ errOn(value, "unknown FDE encoding");
+ return {};
+ }
+ if (signedValue < 0) {
+ errOn(value, "invalid negative PC range in FDE");
+ return {};
+ }
+ return (uint64_t)signedValue;
+}
+
uint8_t elf::getFdeEncoding(EhSectionPiece *p) {
return EhReader(p->sec, p->data()).getFdeEncoding();
}
@@ -203,3 +258,18 @@ bool EhReader::hasLSDA() {
}
return false;
}
+
+std::optional<uint64_t> EhReader::getPcRangeSize(uint8_t encoding) {
+ // Skip Record Length, CIE Pointer
+ skipBytes(8);
+ // Skip PC Begin
+ skipEncoded(encoding);
+ // PC Range
+ return readEncodedLength(encoding);
+}
+
+std::optional<uint64_t> elf::getPcRangeSize(const EhSectionPiece &p,
+ uint8_t encoding) {
+
+ return EhReader(p.sec, p.data()).getPcRangeSize(encoding);
+}
diff --git a/lld/ELF/EhFrame.h b/lld/ELF/EhFrame.h
index 95264166e36cb..3f6810fb89786 100644
--- a/lld/ELF/EhFrame.h
+++ b/lld/ELF/EhFrame.h
@@ -16,6 +16,8 @@ struct EhSectionPiece;
uint8_t getFdeEncoding(EhSectionPiece *p);
bool hasLSDA(const EhSectionPiece &p);
+std::optional<uint64_t> getPcRangeSize(const EhSectionPiece &p,
+ uint8_t encoding);
}
#endif
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index e2b9bb7590e26..4091045aaa56f 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -466,18 +466,26 @@ bool EhFrameHeader::updateAllocSize(Ctx &ctx) {
auto *isec = cast<EhInputSection>(fde->sec);
auto &reloc = isec->rels[fde->firstRelocation];
assert(isa<Defined>(reloc.sym) && "isFdeLive should have checked this");
+
+ auto size = getPcRangeSize(*fde, enc);
+ auto zeroSized = size.has_value() && *size == 0;
int64_t pcRel = reloc.sym->getVA(ctx) + reloc.addend - hdrVA;
int64_t fdeVARel = ehFrame->getParent()->addr + fde->outputOff - hdrVA;
- fdes.push_back({pcRel, fdeVARel});
+ fdes.push_back({pcRel, fdeVARel, zeroSized});
newLarge |= !isInt<32>(pcRel) || !isInt<32>(fdeVARel);
}
}
// Sort the FDE list by their PC and uniquify. Usually there is only one FDE
// at an address, but there can be more than one FDEs pointing to the address.
- llvm::stable_sort(
- fdes, [](const EhFrameSection::FdeData &a,
- const EhFrameSection::FdeData &b) { return a.pcRel < b.pcRel; });
+ // In that case, we prefer the one that has a PC range of more than 0 bytes,
+ // as the zero-sized range might belong to another function that has a size
+ // of zero and just happens to be at the same address.
+ llvm::stable_sort(fdes, [](const EhFrameSection::FdeData &a,
+ const EhFrameSection::FdeData &b) {
+ return a.pcRel < b.pcRel ||
+ (a.pcRel == b.pcRel && !a.zeroSized && b.zeroSized);
+ });
fdes.erase(llvm::unique(fdes,
[](const EhFrameSection::FdeData &a,
const EhFrameSection::FdeData &b) {
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index cbdadcceceaaf..d09238409d70c 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -65,6 +65,7 @@ class EhFrameSection final : public SyntheticSection {
struct FdeData {
int64_t pcRel;
int64_t fdeVARel;
+ bool zeroSized;
};
ArrayRef<CieRecord *> getCieRecords() const { return cieRecords; }
diff --git a/lld/test/ELF/eh-frame-fde-encoding.s b/lld/test/ELF/eh-frame-fde-encoding.s
index 0a7943c8b2077..65b9a90742e9c 100644
--- a/lld/test/ELF/eh-frame-fde-encoding.s
+++ b/lld/test/ELF/eh-frame-fde-encoding.s
@@ -28,7 +28,8 @@
# ABSPTR-NEXT: 0x00002014 24000000
# ABSPTR: Hex dump of section '.eh_frame':
# ABSPTR-NEXT: 0x00002018 0c000000 00000000 01520001 010100ff
-# ABSPTR-NEXT: 0x00002028 0c000000 14000000 34120000 00000000
+# ABSPTR-NEXT: 0x00002028 14000000 14000000 34120000 00000000
+# ABSPTR-NEXT: 0x00002038 01000000 00000000
## CIE offset--^ ^-- PC begin = 0x1234 (foo + 0x234)
# RUN: llvm-readobj -x .eh_frame_hdr -x .eh_frame udata2 | FileCheck %s --check-prefix=UDATA2
@@ -37,8 +38,8 @@
# UDATA2-NEXT: 0x00002014 26000000
# UDATA2: Hex dump of section '.eh_frame':
# UDATA2-NEXT: 0x00002018 0e000000 00000000 01525300 01010102
-# UDATA2-NEXT: 0x00002028 ff000600 00001600 00003412
-## CIE offset--^ ^-- PC begin = 0x1234 (foo + 0x234)
+# UDATA2-NEXT: 0x00002028 ff000800 00001600 00003412 0100
+## CIE offset--^ ^-- PC begin = 0x1234 (foo + 0x234)
# RUN: llvm-readelf -x .eh_frame_hdr sdata4 udata4 | FileCheck %s --check-prefix=HDR4
# HDR4: 0x00003004 011b033b 10000000 01000000 fcefffff
@@ -65,9 +66,10 @@ foo:
.byte 0x00 # DW_EH_PE_absptr
.byte 0xFF
- .long 12 # Size
+ .long 20 # Size
.long 0x14 # CIE offset
.quad foo + 0x234 # PC begin
+ .quad 1 # PC range
#--- sdata2.s
## DW_EH_PE_sdata2 (0x0A)
@@ -159,9 +161,10 @@ foo:
.byte 0xFF
.byte 0x00
- .long 6 # Size
+ .long 8 # Size
.long 0x16 # CIE offset
.short foo + 0x234 # PC begin
+ .short 1 # PC range
#--- udata4.s
## DW_EH_PE_udata4 (0x03) with FDE for verification
diff --git a/lld/test/ELF/eh-frame-hdr-zerosized.s b/lld/test/ELF/eh-frame-hdr-zerosized.s
new file mode 100644
index 0000000000000..c4c85e9520a44
--- /dev/null
+++ b/lld/test/ELF/eh-frame-hdr-zerosized.s
@@ -0,0 +1,27 @@
+# REQUIRES: x86
+
+## Check that we correctly skip FDEs with zero-size PC ranges as
+## they may hide another non-trivial FDE at the same address.
+## Here, f1 has the same address as f2, but we must use f2's
+## FDE in the header.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
+# RUN: ld.lld --eh-frame-hdr %t.o -o %t
+# RUN: llvm-readelf -S -x .eh_frame_hdr %t | FileCheck %s
+
+# CHECK: Hex dump of section '.eh_frame_hdr':
+# CHECK-NEXT: 011b033b 1c000000 01000000 {{[0-9a-f]+}}
+# CHECK-NEXT: 4c000000
+## ^ offset of second FDE
+
+.text
+.globl f1, f2
+
+f1:
+ .cfi_startproc
+ .cfi_endproc
+
+f2:
+ .cfi_startproc
+ ret
+ .cfi_endproc
diff --git a/lld/test/ELF/eh-frame-invalid-cie.s b/lld/test/ELF/eh-frame-invalid-cie.s
index 0a40a1121a9db..7132740ad34c0 100644
--- a/lld/test/ELF/eh-frame-invalid-cie.s
+++ b/lld/test/ELF/eh-frame-invalid-cie.s
@@ -12,10 +12,10 @@
# RUN: not ld.lld --eh-frame-hdr failed-string.o 2>&1 | FileCheck %s --check-prefix=FAILED-STRING --implicit-check-not=error:
# RUN: not ld.lld --eh-frame-hdr failed-leb128.o 2>&1 | FileCheck %s --check-prefix=FAILED-LEB128
-# TOO-SMALL: error: corrupted .eh_frame: CIE is too small
+# TOO-SMALL: error: corrupted .eh_frame: CIE/FDE is too small
# TOO-SMALL-NEXT: >>> defined in too-small.o:(.eh_frame+0x0)
-# UNEXPECTED-END: error: corrupted .eh_frame: unexpected end of CIE
+# UNEXPECTED-END: error: corrupted .eh_frame: unexpected end of CIE/FDE
# UNEXPECTED-END-NEXT: >>> defined in unexpected-end.o:(.eh_frame+0x8)
# FAILED-STRING: error: corrupted .eh_frame: corrupted CIE (failed to read string)
diff --git a/lld/test/ELF/eh-frame-invalid-fde-encoding.s b/lld/test/ELF/eh-frame-invalid-fde-encoding.s
index 9a463f95b5568..2a6f11afcea2b 100644
--- a/lld/test/ELF/eh-frame-invalid-fde-encoding.s
+++ b/lld/test/ELF/eh-frame-invalid-fde-encoding.s
@@ -21,7 +21,7 @@
# CORRUPTED: error: corrupted .eh_frame: corrupted CIE
# UNKNOWN-FDE: error: corrupted .eh_frame: unknown FDE encoding
-# UNKNOWN-FDE-NEXT: >>> defined in unknown-fde-encoding.o:(.eh_frame+0xe)
+# UNKNOWN-FDE-NEXT: >>> defined in unknown-fde-encoding.o:(.eh_frame+0xf)
# ALIGNED: error: corrupted .eh_frame: DW_EH_PE_aligned encoding is not supported
diff --git a/lld/test/ELF/eh-frame-negative-pcrel-sdata2.s b/lld/test/ELF/eh-frame-negative-pcrel-sdata2.s
index 72325bbf14407..e1b8ee6585dfe 100644
--- a/lld/test/ELF/eh-frame-negative-pcrel-sdata2.s
+++ b/lld/test/ELF/eh-frame-negative-pcrel-sdata2.s
@@ -24,8 +24,8 @@
# CHECK-NEXT: EntrySize:
# CHECK-NEXT: SectionData (
# CHECK-NEXT: 0000: 10000000 00000000 017A5200 01010101
-# CHECK-NEXT: 0010: 1A000000 0A000000 18000000 E3FFFFFF
-# CHECK-NEXT: 0020: 00000000 0000
+# CHECK-NEXT: 0010: 1A000000 08000000 18000000 E3FF0000
+# CHECK-NEXT: 0020: 00000000
# ^
# DFFFFFFF = _start(0x1000) - PC(.eh_frame(0x1001) + 0x20)
@@ -78,8 +78,8 @@ _start:
.byte 0x00
.byte 0x00
- .long 10 # Size
+ .long 8 # Size
.long 24 # ID
fde:
- .long _start - fde
+ .word _start - fde
.word 0
diff --git a/lld/test/ELF/eh-frame-negative-pcrel-sdata8.s b/lld/test/ELF/eh-frame-negative-pcrel-sdata8.s
index 794258f75803f..101aec11da680 100644
--- a/lld/test/ELF/eh-frame-negative-pcrel-sdata8.s
+++ b/lld/test/ELF/eh-frame-negative-pcrel-sdata8.s
@@ -24,8 +24,8 @@
# CHECK-NEXT: EntrySize:
# CHECK-NEXT: SectionData (
# CHECK-NEXT: 0000: 10000000 00000000 017A5200 01010101
-# CHECK-NEXT: 0010: 1C000000 10000000 18000000 E3FFFFFF
-# CHECK-NEXT: 0020: FFFFFFFF 00000000 00000000
+# CHECK-NEXT: 0010: 1C000000 14000000 18000000 E3FFFFFF
+# CHECK-NEXT: 0020: FFFFFFFF 00000000 00000000 00000000
# ^
# E3FFFFFF FFFFFFFF = _start(0x1000) - PC(.eh_frame(0x1001) + 0x1C)
@@ -36,21 +36,21 @@
# CHECK-NEXT: Flags [
# CHECK-NEXT: SHF_ALLOC
# CHECK-NEXT: ]
-# CHECK-NEXT: Address: 0x1030
-# CHECK-NEXT: Offset: 0x1030
+# CHECK-NEXT: Address: 0x1034
+# CHECK-NEXT: Offset: 0x1034
# CHECK-NEXT: Size: 20
# CHECK-NEXT: Link: 0
# CHECK-NEXT: Info: 0
# CHECK-NEXT: AddressAlignment: 4
# CHECK-NEXT: EntrySize: 0
# CHECK-NEXT: SectionData (
-# CHECK-NEXT: 0000: 011B033B CDFFFFFF 01000000 D0FFFFFF
-# CHECK-NEXT: 0010: E5FFFFFF
+# CHECK-NEXT: 0000: 011B033B C9FFFFFF 01000000 CCFFFFFF
+# CHECK-NEXT: 0010: E1FFFFFF
# Header (always 4 bytes): 011B033B
-# CDFFFFFF = .eh_frame(0x1001) - .eh_frame_hdr(0x1030) - 4
+# C9FFFFFF = .eh_frame(0x1001) - .eh_frame_hdr(0x1034) - 4
# 01000000 = 1 = the number of FDE pointers in the table.
-# D0FFFFFF = _start(0x1000) - .eh_frame_hdr(0x1030)
-# E5FFFFFF = FDE(.eh_frame(0x1001) + 0x18) - .eh_frame_hdr(0x1030)
+# CCFFFFFF = _start(0x1000) - .eh_frame_hdr(0x1034)
+# E1FFFFFF = FDE(.eh_frame(0x1001) + 0x18) - .eh_frame_hdr(0x1034)
.text
.global _start
@@ -78,8 +78,8 @@ _start:
.byte 0x00
.byte 0x00
- .long 16 # Size
+ .long 20 # Size
.long 24 # ID
fde:
.quad _start - fde
- .long 0
+ .quad 0
More information about the llvm-commits
mailing list