[llvm] [DebugInfo] Allow .eh_frame CFI programs to be parsed lazily (PR #214895)

Rafael Auler via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 12:10:46 PDT 2026


https://github.com/rafaelauler updated https://github.com/llvm/llvm-project/pull/214895

>From a0c7c534eef001c705b693c63a87e693ee10df3a Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at meta.com>
Date: Wed, 29 Jul 2026 16:37:58 -0700
Subject: [PATCH 1/4] [DebugInfo] Allow .eh_frame CFI programs to be parsed
 lazily

BOLT read the entire .eh_frame up front via DwCtx->getEHFrame(), which
parses and caches the CFI instruction program of every CIE/FDE in the
binary for the whole run. On a large binary, this dominated
file-object discovery: CFIProgram::parse accounted for ~6.5 GB and the
cached DWARFDebugFrame ~6.9 GB of live memory (from 5 to 10% of total
anon peak RSS).

This new interface allows DebugInfo's users to optionally parse CFIs
on demand, only when necessary. On BOLT, this is an important lever
to manage memory utilization when processing large binaries.
---
 .../llvm/DebugInfo/DWARF/DWARFDebugFrame.h    | 26 ++++++++++++++++++-
 llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp  | 11 +++++++-
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
index c1d9be774ca97..44623f7ada676 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
@@ -72,6 +72,21 @@ class FrameEntry {
   const CFIProgram &cfis() const { return CFIs; }
   CFIProgram &cfis() { return CFIs; }
 
+  /// Section offset at which this entry's CFI instructions begin. Recorded by
+  /// parse() even when the CFI program itself is not parsed, so the program can
+  /// be parsed on demand later via cfis().parse(Data, ..., getEndOffset()).
+  uint64_t getCFIStartOffset() const { return CFIStartOffset; }
+  void setCFIStartOffset(uint64_t O) { CFIStartOffset = O; }
+
+  /// Section offset one past the end of this entry (exclusive end of its CFI
+  /// instructions).
+  uint64_t getEndOffset() const {
+    // End is Offset plus the size of the initial length field plus Length.
+    // The initial length field is 4 bytes in DWARF32 and 12 bytes in DWARF64
+    // (a 0xffffffff escape marker followed by an 8-byte length).
+    return Offset + (IsDWARF64 ? 12 : 4) + Length;
+  }
+
   /// Dump the instructions in this CFI fragment
   virtual void dump(raw_ostream &OS, DIDumpOptions DumpOpts) const = 0;
 
@@ -86,6 +101,9 @@ class FrameEntry {
   /// Entry length as specified in DWARF.
   const uint64_t Length;
 
+  /// Section offset at which this entry's CFI instructions begin.
+  uint64_t CFIStartOffset = 0;
+
   CFIProgram CFIs;
 };
 
@@ -218,7 +236,13 @@ class DWARFDebugFrame {
 
   /// Parse the section from raw data. \p Data is assumed to contain the whole
   /// frame section contents to be parsed.
-  LLVM_ABI Error parse(DWARFDataExtractor Data);
+  ///
+  /// If \p ParseCFIProgram is false, the CFI instruction program of each entry
+  /// is not decoded; each entry still records where its instructions begin (see
+  /// FrameEntry::getCFIStartOffset()) so callers can parse individual programs
+  /// on demand. This avoids materializing every entry's instructions when only
+  /// a subset (or none) is needed, which can be a large memory saving.
+  LLVM_ABI Error parse(DWARFDataExtractor Data, bool ParseCFIProgram = true);
 
   /// Return whether the section has any entries.
   bool empty() const { return Entries.empty(); }
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
index 85167bf5c1ebd..5c6765894342b 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
@@ -199,7 +199,7 @@ DWARFDebugFrame::~DWARFDebugFrame() = default;
   errs() << "\n";
 }
 
-Error DWARFDebugFrame::parse(DWARFDataExtractor Data) {
+Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
   uint64_t Offset = 0;
   DenseMap<uint64_t, CIE *> CIEs;
 
@@ -385,6 +385,15 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data) {
                                    LSDAAddress, Arch));
     }
 
+    // Optionally skip the CFI instruction program without decoding it.
+    if (!ParseCFIProgram) {
+      // Record where this entry's CFI instructions begin so they can be parsed
+      // on demand later.
+      Entries.back()->setCFIStartOffset(Offset);
+      Offset = EndStructureOffset;
+      continue;
+    }
+
     if (Error E =
             Entries.back()->cfis().parse(Data, &Offset, EndStructureOffset))
       return E;

>From c5359f43990837f94dae912eba2f1295cec47eed Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Fri, 14 Aug 2026 12:16:22 -0700
Subject: [PATCH 2/4] Implement partial dump of debug info to showcase lazy
 ehframe parsing

---
 .../llvm/DebugInfo/DWARF/DWARFContext.h       | 19 +++--
 .../llvm/DebugInfo/DWARF/DWARFDebugFrame.h    | 36 +++++++--
 llvm/lib/DebugInfo/DWARF/DWARFContext.cpp     | 51 ++++++++----
 llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp  | 71 +++++++++++++++--
 .../X86/debug_frame_invalid_cfi_program.s     | 79 +++++++++++++++++++
 5 files changed, 221 insertions(+), 35 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfdump/X86/debug_frame_invalid_cfi_program.s

diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h
index 13837e40b0e53..b9cf7981a58c2 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h
@@ -83,8 +83,10 @@ class LLVM_ABI DWARFContext : public DIContext {
         getLineTableForUnit(DWARFUnit *U,
                             function_ref<void(Error)> RecoverableErrHandler) = 0;
     virtual void clearLineTableForUnit(DWARFUnit *U) = 0;
-    virtual Expected<const DWARFDebugFrame *> getDebugFrame() = 0;
-    virtual Expected<const DWARFDebugFrame *> getEHFrame() = 0;
+    virtual Expected<const DWARFDebugFrame *>
+    getDebugFrame(bool ParseCFIProgram) = 0;
+    virtual Expected<const DWARFDebugFrame *>
+    getEHFrame(bool ParseCFIProgram) = 0;
     virtual const DWARFDebugMacro *getDebugMacinfo() = 0;
     virtual const DWARFDebugMacro *getDebugMacinfoDWO() = 0;
     virtual const DWARFDebugMacro *getDebugMacro() = 0;
@@ -310,10 +312,15 @@ class LLVM_ABI DWARFContext : public DIContext {
   const DWARFDebugAranges *getDebugAranges();
 
   /// Get a pointer to the parsed frame information object.
-  Expected<const DWARFDebugFrame *> getDebugFrame();
-
-  /// Get a pointer to the parsed eh frame information object.
-  Expected<const DWARFDebugFrame *> getEHFrame();
+  ///
+  /// If \p ParseCFIProgram is false, the returned object has not decoded the
+  /// CFI instruction program of its entries; use
+  /// DWARFDebugFrame::parseCFIProgram() to decode the ones that are needed.
+  Expected<const DWARFDebugFrame *> getDebugFrame(bool ParseCFIProgram = true);
+
+  /// Get a pointer to the parsed eh frame information object. See
+  /// getDebugFrame() for \p ParseCFIProgram.
+  Expected<const DWARFDebugFrame *> getEHFrame(bool ParseCFIProgram = true);
 
   /// Get a pointer to the parsed DebugMacinfo information object.
   const DWARFDebugMacro *getDebugMacinfo();
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
index 44623f7ada676..21179746491b0 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
@@ -72,12 +72,17 @@ class FrameEntry {
   const CFIProgram &cfis() const { return CFIs; }
   CFIProgram &cfis() { return CFIs; }
 
-  /// Section offset at which this entry's CFI instructions begin. Recorded by
-  /// parse() even when the CFI program itself is not parsed, so the program can
-  /// be parsed on demand later via cfis().parse(Data, ..., getEndOffset()).
+  /// Section offset at which this entry's CFI instructions begin, recorded so a
+  /// program left undecoded can be parsed on demand later via
+  /// cfis().parse(Data, ..., getEndOffset()).
   uint64_t getCFIStartOffset() const { return CFIStartOffset; }
   void setCFIStartOffset(uint64_t O) { CFIStartOffset = O; }
 
+  /// Whether this entry's CFI instruction program has been decoded. False for
+  /// an entry whose program parse() was told to skip.
+  bool isCFIProgramParsed() const { return CFIsParsed; }
+  void setCFIProgramParsed() { CFIsParsed = true; }
+
   /// Section offset one past the end of this entry (exclusive end of its CFI
   /// instructions).
   uint64_t getEndOffset() const {
@@ -104,6 +109,9 @@ class FrameEntry {
   /// Section offset at which this entry's CFI instructions begin.
   uint64_t CFIStartOffset = 0;
 
+  /// Whether CFIs holds the decoded CFI instruction program.
+  bool CFIsParsed = false;
+
   CFIProgram CFIs;
 };
 
@@ -218,9 +226,18 @@ class DWARFDebugFrame {
   std::vector<std::unique_ptr<dwarf::FrameEntry>> Entries;
   using iterator = pointee_iterator<decltype(Entries)::const_iterator>;
 
+  /// Section contents, retained by parse() when it was asked to leave the CFI
+  /// instruction programs undecoded, so that they can be parsed on demand.
+  std::unique_ptr<DWARFDataExtractor> Data;
+
   /// Return the entry at the given offset or nullptr.
   dwarf::FrameEntry *getEntryAtOffset(uint64_t Offset) const;
 
+  /// Make sure CFIs of \p Entry are fully parsed, then dump the entry.
+  /// Failure to decode the CFI is reported through \p DumpOpts.
+  void dumpEntry(dwarf::FrameEntry &Entry, raw_ostream &OS,
+                 DIDumpOptions DumpOpts) const;
+
 public:
   // If IsEH is true, assume it is a .eh_frame section. Otherwise,
   // it is a .debug_frame section. EHFrameAddress should be different
@@ -238,12 +255,17 @@ class DWARFDebugFrame {
   /// frame section contents to be parsed.
   ///
   /// If \p ParseCFIProgram is false, the CFI instruction program of each entry
-  /// is not decoded; each entry still records where its instructions begin (see
-  /// FrameEntry::getCFIStartOffset()) so callers can parse individual programs
-  /// on demand. This avoids materializing every entry's instructions when only
-  /// a subset (or none) is needed, which can be a large memory saving.
+  /// is not decoded; callers can parse individual programs on demand through
+  /// parseCFIProgram(), as long as \p Data stays valid.
   LLVM_ABI Error parse(DWARFDataExtractor Data, bool ParseCFIProgram = true);
 
+  /// Decode the CFI instruction program of \p Entry if parse() was told to
+  /// skip it.
+  LLVM_ABI Error parseCFIProgram(dwarf::FrameEntry &Entry) const;
+
+  /// Decode all the CFI instruction programs that parse() was told to skip.
+  LLVM_ABI Error parseAllCFIPrograms() const;
+
   /// Return whether the section has any entries.
   bool empty() const { return Entries.empty(); }
 
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
index 3cbfb0959e5f1..385cb562e9128 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp
@@ -439,9 +439,20 @@ class ThreadUnsafeDWARFContextState : public DWARFContext::DWARFContextState {
     Line->clearLineTable(stmtOffset);
   }
 
-  Expected<const DWARFDebugFrame *> getDebugFrame() override {
+  /// Return a cached frame section, decoding the CFI instruction programs it
+  /// was parsed without if this caller needs them.
+  static Expected<const DWARFDebugFrame *> useCached(const DWARFDebugFrame &DF,
+                                                     bool ParseCFIProgram) {
+    if (ParseCFIProgram)
+      if (Error E = DF.parseAllCFIPrograms())
+        return std::move(E);
+    return &DF;
+  }
+
+  Expected<const DWARFDebugFrame *>
+  getDebugFrame(bool ParseCFIProgram) override {
     if (DebugFrame)
-      return DebugFrame.get();
+      return useCached(*DebugFrame, ParseCFIProgram);
     const DWARFObject &DObj = D.getDWARFObj();
     const DWARFSection &DS = DObj.getFrameSection();
 
@@ -459,16 +470,16 @@ class ThreadUnsafeDWARFContextState : public DWARFContext::DWARFContextState {
     auto DF =
         std::make_unique<DWARFDebugFrame>(D.getArch(), /*IsEH=*/false,
                                           DS.Address);
-    if (Error E = DF->parse(Data))
+    if (Error E = DF->parse(Data, ParseCFIProgram))
       return std::move(E);
 
     DebugFrame.swap(DF);
     return DebugFrame.get();
   }
 
-  Expected<const DWARFDebugFrame *> getEHFrame() override {
+  Expected<const DWARFDebugFrame *> getEHFrame(bool ParseCFIProgram) override {
     if (EHFrame)
-      return EHFrame.get();
+      return useCached(*EHFrame, ParseCFIProgram);
     const DWARFObject &DObj = D.getDWARFObj();
 
     const DWARFSection &DS = DObj.getEHFrameSection();
@@ -477,7 +488,7 @@ class ThreadUnsafeDWARFContextState : public DWARFContext::DWARFContextState {
     auto DF =
         std::make_unique<DWARFDebugFrame>(D.getArch(), /*IsEH=*/true,
                                           DS.Address);
-    if (Error E = DF->parse(Data))
+    if (Error E = DF->parse(Data, ParseCFIProgram))
       return std::move(E);
     EHFrame.swap(DF);
     return EHFrame.get();
@@ -679,13 +690,14 @@ class ThreadSafeState : public ThreadUnsafeDWARFContextState {
     std::unique_lock<std::recursive_mutex> LockGuard(Mutex);
     return ThreadUnsafeDWARFContextState::clearLineTableForUnit(U);
   }
-  Expected<const DWARFDebugFrame *> getDebugFrame() override {
+  Expected<const DWARFDebugFrame *>
+  getDebugFrame(bool ParseCFIProgram) override {
     std::unique_lock<std::recursive_mutex> LockGuard(Mutex);
-    return ThreadUnsafeDWARFContextState::getDebugFrame();
+    return ThreadUnsafeDWARFContextState::getDebugFrame(ParseCFIProgram);
   }
-  Expected<const DWARFDebugFrame *> getEHFrame() override {
+  Expected<const DWARFDebugFrame *> getEHFrame(bool ParseCFIProgram) override {
     std::unique_lock<std::recursive_mutex> LockGuard(Mutex);
-    return ThreadUnsafeDWARFContextState::getEHFrame();
+    return ThreadUnsafeDWARFContextState::getEHFrame(ParseCFIProgram);
   }
   const DWARFDebugMacro *getDebugMacinfo() override {
     std::unique_lock<std::recursive_mutex> LockGuard(Mutex);
@@ -1116,7 +1128,11 @@ void DWARFContext::dump(
   if (const std::optional<uint64_t> *Off =
           shouldDump(Explicit, ".debug_frame", DIDT_ID_DebugFrame,
                      DObj->getFrameSection().Data)) {
-    if (Expected<const DWARFDebugFrame *> DF = getDebugFrame())
+    // Dumping decodes the instructions of the entries it prints, and only
+    // those, so a corrupt program elsewhere in the section does not keep the
+    // rest of it from being dumped.
+    if (Expected<const DWARFDebugFrame *> DF =
+            getDebugFrame(/*ParseCFIProgram=*/false))
       (*DF)->dump(OS, DumpOpts, *Off);
     else
       RecoverableErrorHandler(DF.takeError());
@@ -1125,7 +1141,8 @@ void DWARFContext::dump(
   if (const std::optional<uint64_t> *Off =
           shouldDump(Explicit, ".eh_frame", DIDT_ID_DebugFrame,
                      DObj->getEHFrameSection().Data)) {
-    if (Expected<const DWARFDebugFrame *> DF = getEHFrame())
+    if (Expected<const DWARFDebugFrame *> DF =
+            getEHFrame(/*ParseCFIProgram=*/false))
       (*DF)->dump(OS, DumpOpts, *Off);
     else
       RecoverableErrorHandler(DF.takeError());
@@ -1459,12 +1476,14 @@ const DWARFDebugAranges *DWARFContext::getDebugAranges() {
   return State->getDebugAranges();
 }
 
-Expected<const DWARFDebugFrame *> DWARFContext::getDebugFrame() {
-  return State->getDebugFrame();
+Expected<const DWARFDebugFrame *>
+DWARFContext::getDebugFrame(bool ParseCFIProgram) {
+  return State->getDebugFrame(ParseCFIProgram);
 }
 
-Expected<const DWARFDebugFrame *> DWARFContext::getEHFrame() {
-  return State->getEHFrame();
+Expected<const DWARFDebugFrame *>
+DWARFContext::getEHFrame(bool ParseCFIProgram) {
+  return State->getEHFrame(ParseCFIProgram);
 }
 
 const DWARFDebugMacro *DWARFContext::getDebugMacro() {
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
index 5c6765894342b..bfb9e69c3f606 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
@@ -203,6 +203,11 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
   uint64_t Offset = 0;
   DenseMap<uint64_t, CIE *> CIEs;
 
+  // Retain the section contents so that the programs left undecoded below can
+  // be parsed on demand later.
+  if (!ParseCFIProgram)
+    this->Data = std::make_unique<DWARFDataExtractor>(Data);
+
   while (Data.isValidOffset(Offset)) {
     uint64_t StartOffset = Offset;
 
@@ -218,6 +223,9 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
       auto Cie = std::make_unique<CIE>(
           IsDWARF64, StartOffset, 0, 0, SmallString<8>(), 0, 0, 0, 0, 0,
           SmallString<8>(), 0, 0, std::nullopt, std::nullopt, Arch);
+      // A terminator has no instructions: it is fully parsed either way.
+      Cie->setCFIStartOffset(Offset);
+      Cie->setCFIProgramParsed();
       CIEs[StartOffset] = Cie.get();
       Entries.push_back(std::move(Cie));
       break;
@@ -385,11 +393,11 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
                                    LSDAAddress, Arch));
     }
 
-    // Optionally skip the CFI instruction program without decoding it.
+    // Record where this entry's CFI instructions begin, so that a program left
+    // undecoded below can be parsed on demand later.
+    Entries.back()->setCFIStartOffset(Offset);
+
     if (!ParseCFIProgram) {
-      // Record where this entry's CFI instructions begin so they can be parsed
-      // on demand later.
-      Entries.back()->setCFIStartOffset(Offset);
       Offset = EndStructureOffset;
       continue;
     }
@@ -397,6 +405,7 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
     if (Error E =
             Entries.back()->cfis().parse(Data, &Offset, EndStructureOffset))
       return E;
+    Entries.back()->setCFIProgramParsed();
 
     if (Offset != EndStructureOffset)
       return createStringError(
@@ -416,16 +425,66 @@ FrameEntry *DWARFDebugFrame::getEntryAtOffset(uint64_t Offset) const {
   return nullptr;
 }
 
+Error DWARFDebugFrame::parseCFIProgram(FrameEntry &Entry) const {
+  if (Entry.isCFIProgramParsed())
+    return Error::success();
+
+  if (!Data)
+    return createStringError(
+        errc::invalid_argument,
+        "cannot parse the instructions of the entry at 0x%" PRIx64
+        " on demand: the section contents were not retained",
+        Entry.getOffset());
+
+  uint64_t Offset = Entry.getCFIStartOffset();
+  const uint64_t EndOffset = Entry.getEndOffset();
+  assert(Offset >= Entry.getOffset() && Offset <= EndOffset &&
+         "entry does not know where its instructions begin");
+  DWARFDataExtractor EntryData = *Data;
+  Entry.setCFIProgramParsed();
+  if (Error E = Entry.cfis().parse(EntryData, &Offset, EndOffset))
+    return E;
+
+  if (Offset != EndOffset)
+    return createStringError(errc::invalid_argument,
+                             "parsing entry instructions at 0x%" PRIx64
+                             " failed",
+                             Entry.getOffset());
+
+  return Error::success();
+}
+
+Error DWARFDebugFrame::parseAllCFIPrograms() const {
+  for (const auto &Entry : Entries)
+    if (Error E = parseCFIProgram(*Entry))
+      return E;
+  return Error::success();
+}
+
+void DWARFDebugFrame::dumpEntry(FrameEntry &Entry, raw_ostream &OS,
+                                DIDumpOptions DumpOpts) const {
+  if (const auto *Fde = dyn_cast<FDE>(&Entry))
+    if (const CIE *Cie = Fde->getLinkedCIE())
+      if (FrameEntry *CieEntry = getEntryAtOffset(Cie->getOffset()))
+        if (Error E = parseCFIProgram(*CieEntry))
+          DumpOpts.RecoverableErrorHandler(std::move(E));
+
+  if (Error E = parseCFIProgram(Entry))
+    DumpOpts.RecoverableErrorHandler(std::move(E));
+
+  Entry.dump(OS, DumpOpts);
+}
+
 void DWARFDebugFrame::dump(raw_ostream &OS, DIDumpOptions DumpOpts,
                            std::optional<uint64_t> Offset) const {
   DumpOpts.IsEH = IsEH;
   if (Offset) {
     if (auto *Entry = getEntryAtOffset(*Offset))
-      Entry->dump(OS, DumpOpts);
+      dumpEntry(*Entry, OS, DumpOpts);
     return;
   }
 
   OS << "\n";
   for (const auto &Entry : Entries)
-    Entry->dump(OS, DumpOpts);
+    dumpEntry(*Entry, OS, DumpOpts);
 }
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/debug_frame_invalid_cfi_program.s b/llvm/test/tools/llvm-dwarfdump/X86/debug_frame_invalid_cfi_program.s
new file mode 100644
index 0000000000000..24d103802cc2b
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfdump/X86/debug_frame_invalid_cfi_program.s
@@ -0,0 +1,79 @@
+## A .debug_frame section whose second FDE has an invalid CFI instruction
+## program.
+# RUN: llvm-mc -triple x86_64-unknown-linux-gnu %s -filetype=obj -o %t.o
+
+## Test that dumping a single (valid) entry does not touch the invalid program
+## at all: the dump succeeds and nothing is reported.
+# RUN: llvm-dwarfdump --debug-frame=0x4c %t.o 2>%t.one.err | FileCheck %s --check-prefix=ONE
+# RUN: count 0 < %t.one.err
+
+# ONE:       .debug_frame contents:
+# ONE-NEXT:  0000004c 00000018 00000000 FDE cie=00000000 pc=00003000...00003020
+# ONE-NEXT:    Format:       DWARF32
+# ONE-NEXT:    DW_CFA_advance_loc: 1 to 0x3001
+# ONE-NEXT:    DW_CFA_def_cfa_offset: +24
+# ONE-NEXT:    DW_CFA_nop:
+
+## Dumping the whole section reports the invalid program of the entry at 0x30
+## and keeps dumping the entries around it.
+# RUN: not llvm-dwarfdump --debug-frame %t.o 2>%t.all.err | FileCheck %s --check-prefix=ALL
+# RUN: FileCheck %s --check-prefix=ERR --input-file=%t.all.err
+
+# ALL:       .debug_frame contents:
+# ALL:       00000000 00000010 ffffffff CIE
+# ALL:       00000014 00000018 00000000 FDE cie=00000000 pc=00001000...00001020
+# ALL:         DW_CFA_def_cfa_offset: +16
+## The entry with the invalid program is still listed, with the instructions
+## that could be decoded before the invalid opcode.
+# ALL:       00000030 00000018 00000000 FDE cie=00000000 pc=00002000...00002020
+# ALL:       0000004c 00000018 00000000 FDE cie=00000000 pc=00003000...00003020
+# ALL:         DW_CFA_def_cfa_offset: +24
+
+# ERR:       error: invalid extended CFI opcode 0x1a
+
+	.section	.debug_frame,"", at progbits
+.Lcie:
+	.long	.Lcie_end-.Lcie_start   # Length
+.Lcie_start:
+	.long	0xffffffff              # CIE id
+	.byte	1                       # Version
+	.byte	0                       # Augmentation string
+	.byte	1                       # Code alignment factor
+	.byte	0x78                    # Data alignment factor (-8)
+	.byte	16                      # Return address register
+	.byte	0x0c, 0x07, 0x08        # DW_CFA_def_cfa reg7 +8
+	.byte	0x90, 0x01              # DW_CFA_offset reg16 -8
+	.byte	0, 0                    # DW_CFA_nop
+.Lcie_end:
+
+## An FDE with a valid CFI program.
+	.long	.Lfde0_end-.Lfde0_start # Length
+.Lfde0_start:
+	.long	.Lcie                   # CIE pointer
+	.quad	0x1000                  # Initial location
+	.quad	0x20                    # Address range
+	.byte	0x41                    # DW_CFA_advance_loc 1
+	.byte	0x0e, 0x10              # DW_CFA_def_cfa_offset +16
+	.byte	0                       # DW_CFA_nop
+.Lfde0_end:
+
+## An FDE whose CFI program uses an opcode that does not exist.
+	.long	.Lfde1_end-.Lfde1_start # Length
+.Lfde1_start:
+	.long	.Lcie                   # CIE pointer
+	.quad	0x2000                  # Initial location
+	.quad	0x20                    # Address range
+	.byte	0x1a                    # Invalid extended opcode
+	.byte	0, 0, 0                 # DW_CFA_nop
+.Lfde1_end:
+
+## Another FDE with a valid CFI program.
+	.long	.Lfde2_end-.Lfde2_start # Length
+.Lfde2_start:
+	.long	.Lcie                   # CIE pointer
+	.quad	0x3000                  # Initial location
+	.quad	0x20                    # Address range
+	.byte	0x41                    # DW_CFA_advance_loc 1
+	.byte	0x0e, 0x18              # DW_CFA_def_cfa_offset +24
+	.byte	0                       # DW_CFA_nop
+.Lfde2_end:

>From aa190a6acd6d48f3999828c5d5f25e072d28df53 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Tue, 18 Aug 2026 15:17:09 -0700
Subject: [PATCH 3/4] Make Data optional

---
 llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h | 11 ++++++-----
 llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp        |  2 +-
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
index 21179746491b0..1e94793bf630a 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
@@ -11,6 +11,7 @@
 
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/iterator.h"
+#include "llvm/DebugInfo/DWARF/DWARFDataExtractor.h"
 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h"
 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
@@ -18,12 +19,12 @@
 #include "llvm/Support/Error.h"
 #include "llvm/TargetParser/Triple.h"
 #include <memory>
+#include <optional>
 #include <vector>
 
 namespace llvm {
 
 class raw_ostream;
-class DWARFDataExtractor;
 class MCRegisterInfo;
 struct DIDumpOptions;
 
@@ -100,6 +101,9 @@ class FrameEntry {
 
   const bool IsDWARF64;
 
+  /// Whether CFIs holds the decoded CFI instruction program.
+  bool CFIsParsed = false;
+
   /// Offset of this entry in the section.
   const uint64_t Offset;
 
@@ -109,9 +113,6 @@ class FrameEntry {
   /// Section offset at which this entry's CFI instructions begin.
   uint64_t CFIStartOffset = 0;
 
-  /// Whether CFIs holds the decoded CFI instruction program.
-  bool CFIsParsed = false;
-
   CFIProgram CFIs;
 };
 
@@ -228,7 +229,7 @@ class DWARFDebugFrame {
 
   /// Section contents, retained by parse() when it was asked to leave the CFI
   /// instruction programs undecoded, so that they can be parsed on demand.
-  std::unique_ptr<DWARFDataExtractor> Data;
+  std::optional<DWARFDataExtractor> Data;
 
   /// Return the entry at the given offset or nullptr.
   dwarf::FrameEntry *getEntryAtOffset(uint64_t Offset) const;
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
index bfb9e69c3f606..8b16090b94d8c 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
@@ -206,7 +206,7 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
   // Retain the section contents so that the programs left undecoded below can
   // be parsed on demand later.
   if (!ParseCFIProgram)
-    this->Data = std::make_unique<DWARFDataExtractor>(Data);
+    this->Data = Data;
 
   while (Data.isValidOffset(Offset)) {
     uint64_t StartOffset = Offset;

>From bae46aed968bee87c2dc9ad9b27176f9bdbeb334 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Wed, 26 Aug 2026 18:04:05 -0700
Subject: [PATCH 4/4] Bundle the lazy parsing state of an entry into a
 std::optional

---
 .../llvm/DebugInfo/DWARF/DWARFDebugFrame.h    | 31 ++++++++-----------
 .../DWARF/LowLevel/DWARFCFIProgram.h          |  5 +++
 llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp  | 23 +++++++-------
 3 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
index 1e94793bf630a..cb79732ad1315 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFDebugFrame.h
@@ -73,19 +73,17 @@ class FrameEntry {
   const CFIProgram &cfis() const { return CFIs; }
   CFIProgram &cfis() { return CFIs; }
 
-  /// Section offset at which this entry's CFI instructions begin, recorded so a
-  /// program left undecoded can be parsed on demand later via
-  /// cfis().parse(Data, ..., getEndOffset()).
-  uint64_t getCFIStartOffset() const { return CFIStartOffset; }
-  void setCFIStartOffset(uint64_t O) { CFIStartOffset = O; }
-
-  /// Whether this entry's CFI instruction program has been decoded. False for
-  /// an entry whose program parse() was told to skip.
-  bool isCFIProgramParsed() const { return CFIsParsed; }
-  void setCFIProgramParsed() { CFIsParsed = true; }
-
-  /// Section offset one past the end of this entry (exclusive end of its CFI
-  /// instructions).
+  /// If using lazily parsed CFIs, this returns the section offset where the
+  /// unparsed CFIs start so user can parse them on-demand through the
+  /// CFIProgram parsing interface cfis().parse(Data, ..., getEndOffset()).
+  /// This returns std::nullopt if CFIs are already succesfully parsed.
+  std::optional<uint64_t> getUnparsedCFIStartOffset() const {
+    return UnparsedCFIStartOffset;
+  }
+  void markCFIProgramUnparsed(uint64_t StartOffset) {
+    UnparsedCFIStartOffset = StartOffset;
+  }
+  void markCFIProgramParsed() { UnparsedCFIStartOffset = std::nullopt; }
   uint64_t getEndOffset() const {
     // End is Offset plus the size of the initial length field plus Length.
     // The initial length field is 4 bytes in DWARF32 and 12 bytes in DWARF64
@@ -101,17 +99,14 @@ class FrameEntry {
 
   const bool IsDWARF64;
 
-  /// Whether CFIs holds the decoded CFI instruction program.
-  bool CFIsParsed = false;
-
   /// Offset of this entry in the section.
   const uint64_t Offset;
 
   /// Entry length as specified in DWARF.
   const uint64_t Length;
 
-  /// Section offset at which this entry's CFI instructions begin.
-  uint64_t CFIStartOffset = 0;
+  /// Offset for lazy parsing; std::nullopt if CFIs are already parsed.
+  std::optional<uint64_t> UnparsedCFIStartOffset;
 
   CFIProgram CFIs;
 };
diff --git a/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h b/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h
index 3500d5dd9d168..a0160c2b6eca8 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h
@@ -63,6 +63,11 @@ class CFIProgram {
 
   unsigned size() const { return (unsigned)Instructions.size(); }
   bool empty() const { return Instructions.empty(); }
+
+  /// Discard the instructions decoded so far, e.g. the partial program left
+  /// behind by a parse() that failed, before decoding it again.
+  void clear() { Instructions.clear(); }
+
   uint64_t codeAlign() const { return CodeAlignmentFactor; }
   int64_t dataAlign() const { return DataAlignmentFactor; }
   Triple::ArchType triple() const { return Arch; }
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
index 8b16090b94d8c..be4d2f2fd34d3 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFDebugFrame.cpp
@@ -223,9 +223,6 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
       auto Cie = std::make_unique<CIE>(
           IsDWARF64, StartOffset, 0, 0, SmallString<8>(), 0, 0, 0, 0, 0,
           SmallString<8>(), 0, 0, std::nullopt, std::nullopt, Arch);
-      // A terminator has no instructions: it is fully parsed either way.
-      Cie->setCFIStartOffset(Offset);
-      Cie->setCFIProgramParsed();
       CIEs[StartOffset] = Cie.get();
       Entries.push_back(std::move(Cie));
       break;
@@ -393,11 +390,10 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
                                    LSDAAddress, Arch));
     }
 
-    // Record where this entry's CFI instructions begin, so that a program left
-    // undecoded below can be parsed on demand later.
-    Entries.back()->setCFIStartOffset(Offset);
-
     if (!ParseCFIProgram) {
+      // Record where this entry's CFI instructions begin, so that the program
+      // left undecoded here can be parsed on demand later.
+      Entries.back()->markCFIProgramUnparsed(Offset);
       Offset = EndStructureOffset;
       continue;
     }
@@ -405,7 +401,6 @@ Error DWARFDebugFrame::parse(DWARFDataExtractor Data, bool ParseCFIProgram) {
     if (Error E =
             Entries.back()->cfis().parse(Data, &Offset, EndStructureOffset))
       return E;
-    Entries.back()->setCFIProgramParsed();
 
     if (Offset != EndStructureOffset)
       return createStringError(
@@ -426,7 +421,8 @@ FrameEntry *DWARFDebugFrame::getEntryAtOffset(uint64_t Offset) const {
 }
 
 Error DWARFDebugFrame::parseCFIProgram(FrameEntry &Entry) const {
-  if (Entry.isCFIProgramParsed())
+  std::optional<uint64_t> StartOffset = Entry.getUnparsedCFIStartOffset();
+  if (!StartOffset)
     return Error::success();
 
   if (!Data)
@@ -436,12 +432,13 @@ Error DWARFDebugFrame::parseCFIProgram(FrameEntry &Entry) const {
         " on demand: the section contents were not retained",
         Entry.getOffset());
 
-  uint64_t Offset = Entry.getCFIStartOffset();
-  const uint64_t EndOffset = Entry.getEndOffset();
+  uint64_t Offset = *StartOffset;
+  uint64_t EndOffset = Entry.getEndOffset();
   assert(Offset >= Entry.getOffset() && Offset <= EndOffset &&
          "entry does not know where its instructions begin");
   DWARFDataExtractor EntryData = *Data;
-  Entry.setCFIProgramParsed();
+  // Clear previous unsuccessful parsing attempts, if any.
+  Entry.cfis().clear();
   if (Error E = Entry.cfis().parse(EntryData, &Offset, EndOffset))
     return E;
 
@@ -451,6 +448,8 @@ Error DWARFDebugFrame::parseCFIProgram(FrameEntry &Entry) const {
                              " failed",
                              Entry.getOffset());
 
+  // Signal we have a valid fully parsed CFI program.
+  Entry.markCFIProgramParsed();
   return Error::success();
 }
 



More information about the llvm-commits mailing list