//===- llvm/unittest/MC/DwarfLineTableHeaders.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 // //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCAssembler.h" #include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDwarf.h" #include "llvm/MC/MCInstrInfo.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/Binary.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "gtest/gtest.h" using namespace llvm; namespace { class DwarfLineTableHeaders : public ::testing::Test { public: const char *TripleName = "x86_64-pc-linux"; std::unique_ptr MRI; std::unique_ptr MAI; std::unique_ptr STI; const Target *TheTarget; struct StreamerContext { std::unique_ptr MOFI; std::unique_ptr Ctx; std::unique_ptr MII; std::unique_ptr Streamer; }; DwarfLineTableHeaders() { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllDisassemblers(); // If we didn't build x86, do not run the test. std::string Error; TheTarget = TargetRegistry::lookupTarget(TripleName, Error); if (!TheTarget) return; MRI.reset(TheTarget->createMCRegInfo(TripleName)); MCTargetOptions MCOptions; MAI.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); STI.reset(TheTarget->createMCSubtargetInfo(TripleName, "", "")); } /// Create all data structures necessary to operate an assembler StreamerContext createStreamer(raw_pwrite_stream &OS) { StreamerContext Res; Res.Ctx = std::make_unique(Triple(TripleName), MAI.get(), MRI.get(), /*MSTI=*/nullptr); Res.MOFI.reset(TheTarget->createMCObjectFileInfo(*Res.Ctx.get(), /*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( Triple(TripleName), *Res.Ctx, std::unique_ptr(MAB), std::move(OW), std::unique_ptr(MCE), *STI, /* RelaxAll */ false, /* IncrementalLinkerCompatible */ false, /* DWARFMustBeAtTheEnd */ false)); return Res; } /// Emit a .debug_line section with the given context parameters void emitDebugLineSection(StreamerContext &C) { MCContext &Ctx = *C.Ctx; MCStreamer *TheStreamer = C.Streamer.get(); MCAssembler &Assembler = static_cast(TheStreamer)->getAssembler(); TheStreamer->initSections(false, *STI); // Create a mock function MCSection *Section = C.MOFI->getTextSection(); Section->setHasInstructions(true); TheStreamer->switchSection(Section); TheStreamer->emitCFIStartProc(true); // Create a mock dwarfloc Ctx.setCurrentDwarfLoc(/*FileNo=*/0, /*Line=*/1, /*Column=*/1, /*Flags=*/0, /*Isa=*/0, /*Discriminator=*/0); MCDwarfLoc Loc = Ctx.getCurrentDwarfLoc(); MCSymbol *LineSym = Ctx.createTempSymbol(); // Set the value of the symbol to use for the MCDwarfLineEntry. TheStreamer->emitLabel(LineSym); TheStreamer->emitNops(4, 1, SMLoc(), *STI); TheStreamer->emitCFIEndProc(); // Start emission of .debug_line TheStreamer->switchSection(C.MOFI->getDwarfLineSection()); MCDwarfLineTableHeader Header; MCDwarfLineTableParams Params = Assembler.getDWARFLinetableParams(); std::optional LineStr(std::nullopt); if (Ctx.getDwarfVersion() >= 5) { LineStr.emplace(Ctx); Header.setRootFile("dir", "file", std::nullopt, std::nullopt); } MCSymbol *LineEndSym = Header.Emit(TheStreamer, Params, LineStr).second; // Put out the line tables. MCLineSection::MCDwarfLineEntryCollection LineEntries; MCDwarfLineEntry LineEntry(LineSym, Loc); LineEntries.push_back(LineEntry); MCDwarfLineTable::emitOne(TheStreamer, Section, LineEntries); TheStreamer->emitLabel(LineEndSym); if (LineStr) { SmallString<0> Data = LineStr->getFinalizedData(); TheStreamer->switchSection(TheStreamer->getContext() .getObjectFileInfo() ->getDwarfLineStrSection()); TheStreamer->emitBinaryData(Data.str()); } } /// Check contents of .debug_line section void verifyDebugLineContents(const llvm::object::ObjectFile &E, ArrayRef ExpectedEncoding) { for (const llvm::object::SectionRef &Section : E.sections()) { Expected SectionNameOrErr = Section.getName(); ASSERT_TRUE(static_cast(SectionNameOrErr)); StringRef SectionName = *SectionNameOrErr; if (SectionName.empty() || SectionName != ".debug_line") continue; Expected ContentsOrErr = Section.getContents(); ASSERT_TRUE(static_cast(ContentsOrErr)); StringRef Contents = *ContentsOrErr; ASSERT_TRUE(Contents.size() > ExpectedEncoding.size()); EXPECT_EQ( arrayRefFromStringRef(Contents.slice(0, ExpectedEncoding.size())), ExpectedEncoding); return; } llvm_unreachable(".debug_line not found"); } /// Check contents of .debug_line_str section void verifyDebugLineStrContents(const llvm::object::ObjectFile &E) { for (const llvm::object::SectionRef &Section : E.sections()) { Expected SectionNameOrErr = Section.getName(); ASSERT_TRUE(static_cast(SectionNameOrErr)); StringRef SectionName = *SectionNameOrErr; if (SectionName.empty() || SectionName != ".debug_line_str") continue; Expected ContentsOrErr = Section.getContents(); ASSERT_TRUE(static_cast(ContentsOrErr)); StringRef Contents = *ContentsOrErr; ASSERT_TRUE(Contents.contains("dir")); ASSERT_TRUE(Contents.contains("file")); ASSERT_TRUE(Contents.size() == 9); return; } llvm_unreachable(".debug_line_str not found"); } /// Open ObjFileData as an object file and read its .debug_line section void readAndCheckDebugContents(StringRef ObjFileData, ArrayRef Expected, uint8_t DwarfVersion) { std::unique_ptr MB = MemoryBuffer::getMemBuffer(ObjFileData, "", false); std::unique_ptr Bin = cantFail(llvm::object::createBinary(MB->getMemBufferRef())); if (auto *E = dyn_cast(&*Bin)) { verifyDebugLineContents(*E, Expected); if (DwarfVersion >= 5) verifyDebugLineStrContents(*E); return; } llvm_unreachable("ELF object file not found"); } }; } // namespace TEST_F(DwarfLineTableHeaders, TestDWARF4HeaderEmission) { if (!MRI) GTEST_SKIP(); SmallString<0> EmittedBinContents; raw_svector_ostream VecOS(EmittedBinContents); StreamerContext C = createStreamer(VecOS); constexpr uint8_t DwarfVersion = 4; C.Ctx->setDwarfVersion(DwarfVersion); emitDebugLineSection(C); C.Streamer->finish(); readAndCheckDebugContents( EmittedBinContents.str(), {/* Total length=*/0x30, 0, 0, 0, /* DWARF version=*/DwarfVersion, 0, /* Prologue length=*/0x14, 0, 0, 0, /* min_inst_length=*/1, /*max_ops_per_inst=*/1, /* default_is_stmt=*/DWARF2_LINE_DEFAULT_IS_STMT, /* line_base=*/static_cast(-5), /* line_range=*/14, /* opcode_base=*/13}, DwarfVersion); } TEST_F(DwarfLineTableHeaders, TestDWARF5HeaderEmission) { if (!MRI) GTEST_SKIP(); SmallString<0> EmittedBinContents; raw_svector_ostream VecOS(EmittedBinContents); StreamerContext C = createStreamer(VecOS); constexpr uint8_t DwarfVersion = 5; C.Ctx->setDwarfVersion(DwarfVersion); emitDebugLineSection(C); C.Streamer->finish(); readAndCheckDebugContents( EmittedBinContents.str(), {/* Total length=*/0x43, 0, 0, 0, /* DWARF version=*/DwarfVersion, 0, /* ptr size=*/8, /* segment=*/0, /* Prologue length=*/0x25, 0, 0, 0, /* min_inst_length=*/1, /*max_ops_per_inst=*/1, /* default_is_stmt=*/DWARF2_LINE_DEFAULT_IS_STMT, /* line_base=*/static_cast(-5), /* line_range=*/14, /* opcode_base=*/13}, DwarfVersion); }