Javier Lopez-Gomez 0f38c54c6f
[llvm-debuginfo-analyzer] Add support for parsing DWARF / CodeView SourceLanguage (#137223)
This pull request adds support for parsing the source language in both
DWARF and CodeView. Specifically,

- The `LVSourceLanguage` class is introduced to represent any supported
language by any of the debug info representations.

- Update `LVDWARFReader.cpp` and `LVCodeViewVisitor.cpp` to parse the
source language where it applies. Added a new `=Language` attribute;
`getAttributeLanguage()` is internally used to control whether this
information is being printed.
2025-06-06 15:03:07 +01:00

431 lines
15 KiB
C++

//===- llvm/unittest/DebugInfo/LogicalView/DWARFReaderTest.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/DebugInfo/LogicalView/Core/LVCompare.h"
#include "llvm/DebugInfo/LogicalView/Core/LVLine.h"
#include "llvm/DebugInfo/LogicalView/Core/LVScope.h"
#include "llvm/DebugInfo/LogicalView/Core/LVSymbol.h"
#include "llvm/DebugInfo/LogicalView/Core/LVType.h"
#include "llvm/DebugInfo/LogicalView/LVReaderHandler.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/COM.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::logicalview;
extern const char *TestMainArgv0;
namespace {
const char *DwarfClang = "test-dwarf-clang.o";
// Two compile units: one declares `extern int foo_printf(const char *, ...);`
// and another one that defines the function.
const char *DwarfClangUnspecParams = "test-dwarf-clang-unspec-params.elf";
const char *DwarfClangModule = "test-dwarf-clang-module.o";
const char *DwarfGcc = "test-dwarf-gcc.o";
// Helper function to get the first compile unit.
LVScopeCompileUnit *getFirstCompileUnit(LVScopeRoot *Root) {
EXPECT_NE(Root, nullptr);
const LVScopes *CompileUnits = Root->getScopes();
EXPECT_NE(CompileUnits, nullptr);
EXPECT_GT(CompileUnits->size(), 0u);
LVScopes::const_iterator Iter = CompileUnits->begin();
EXPECT_NE(Iter, nullptr);
LVScopeCompileUnit *CompileUnit = static_cast<LVScopeCompileUnit *>(*Iter);
EXPECT_NE(CompileUnit, nullptr);
return CompileUnit;
}
// Helper function to create a reader.
std::unique_ptr<LVReader> createReader(LVReaderHandler &ReaderHandler,
SmallString<128> &InputsDir,
StringRef Filename) {
SmallString<128> ObjectName(InputsDir);
llvm::sys::path::append(ObjectName, Filename);
Expected<std::unique_ptr<LVReader>> ReaderOrErr =
ReaderHandler.createReader(std::string(ObjectName));
EXPECT_THAT_EXPECTED(ReaderOrErr, Succeeded());
std::unique_ptr<LVReader> Reader = std::move(*ReaderOrErr);
EXPECT_NE(Reader, nullptr);
return Reader;
}
// Check the logical elements basic properties.
void checkElementProperties(LVReader *Reader) {
LVScopeRoot *Root = Reader->getScopesRoot();
LVScopeCompileUnit *CompileUnit = getFirstCompileUnit(Root);
EXPECT_EQ(Root->getFileFormatName(), "elf64-x86-64");
EXPECT_EQ(Root->getName(), DwarfClang);
EXPECT_EQ(CompileUnit->getBaseAddress(), 0u);
EXPECT_TRUE(CompileUnit->getProducer().starts_with("clang"));
EXPECT_EQ(CompileUnit->getName(), "test.cpp");
LVSourceLanguage Language = CompileUnit->getSourceLanguage();
EXPECT_TRUE(Language.isValid());
EXPECT_EQ(Language, LVSourceLanguage::DW_LANG_C_plus_plus_14);
EXPECT_EQ(Language.getName(), "DW_LANG_C_plus_plus_14");
EXPECT_EQ(CompileUnit->lineCount(), 0u);
EXPECT_EQ(CompileUnit->scopeCount(), 1u);
EXPECT_EQ(CompileUnit->symbolCount(), 0u);
EXPECT_EQ(CompileUnit->typeCount(), 7u);
EXPECT_EQ(CompileUnit->rangeCount(), 1u);
const LVLocations *Ranges = CompileUnit->getRanges();
ASSERT_NE(Ranges, nullptr);
ASSERT_EQ(Ranges->size(), 1u);
LVLocations::const_iterator IterLocation = Ranges->begin();
LVLocation *Location = (*IterLocation);
EXPECT_STREQ(Location->getIntervalInfo().c_str(),
"{Range} Lines 2:9 [0x0000000000:0x000000003a]");
LVRange RangeList;
CompileUnit->getRanges(RangeList);
const LVRangeEntries &RangeEntries = RangeList.getEntries();
ASSERT_EQ(RangeEntries.size(), 2u);
LVRangeEntries::const_iterator IterRanges = RangeEntries.cbegin();
LVRangeEntry RangeEntry = *IterRanges;
EXPECT_EQ(RangeEntry.lower(), 0u);
EXPECT_EQ(RangeEntry.upper(), 0x3au);
EXPECT_EQ(RangeEntry.scope()->getLineNumber(), 0u);
EXPECT_EQ(RangeEntry.scope()->getName(), "test.cpp");
EXPECT_EQ(RangeEntry.scope()->getOffset(), 0x0bu);
++IterRanges;
RangeEntry = *IterRanges;
EXPECT_EQ(RangeEntry.lower(), 0x1cu);
EXPECT_EQ(RangeEntry.upper(), 0x2fu);
EXPECT_EQ(RangeEntry.scope()->getLineNumber(), 0u);
EXPECT_EQ(RangeEntry.scope()->getName(), "foo::?");
EXPECT_EQ(RangeEntry.scope()->getOffset(), 0x71u);
const LVPublicNames &PublicNames = CompileUnit->getPublicNames();
ASSERT_EQ(PublicNames.size(), 1u);
LVPublicNames::const_iterator IterNames = PublicNames.cbegin();
LVScope *Function = (*IterNames).first;
EXPECT_EQ(Function->getName(), "foo");
EXPECT_EQ(Function->getLineNumber(), 2u);
LVNameInfo NameInfo = (*IterNames).second;
EXPECT_EQ(NameInfo.first, 0u);
EXPECT_EQ(NameInfo.second, 0x3au);
// Lines (debug and assembler) for 'foo'.
const LVLines *Lines = Function->getLines();
ASSERT_NE(Lines, nullptr);
ASSERT_EQ(Lines->size(), 0x12u);
// Check size of types in CompileUnit.
const LVTypes *Types = CompileUnit->getTypes();
ASSERT_NE(Types, nullptr);
EXPECT_EQ(Types->size(), 7u);
const auto BoolType = llvm::find_if(
*Types, [](const LVElement *elt) { return elt->getName() == "bool"; });
ASSERT_NE(BoolType, Types->end());
const auto IntType = llvm::find_if(
*Types, [](const LVElement *elt) { return elt->getName() == "int"; });
ASSERT_NE(IntType, Types->end());
EXPECT_EQ(static_cast<LVType *>(*BoolType)->getBitSize(), 8u);
EXPECT_EQ(static_cast<LVType *>(*BoolType)->getStorageSizeInBytes(), 1u);
EXPECT_EQ(static_cast<LVType *>(*IntType)->getBitSize(), 32u);
EXPECT_EQ(static_cast<LVType *>(*IntType)->getStorageSizeInBytes(), 4u);
}
// Check proper handling of DW_AT_unspecified_parameters in
// LVScope::addMissingElements().
void checkUnspecifiedParameters(LVReader *Reader) {
LVScopeRoot *Root = Reader->getScopesRoot();
LVScopeCompileUnit *CompileUnit = getFirstCompileUnit(Root);
EXPECT_EQ(Root->getFileFormatName(), "elf64-x86-64");
EXPECT_EQ(Root->getName(), DwarfClangUnspecParams);
const LVPublicNames &PublicNames = CompileUnit->getPublicNames();
ASSERT_EQ(PublicNames.size(), 1u);
LVPublicNames::const_iterator IterNames = PublicNames.cbegin();
LVScope *Function = (*IterNames).first;
EXPECT_EQ(Function->getName(), "foo_printf");
const LVElements *Elements = Function->getChildren();
ASSERT_NE(Elements, nullptr);
// foo_printf is a variadic function whose prototype is
// `int foo_printf(const char *, ...)`, where the '...' is represented by a
// DW_TAG_unspecified_parameters, i.e. we expect to find at least one child
// for which getIsUnspecified() returns true.
EXPECT_TRUE(llvm::any_of(*Elements, [](const LVElement *elt) {
return elt->getIsSymbol() &&
static_cast<const LVSymbol *>(elt)->getIsUnspecified();
}));
}
// Check the basic properties on parsed DW_TAG_module.
void checkScopeModule(LVReader *Reader) {
LVScopeRoot *Root = Reader->getScopesRoot();
LVScopeCompileUnit *CompileUnit = getFirstCompileUnit(Root);
EXPECT_EQ(Root->getFileFormatName(), "Mach-O 64-bit x86-64");
EXPECT_EQ(Root->getName(), DwarfClangModule);
ASSERT_NE(CompileUnit->getChildren(), nullptr);
LVElement *FirstChild = *(CompileUnit->getChildren()->begin());
EXPECT_EQ(FirstChild->getIsScope(), 1);
LVScopeModule *Module = static_cast<LVScopeModule *>(FirstChild);
EXPECT_EQ(Module->getIsModule(), 1);
EXPECT_EQ(Module->getName(), "DebugModule");
}
// Check the logical elements selection.
void checkElementSelection(LVReader *Reader) {
LVScopeRoot *Root = Reader->getScopesRoot();
LVScopeCompileUnit *CompileUnit = getFirstCompileUnit(Root);
// Get the matched elements.
LVElements MatchedElements = CompileUnit->getMatchedElements();
std::map<LVOffset, LVElement *> MapElements;
for (LVElement *Element : MatchedElements)
MapElements[Element->getOffset()] = Element;
ASSERT_EQ(MapElements.size(), 0xeu);
LVElement *Element = MapElements[0x000000004b]; // 'foo'
ASSERT_NE(Element, nullptr);
EXPECT_NE(Element->getName().find("foo"), StringRef::npos);
EXPECT_EQ(Element->getIsScope(), 1);
Element = MapElements[0x00000000c0]; // 'CONSTANT'
ASSERT_NE(Element, nullptr);
EXPECT_NE(Element->getName().find("CONSTANT"), StringRef::npos);
EXPECT_EQ(Element->getIsSymbol(), 1);
Element = MapElements[0x000000002d]; // 'INTPTR'
ASSERT_NE(Element, nullptr);
EXPECT_NE(Element->getName().find("INTPTR"), StringRef::npos);
EXPECT_EQ(Element->getIsType(), 1);
Element = MapElements[0x00000000af]; // 'INTEGER'
ASSERT_NE(Element, nullptr);
EXPECT_NE(Element->getName().find("INTEGER"), StringRef::npos);
EXPECT_EQ(Element->getIsType(), 1);
Element = MapElements[0x000000000f]; // 'movl %edx, %eax'
ASSERT_NE(Element, nullptr);
EXPECT_NE(Element->getName().find("movl"), StringRef::npos);
EXPECT_EQ(Element->getIsLine(), 1);
// Get the parents for the matched elements.
LVScopes MatchedScopes = CompileUnit->getMatchedScopes();
std::set<LVOffset> SetScopes;
for (LVScope *Scope : MatchedScopes)
SetScopes.insert(Scope->getOffset());
std::set<LVOffset>::iterator Iter;
ASSERT_EQ(SetScopes.size(), 3u);
Iter = SetScopes.find(0x000000000b); // CompileUnit <- 'foo'
EXPECT_NE(Iter, SetScopes.end());
Iter = SetScopes.find(0x000000009e); // Function <- 'movl %edx, %eax'
EXPECT_NE(Iter, SetScopes.end());
Iter = SetScopes.find(0x000000009e); // LexicalScope <- 'INTEGER'
EXPECT_NE(Iter, SetScopes.end());
}
// Check the logical elements comparison.
void checkElementComparison(LVReader *Reference, LVReader *Target) {
LVCompare Compare(nulls());
Error Err = Compare.execute(Reference, Target);
ASSERT_THAT_ERROR(std::move(Err), Succeeded());
// Get comparison table.
LVPassTable PassTable = Compare.getPassTable();
ASSERT_EQ(PassTable.size(), 5u);
LVReader *Reader;
LVElement *Element;
LVComparePass Pass;
// Reference: Missing Variable 'CONSTANT'
std::tie(Reader, Element, Pass) = PassTable[0];
ASSERT_NE(Reader, nullptr);
ASSERT_NE(Element, nullptr);
EXPECT_EQ(Reader, Reference);
EXPECT_EQ(Element->getLevel(), 4u);
EXPECT_EQ(Element->getLineNumber(), 5u);
EXPECT_EQ(Element->getName(), "CONSTANT");
EXPECT_EQ(Pass, LVComparePass::Missing);
// Reference: Missing TypeDefinition 'INTEGER'
std::tie(Reader, Element, Pass) = PassTable[1];
ASSERT_NE(Reader, nullptr);
ASSERT_NE(Element, nullptr);
EXPECT_EQ(Reader, Reference);
EXPECT_EQ(Element->getLevel(), 3u);
EXPECT_EQ(Element->getLineNumber(), 4u);
EXPECT_EQ(Element->getName(), "INTEGER");
EXPECT_EQ(Pass, LVComparePass::Missing);
// Reference: Missing DebugLine
std::tie(Reader, Element, Pass) = PassTable[2];
ASSERT_NE(Reader, nullptr);
ASSERT_NE(Element, nullptr);
EXPECT_EQ(Reader, Reference);
EXPECT_EQ(Element->getLevel(), 3u);
EXPECT_EQ(Element->getLineNumber(), 8u);
EXPECT_EQ(Element->getName(), "");
EXPECT_EQ(Pass, LVComparePass::Missing);
// Target: Added Variable 'CONSTANT'
std::tie(Reader, Element, Pass) = PassTable[3];
ASSERT_NE(Reader, nullptr);
ASSERT_NE(Element, nullptr);
EXPECT_EQ(Reader, Target);
EXPECT_EQ(Element->getLevel(), 4u);
EXPECT_EQ(Element->getLineNumber(), 5u);
EXPECT_EQ(Element->getName(), "CONSTANT");
EXPECT_EQ(Pass, LVComparePass::Added);
// Target: Added TypeDefinition 'INTEGER'
std::tie(Reader, Element, Pass) = PassTable[4];
ASSERT_NE(Reader, nullptr);
ASSERT_NE(Element, nullptr);
EXPECT_EQ(Reader, Target);
EXPECT_EQ(Element->getLevel(), 4u);
EXPECT_EQ(Element->getLineNumber(), 4u);
EXPECT_EQ(Element->getName(), "INTEGER");
EXPECT_EQ(Pass, LVComparePass::Added);
}
// Logical elements properties.
void elementProperties(SmallString<128> &InputsDir) {
// Reader options.
LVOptions ReaderOptions;
ReaderOptions.setAttributeOffset();
ReaderOptions.setAttributeFormat();
ReaderOptions.setAttributeFilename();
ReaderOptions.setAttributeProducer();
ReaderOptions.setAttributePublics();
ReaderOptions.setAttributeRange();
ReaderOptions.setAttributeLanguage();
ReaderOptions.setAttributeLocation();
ReaderOptions.setAttributeInserted();
ReaderOptions.setAttributeSize();
ReaderOptions.setPrintAll();
ReaderOptions.resolveDependencies();
std::vector<std::string> Objects;
ScopedPrinter W(outs());
LVReaderHandler ReaderHandler(Objects, W, ReaderOptions);
// Check logical elements properties.
std::unique_ptr<LVReader> Reader =
createReader(ReaderHandler, InputsDir, DwarfClang);
checkElementProperties(Reader.get());
Reader = createReader(ReaderHandler, InputsDir, DwarfClangUnspecParams);
checkUnspecifiedParameters(Reader.get());
Reader = createReader(ReaderHandler, InputsDir, DwarfClangModule);
checkScopeModule(Reader.get());
}
// Logical elements selection.
void elementSelection(SmallString<128> &InputsDir) {
// Reader options.
LVOptions ReaderOptions;
ReaderOptions.setAttributeOffset();
ReaderOptions.setPrintAll();
ReaderOptions.setSelectIgnoreCase();
ReaderOptions.setSelectUseRegex();
ReaderOptions.setReportList(); // Matched elements.
ReaderOptions.setReportView(); // Parents for matched elements.
// Add patterns.
ReaderOptions.Select.Generic.insert("foo");
ReaderOptions.Select.Generic.insert("movl[ \t]?%");
ReaderOptions.Select.Generic.insert("INT[a-z]*");
ReaderOptions.Select.Generic.insert("CONSTANT");
ReaderOptions.resolveDependencies();
std::vector<std::string> Objects;
ScopedPrinter W(outs());
LVReaderHandler ReaderHandler(Objects, W, ReaderOptions);
// Check logical elements selection.
std::unique_ptr<LVReader> Reader =
createReader(ReaderHandler, InputsDir, DwarfGcc);
checkElementSelection(Reader.get());
}
// Compare logical elements.
void compareElements(SmallString<128> &InputsDir) {
// Reader options.
LVOptions ReaderOptions;
ReaderOptions.setAttributeOffset();
ReaderOptions.setPrintLines();
ReaderOptions.setPrintSymbols();
ReaderOptions.setPrintTypes();
ReaderOptions.setCompareLines();
ReaderOptions.setCompareSymbols();
ReaderOptions.setCompareTypes();
ReaderOptions.resolveDependencies();
std::vector<std::string> Objects;
ScopedPrinter W(outs());
LVReaderHandler ReaderHandler(Objects, W, ReaderOptions);
// Check logical comparison.
std::unique_ptr<LVReader> Reference =
createReader(ReaderHandler, InputsDir, DwarfClang);
std::unique_ptr<LVReader> Target =
createReader(ReaderHandler, InputsDir, DwarfGcc);
checkElementComparison(Reference.get(), Target.get());
}
TEST(LogicalViewTest, DWARFReader) {
// Initialize targets and assembly printers/parsers.
llvm::InitializeAllTargetInfos();
llvm::InitializeAllTargetMCs();
InitializeAllDisassemblers();
llvm::sys::InitializeCOMRAII COM(llvm::sys::COMThreadingMode::MultiThreaded);
// This test requires a x86-registered-target.
Triple TT;
TT.setArch(Triple::x86_64);
TT.setVendor(Triple::UnknownVendor);
TT.setOS(Triple::UnknownOS);
std::string TargetLookupError;
if (!TargetRegistry::lookupTarget(TT, TargetLookupError))
GTEST_SKIP();
SmallString<128> InputsDir = unittest::getInputFileDirectory(TestMainArgv0);
// Logical elements general properties and selection.
elementProperties(InputsDir);
elementSelection(InputsDir);
// Compare logical elements.
compareElements(InputsDir);
}
} // namespace