[llvm] [MC][debug_frame] Fix a bug in MCDwarfFrameEmitter::emit() so that per-function CIE can be generated when CIEs are different (PR #192727)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 20:16:08 PDT 2026
https://github.com/jaewookshin701 updated https://github.com/llvm/llvm-project/pull/192727
>From 3a0fe5513f19bc6bc5824255e2a2f4823d29ca57 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Fri, 17 Apr 2026 19:36:38 +0000
Subject: [PATCH 1/4] Add a support for per function CIE
---
llvm/include/llvm/MC/MCAsmInfo.h | 9 +++++++++
llvm/lib/MC/MCDwarf.cpp | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h
index 1e55bef20f2e8..8fc99072bde61 100644
--- a/llvm/include/llvm/MC/MCAsmInfo.h
+++ b/llvm/include/llvm/MC/MCAsmInfo.h
@@ -383,6 +383,12 @@ class LLVM_ABI MCAsmInfo {
/// names in .cfi_* directives. Defaults to false.
bool DwarfRegNumForCFI = false;
+ /// When true, emit a new CIE in .debug_frame whenever the CIE key changes
+ /// (e.g. different return-address register per function). The default LLVM
+ /// behaviour reuses a single CIE for all .debug_frame FDEs; targets that
+ /// need per-function CIE variation must opt in.
+ bool UsePerFunctionDebugFrameCIE = false;
+
/// True if target uses @ (expr at specifier) for relocation specifiers.
bool UseAtForSpecifier = false;
@@ -674,6 +680,9 @@ class LLVM_ABI MCAsmInfo {
bool doDwarfFDESymbolsUseAbsDiff() const { return DwarfFDESymbolsUseAbsDiff; }
bool useDwarfRegNumForCFI() const { return DwarfRegNumForCFI; }
+ bool usePerFunctionDebugFrameCIE() const {
+ return UsePerFunctionDebugFrameCIE;
+ }
bool useAtForSpecifier() const { return UseAtForSpecifier; }
bool useParensForSpecifier() const { return UseParensForSpecifier; }
bool supportsExtendedDwarfLocDirective() const {
diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp
index f24e3fb03433f..9cc90f6c5dbf3 100644
--- a/llvm/lib/MC/MCDwarf.cpp
+++ b/llvm/lib/MC/MCDwarf.cpp
@@ -1979,7 +1979,8 @@ void MCDwarfFrameEmitter::emit(MCObjectStreamer &Streamer, bool IsEH) {
continue;
CIEKey Key(Frame);
- if (!LastCIEStart || (IsEH && Key != LastKey)) {
+ if (!LastCIEStart ||
+ (Key != LastKey && (IsEH || AsmInfo->usePerFunctionDebugFrameCIE()))) {
LastKey = Key;
LastCIEStart = &Emitter.EmitCIE(Frame);
}
>From 3577405722fd6f3e236d0a8fd3a405c593bf46f2 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 00:50:17 +0000
Subject: [PATCH 2/4] Change to a bug fix in MCDwarfFrameEmitter::emit
---
llvm/include/llvm/MC/MCAsmInfo.h | 9 ---------
llvm/lib/MC/MCDwarf.cpp | 3 +--
2 files changed, 1 insertion(+), 11 deletions(-)
diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h
index 8fc99072bde61..1e55bef20f2e8 100644
--- a/llvm/include/llvm/MC/MCAsmInfo.h
+++ b/llvm/include/llvm/MC/MCAsmInfo.h
@@ -383,12 +383,6 @@ class LLVM_ABI MCAsmInfo {
/// names in .cfi_* directives. Defaults to false.
bool DwarfRegNumForCFI = false;
- /// When true, emit a new CIE in .debug_frame whenever the CIE key changes
- /// (e.g. different return-address register per function). The default LLVM
- /// behaviour reuses a single CIE for all .debug_frame FDEs; targets that
- /// need per-function CIE variation must opt in.
- bool UsePerFunctionDebugFrameCIE = false;
-
/// True if target uses @ (expr at specifier) for relocation specifiers.
bool UseAtForSpecifier = false;
@@ -680,9 +674,6 @@ class LLVM_ABI MCAsmInfo {
bool doDwarfFDESymbolsUseAbsDiff() const { return DwarfFDESymbolsUseAbsDiff; }
bool useDwarfRegNumForCFI() const { return DwarfRegNumForCFI; }
- bool usePerFunctionDebugFrameCIE() const {
- return UsePerFunctionDebugFrameCIE;
- }
bool useAtForSpecifier() const { return UseAtForSpecifier; }
bool useParensForSpecifier() const { return UseParensForSpecifier; }
bool supportsExtendedDwarfLocDirective() const {
diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp
index 9cc90f6c5dbf3..9c313fb2930d7 100644
--- a/llvm/lib/MC/MCDwarf.cpp
+++ b/llvm/lib/MC/MCDwarf.cpp
@@ -1979,8 +1979,7 @@ void MCDwarfFrameEmitter::emit(MCObjectStreamer &Streamer, bool IsEH) {
continue;
CIEKey Key(Frame);
- if (!LastCIEStart ||
- (Key != LastKey && (IsEH || AsmInfo->usePerFunctionDebugFrameCIE()))) {
+ if (!LastCIEStart || Key != LastKey) {
LastKey = Key;
LastCIEStart = &Emitter.EmitCIE(Frame);
}
>From 57ef40d4861727d3ec7ea683c06a2373d777ec01 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 01:25:28 +0000
Subject: [PATCH 3/4] Add a test case for the bug fix in MCDwarf.cpp
---
llvm/unittests/MC/CMakeLists.txt | 2 +
llvm/unittests/MC/DwarfDebugFrameCIE.cpp | 253 +++++++++++++++++++++++
2 files changed, 255 insertions(+)
create mode 100644 llvm/unittests/MC/DwarfDebugFrameCIE.cpp
diff --git a/llvm/unittests/MC/CMakeLists.txt b/llvm/unittests/MC/CMakeLists.txt
index da8e219113f46..4881888f03742 100644
--- a/llvm/unittests/MC/CMakeLists.txt
+++ b/llvm/unittests/MC/CMakeLists.txt
@@ -6,6 +6,7 @@ endforeach()
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
+ DebugInfoDWARF
MC
MCDisassembler
Object
@@ -15,6 +16,7 @@ set(LLVM_LINK_COMPONENTS
add_llvm_unittest(MCTests
Disassembler.cpp
+ DwarfDebugFrameCIE.cpp
DwarfLineTables.cpp
DwarfLineTableHeaders.cpp
MCInstPrinter.cpp
diff --git a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
new file mode 100644
index 0000000000000..08446c0fc2c7d
--- /dev/null
+++ b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
@@ -0,0 +1,253 @@
+//===- llvm/unittest/MC/DwarfDebugFrameCIE.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Verify that .debug_frame emits distinct CIEs when frame parameters (e.g.
+/// the return-address register) differ between functions. This is a
+/// regression test for a bug where only the first CIE was emitted for
+/// .debug_frame, causing all FDEs to silently reuse that CIE's
+/// return-address register regardless of per-function overrides via
+/// emitCFIReturnColumn.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/DebugInfo/DWARF/DWARFDataExtractor.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugFrame.h"
+#include "llvm/MC/MCAsmBackend.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCCodeEmitter.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCDwarf.h"
+#include "llvm/MC/MCInstrInfo.h"
+#include "llvm/MC/MCObjectFileInfo.h"
+#include "llvm/MC/MCObjectStreamer.h"
+#include "llvm/MC/MCObjectWriter.h"
+#include "llvm/MC/MCRegisterInfo.h"
+#include "llvm/MC/MCStreamer.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCTargetOptions.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/TargetSelect.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+class DwarfDebugFrameCIE : public ::testing::Test {
+public:
+ static constexpr const char *TripleName = "x86_64-pc-linux";
+ Triple TT;
+ std::unique_ptr<MCRegisterInfo> MRI;
+ std::unique_ptr<MCAsmInfo> MAI;
+ std::unique_ptr<const MCSubtargetInfo> STI;
+ MCTargetOptions MCOptions;
+ const Target *TheTarget = nullptr;
+
+ struct StreamerContext {
+ std::unique_ptr<MCObjectFileInfo> MOFI;
+ std::unique_ptr<MCContext> Ctx;
+ std::unique_ptr<const MCInstrInfo> MII;
+ std::unique_ptr<MCStreamer> Streamer;
+ };
+
+ DwarfDebugFrameCIE() : TT(TripleName) {
+ llvm::InitializeAllTargetInfos();
+ llvm::InitializeAllTargetMCs();
+ llvm::InitializeAllDisassemblers();
+
+ std::string Error;
+ TheTarget = TargetRegistry::lookupTarget(TT, Error);
+ if (!TheTarget)
+ return;
+
+ MRI.reset(TheTarget->createMCRegInfo(TT));
+ MAI.reset(TheTarget->createMCAsmInfo(*MRI, TT, MCOptions));
+ STI.reset(TheTarget->createMCSubtargetInfo(TT, "", ""));
+ }
+
+ StreamerContext createStreamer(raw_pwrite_stream &OS) {
+ StreamerContext Res;
+ Res.Ctx = std::make_unique<MCContext>(TT, MAI.get(), MRI.get(),
+ /*MSTI=*/nullptr);
+ Res.MOFI.reset(
+ TheTarget->createMCObjectFileInfo(*Res.Ctx, /*PIC=*/false));
+ Res.Ctx->setObjectFileInfo(Res.MOFI.get());
+
+ Res.MII.reset(TheTarget->createMCInstrInfo());
+ MCCodeEmitter *MCE =
+ TheTarget->createMCCodeEmitter(*Res.MII, *Res.Ctx);
+ MCAsmBackend *MAB =
+ TheTarget->createMCAsmBackend(*STI, *MRI, MCTargetOptions());
+ std::unique_ptr<MCObjectWriter> OW = MAB->createObjectWriter(OS);
+ Res.Streamer.reset(TheTarget->createMCObjectStreamer(
+ TT, *Res.Ctx, std::unique_ptr<MCAsmBackend>(MAB), std::move(OW),
+ std::unique_ptr<MCCodeEmitter>(MCE), *STI));
+ return Res;
+ }
+
+ /// Enable .debug_frame emission (instead of the default .eh_frame).
+ void enableDebugFrame(StreamerContext &C) {
+ C.Streamer->emitCFISections(/*EH=*/false, /*Debug=*/true, /*SFrame=*/false);
+ }
+
+ /// Emit a mock function with the given return-address register in its CIE.
+ void emitFunction(StreamerContext &C, StringRef Name, unsigned RAReg) {
+ MCStreamer *S = C.Streamer.get();
+ MCContext &Ctx = *C.Ctx;
+
+ MCSection *TextSec = C.MOFI->getTextSection();
+ TextSec->setHasInstructions(true);
+ S->switchSection(TextSec);
+
+ MCSymbol *FuncSym = Ctx.getOrCreateSymbol(Name);
+ S->emitLabel(FuncSym);
+
+ S->emitCFIStartProc(/*IsSimple=*/true);
+ S->emitCFIReturnColumn(RAReg);
+ S->emitNops(4, 1, SMLoc(), *STI);
+ S->emitCFIEndProc();
+ }
+};
+
+/// Test that two functions with different return-address registers produce
+/// two distinct CIEs in .debug_frame.
+TEST_F(DwarfDebugFrameCIE, DistinctReturnColumnsGetDistinctCIEs) {
+ if (!TheTarget)
+ GTEST_SKIP();
+
+ SmallString<0> ObjContents;
+ raw_svector_ostream VecOS(ObjContents);
+ StreamerContext C = createStreamer(VecOS);
+
+ C.Streamer->initSections(*STI);
+ enableDebugFrame(C);
+
+ // Function A: return-address register = 16 (x86_64 RA)
+ emitFunction(C, "funcA", /*RAReg=*/16);
+ // Function B: return-address register = 0 (different)
+ emitFunction(C, "funcB", /*RAReg=*/0);
+
+ C.Streamer->finish();
+
+ // Parse the emitted ELF and find .debug_frame.
+ std::unique_ptr<MemoryBuffer> MB =
+ MemoryBuffer::getMemBuffer(ObjContents.str(), "", /*RequiresNullTerminator=*/false);
+ auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef());
+ ASSERT_TRUE(static_cast<bool>(BinOrErr));
+ auto *ELF = dyn_cast<llvm::object::ELFObjectFileBase>(&**BinOrErr);
+ ASSERT_NE(ELF, nullptr);
+
+ // Extract .debug_frame section contents.
+ StringRef FrameContents;
+ bool Found = false;
+ for (const auto &Section : ELF->sections()) {
+ Expected<StringRef> NameOrErr = Section.getName();
+ ASSERT_TRUE(static_cast<bool>(NameOrErr));
+ if (*NameOrErr == ".debug_frame") {
+ Expected<StringRef> ContentsOrErr = Section.getContents();
+ ASSERT_TRUE(static_cast<bool>(ContentsOrErr));
+ FrameContents = *ContentsOrErr;
+ Found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(Found) << ".debug_frame section not found";
+ ASSERT_FALSE(FrameContents.empty());
+
+ // Parse the .debug_frame section using DWARFDebugFrame.
+ DWARFDataExtractor Data(FrameContents, /*isLittleEndian=*/true,
+ /*AddressSize=*/8);
+ DWARFDebugFrame DebugFrame(Triple::x86_64, /*IsEH=*/false);
+ Error Err = DebugFrame.parse(Data);
+ ASSERT_FALSE(static_cast<bool>(Err)) << toString(std::move(Err));
+
+ // Collect CIEs and their return-address registers.
+ SmallVector<uint64_t, 4> CIEReturnRegs;
+ unsigned FDECount = 0;
+ for (const auto &Entry : DebugFrame) {
+ if (const auto *CIEp = dyn_cast<dwarf::CIE>(&Entry))
+ CIEReturnRegs.push_back(CIEp->getReturnAddressRegister());
+ else if (isa<dwarf::FDE>(Entry))
+ ++FDECount;
+ }
+
+ // We emitted two functions, so expect two FDEs.
+ EXPECT_EQ(FDECount, 2u);
+ // The two functions use different return-address registers, so there must
+ // be two distinct CIEs.
+ ASSERT_EQ(CIEReturnRegs.size(), 2u);
+ EXPECT_NE(CIEReturnRegs[0], CIEReturnRegs[1]);
+ // Verify both registers are present (order depends on CIEKey sort).
+ llvm::sort(CIEReturnRegs);
+ EXPECT_EQ(CIEReturnRegs[0], 0u);
+ EXPECT_EQ(CIEReturnRegs[1], 16u);
+}
+
+/// Test that two functions with the same return-address register share a
+/// single CIE (deduplication still works).
+TEST_F(DwarfDebugFrameCIE, SameReturnColumnsShareCIE) {
+ if (!TheTarget)
+ GTEST_SKIP();
+
+ SmallString<0> ObjContents;
+ raw_svector_ostream VecOS(ObjContents);
+ StreamerContext C = createStreamer(VecOS);
+
+ C.Streamer->initSections(*STI);
+ enableDebugFrame(C);
+
+ emitFunction(C, "funcC", /*RAReg=*/16);
+ emitFunction(C, "funcD", /*RAReg=*/16);
+
+ C.Streamer->finish();
+
+ std::unique_ptr<MemoryBuffer> MB =
+ MemoryBuffer::getMemBuffer(ObjContents.str(), "", false);
+ auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef());
+ ASSERT_TRUE(static_cast<bool>(BinOrErr));
+ auto *ELF = dyn_cast<llvm::object::ELFObjectFileBase>(&**BinOrErr);
+ ASSERT_NE(ELF, nullptr);
+
+ StringRef FrameContents;
+ bool Found = false;
+ for (const auto &Section : ELF->sections()) {
+ Expected<StringRef> NameOrErr = Section.getName();
+ ASSERT_TRUE(static_cast<bool>(NameOrErr));
+ if (*NameOrErr == ".debug_frame") {
+ Expected<StringRef> ContentsOrErr = Section.getContents();
+ ASSERT_TRUE(static_cast<bool>(ContentsOrErr));
+ FrameContents = *ContentsOrErr;
+ Found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(Found) << ".debug_frame section not found";
+
+ DWARFDataExtractor Data(FrameContents, true, 8);
+ DWARFDebugFrame DebugFrame(Triple::x86_64, /*IsEH=*/false);
+ Error Err = DebugFrame.parse(Data);
+ ASSERT_FALSE(static_cast<bool>(Err)) << toString(std::move(Err));
+
+ unsigned CIECount = 0;
+ unsigned FDECount = 0;
+ for (const auto &Entry : DebugFrame) {
+ if (isa<dwarf::CIE>(Entry))
+ ++CIECount;
+ else if (isa<dwarf::FDE>(Entry))
+ ++FDECount;
+ }
+
+ EXPECT_EQ(FDECount, 2u);
+ // Same return-address register → single shared CIE.
+ EXPECT_EQ(CIECount, 1u);
+}
+
+} // namespace
>From 10703f2cad19c7126dc172038bd14f689c5d5f35 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 03:13:45 +0000
Subject: [PATCH 4/4] changing type from auto to dwarf::FrameEntry
---
llvm/unittests/MC/DwarfDebugFrameCIE.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
index 08446c0fc2c7d..d57e336ec77b5 100644
--- a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
+++ b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
@@ -1,4 +1,4 @@
-//===- llvm/unittest/MC/DwarfDebugFrameCIE.cpp ----------------------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -172,7 +172,7 @@ TEST_F(DwarfDebugFrameCIE, DistinctReturnColumnsGetDistinctCIEs) {
// Collect CIEs and their return-address registers.
SmallVector<uint64_t, 4> CIEReturnRegs;
unsigned FDECount = 0;
- for (const auto &Entry : DebugFrame) {
+ for (const dwarf::FrameEntry &Entry : DebugFrame) {
if (const auto *CIEp = dyn_cast<dwarf::CIE>(&Entry))
CIEReturnRegs.push_back(CIEp->getReturnAddressRegister());
else if (isa<dwarf::FDE>(Entry))
@@ -238,7 +238,7 @@ TEST_F(DwarfDebugFrameCIE, SameReturnColumnsShareCIE) {
unsigned CIECount = 0;
unsigned FDECount = 0;
- for (const auto &Entry : DebugFrame) {
+ for (const dwarf::FrameEntry &Entry : DebugFrame) {
if (isa<dwarf::CIE>(Entry))
++CIECount;
else if (isa<dwarf::FDE>(Entry))
More information about the llvm-commits
mailing list