//===----------------------------------------------------------------------===// // // 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 MRI; std::unique_ptr MAI; std::unique_ptr STI; MCTargetOptions MCOptions; const Target *TheTarget = nullptr; struct StreamerContext { std::unique_ptr MOFI; std::unique_ptr Ctx; std::unique_ptr MII; std::unique_ptr 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(TT, *MAI, *MRI, *STI); 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 OW = MAB->createObjectWriter(OS); Res.Streamer.reset(TheTarget->createMCObjectStreamer( TT, *Res.Ctx, std::unique_ptr(MAB), std::move(OW), std::unique_ptr(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 MB = MemoryBuffer::getMemBuffer( ObjContents.str(), "", /*RequiresNullTerminator=*/false); auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef()); ASSERT_TRUE(static_cast(BinOrErr)); auto *ELF = dyn_cast(&**BinOrErr); ASSERT_NE(ELF, nullptr); // Extract .debug_frame section contents. StringRef FrameContents; bool Found = false; for (const object::SectionRef &Section : ELF->sections()) { Expected NameOrErr = Section.getName(); ASSERT_TRUE(static_cast(NameOrErr)); if (*NameOrErr == ".debug_frame") { Expected ContentsOrErr = Section.getContents(); ASSERT_TRUE(static_cast(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(Err)) << toString(std::move(Err)); // Collect CIEs and their return-address registers. SmallVector CIEReturnRegs; unsigned FDECount = 0; for (const dwarf::FrameEntry &Entry : DebugFrame) { if (const auto *CIEp = dyn_cast(&Entry)) CIEReturnRegs.push_back(CIEp->getReturnAddressRegister()); else if (isa(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 MB = MemoryBuffer::getMemBuffer(ObjContents.str(), "", false); auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef()); ASSERT_TRUE(static_cast(BinOrErr)); auto *ELF = dyn_cast(&**BinOrErr); ASSERT_NE(ELF, nullptr); StringRef FrameContents; bool Found = false; for (const object::SectionRef &Section : ELF->sections()) { Expected NameOrErr = Section.getName(); ASSERT_TRUE(static_cast(NameOrErr)); if (*NameOrErr == ".debug_frame") { Expected ContentsOrErr = Section.getContents(); ASSERT_TRUE(static_cast(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(Err)) << toString(std::move(Err)); unsigned CIECount = 0; unsigned FDECount = 0; for (const dwarf::FrameEntry &Entry : DebugFrame) { if (isa(Entry)) ++CIECount; else if (isa(Entry)) ++FDECount; } EXPECT_EQ(FDECount, 2u); // Same return-address register -> single shared CIE. EXPECT_EQ(CIECount, 1u); } } // namespace