diff --git a/llvm/include/llvm/MC/MCDwarf.h b/llvm/include/llvm/MC/MCDwarf.h --- a/llvm/include/llvm/MC/MCDwarf.h +++ b/llvm/include/llvm/MC/MCDwarf.h @@ -20,6 +20,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/MC/MCSection.h" +#include "llvm/MC/StringTableBuilder.h" #include "llvm/Support/Error.h" #include "llvm/Support/MD5.h" #include @@ -34,7 +35,6 @@ template class ArrayRef; class MCAsmBackend; class MCContext; -class MCDwarfLineStr; class MCObjectStreamer; class MCStreamer; class MCSymbol; @@ -47,6 +47,24 @@ MCSymbol *emitListsTableHeaderStart(MCStreamer &S); } // namespace mcdwarf +/// Manage the .debug_line_str section contents, if we use it. +class MCDwarfLineStr { + MCSymbol *LineStrLabel = nullptr; + StringTableBuilder LineStrings{StringTableBuilder::DWARF}; + bool UseRelocs = false; + +public: + /// Construct an instance that can emit .debug_line_str (for use in a normal + /// v5 line table). + explicit MCDwarfLineStr(MCContext &Ctx); + + /// Emit a reference to the string. + void emitRef(MCStreamer *MCOS, StringRef Path); + + /// Emit the .debug_line_str section if appropriate. + void emitSection(MCStreamer *MCOS); +}; + /// Instances of this class represent the name of the dwarf .file directive and /// its associated dwarf file number in the MC file. MCDwarfFile's are created /// and uniqued by the MCContext class. In Dwarf 4 file numbers start from 1; @@ -317,6 +335,11 @@ void emitCU(MCStreamer *MCOS, MCDwarfLineTableParams Params, Optional &LineStr) const; + // This emits a single line table associated with a given Section. + static void + emitOne(MCStreamer *MCOS, MCSection *Section, + const MCLineSection::MCDwarfLineEntryCollection &LineEntries); + Expected tryGetFile(StringRef &Directory, StringRef &FileName, Optional Checksum, Optional Source, diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp --- a/llvm/lib/MC/MCDwarf.cpp +++ b/llvm/lib/MC/MCDwarf.cpp @@ -27,7 +27,6 @@ #include "llvm/MC/MCSection.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSymbol.h" -#include "llvm/MC/StringTableBuilder.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Endian.h" #include "llvm/Support/EndianStream.h" @@ -66,29 +65,6 @@ return End; } -/// Manage the .debug_line_str section contents, if we use it. -class llvm::MCDwarfLineStr { - MCSymbol *LineStrLabel = nullptr; - StringTableBuilder LineStrings{StringTableBuilder::DWARF}; - bool UseRelocs = false; - -public: - /// Construct an instance that can emit .debug_line_str (for use in a normal - /// v5 line table). - explicit MCDwarfLineStr(MCContext &Ctx) { - UseRelocs = Ctx.getAsmInfo()->doesDwarfUseRelocationsAcrossSections(); - if (UseRelocs) - LineStrLabel = - Ctx.getObjectFileInfo()->getDwarfLineStrSection()->getBeginSymbol(); - } - - /// Emit a reference to the string. - void emitRef(MCStreamer *MCOS, StringRef Path); - - /// Emit the .debug_line_str section if appropriate. - void emitSection(MCStreamer *MCOS); -}; - static inline uint64_t ScaleAddrDelta(MCContext &Context, uint64_t AddrDelta) { unsigned MinInsnLength = Context.getAsmInfo()->getMinInstAlignment(); if (MinInsnLength == 1) @@ -100,6 +76,13 @@ return AddrDelta / MinInsnLength; } +MCDwarfLineStr::MCDwarfLineStr(MCContext &Ctx) { + UseRelocs = Ctx.getAsmInfo()->doesDwarfUseRelocationsAcrossSections(); + if (UseRelocs) + LineStrLabel = + Ctx.getObjectFileInfo()->getDwarfLineStrSection()->getBeginSymbol(); +} + // // This is called when an instruction is assembled into the specified section // and if there is information from the last .loc directive that has yet to have @@ -162,7 +145,7 @@ // This emits the Dwarf line table for the specified section from the entries // in the LineSection. // -static inline void emitDwarfLineTable( +void MCDwarfLineTable::emitOne( MCStreamer *MCOS, MCSection *Section, const MCLineSection::MCDwarfLineEntryCollection &LineEntries) { unsigned FileNum = 1; @@ -522,7 +505,7 @@ // Put out the line tables. for (const auto &LineSec : MCLineSections.getMCLineEntries()) - emitDwarfLineTable(MCOS, LineSec.first, LineSec.second); + emitOne(MCOS, LineSec.first, LineSec.second); // This is the end of the section, so set the value of the symbol at the end // of this section (that was used in a previous expression). diff --git a/llvm/unittests/MC/CMakeLists.txt b/llvm/unittests/MC/CMakeLists.txt --- a/llvm/unittests/MC/CMakeLists.txt +++ b/llvm/unittests/MC/CMakeLists.txt @@ -14,6 +14,7 @@ add_llvm_unittest(MCTests Disassembler.cpp DwarfLineTables.cpp + DwarfLineTableHeaders.cpp MCInstPrinter.cpp StringTableBuilderTest.cpp TargetRegistry.cpp diff --git a/llvm/unittests/MC/DwarfLineTableHeaders.cpp b/llvm/unittests/MC/DwarfLineTableHeaders.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/MC/DwarfLineTableHeaders.cpp @@ -0,0 +1,218 @@ +//===- 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/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/MCTargetOptions.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/TargetRegistry.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, *MRI, *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(); + Optional LineStr(None); + if (Ctx.getDwarfVersion() >= 5) { + LineStr = MCDwarfLineStr(Ctx); + Header.setRootFile("dir", "file", None, None); + } + 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) + LineStr->emitSection(TheStreamer); + } + + /// 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"); + } + + /// Open ObjFileData as an object file and read its .debug_line section + void readAndCheckDebugLineContents(StringRef ObjFileData, + ArrayRef Expected) { + std::unique_ptr MB = + MemoryBuffer::getMemBuffer(ObjFileData, "", false); + std::unique_ptr Bin = + cantFail(llvm::object::createBinary(MB->getMemBufferRef())); + if (auto *E = dyn_cast(&*Bin)) { + return verifyDebugLineContents(*E, Expected); + } + llvm_unreachable("ELF object file not found"); + } +}; +} // namespace + +TEST_F(DwarfLineTableHeaders, TestDWARF4HeaderEmission) { + if (!MRI) + return; + + SmallString<0> EmittedBinContents; + raw_svector_ostream VecOS(EmittedBinContents); + StreamerContext C = createStreamer(VecOS); + C.Ctx->setDwarfVersion(4); + emitDebugLineSection(C); + C.Streamer->Finish(); + readAndCheckDebugLineContents( + EmittedBinContents.str(), + {/* Total length=*/0x30, 0, 0, 0, + /* DWARF version=*/4, 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}); +} + +TEST_F(DwarfLineTableHeaders, TestDWARF5HeaderEmission) { + if (!MRI) + return; + + SmallString<0> EmittedBinContents; + raw_svector_ostream VecOS(EmittedBinContents); + StreamerContext C = createStreamer(VecOS); + C.Ctx->setDwarfVersion(5); + emitDebugLineSection(C); + C.Streamer->Finish(); + readAndCheckDebugLineContents( + EmittedBinContents.str(), + {/* Total length=*/0x43, 0, 0, 0, + /* DWARF version=*/5, 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}); +}