[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