Index: llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h =================================================================== --- llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h +++ llvm/include/llvm/DebugInfo/DWARF/DWARFDebugLine.h @@ -268,6 +268,11 @@ DILineInfoSpecifier::FileLineInfoKind Kind, DILineInfo &Result) const; + /// Extracts directory name by its Entry in include directories table + /// in prologue. Returns true on success. + bool getDirectoryForEntry(const FileNameEntry &Entry, + std::string &Directory) const; + void dump(raw_ostream &OS, DIDumpOptions DumpOptions) const; void clear(); Index: llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h =================================================================== --- llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h +++ llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h @@ -164,6 +164,11 @@ StringRef getData() const { return Data.getData(); } + static bool prettyPrintRegisterOp(DWARFUnit *U, raw_ostream &OS, + DIDumpOptions DumpOpts, uint8_t Opcode, + const uint64_t Operands[2], + const MCRegisterInfo *MRI, bool isEH); + private: DataExtractor Data; uint8_t AddressSize; Index: llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h =================================================================== --- llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h +++ llvm/include/llvm/DebugInfo/DWARF/DWARFUnit.h @@ -430,11 +430,11 @@ return DWARFDie(this, &DieArray[0]); } - DWARFDie getNonSkeletonUnitDIE(bool ExtractUnitDIEOnly = true) { - parseDWO(); - if (DWO) - return DWO->getUnitDIE(ExtractUnitDIEOnly); - return getUnitDIE(ExtractUnitDIEOnly); + DWARFDie getNonSkeletonUnitDIE(bool ExtractUnitDIEOnly = true, + StringRef DWOAlternativeLocation = {}) { + parseDWO(DWOAlternativeLocation); + return DWO ? DWO->getUnitDIE(ExtractUnitDIEOnly) + : getUnitDIE(ExtractUnitDIEOnly); } const char *getCompilationDir(); @@ -569,7 +569,10 @@ /// parseDWO - Parses .dwo file for current compile unit. Returns true if /// it was actually constructed. - bool parseDWO(); + /// The \p AlternativeLocation specifies an alternative location to get + /// the DWARF context for the DWO object; this is the case when it has + /// been moved from its original location. + bool parseDWO(StringRef AlternativeLocation = {}); }; inline bool isCompileUnit(const std::unique_ptr &U) { Index: llvm/include/llvm/DebugInfo/LogicalView/Core/LVLocation.h =================================================================== --- llvm/include/llvm/DebugInfo/LogicalView/Core/LVLocation.h +++ llvm/include/llvm/DebugInfo/LogicalView/Core/LVLocation.h @@ -19,6 +19,8 @@ namespace llvm { namespace logicalview { +using LVLineRange = std::pair; + // The DW_AT_data_member_location attribute is a simple member offset. const LVSmall LVLocationMemberOffset = 0; Index: llvm/include/llvm/DebugInfo/LogicalView/Core/LVObject.h =================================================================== --- llvm/include/llvm/DebugInfo/LogicalView/Core/LVObject.h +++ llvm/include/llvm/DebugInfo/LogicalView/Core/LVObject.h @@ -33,6 +33,7 @@ namespace llvm { namespace logicalview { +using LVSectionIndex = uint64_t; using LVAddress = uint64_t; using LVHalf = uint16_t; using LVLevel = uint32_t; Index: llvm/include/llvm/DebugInfo/LogicalView/Core/LVReader.h =================================================================== --- llvm/include/llvm/DebugInfo/LogicalView/Core/LVReader.h +++ llvm/include/llvm/DebugInfo/LogicalView/Core/LVReader.h @@ -16,6 +16,7 @@ #include "llvm/DebugInfo/LogicalView/Core/LVOptions.h" #include "llvm/DebugInfo/LogicalView/Core/LVRange.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/ToolOutputFile.h" @@ -24,6 +25,8 @@ namespace llvm { namespace logicalview { +constexpr LVSectionIndex UndefinedSectionIndex = 0; + class LVScopeCompileUnit; class LVObject; @@ -79,6 +82,9 @@ raw_ostream &OS; LVScopeCompileUnit *CompileUnit = nullptr; + // Only for ELF format. The CodeView is handled in a different way. + LVSectionIndex DotTextSectionIndex = UndefinedSectionIndex; + // Record Compilation Unit entry. void addCompileUnitOffset(LVOffset Offset, LVScopeCompileUnit *CompileUnit) { CompileUnits.emplace(Offset, CompileUnit); @@ -93,6 +99,23 @@ return Error::success(); } + // Return a pathname composed by: parent_path(InputFilename)/filename(From). + // This is useful when a type server (PDB file associated with an object + // file or a precompiled header file) or a DWARF split object have been + // moved from their original location. That is the case when running + // regression tests, where object files are created in one location and + // executed in a different location. + std::string createAlternativePath(StringRef From) { + // During the reader initialization, any backslashes in 'InputFilename' + // are converted to forward slashes. + SmallString<128> Path; + sys::path::append(Path, sys::path::Style::posix, + sys::path::parent_path(InputFilename), + sys::path::filename(sys::path::convert_to_slash( + From, sys::path::Style::windows))); + return std::string(Path); + } + virtual Error printScopes(); virtual Error printMatchedElements(bool UseMatchedElements); virtual void sortScopes() {} @@ -139,7 +162,12 @@ return {}; } - virtual bool isSystemEntry(LVElement *Element, StringRef Name = {}) { + LVSectionIndex getDotTextSectionIndex() const { return DotTextSectionIndex; } + virtual LVSectionIndex getSectionIndex(LVScope *Scope) { + return getDotTextSectionIndex(); + } + + virtual bool isSystemEntry(LVElement *Element, StringRef Name = {}) const { return false; }; Index: llvm/include/llvm/DebugInfo/LogicalView/Core/LVScope.h =================================================================== --- llvm/include/llvm/DebugInfo/LogicalView/Core/LVScope.h +++ llvm/include/llvm/DebugInfo/LogicalView/Core/LVScope.h @@ -17,6 +17,7 @@ #include "llvm/DebugInfo/LogicalView/Core/LVElement.h" #include "llvm/DebugInfo/LogicalView/Core/LVLocation.h" #include "llvm/DebugInfo/LogicalView/Core/LVSort.h" +#include "llvm/Object/ObjectFile.h" #include #include #include @@ -24,6 +25,11 @@ namespace llvm { namespace logicalview { +// Name address, Code size. +using LVNameInfo = std::pair; +using LVPublicNames = std::map; +using LVPublicAddresses = std::map; + class LVRange; enum class LVScopeKind { @@ -392,6 +398,12 @@ // Names (files and directories) used by the Compile Unit. std::vector Filenames; + // As the .debug_pubnames section has been removed in DWARF5, we have a + // similar functionality, which is used by the decoded functions. We use + // the low-pc and high-pc for those scopes that are marked as public, in + // order to support DWARF and CodeView. + LVPublicNames PublicNames; + // Toolchain producer. size_t ProducerIndex = 0; @@ -410,9 +422,10 @@ // It records the mapping between logical lines representing a debug line // entry and its address in the text section. It is used to find a line - // giving its exact or closest address. + // giving its exact or closest address. To support comdat functions, all + // addresses for the same section are recorded in the same map. using LVAddressToLine = std::map; - LVAddressToLine AddressToLine; + LVDoubleMap SectionMappings; // DWARF Tags (Tag, Element list). LVTagOffsetsMap DebugTags; @@ -456,6 +469,10 @@ // in the 'Totals' vector are valid values. LVLevel MaxSeenLevel = 0; + // Get the line located at the given address. + LVLine *lineLowerBound(LVAddress Address, LVScope *Scope) const; + LVLine *lineUpperBound(LVAddress Address, LVScope *Scope) const; + void printScopeSize(const LVScope *Scope, raw_ostream &OS); void printScopeSize(const LVScope *Scope, raw_ostream &OS) const { (const_cast(this))->printScopeSize(Scope, OS); @@ -483,9 +500,28 @@ return static_cast(const_cast(this)); } - // Get the line located at the given address. - LVLine *lineLowerBound(LVAddress Address) const; - LVLine *lineUpperBound(LVAddress Address) const; + // Add line to address mapping. + void addMapping(LVLine *Line, LVSectionIndex SectionIndex); + LVLineRange lineRange(LVLocation *Location) const; + + LVNameInfo NameNone = {UINT64_MAX, 0}; + void addPublicName(LVScope *Scope, LVAddress LowPC, LVAddress HighPC) { + PublicNames.emplace(std::piecewise_construct, std::forward_as_tuple(Scope), + std::forward_as_tuple(LowPC, HighPC - LowPC)); + } + const LVNameInfo &findPublicName(LVScope *Scope) { + LVPublicNames::iterator Iter = PublicNames.find(Scope); + return (Iter != PublicNames.end()) ? Iter->second : NameNone; + } + const LVPublicNames &getPublicNames() const { return PublicNames; } + + // The base address of the scope for any of the debugging information + // entries listed, is given by either the DW_AT_low_pc attribute or the + // first address in the first range entry in the list of ranges given by + // the DW_AT_ranges attribute. + LVAddress getBaseAddress() const { + return Ranges ? Ranges->front()->getLowerAddress() : 0; + } StringRef getCompilationDirectory() const { return getStringPool().getString(CompilationDirectoryIndex); Index: llvm/include/llvm/DebugInfo/LogicalView/Core/LVSupport.h =================================================================== --- llvm/include/llvm/DebugInfo/LogicalView/Core/LVSupport.h +++ llvm/include/llvm/DebugInfo/LogicalView/Core/LVSupport.h @@ -21,6 +21,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace llvm { @@ -163,6 +164,71 @@ delete Entry.second; } +// Double map data structure. +template +class LVDoubleMap { + static_assert(std::is_pointer::value, + "ValueType must be a pointer."); + using LVSecondMapType = std::map; + using LVFirstMapType = std::map; + using LVValueTypes = std::vector; + LVFirstMapType FirstMap; + +public: + LVDoubleMap() = default; + ~LVDoubleMap() { + for (auto &Entry : FirstMap) + delete Entry.second; + } + + void add(FirstKeyType FirstKey, SecondKeyType SecondKey, ValueType Value) { + LVSecondMapType *SecondMap = nullptr; + typename LVFirstMapType::iterator FirstIter = FirstMap.find(FirstKey); + if (FirstIter == FirstMap.end()) { + SecondMap = new LVSecondMapType(); + FirstMap.emplace(FirstKey, SecondMap); + } else { + SecondMap = FirstIter->second; + } + + assert(SecondMap && "SecondMap is null."); + if (SecondMap && SecondMap->find(SecondKey) == SecondMap->end()) + SecondMap->emplace(SecondKey, Value); + } + + LVSecondMapType *findMap(FirstKeyType FirstKey) const { + typename LVFirstMapType::const_iterator FirstIter = FirstMap.find(FirstKey); + if (FirstIter == FirstMap.end()) + return nullptr; + + LVSecondMapType *SecondMap = FirstIter->second; + return SecondMap; + } + + ValueType find(FirstKeyType FirstKey, SecondKeyType SecondKey) const { + LVSecondMapType *SecondMap = findMap(FirstKey); + if (!SecondMap) + return nullptr; + + typename LVSecondMapType::const_iterator SecondIter = + SecondMap->find(SecondKey); + return (SecondIter != SecondMap->end()) ? SecondIter->second : nullptr; + } + + // Return a vector with all the 'ValueType' values. + LVValueTypes find() const { + LVValueTypes Values; + if (FirstMap.empty()) + return Values; + for (typename LVFirstMapType::const_reference FirstEntry : FirstMap) { + LVSecondMapType *SecondMap = FirstEntry.second; + for (typename LVSecondMapType::const_reference SecondEntry : *SecondMap) + Values.push_back(SecondEntry.second); + } + return Values; + } +}; + // Unified and flattened pathnames. std::string transformPath(StringRef Path); std::string flattenedFilePath(StringRef Path); Index: llvm/include/llvm/DebugInfo/LogicalView/LVReaderHandler.h =================================================================== --- /dev/null +++ llvm/include/llvm/DebugInfo/LogicalView/LVReaderHandler.h @@ -0,0 +1,100 @@ +//===-- LVReaderHandler.h ---------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This class implements the Reader handler. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVREADERHANDLER_H +#define LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVREADERHANDLER_H + +#include "llvm/ADT/PointerUnion.h" +#include "llvm/DebugInfo/LogicalView/Core/LVReader.h" +#include "llvm/DebugInfo/PDB/Native/PDBFile.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/MachOUniversal.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/ScopedPrinter.h" +#include +#include + +namespace llvm { +namespace logicalview { + +using LVReaders = std::vector; +using ArgVector = std::vector; +using PdbOrObj = PointerUnion; + +// This class performs the following tasks: +// - Creates a logical reader for every binary file in the command line, +// that parses the debug information and creates a high level logical +// view representation containing scopes, symbols, types and lines. +// - Prints and compares the logical views. +// +// The supported binary formats are: ELF, Mach-O and CodeView. +class LVReaderHandler { + ArgVector &Objects; + ScopedPrinter &W; + raw_ostream &OS; + LVReaders TheReaders; + + Error createReaders(); + void destroyReaders(); + Error printReaders(); + Error compareReaders(); + + Error handleArchive(LVReaders &Readers, StringRef Filename, + object::Archive &Arch); + Error handleBuffer(LVReaders &Readers, StringRef Filename, + MemoryBufferRef Buffer, StringRef ExePath = {}); + Error handleFile(LVReaders &Readers, StringRef Filename, + StringRef ExePath = {}); + Error handleMach(LVReaders &Readers, StringRef Filename, + object::MachOUniversalBinary &Mach); + Error handleObject(LVReaders &Readers, StringRef Filename, + object::Binary &Binary); + + Error createReader(StringRef Filename, LVReaders &Readers, PdbOrObj &Input, + StringRef FileFormatName, StringRef ExePath = {}); + +public: + LVReaderHandler() = delete; + LVReaderHandler(ArgVector &Objects, ScopedPrinter &W, + LVOptions &ReaderOptions) + : Objects(Objects), W(W), OS(W.getOStream()) { + setOptions(&ReaderOptions); + } + LVReaderHandler(const LVReaderHandler &) = delete; + LVReaderHandler &operator=(const LVReaderHandler &) = delete; + ~LVReaderHandler() { destroyReaders(); } + + Error createReader(StringRef Filename, LVReaders &Readers) { + return handleFile(Readers, Filename); + } + Error process(); + + Expected createReader(StringRef Pathname) { + LVReaders Readers; + if (Error Err = createReader(Pathname, Readers)) + return std::move(Err); + return Readers[0]; + } + void deleteReader(LVReader *Reader) { delete Reader; } + + void print(raw_ostream &OS) const; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + void dump() const { print(dbgs()); } +#endif +}; + +} // end namespace logicalview +} // namespace llvm + +#endif // LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVREADERHANDLER_H Index: llvm/include/llvm/DebugInfo/LogicalView/Readers/LVBinaryReader.h =================================================================== --- /dev/null +++ llvm/include/llvm/DebugInfo/LogicalView/Readers/LVBinaryReader.h @@ -0,0 +1,180 @@ +//===-- LVBinaryReader.h ----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines the LVBinaryReader class, which is used to describe a +// binary reader. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVBINARYREADER_H +#define LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVBINARYREADER_H + +#include "llvm/DebugInfo/LogicalView/Core/LVReader.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" +#include "llvm/MC/MCInstPrinter.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Object/ObjectFile.h" + +namespace llvm { +namespace logicalview { + +constexpr bool UpdateHighAddress = false; + +// Logical scope, Section address, Section index, IsComdat. +struct LVSymbolTableEntry final { + LVScope *Scope = nullptr; + LVAddress Address = 0; + LVSectionIndex SectionIndex = 0; + bool IsComdat = false; + LVSymbolTableEntry() = default; + LVSymbolTableEntry(LVScope *Scope, LVAddress Address, + LVSectionIndex SectionIndex, bool IsComdat) + : Scope(Scope), Address(Address), SectionIndex(SectionIndex), + IsComdat(IsComdat) {} +}; + +// Function names extracted from the object symbol table. +class LVSymbolTable final { + using LVSymbolNames = std::map; + LVSymbolNames SymbolNames; + +public: + LVSymbolTable() = default; + + void add(StringRef Name, LVScope *Function, LVSectionIndex SectionIndex = 0); + void add(StringRef Name, LVAddress Address, LVSectionIndex SectionIndex, + bool IsComdat); + LVSectionIndex update(LVScope *Function); + + const LVSymbolTableEntry &getEntry(StringRef Name); + LVAddress getAddress(StringRef Name); + LVSectionIndex getIndex(StringRef Name); + bool getIsComdat(StringRef Name); + + void print(raw_ostream &OS); +}; + +class LVBinaryReader : public LVReader { + // Function names extracted from the object symbol table. + LVSymbolTable SymbolTable; + + // Instruction lines for a logical scope. These instructions are fetched + // during its merge with the debug lines. + LVDoubleMap ScopeInstructions; + + // Links the scope with its first assembler address line. + LVDoubleMap AssemblerMappings; + + // Mapping from virtual address to section. + // The virtual address refers to the address where the section is loaded. + using LVSectionAddresses = std::map; + LVSectionAddresses SectionAddresses; + + void addSectionAddress(const object::SectionRef &Section) { + if (SectionAddresses.find(Section.getAddress()) == SectionAddresses.end()) + SectionAddresses.emplace(Section.getAddress(), Section); + } + + // Scopes with ranges for current compile unit. It is used to find a line + // giving its exact or closest address. To support comdat functions, all + // addresses for the same section are recorded in the same map. + using LVSectionRanges = std::map; + LVSectionRanges SectionRanges; + + // Image base and virtual address for Executable file. + uint64_t ImageBaseAddress = 0; + uint64_t VirtualAddress = 0; + + // Object sections with machine code. + using LVSections = std::map; + LVSections Sections; + +protected: + // It contains the LVLineDebug elements representing the logical lines for + // the current compile unit, created by parsing the debug line section. + LVLines CULines; + + std::unique_ptr MRI; + std::unique_ptr MAI; + std::unique_ptr STI; + std::unique_ptr MII; + std::unique_ptr MD; + std::unique_ptr MC; + std::unique_ptr MIP; + + // Loads all info for the architecture of the provided object file. + Error loadGenericTargetInfo(StringRef TheTriple, StringRef TheFeatures); + + virtual void mapRangeAddress(const object::ObjectFile &Obj) {} + virtual void mapRangeAddress(const object::ObjectFile &Obj, + const object::SectionRef &Section, + bool IsComdat) {} + + // Create a mapping from virtual address to section. + void mapVirtualAddress(const object::ObjectFile &Obj); + void mapVirtualAddress(const object::COFFObjectFile &COFFObj); + + Expected> + getSection(LVScope *Scope, LVAddress Address, LVSectionIndex SectionIndex); + + void addSectionRange(LVSectionIndex SectionIndex, LVScope *Scope); + void addSectionRange(LVSectionIndex SectionIndex, LVScope *Scope, + LVAddress LowerAddress, LVAddress UpperAddress); + LVRange *getSectionRanges(LVSectionIndex SectionIndex); + + Error createInstructions(); + Error createInstructions(LVScope *Function, LVSectionIndex SectionIndex); + Error createInstructions(LVScope *Function, LVSectionIndex SectionIndex, + const LVNameInfo &NameInfo); + + void processLines(LVLines *DebugLines, LVSectionIndex SectionIndex); + void processLines(LVLines *DebugLines, LVSectionIndex SectionIndex, + LVScope *Function); + +public: + LVBinaryReader() = delete; + LVBinaryReader(StringRef Filename, StringRef FileFormatName, ScopedPrinter &W, + LVBinaryType BinaryType) + : LVReader(Filename, FileFormatName, W, BinaryType) {} + LVBinaryReader(const LVBinaryReader &) = delete; + LVBinaryReader &operator=(const LVBinaryReader &) = delete; + virtual ~LVBinaryReader(); + + void addToSymbolTable(StringRef Name, LVScope *Function, + LVSectionIndex SectionIndex = 0); + void addToSymbolTable(StringRef Name, LVAddress Address, + LVSectionIndex SectionIndex, bool IsComdat); + LVSectionIndex updateSymbolTable(LVScope *Function); + + const LVSymbolTableEntry &getSymbolTableEntry(StringRef Name); + LVAddress getSymbolTableAddress(StringRef Name); + LVSectionIndex getSymbolTableIndex(StringRef Name); + bool getSymbolTableIsComdat(StringRef Name); + + LVSectionIndex getSectionIndex(LVScope *Scope) override { + return Scope ? getSymbolTableIndex(Scope->getLinkageName()) + : DotTextSectionIndex; + } + + void print(raw_ostream &OS) const; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + void dump() const { print(dbgs()); } +#endif +}; + +} // end namespace logicalview +} // end namespace llvm + +#endif // LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVBINARYREADER_H Index: llvm/include/llvm/DebugInfo/LogicalView/Readers/LVELFReader.h =================================================================== --- /dev/null +++ llvm/include/llvm/DebugInfo/LogicalView/Readers/LVELFReader.h @@ -0,0 +1,154 @@ +//===-- LVELFReader.h -------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines the LVELFReader class, which is used to describe a +// debug information (DWARF) reader. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVELFREADER_H +#define LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVELFREADER_H + +#include "llvm/DebugInfo/DWARF/DWARFAbbreviationDeclaration.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/DebugInfo/LogicalView/Readers/LVBinaryReader.h" +#include + +namespace llvm { +namespace logicalview { + +class LVElement; +class LVLine; +class LVScopeCompileUnit; +class LVSymbol; +class LVType; + +using AttributeSpec = DWARFAbbreviationDeclaration::AttributeSpec; + +class LVELFReader final : public LVBinaryReader { + object::ObjectFile &Obj; + + // Indicates if ranges data are available; in the case of split DWARF any + // reference to ranges is valid only if the skeleton DIE has been loaded. + bool RangesDataAvailable = false; + LVAddress CUBaseAddress = 0; + LVAddress CUHighAddress = 0; + + // Current elements during the processing of a DIE. + LVElement *CurrentElement = nullptr; + LVScope *CurrentScope = nullptr; + LVSymbol *CurrentSymbol = nullptr; + LVType *CurrentType = nullptr; + LVOffset CurrentOffset = 0; + LVOffset CurrentEndOffset = 0; + + // In DWARF v4, the files are 1-indexed. + // In DWARF v5, the files are 0-indexed. + // The ELF reader expects the indexes as 1-indexed. + bool IncrementFileIndex = false; + + // Address ranges collected for current DIE. + std::vector CurrentRanges; + + // Symbols with locations for current compile unit. + LVSymbols SymbolsWithLocations; + + // Global Offsets (Offset, Element). + LVOffsetElementMap GlobalOffsets; + + // Low PC and High PC values for DIE being processed. + LVAddress CurrentLowPC = 0; + LVAddress CurrentHighPC = 0; + bool FoundLowPC = false; + bool FoundHighPC = false; + + // Cross references (Elements). + using LVElementSet = std::unordered_set; + using LVElementEntry = std::pair; + using LVElementReference = std::unordered_map; + LVElementReference ElementTable; + + Error loadTargetInfo(const object::ObjectFile &Obj); + + void mapRangeAddress(const object::ObjectFile &Obj) override; + + LVElement *createElement(dwarf::Tag Tag); + void traverseDieAndChildren(DWARFDie &DIE, LVScope *Parent, + DWARFDie &SkeletonDie); + // Process the attributes for the given DIE. + LVScope *processOneDie(const DWARFDie &InputDIE, LVScope *Parent, + DWARFDie &SkeletonDie); + void processOneAttribute(const DWARFDie &Die, LVOffset *OffsetPtr, + const AttributeSpec &AttrSpec); + void createLineAndFileRecords(const DWARFDebugLine::LineTable *Lines); + void processLocationGaps(); + + // Add offset to global map. + void addGlobalOffset(LVOffset Offset) { + if (GlobalOffsets.find(Offset) == GlobalOffsets.end()) + // Just associate the DIE offset with a null element, as we do not + // know if the referenced element has been created. + GlobalOffsets.emplace(Offset, nullptr); + } + + // Remove offset from global map. + void removeGlobalOffset(LVOffset Offset) { + LVOffsetElementMap::iterator Iter = GlobalOffsets.find(Offset); + if (Iter != GlobalOffsets.end()) + GlobalOffsets.erase(Iter); + } + + // Get the location information for DW_AT_data_member_location. + void processLocationMember(dwarf::Attribute Attr, + const DWARFFormValue &FormValue, + const DWARFDie &Die, uint64_t OffsetOnEntry); + void processLocationList(dwarf::Attribute Attr, + const DWARFFormValue &FormValue, const DWARFDie &Die, + uint64_t OffsetOnEntry, + bool CallSiteLocation = false); + void updateReference(dwarf::Attribute Attr, const DWARFFormValue &FormValue); + + // Get an element given the DIE offset. + LVElement *getElementForOffset(LVOffset offset, LVElement *Element); + +protected: + Error createScopes() override; + void sortScopes() override; + +public: + LVELFReader() = delete; + LVELFReader(StringRef Filename, StringRef FileFormatName, + object::ObjectFile &Obj, ScopedPrinter &W) + : LVBinaryReader(Filename, FileFormatName, W, LVBinaryType::ELF), + Obj(Obj) {} + LVELFReader(const LVELFReader &) = delete; + LVELFReader &operator=(const LVELFReader &) = delete; + ~LVELFReader() = default; + + LVAddress getCUBaseAddress() const { return CUBaseAddress; } + void setCUBaseAddress(LVAddress Address) { CUBaseAddress = Address; } + LVAddress getCUHighAddress() const { return CUHighAddress; } + void setCUHighAddress(LVAddress Address) { CUHighAddress = Address; } + + const LVSymbols &GetSymbolsWithLocations() const { + return SymbolsWithLocations; + } + + std::string getRegisterName(LVSmall Opcode, uint64_t Operands[2]) override; + + void print(raw_ostream &OS) const; + +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) + void dump() const { print(dbgs()); } +#endif +}; + +} // end namespace logicalview +} // end namespace llvm + +#endif // LLVM_DEBUGINFO_LOGICALVIEW_READERS_LVELFREADER_H Index: llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp =================================================================== --- llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp +++ llvm/lib/DebugInfo/DWARF/DWARFDebugLine.cpp @@ -1419,6 +1419,24 @@ return true; } +bool DWARFDebugLine::LineTable::getDirectoryForEntry( + const FileNameEntry &Entry, std::string &Directory) const { + if (Prologue.getVersion() >= 5) { + if (Entry.DirIdx < Prologue.IncludeDirectories.size()) { + Directory = + dwarf::toString(Prologue.IncludeDirectories[Entry.DirIdx], ""); + return true; + } + return false; + } + if (0 < Entry.DirIdx && Entry.DirIdx <= Prologue.IncludeDirectories.size()) { + Directory = + dwarf::toString(Prologue.IncludeDirectories[Entry.DirIdx - 1], ""); + return true; + } + return false; +} + // We want to supply the Unit associated with a .debug_line[.dwo] table when // we dump it, if possible, but still dump the table even if there isn't a Unit. // Therefore, collect up handles on all the Units that point into the Index: llvm/lib/DebugInfo/DWARF/DWARFExpression.cpp =================================================================== --- llvm/lib/DebugInfo/DWARF/DWARFExpression.cpp +++ llvm/lib/DebugInfo/DWARF/DWARFExpression.cpp @@ -225,10 +225,9 @@ } } -static bool prettyPrintRegisterOp(DWARFUnit *U, raw_ostream &OS, - DIDumpOptions DumpOpts, uint8_t Opcode, - const uint64_t Operands[2], - const MCRegisterInfo *MRI, bool isEH) { +bool DWARFExpression::prettyPrintRegisterOp( + DWARFUnit *U, raw_ostream &OS, DIDumpOptions DumpOpts, uint8_t Opcode, + const uint64_t Operands[2], const MCRegisterInfo *MRI, bool isEH) { if (!MRI) return false; Index: llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp =================================================================== --- llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp +++ llvm/lib/DebugInfo/DWARF/DWARFUnit.cpp @@ -587,7 +587,7 @@ return Error::success(); } -bool DWARFUnit::parseDWO() { +bool DWARFUnit::parseDWO(StringRef DWOAlternativeLocation) { if (IsDWO) return false; if (DWO.get()) @@ -611,8 +611,17 @@ if (!DWOId) return false; auto DWOContext = Context.getDWOContext(AbsolutePath); - if (!DWOContext) - return false; + if (!DWOContext) { + // Use the alternative location to get the DWARF context for the DWO object. + if (DWOAlternativeLocation.empty()) + return false; + // If the alternative context does not correspond to the original DWO object + // (different hashes), the below 'getDWOCompileUnitForHash' call will catch + // the issue, with a returned null context. + DWOContext = Context.getDWOContext(DWOAlternativeLocation); + if (!DWOContext) + return false; + } DWARFCompileUnit *DWOCU = DWOContext->getDWOCompileUnitForHash(*DWOId); if (!DWOCU) Index: llvm/lib/DebugInfo/LogicalView/CMakeLists.txt =================================================================== --- llvm/lib/DebugInfo/LogicalView/CMakeLists.txt +++ llvm/lib/DebugInfo/LogicalView/CMakeLists.txt @@ -30,9 +30,16 @@ Core/LVType.cpp ) +add_lv_impl_folder(Readers + LVReaderHandler.cpp + Readers/LVBinaryReader.cpp + Readers/LVELFReader.cpp + ) + list(APPEND LIBLV_ADDITIONAL_HEADER_DIRS "${LLVM_MAIN_INCLUDE_DIR}/llvm/DebugInfo/LogicalView" "${LLVM_MAIN_INCLUDE_DIR}/llvm/DebugInfo/LogicalView/Core" + "${LLVM_MAIN_INCLUDE_DIR}/llvm/DebugInfo/LogicalView/Readers" ) add_llvm_library(LLVMDebugInfoLogicalView Index: llvm/lib/DebugInfo/LogicalView/Core/LVElement.cpp =================================================================== --- llvm/lib/DebugInfo/LogicalView/Core/LVElement.cpp +++ llvm/lib/DebugInfo/LogicalView/Core/LVElement.cpp @@ -516,3 +516,15 @@ /*UseQuotes=*/true, /*PrintRef=*/false); } } + +void LVElement::printLinkageName(raw_ostream &OS, bool Full, LVElement *Parent, + LVScope *Scope) const { + if (options().getPrintFormatting() && options().getAttributeLinkage()) { + LVSectionIndex SectionIndex = getReader().getSectionIndex(Scope); + std::string Text = (Twine(" 0x") + Twine::utohexstr(SectionIndex) + + Twine(" '") + Twine(getLinkageName()) + Twine("'")) + .str(); + printAttributes(OS, Full, "{Linkage} ", Parent, Text, + /*UseQuotes=*/false, /*PrintRef=*/false); + } +} Index: llvm/lib/DebugInfo/LogicalView/Core/LVLocation.cpp =================================================================== --- llvm/lib/DebugInfo/LogicalView/Core/LVLocation.cpp +++ llvm/lib/DebugInfo/LogicalView/Core/LVLocation.cpp @@ -469,9 +469,9 @@ if (!hasAssociatedRange()) return true; - LVScopeCompileUnit *Scope = getReader().getCompileUnit(); - LVLine *LowLine = Scope->lineLowerBound(getLowerAddress()); - LVLine *HighLine = Scope->lineUpperBound(getUpperAddress()); + LVLineRange Range = getReaderCompileUnit()->lineRange(this); + LVLine *LowLine = Range.first; + LVLine *HighLine = Range.second; if (LowLine) setLowerLine(LowLine); else { Index: llvm/lib/DebugInfo/LogicalView/Core/LVScope.cpp =================================================================== --- llvm/lib/DebugInfo/LogicalView/Core/LVScope.cpp +++ llvm/lib/DebugInfo/LogicalView/Core/LVScope.cpp @@ -1252,20 +1252,43 @@ } } -LVLine *LVScopeCompileUnit::lineLowerBound(LVAddress Address) const { - LVAddressToLine::const_iterator Iter = AddressToLine.lower_bound(Address); - return (Iter != AddressToLine.end()) ? Iter->second : nullptr; +void LVScopeCompileUnit::addMapping(LVLine *Line, LVSectionIndex SectionIndex) { + LVAddress Address = Line->getOffset(); + SectionMappings.add(SectionIndex, Address, Line); } -LVLine *LVScopeCompileUnit::lineUpperBound(LVAddress Address) const { - if (AddressToLine.empty()) +LVLine *LVScopeCompileUnit::lineLowerBound(LVAddress Address, + LVScope *Scope) const { + LVSectionIndex SectionIndex = getReader().getSectionIndex(Scope); + LVAddressToLine *Map = SectionMappings.findMap(SectionIndex); + if (!Map || Map->empty()) return nullptr; - LVAddressToLine::const_iterator Iter = AddressToLine.upper_bound(Address); - if (Iter != AddressToLine.begin()) + LVAddressToLine::const_iterator Iter = Map->lower_bound(Address); + return (Iter != Map->end()) ? Iter->second : nullptr; +} + +LVLine *LVScopeCompileUnit::lineUpperBound(LVAddress Address, + LVScope *Scope) const { + LVSectionIndex SectionIndex = getReader().getSectionIndex(Scope); + LVAddressToLine *Map = SectionMappings.findMap(SectionIndex); + if (!Map || Map->empty()) + return nullptr; + LVAddressToLine::const_iterator Iter = Map->upper_bound(Address); + if (Iter != Map->begin()) Iter = std::prev(Iter); return Iter->second; } +LVLineRange LVScopeCompileUnit::lineRange(LVLocation *Location) const { + // The parent of a location can be a symbol or a scope. + LVElement *Element = Location->getParent(); + LVScope *Parent = Element->getIsScope() ? static_cast(Element) + : Element->getParentScope(); + LVLine *LowLine = lineLowerBound(Location->getLowerAddress(), Parent); + LVLine *HighLine = lineUpperBound(Location->getUpperAddress(), Parent); + return LVLineRange(LowLine, HighLine); +} + StringRef LVScopeCompileUnit::getFilename(size_t Index) const { if (Index <= 0 || Index > Filenames.size()) return StringRef(); @@ -1403,6 +1426,30 @@ PrintNames(Option::Directory); if (options().getAttributeFiles()) PrintNames(Option::File); + if (options().getAttributePublics()) { + StringRef Kind = "Public"; + // The public names are indexed by 'LVScope *'. We want to print + // them by logical element address, to show the scopes layout. + using OffsetSorted = std::map; + OffsetSorted SortedNames; + for (LVPublicNames::const_iterator Iter = PublicNames.begin(); + Iter != PublicNames.end(); ++Iter) + SortedNames.emplace(Iter->first->getOffset(), Iter); + + LVPublicNames::const_iterator Iter; + for (OffsetSorted::reference Entry : SortedNames) { + Iter = Entry.second; + OS << std::string(Indentation, ' ') << formattedKind(Kind) << " " + << formattedName((*Iter).first->getName()); + if (options().getAttributeOffset()) { + LVAddress Address = (*Iter).second.first; + size_t Size = (*Iter).second.second; + OS << " [" << hexString(Address) << ":" << hexString(Address + Size) + << "]"; + } + OS << "\n"; + } + } } void LVScopeCompileUnit::printWarnings(raw_ostream &OS, bool Full) const { @@ -1848,6 +1895,9 @@ if (getIsTemplateResolved()) printEncodedArgs(OS, Full); printActiveRanges(OS, Full); + if (getLinkageNameIndex()) + printLinkageName(OS, Full, const_cast(this), + const_cast(this)); if (Reference) Reference->printReference(OS, Full, const_cast(this)); } Index: llvm/lib/DebugInfo/LogicalView/LVReaderHandler.cpp =================================================================== --- /dev/null +++ llvm/lib/DebugInfo/LogicalView/LVReaderHandler.cpp @@ -0,0 +1,194 @@ +//===-- LVReaderHandler.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 +// +//===----------------------------------------------------------------------===// +// +// This class implements the Reader Handler. +// +//===----------------------------------------------------------------------===// + +#include "llvm/DebugInfo/LogicalView/LVReaderHandler.h" +#include "llvm/DebugInfo/LogicalView/Core/LVCompare.h" +#include "llvm/DebugInfo/LogicalView/Readers/LVELFReader.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::pdb; +using namespace llvm::logicalview; + +#define DEBUG_TYPE "ReaderHandler" + +Error LVReaderHandler::process() { + if (Error Err = createReaders()) + return Err; + if (Error Err = printReaders()) + return Err; + if (Error Err = compareReaders()) + return Err; + + return Error::success(); +} + +void LVReaderHandler::destroyReaders() { + LLVM_DEBUG(dbgs() << "destroyReaders\n"); + for (const LVReader *Reader : TheReaders) + delete Reader; +} + +Error LVReaderHandler::createReader(StringRef Filename, LVReaders &Readers, + PdbOrObj &Input, StringRef FileFormatName, + StringRef ExePath) { + auto CreateOneReader = [&]() -> LVReader * { + if (Input.is()) { + ObjectFile &Obj = *Input.get(); + if (Obj.isELF() || Obj.isMachO()) + return new LVELFReader(Filename, FileFormatName, Obj, W); + } + return nullptr; + }; + + LVReader *Reader = CreateOneReader(); + if (!Reader) + return createStringError(errc::invalid_argument, + "unable to create reader for: '%s'", + Filename.str().c_str()); + + Readers.push_back(Reader); + return Reader->doLoad(); +} + +Error LVReaderHandler::handleArchive(LVReaders &Readers, StringRef Filename, + Archive &Arch) { + Error Err = Error::success(); + for (const Archive::Child &Child : Arch.children(Err)) { + Expected BuffOrErr = Child.getMemoryBufferRef(); + if (Error Err = BuffOrErr.takeError()) + return createStringError(errorToErrorCode(std::move(Err)), "%s", + Filename.str().c_str()); + Expected NameOrErr = Child.getName(); + if (Error Err = NameOrErr.takeError()) + return createStringError(errorToErrorCode(std::move(Err)), "%s", + Filename.str().c_str()); + std::string Name = (Filename + "(" + NameOrErr.get() + ")").str(); + if (Error Err = handleBuffer(Readers, Name, BuffOrErr.get())) + return createStringError(errorToErrorCode(std::move(Err)), "%s", + Filename.str().c_str()); + } + + return Error::success(); +} + +Error LVReaderHandler::handleBuffer(LVReaders &Readers, StringRef Filename, + MemoryBufferRef Buffer, StringRef ExePath) { + Expected> BinOrErr = createBinary(Buffer); + if (errorToErrorCode(BinOrErr.takeError())) { + return createStringError(errc::not_supported, + "Binary object format in '%s' is not supported.", + Filename.str().c_str()); + } + return handleObject(Readers, Filename, *BinOrErr.get()); +} + +Error LVReaderHandler::handleFile(LVReaders &Readers, StringRef Filename, + StringRef ExePath) { + // Convert any Windows backslashes into forward slashes to get the path. + std::string ConvertedPath = + sys::path::convert_to_slash(Filename, sys::path::Style::windows); + ErrorOr> BuffOrErr = + MemoryBuffer::getFileOrSTDIN(ConvertedPath); + if (BuffOrErr.getError()) { + return createStringError(errc::bad_file_descriptor, + "File '%s' does not exist.", + ConvertedPath.c_str()); + } + std::unique_ptr Buffer = std::move(BuffOrErr.get()); + return handleBuffer(Readers, ConvertedPath, *Buffer, ExePath); +} + +Error LVReaderHandler::handleMach(LVReaders &Readers, StringRef Filename, + MachOUniversalBinary &Mach) { + for (const MachOUniversalBinary::ObjectForArch &ObjForArch : Mach.objects()) { + std::string ObjName = (Twine(Filename) + Twine("(") + + Twine(ObjForArch.getArchFlagName()) + Twine(")")) + .str(); + if (Expected> MachOOrErr = + ObjForArch.getAsObjectFile()) { + MachOObjectFile &Obj = **MachOOrErr; + PdbOrObj Input = &Obj; + if (Error Err = + createReader(Filename, Readers, Input, Obj.getFileFormatName())) + return Err; + continue; + } else + consumeError(MachOOrErr.takeError()); + if (Expected> ArchiveOrErr = + ObjForArch.getAsArchive()) { + if (Error Err = handleArchive(Readers, ObjName, *ArchiveOrErr.get())) + return Err; + continue; + } else + consumeError(ArchiveOrErr.takeError()); + } + return Error::success(); +} + +Error LVReaderHandler::handleObject(LVReaders &Readers, StringRef Filename, + Binary &Binary) { + if (PdbOrObj Input = dyn_cast(&Binary)) + return createReader(Filename, Readers, Input, + Input.get()->getFileFormatName()); + + if (MachOUniversalBinary *Fat = dyn_cast(&Binary)) + return handleMach(Readers, Filename, *Fat); + + if (Archive *Arch = dyn_cast(&Binary)) + return handleArchive(Readers, Filename, *Arch); + + return createStringError(errc::not_supported, + "Binary object format in '%s' is not supported.", + Filename.str().c_str()); +} + +Error LVReaderHandler::createReaders() { + LLVM_DEBUG(dbgs() << "createReaders\n"); + for (std::string &Object : Objects) { + LVReaders Readers; + if (Error Err = createReader(Object, Readers)) + return Err; + TheReaders.insert(TheReaders.end(), Readers.begin(), Readers.end()); + } + + return Error::success(); +} + +Error LVReaderHandler::printReaders() { + LLVM_DEBUG(dbgs() << "printReaders\n"); + if (options().getPrintExecute()) + for (LVReader *Reader : TheReaders) + if (Error Err = Reader->doPrint()) + return Err; + + return Error::success(); +} + +Error LVReaderHandler::compareReaders() { + LLVM_DEBUG(dbgs() << "compareReaders\n"); + size_t ReadersCount = TheReaders.size(); + if (options().getCompareExecute() && ReadersCount >= 2) { + // If we have more than 2 readers, compare them by pairs. + size_t ViewPairs = ReadersCount / 2; + LVCompare Compare(OS); + for (size_t Pair = 0, Index = 0; Pair < ViewPairs; ++Pair) { + if (Error Err = Compare.execute(TheReaders[Index], TheReaders[Index + 1])) + return Err; + Index += 2; + } + } + + return Error::success(); +} + +void LVReaderHandler::print(raw_ostream &OS) const { OS << "ReaderHandler\n"; } Index: llvm/lib/DebugInfo/LogicalView/Readers/LVBinaryReader.cpp =================================================================== --- /dev/null +++ llvm/lib/DebugInfo/LogicalView/Readers/LVBinaryReader.cpp @@ -0,0 +1,794 @@ +//===-- LVBinaryReader.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 +// +//===----------------------------------------------------------------------===// +// +// This implements the LVBinaryReader class. +// +//===----------------------------------------------------------------------===// + +#include "llvm/DebugInfo/LogicalView/Readers/LVBinaryReader.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace llvm; +using namespace llvm::logicalview; + +#define DEBUG_TYPE "BinaryReader" + +// Function names extracted from the object symbol table. +void LVSymbolTable::add(StringRef Name, LVScope *Function, + LVSectionIndex SectionIndex) { + std::string SymbolName(Name); + if (SymbolNames.find(SymbolName) == SymbolNames.end()) { + SymbolNames.emplace( + std::piecewise_construct, std::forward_as_tuple(SymbolName), + std::forward_as_tuple(Function, 0, SectionIndex, false)); + } else { + // Update a recorded entry with its logical scope and section index. + SymbolNames[SymbolName].Scope = Function; + if (SectionIndex) + SymbolNames[SymbolName].SectionIndex = SectionIndex; + } + + if (Function && SymbolNames[SymbolName].IsComdat) + Function->setIsComdat(); + + LLVM_DEBUG({ print(dbgs()); }); +} + +void LVSymbolTable::add(StringRef Name, LVAddress Address, + LVSectionIndex SectionIndex, bool IsComdat) { + std::string SymbolName(Name); + if (SymbolNames.find(SymbolName) == SymbolNames.end()) + SymbolNames.emplace( + std::piecewise_construct, std::forward_as_tuple(SymbolName), + std::forward_as_tuple(nullptr, Address, SectionIndex, IsComdat)); + else + // Update a recorded symbol name with its logical scope. + SymbolNames[SymbolName].Address = Address; + + LVScope *Function = SymbolNames[SymbolName].Scope; + if (Function && IsComdat) + Function->setIsComdat(); + LLVM_DEBUG({ print(dbgs()); }); +} + +LVSectionIndex LVSymbolTable::update(LVScope *Function) { + LVSectionIndex SectionIndex = getReader().getDotTextSectionIndex(); + StringRef Name = Function->getLinkageName(); + if (Name.empty()) + Name = Function->getName(); + std::string SymbolName(Name); + + if (SymbolName.empty() || (SymbolNames.find(SymbolName) == SymbolNames.end())) + return SectionIndex; + + // Update a recorded entry with its logical scope, only if the scope has + // ranges. That is the case when in DWARF there are 2 DIEs connected via + // the DW_AT_specification. + if (Function->getHasRanges()) { + SymbolNames[SymbolName].Scope = Function; + SectionIndex = SymbolNames[SymbolName].SectionIndex; + } else { + SectionIndex = UndefinedSectionIndex; + } + + if (SymbolNames[SymbolName].IsComdat) + Function->setIsComdat(); + + LLVM_DEBUG({ print(dbgs()); }); + return SectionIndex; +} + +const LVSymbolTableEntry &LVSymbolTable::getEntry(StringRef Name) { + static LVSymbolTableEntry Empty = LVSymbolTableEntry(); + LVSymbolNames::iterator Iter = SymbolNames.find(std::string(Name)); + return Iter != SymbolNames.end() ? Iter->second : Empty; +} +LVAddress LVSymbolTable::getAddress(StringRef Name) { + LVSymbolNames::iterator Iter = SymbolNames.find(std::string(Name)); + return Iter != SymbolNames.end() ? Iter->second.Address : 0; +} +LVSectionIndex LVSymbolTable::getIndex(StringRef Name) { + LVSymbolNames::iterator Iter = SymbolNames.find(std::string(Name)); + return Iter != SymbolNames.end() ? Iter->second.SectionIndex + : getReader().getDotTextSectionIndex(); +} +bool LVSymbolTable::getIsComdat(StringRef Name) { + LVSymbolNames::iterator Iter = SymbolNames.find(std::string(Name)); + return Iter != SymbolNames.end() ? Iter->second.IsComdat : false; +} + +void LVSymbolTable::print(raw_ostream &OS) { + OS << "Symbol Table\n"; + for (LVSymbolNames::reference Entry : SymbolNames) { + LVSymbolTableEntry &SymbolName = Entry.second; + LVScope *Scope = SymbolName.Scope; + LVOffset Offset = Scope ? Scope->getOffset() : 0; + OS << "Index: " << hexValue(SymbolName.SectionIndex, 5) + << " Comdat: " << (SymbolName.IsComdat ? "Y" : "N") + << " Scope: " << hexValue(Offset) + << " Address: " << hexValue(SymbolName.Address) + << " Name: " << Entry.first << "\n"; + } +} + +void LVBinaryReader::addToSymbolTable(StringRef Name, LVScope *Function, + LVSectionIndex SectionIndex) { + SymbolTable.add(Name, Function, SectionIndex); +} +void LVBinaryReader::addToSymbolTable(StringRef Name, LVAddress Address, + LVSectionIndex SectionIndex, + bool IsComdat) { + SymbolTable.add(Name, Address, SectionIndex, IsComdat); +} +LVSectionIndex LVBinaryReader::updateSymbolTable(LVScope *Function) { + return SymbolTable.update(Function); +} + +const LVSymbolTableEntry &LVBinaryReader::getSymbolTableEntry(StringRef Name) { + return SymbolTable.getEntry(Name); +} +LVAddress LVBinaryReader::getSymbolTableAddress(StringRef Name) { + return SymbolTable.getAddress(Name); +} +LVSectionIndex LVBinaryReader::getSymbolTableIndex(StringRef Name) { + return SymbolTable.getIndex(Name); +} +bool LVBinaryReader::getSymbolTableIsComdat(StringRef Name) { + return SymbolTable.getIsComdat(Name); +} + +void LVBinaryReader::mapVirtualAddress(const object::ObjectFile &Obj) { + for (const object::SectionRef &Section : Obj.sections()) { + if (!Section.isText() || Section.isVirtual() || !Section.getSize()) + continue; + + // Record section information required for symbol resolution. + // Note: The section index returned by 'getIndex()' is one based. + Sections.emplace(Section.getIndex(), Section); + addSectionAddress(Section); + + // Identify the ".text" section. + Expected SectionNameOrErr = Section.getName(); + if (!SectionNameOrErr) { + consumeError(SectionNameOrErr.takeError()); + continue; + } + if ((*SectionNameOrErr).equals(".text") || + (*SectionNameOrErr).equals(".code")) + DotTextSectionIndex = Section.getIndex(); + } + + // Process the symbol table. + mapRangeAddress(Obj); + + LLVM_DEBUG({ + dbgs() << "\nSections Information:\n"; + for (LVSections::reference Entry : Sections) { + LVSectionIndex SectionIndex = Entry.first; + const object::SectionRef Section = Entry.second; + Expected SectionNameOrErr = Section.getName(); + if (!SectionNameOrErr) + consumeError(SectionNameOrErr.takeError()); + dbgs() << "\nIndex: " << format_decimal(SectionIndex, 3) + << " Name: " << *SectionNameOrErr << "\n" + << "Size: " << hexValue(Section.getSize()) << "\n" + << "VirtualAddress: " << hexValue(VirtualAddress) << "\n" + << "SectionAddress: " << hexValue(Section.getAddress()) << "\n"; + } + dbgs() << "\nObject Section Information:\n"; + for (LVSectionAddresses::const_reference Entry : SectionAddresses) + dbgs() << "[" << hexValue(Entry.first) << ":" + << hexValue(Entry.first + Entry.second.getSize()) + << "] Size: " << hexValue(Entry.second.getSize()) << "\n"; + }); +} + +Error LVBinaryReader::loadGenericTargetInfo(StringRef TheTriple, + StringRef TheFeatures) { + std::string TargetLookupError; + const Target *TheTarget = + TargetRegistry::lookupTarget(std::string(TheTriple), TargetLookupError); + if (!TheTarget) + return createStringError(errc::invalid_argument, TargetLookupError.c_str()); + + // Register information. + MCRegisterInfo *RegisterInfo = TheTarget->createMCRegInfo(TheTriple); + if (!RegisterInfo) + return createStringError(errc::invalid_argument, + "no register info for target " + TheTriple); + MRI.reset(RegisterInfo); + + // Assembler properties and features. + MCTargetOptions MCOptions; + MCAsmInfo *AsmInfo(TheTarget->createMCAsmInfo(*MRI, TheTriple, MCOptions)); + if (!AsmInfo) + return createStringError(errc::invalid_argument, + "no assembly info for target " + TheTriple); + MAI.reset(AsmInfo); + + // Target subtargets. + StringRef CPU; + MCSubtargetInfo *SubtargetInfo( + TheTarget->createMCSubtargetInfo(TheTriple, CPU, TheFeatures)); + if (!SubtargetInfo) + return createStringError(errc::invalid_argument, + "no subtarget info for target " + TheTriple); + STI.reset(SubtargetInfo); + + // Instructions Info. + MCInstrInfo *InstructionInfo(TheTarget->createMCInstrInfo()); + if (!InstructionInfo) + return createStringError(errc::invalid_argument, + "no instruction info for target " + TheTriple); + MII.reset(InstructionInfo); + + MC = std::make_unique(Triple(TheTriple), MAI.get(), MRI.get(), + STI.get()); + + // Assembler. + MCDisassembler *DisAsm(TheTarget->createMCDisassembler(*STI, *MC)); + if (!DisAsm) + return createStringError(errc::invalid_argument, + "no disassembler for target " + TheTriple); + MD.reset(DisAsm); + + MCInstPrinter *InstructionPrinter(TheTarget->createMCInstPrinter( + Triple(TheTriple), AsmInfo->getAssemblerDialect(), *MAI, *MII, *MRI)); + if (!InstructionPrinter) + return createStringError(errc::invalid_argument, + "no target assembly language printer for target " + + TheTriple); + MIP.reset(InstructionPrinter); + InstructionPrinter->setPrintImmHex(true); + + return Error::success(); +} + +Expected> +LVBinaryReader::getSection(LVScope *Scope, LVAddress Address, + LVSectionIndex SectionIndex) { + // Return the 'text' section with the code for this logical scope. + // COFF: SectionIndex is zero. Use 'SectionAddresses' data. + // ELF: SectionIndex is the section index in the file. + if (SectionIndex) { + LVSections::iterator Iter = Sections.find(SectionIndex); + if (Iter == Sections.end()) { + return createStringError(errc::invalid_argument, + "invalid section index for: '%s'", + Scope->getName().str().c_str()); + } + const object::SectionRef Section = Iter->second; + return std::make_pair(Section.getAddress(), Section); + } + + // Ensure a valid starting address for the public names. + LVSectionAddresses::const_iterator Iter = + SectionAddresses.upper_bound(Address); + if (Iter == SectionAddresses.begin()) + return createStringError(errc::invalid_argument, + "invalid section address for: '%s'", + Scope->getName().str().c_str()); + + // Get section that contains the code for this function. + Iter = SectionAddresses.lower_bound(Address); + if (Iter != SectionAddresses.begin()) + --Iter; + return std::make_pair(Iter->first, Iter->second); +} + +void LVBinaryReader::addSectionRange(LVSectionIndex SectionIndex, + LVScope *Scope) { + LVRange *ScopesWithRanges = getSectionRanges(SectionIndex); + ScopesWithRanges->addEntry(Scope); +} + +void LVBinaryReader::addSectionRange(LVSectionIndex SectionIndex, + LVScope *Scope, LVAddress LowerAddress, + LVAddress UpperAddress) { + LVRange *ScopesWithRanges = getSectionRanges(SectionIndex); + ScopesWithRanges->addEntry(Scope, LowerAddress, UpperAddress); +} + +LVRange *LVBinaryReader::getSectionRanges(LVSectionIndex SectionIndex) { + LVRange *Range = nullptr; + // Check if we already have a mapping for this section index. + LVSectionRanges::iterator IterSection = SectionRanges.find(SectionIndex); + if (IterSection == SectionRanges.end()) { + Range = new LVRange(); + SectionRanges.emplace(SectionIndex, Range); + } else { + Range = IterSection->second; + } + assert(Range && "Range is null."); + return Range; +} + +LVBinaryReader::~LVBinaryReader() { + // Delete the lines created by 'createInstructions'. + std::vector AllInstructionLines = ScopeInstructions.find(); + for (LVLines *Entry : AllInstructionLines) + delete Entry; + // Delete the ranges created by 'getSectionRanges'. + for (LVSectionRanges::reference Entry : SectionRanges) + delete Entry.second; +} + +Error LVBinaryReader::createInstructions(LVScope *Scope, + LVSectionIndex SectionIndex, + const LVNameInfo &NameInfo) { + assert(Scope && "Scope is null."); + + // Skip stripped functions. + if (Scope->getIsDiscarded()) + return Error::success(); + + // Find associated address and size for the given function entry point. + LVAddress Address = NameInfo.first; + uint64_t Size = NameInfo.second; + + LLVM_DEBUG({ + dbgs() << "\nPublic Name instructions: '" << Scope->getName() << "' / '" + << Scope->getLinkageName() << "'\n"; + dbgs() << "DIE Offset: " << hexValue(Scope->getOffset()) << " Range: [" + << hexValue(Address) << ":" << hexValue(Address + Size) << "]\n"; + }); + + Expected> SectionOrErr = + getSection(Scope, Address, SectionIndex); + if (!SectionOrErr) + return SectionOrErr.takeError(); + const object::SectionRef Section = (*SectionOrErr).second; + uint64_t SectionAddress = (*SectionOrErr).first; + + Expected SectionContentsOrErr = Section.getContents(); + if (!SectionContentsOrErr) + return SectionOrErr.takeError(); + + // There are cases where the section size is smaller than the [LowPC,HighPC] + // range; it causes us to decode invalid addresses. The recorded size in the + // logical scope is one less than the real size. + LLVM_DEBUG({ + dbgs() << " Size: " << hexValue(Size) + << ", Section Size: " << hexValue(Section.getSize()) << "\n"; + }); + Size = std::min(Size + 1, Section.getSize()); + + ArrayRef Bytes = arrayRefFromStringRef(*SectionContentsOrErr); + uint64_t Offset = Address - SectionAddress; + uint8_t const *Begin = Bytes.data() + Offset; + uint8_t const *End = Bytes.data() + Offset + Size; + + LLVM_DEBUG({ + Expected SectionNameOrErr = Section.getName(); + if (!SectionNameOrErr) + consumeError(SectionNameOrErr.takeError()); + else + dbgs() << "Section Index: " << hexValue(Section.getIndex()) << " [" + << hexValue((uint64_t)Section.getAddress()) << ":" + << hexValue((uint64_t)Section.getAddress() + Section.getSize(), 10) + << "] Name: '" << *SectionNameOrErr << "'\n" + << "Begin: " << hexValue((uint64_t)Begin) + << ", End: " << hexValue((uint64_t)End) << "\n"; + }); + + // Address for first instruction line. + LVAddress FirstAddress = Address; + LVLines *Instructions = new LVLines(); + + while (Begin < End) { + MCInst Instruction; + uint64_t BytesConsumed = 0; + SmallVector InsnStr; + raw_svector_ostream Annotations(InsnStr); + MCDisassembler::DecodeStatus const S = + MD->getInstruction(Instruction, BytesConsumed, + ArrayRef(Begin, End), Address, outs()); + switch (S) { + case MCDisassembler::Fail: + LLVM_DEBUG({ dbgs() << "Invalid instruction\n"; }); + if (BytesConsumed == 0) + // Skip invalid bytes + BytesConsumed = 1; + break; + case MCDisassembler::SoftFail: + LLVM_DEBUG({ dbgs() << "Potentially undefined instruction:"; }); + LLVM_FALLTHROUGH; + case MCDisassembler::Success: { + std::string Buffer; + raw_string_ostream Stream(Buffer); + StringRef AnnotationsStr = Annotations.str(); + MIP.get()->printInst(&Instruction, Address, AnnotationsStr, *STI, Stream); + LLVM_DEBUG({ + std::string BufferCodes; + raw_string_ostream StreamCodes(BufferCodes); + StreamCodes << format_bytes( + ArrayRef(Begin, Begin + BytesConsumed), None, 16, 16); + dbgs() << "[" << hexValue((uint64_t)Begin) << "] " + << "Size: " << format_decimal(BytesConsumed, 2) << " (" + << formatv("{0}", + fmt_align(StreamCodes.str(), AlignStyle::Left, 32)) + << ") " << hexValue((uint64_t)Address) << ": " << Stream.str() + << "\n"; + }); + // Here we add logical lines to the Instructions. Later on, + // the 'processLines()' function will move each created logical line + // to its enclosing logical scope, using the debug ranges information + // and they will be released when its scope parent is deleted. + LVLineAssembler *Line = new LVLineAssembler(); + Line->setAddress(Address); + Line->setName(StringRef(Stream.str()).trim()); + Instructions->push_back(Line); + break; + } + } + Address += BytesConsumed; + Begin += BytesConsumed; + } + + LLVM_DEBUG({ + size_t Index = 0; + dbgs() << "\nAddress: " << hexValue(FirstAddress) + << format(" - Collected instructions lines: %d\n", + Instructions->size()); + for (const LVLine *Line : *Instructions) + dbgs() << format_decimal(++Index, 5) << ": " + << hexValue(Line->getOffset()) << ", (" << Line->getName() + << ")\n"; + }); + + // The scope in the assembler names is linked to its own instructions. + ScopeInstructions.add(SectionIndex, Scope, Instructions); + AssemblerMappings.add(SectionIndex, FirstAddress, Scope); + + return Error::success(); +} + +Error LVBinaryReader::createInstructions(LVScope *Function, + LVSectionIndex SectionIndex) { + if (!options().getPrintInstructions()) + return Error::success(); + + LVNameInfo Name = CompileUnit->findPublicName(Function); + if (Name.first != LVAddress(UINT64_MAX)) + return createInstructions(Function, SectionIndex, Name); + + return Error::success(); +} + +Error LVBinaryReader::createInstructions() { + if (!options().getPrintInstructions()) + return Error::success(); + + LLVM_DEBUG({ + dbgs() << "\nPublic Names (Scope):\n"; + for (LVPublicNames::const_reference Name : CompileUnit->getPublicNames()) { + LVScope *Scope = Name.first; + const LVNameInfo &NameInfo = Name.second; + LVAddress Address = NameInfo.first; + uint64_t Size = NameInfo.second; + dbgs() << "DIE Offset: " << hexValue(Scope->getOffset()) << " Range: [" + << hexValue(Address) << ":" << hexValue(Address + Size) << "] " + << "Name: '" << Scope->getName() << "' / '" + << Scope->getLinkageName() << "'\n"; + } + }); + + // For each public name in the current compile unit, create the line + // records that represent the executable instructions. + for (LVPublicNames::const_reference Name : CompileUnit->getPublicNames()) { + LVScope *Scope = Name.first; + // The symbol table extracted from the object file always contains a + // non-empty name (linkage name). However, the logical scope does not + // guarantee to have a name for the linkage name (main is one case). + // For those cases, set the linkage name the same as the name. + if (!Scope->getLinkageNameIndex()) + Scope->setLinkageName(Scope->getName()); + LVSectionIndex SectionIndex = getSymbolTableIndex(Scope->getLinkageName()); + if (Error Err = createInstructions(Scope, SectionIndex, Name.second)) + return Err; + } + + return Error::success(); +} + +// During the traversal of the debug information sections, we created the +// logical lines representing the disassembled instructions from the text +// section and the logical lines representing the line records from the +// debug line section. Using the ranges associated with the logical scopes, +// we will allocate those logical lines to their logical scopes. +void LVBinaryReader::processLines(LVLines *DebugLines, + LVSectionIndex SectionIndex, + LVScope *Function) { + assert(DebugLines && "DebugLines is null."); + + // Just return if this compilation unit does not have any line records + // and no instruction lines were created. + if (DebugLines->empty() && !options().getPrintInstructions()) + return; + + // Merge the debug lines and instruction lines using their text address; + // the logical line representing the debug line record is followed by the + // line(s) representing the disassembled instructions, whose addresses are + // equal or greater that the line address and less than the address of the + // next debug line record. + LLVM_DEBUG({ + size_t Index = 1; + size_t PerLine = 4; + dbgs() << format("\nProcess debug lines: %d\n", DebugLines->size()); + for (const LVLine *Line : *DebugLines) { + dbgs() << format_decimal(Index, 5) << ": " << hexValue(Line->getOffset()) + << ", (" << Line->getLineNumber() << ")"; + dbgs() << ((Index % PerLine) ? " " : "\n"); + ++Index; + } + dbgs() << ((Index % PerLine) ? "\n" : ""); + }); + + bool TraverseLines = true; + LVLines::iterator Iter = DebugLines->begin(); + while (TraverseLines && Iter != DebugLines->end()) { + uint64_t DebugAddress = (*Iter)->getAddress(); + + // Get the function with an entry point that matches this line and + // its associated assembler entries. In the case of COMDAT, the input + // 'Function' is not null. Use it to find its address ranges. + LVScope *Scope = Function; + if (!Function) { + Scope = AssemblerMappings.find(SectionIndex, DebugAddress); + if (!Scope) { + ++Iter; + continue; + } + } + + // Get the associated instructions for the found 'Scope'. + LVLines InstructionLines; + LVLines *Lines = ScopeInstructions.find(SectionIndex, Scope); + if (Lines) + InstructionLines.append(std::move(*Lines)); + + LLVM_DEBUG({ + size_t Index = 0; + dbgs() << format("\nProcess instructions lines: %d\n", + InstructionLines.size()); + for (const LVLine *Line : InstructionLines) + dbgs() << format_decimal(++Index, 5) << ": " + << hexValue(Line->getOffset()) << ", (" << Line->getName() + << ")\n"; + }); + + // Continue with next debug line if there are not instructions lines. + if (InstructionLines.empty()) { + ++Iter; + continue; + } + + for (LVLine *InstructionLine : InstructionLines) { + uint64_t InstructionAddress = InstructionLine->getAddress(); + LLVM_DEBUG({ + dbgs() << "Instruction address: " << hexValue(InstructionAddress) + << "\n"; + }); + if (TraverseLines) { + while (Iter != DebugLines->end()) { + DebugAddress = (*Iter)->getAddress(); + LLVM_DEBUG({ + bool IsDebug = (*Iter)->getIsLineDebug(); + dbgs() << "Line " << (IsDebug ? "dbg:" : "ins:") << " [" + << hexValue(DebugAddress) << "]"; + if (IsDebug) + dbgs() << format(" %d", (*Iter)->getLineNumber()); + dbgs() << "\n"; + }); + // Instruction address before debug line. + if (InstructionAddress < DebugAddress) { + LLVM_DEBUG({ + dbgs() << "Inserted instruction address: " + << hexValue(InstructionAddress) << " before line: " + << format("%d", (*Iter)->getLineNumber()) << " [" + << hexValue(DebugAddress) << "]\n"; + }); + Iter = DebugLines->insert(Iter, InstructionLine); + // The returned iterator points to the inserted instruction. + // Skip it and point to the line acting as reference. + ++Iter; + break; + } + ++Iter; + } + if (Iter == DebugLines->end()) { + // We have reached the end of the source lines and the current + // instruction line address is greater than the last source line. + TraverseLines = false; + DebugLines->push_back(InstructionLine); + } + } else { + DebugLines->push_back(InstructionLine); + } + } + } + + LLVM_DEBUG({ + dbgs() << format("Lines after merge: %d\n", DebugLines->size()); + size_t Index = 0; + for (const LVLine *Line : *DebugLines) { + dbgs() << format_decimal(++Index, 5) << ": " + << hexValue(Line->getOffset()) << ", (" + << ((Line->getIsLineDebug()) + ? Line->lineNumberAsStringStripped(/*ShowZero=*/true) + : Line->getName()) + << ")\n"; + } + }); + + // If this compilation unit does not have line records, traverse its scopes + // and take any collected instruction lines as the working set in order + // to move them to their associated scope. + if (DebugLines->empty()) { + if (const LVScopes *Scopes = CompileUnit->getScopes()) + for (LVScope *Scope : *Scopes) { + LVLines *Lines = ScopeInstructions.find(SectionIndex, Scope); + if (Lines) + DebugLines->append(std::move(*Lines)); + } + } + + LVRange *ScopesWithRanges = getSectionRanges(SectionIndex); + ScopesWithRanges->startSearch(); + + // Process collected lines. + LVScope *Scope; + for (LVLine *Line : *DebugLines) { + // Using the current line address, get its associated lexical scope and + // add the line information to it. + Scope = ScopesWithRanges->getEntry(Line->getAddress()); + if (!Scope) { + // If missing scope, use the compile unit. + Scope = CompileUnit; + LLVM_DEBUG({ + dbgs() << "Adding line to CU: " << hexValue(Line->getOffset()) << ", (" + << ((Line->getIsLineDebug()) + ? Line->lineNumberAsStringStripped(/*ShowZero=*/true) + : Line->getName()) + << ")\n"; + }); + } + + // Add line object to scope. + Scope->addElement(Line); + + // Report any line zero. + if (options().getWarningLines() && Line->getIsLineDebug() && + !Line->getLineNumber()) + CompileUnit->addLineZero(Line); + + // Some compilers generate ranges in the compile unit; other compilers + // only DW_AT_low_pc/DW_AT_high_pc. In order to correctly map global + // variables, we need to generate the map ranges for the compile unit. + // If we use the ranges stored at the scope level, there are cases where + // the address referenced by a symbol location, is not in the enclosing + // scope, but in an outer one. By using the ranges stored in the compile + // unit, we can catch all those addresses. + if (Line->getIsLineDebug()) + CompileUnit->addMapping(Line, SectionIndex); + + // Resolve any given pattern. + patterns().resolvePatternMatch(Line); + } + + ScopesWithRanges->endSearch(); +} + +void LVBinaryReader::processLines(LVLines *DebugLines, + LVSectionIndex SectionIndex) { + assert(DebugLines && "DebugLines is null."); + if (DebugLines->empty() && !ScopeInstructions.findMap(SectionIndex)) + return; + + // If the Compile Unit does not contain comdat functions, use the whole + // set of debug lines, as the addresses don't have conflicts. + if (!CompileUnit->getHasComdatScopes()) { + processLines(DebugLines, SectionIndex, nullptr); + return; + } + + // Find the indexes for the lines whose address is zero. + std::vector AddressZero; + LVLines::iterator It = + std::find_if(std::begin(*DebugLines), std::end(*DebugLines), + [](LVLine *Line) { return !Line->getAddress(); }); + while (It != std::end(*DebugLines)) { + AddressZero.emplace_back(std::distance(std::begin(*DebugLines), It)); + It = std::find_if(std::next(It), std::end(*DebugLines), + [](LVLine *Line) { return !Line->getAddress(); }); + } + + // If the set of debug lines does not contain any line with address zero, + // use the whole set. It means we are dealing with an initialization + // section from a fully linked binary. + if (AddressZero.empty()) { + processLines(DebugLines, SectionIndex, nullptr); + return; + } + + // The Compile unit contains comdat functions. Traverse the collected + // debug lines and identify logical groups based on their start and + // address. Each group starts with a zero address. + // Begin, End, Address, IsDone. + using LVBucket = std::tuple; + std::vector Buckets; + + LVAddress Address; + size_t Begin = 0; + size_t End = 0; + size_t Index = 0; + for (Index = 0; Index < AddressZero.size() - 1; ++Index) { + Begin = AddressZero[Index]; + End = AddressZero[Index + 1] - 1; + Address = (*DebugLines)[End]->getAddress(); + Buckets.emplace_back(Begin, End, Address, false); + } + + // Add the last bucket. + if (Index) { + Begin = AddressZero[Index]; + End = DebugLines->size() - 1; + Address = (*DebugLines)[End]->getAddress(); + Buckets.emplace_back(Begin, End, Address, false); + } + + LLVM_DEBUG({ + dbgs() << "\nDebug Lines buckets: " << Buckets.size() << "\n"; + for (LVBucket &Bucket : Buckets) { + dbgs() << "Begin: " << format_decimal(std::get<0>(Bucket), 5) << ", " + << "End: " << format_decimal(std::get<1>(Bucket), 5) << ", " + << "Address: " << hexValue(std::get<2>(Bucket)) << "\n"; + } + }); + + // Traverse the sections and buckets looking for matches on the section + // sizes. In the unlikely event of different buckets with the same size + // process them in order and mark them as done. + LVLines Group; + for (LVSections::reference Entry : Sections) { + LVSectionIndex SectionIndex = Entry.first; + const object::SectionRef Section = Entry.second; + uint64_t Size = Section.getSize(); + LLVM_DEBUG({ + dbgs() << "\nSection Index: " << format_decimal(SectionIndex, 3) + << " , Section Size: " << hexValue(Section.getSize()) + << " , Section Address: " << hexValue(Section.getAddress()) + << "\n"; + }); + + for (LVBucket &Bucket : Buckets) { + if (std::get<3>(Bucket)) + // Already done for previous section. + continue; + if (Size == std::get<2>(Bucket)) { + // We have a match on the section size. + Group.clear(); + LVLines::iterator IterStart = DebugLines->begin() + std::get<0>(Bucket); + LVLines::iterator IterEnd = + DebugLines->begin() + std::get<1>(Bucket) + 1; + for (LVLines::iterator Iter = IterStart; Iter < IterEnd; ++Iter) + Group.push_back(*Iter); + processLines(&Group, SectionIndex, /*Function=*/nullptr); + std::get<3>(Bucket) = true; + break; + } + } + } +} + +void LVBinaryReader::print(raw_ostream &OS) const { + OS << "LVBinaryReader\n"; + LLVM_DEBUG(dbgs() << "PrintReader\n"); +} Index: llvm/lib/DebugInfo/LogicalView/Readers/LVELFReader.cpp =================================================================== --- /dev/null +++ llvm/lib/DebugInfo/LogicalView/Readers/LVELFReader.cpp @@ -0,0 +1,1235 @@ +//===-- LVELFReader.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 +// +//===----------------------------------------------------------------------===// +// +// This implements the LVELFReader class. +// It supports ELF and Mach-O formats. +// +//===----------------------------------------------------------------------===// + +#include "llvm/DebugInfo/LogicalView/Readers/LVELFReader.h" +#include "llvm/DebugInfo/DIContext.h" +#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" +#include "llvm/DebugInfo/DWARF/DWARFExpression.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/Object/Error.h" +#include "llvm/Object/MachO.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::logicalview; + +#define DEBUG_TYPE "ElfReader" + +LVElement *LVELFReader::createElement(dwarf::Tag Tag) { + CurrentScope = nullptr; + CurrentSymbol = nullptr; + CurrentType = nullptr; + CurrentRanges.clear(); + + if (!options().getPrintSymbols()) { + switch (Tag) { + // As the command line options did not specify a request to print + // logical symbols (--print=symbols or --print=all or --print=elements), + // skip its creation. + case dwarf::DW_TAG_formal_parameter: + case dwarf::DW_TAG_unspecified_parameters: + case dwarf::DW_TAG_member: + case dwarf::DW_TAG_variable: + case dwarf::DW_TAG_inheritance: + case dwarf::DW_TAG_constant: + case dwarf::DW_TAG_call_site_parameter: + case dwarf::DW_TAG_GNU_call_site_parameter: + return nullptr; + default: + break; + } + } + + switch (Tag) { + // Types. + case dwarf::DW_TAG_base_type: + CurrentType = new LVType(); + CurrentType->setIsBase(); + if (options().getAttributeBase()) + CurrentType->setIncludeInPrint(); + return CurrentType; + case dwarf::DW_TAG_const_type: + CurrentType = new LVType(); + CurrentType->setIsConst(); + CurrentType->setName("const"); + return CurrentType; + case dwarf::DW_TAG_enumerator: + CurrentType = new LVTypeEnumerator(); + return CurrentType; + case dwarf::DW_TAG_imported_declaration: + CurrentType = new LVTypeImport(); + CurrentType->setIsImportDeclaration(); + return CurrentType; + case dwarf::DW_TAG_imported_module: + CurrentType = new LVTypeImport(); + CurrentType->setIsImportModule(); + return CurrentType; + case dwarf::DW_TAG_pointer_type: + CurrentType = new LVType(); + CurrentType->setIsPointer(); + CurrentType->setName("*"); + return CurrentType; + case dwarf::DW_TAG_ptr_to_member_type: + CurrentType = new LVType(); + CurrentType->setIsPointerMember(); + CurrentType->setName("*"); + return CurrentType; + case dwarf::DW_TAG_reference_type: + CurrentType = new LVType(); + CurrentType->setIsReference(); + CurrentType->setName("&"); + return CurrentType; + case dwarf::DW_TAG_restrict_type: + CurrentType = new LVType(); + CurrentType->setIsRestrict(); + CurrentType->setName("restrict"); + return CurrentType; + case dwarf::DW_TAG_rvalue_reference_type: + CurrentType = new LVType(); + CurrentType->setIsRvalueReference(); + CurrentType->setName("&&"); + return CurrentType; + case dwarf::DW_TAG_subrange_type: + CurrentType = new LVTypeSubrange(); + return CurrentType; + case dwarf::DW_TAG_template_value_parameter: + CurrentType = new LVTypeParam(); + CurrentType->setIsTemplateValueParam(); + return CurrentType; + case dwarf::DW_TAG_template_type_parameter: + CurrentType = new LVTypeParam(); + CurrentType->setIsTemplateTypeParam(); + return CurrentType; + case dwarf::DW_TAG_GNU_template_template_param: + CurrentType = new LVTypeParam(); + CurrentType->setIsTemplateTemplateParam(); + return CurrentType; + case dwarf::DW_TAG_typedef: + CurrentType = new LVTypeDefinition(); + return CurrentType; + case dwarf::DW_TAG_unspecified_type: + CurrentType = new LVType(); + CurrentType->setIsUnspecified(); + return CurrentType; + case dwarf::DW_TAG_volatile_type: + CurrentType = new LVType(); + CurrentType->setIsVolatile(); + CurrentType->setName("volatile"); + return CurrentType; + + // Symbols. + case dwarf::DW_TAG_formal_parameter: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsParameter(); + return CurrentSymbol; + case dwarf::DW_TAG_unspecified_parameters: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsUnspecified(); + CurrentSymbol->setName("..."); + return CurrentSymbol; + case dwarf::DW_TAG_member: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsMember(); + return CurrentSymbol; + case dwarf::DW_TAG_variable: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsVariable(); + return CurrentSymbol; + case dwarf::DW_TAG_inheritance: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsInheritance(); + return CurrentSymbol; + case dwarf::DW_TAG_call_site_parameter: + case dwarf::DW_TAG_GNU_call_site_parameter: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsCallSiteParameter(); + return CurrentSymbol; + case dwarf::DW_TAG_constant: + CurrentSymbol = new LVSymbol(); + CurrentSymbol->setIsConstant(); + return CurrentSymbol; + + // Scopes. + case dwarf::DW_TAG_catch_block: + CurrentScope = new LVScope(); + CurrentScope->setIsCatchBlock(); + return CurrentScope; + case dwarf::DW_TAG_lexical_block: + CurrentScope = new LVScope(); + CurrentScope->setIsLexicalBlock(); + return CurrentScope; + case dwarf::DW_TAG_try_block: + CurrentScope = new LVScope(); + CurrentScope->setIsTryBlock(); + return CurrentScope; + case dwarf::DW_TAG_compile_unit: + case dwarf::DW_TAG_skeleton_unit: + CurrentScope = new LVScopeCompileUnit(); + CompileUnit = static_cast(CurrentScope); + return CurrentScope; + case dwarf::DW_TAG_inlined_subroutine: + CurrentScope = new LVScopeFunctionInlined(); + return CurrentScope; + case dwarf::DW_TAG_namespace: + CurrentScope = new LVScopeNamespace(); + return CurrentScope; + case dwarf::DW_TAG_template_alias: + CurrentScope = new LVScopeAlias(); + return CurrentScope; + case dwarf::DW_TAG_array_type: + CurrentScope = new LVScopeArray(); + return CurrentScope; + case dwarf::DW_TAG_call_site: + case dwarf::DW_TAG_GNU_call_site: + CurrentScope = new LVScopeFunction(); + CurrentScope->setIsCallSite(); + return CurrentScope; + case dwarf::DW_TAG_entry_point: + CurrentScope = new LVScopeFunction(); + CurrentScope->setIsEntryPoint(); + return CurrentScope; + case dwarf::DW_TAG_subprogram: + CurrentScope = new LVScopeFunction(); + CurrentScope->setIsSubprogram(); + return CurrentScope; + case dwarf::DW_TAG_subroutine_type: + CurrentScope = new LVScopeFunctionType(); + return CurrentScope; + case dwarf::DW_TAG_label: + CurrentScope = new LVScopeFunction(); + CurrentScope->setIsLabel(); + return CurrentScope; + case dwarf::DW_TAG_class_type: + CurrentScope = new LVScopeAggregate(); + CurrentScope->setIsClass(); + return CurrentScope; + case dwarf::DW_TAG_structure_type: + CurrentScope = new LVScopeAggregate(); + CurrentScope->setIsStructure(); + return CurrentScope; + case dwarf::DW_TAG_union_type: + CurrentScope = new LVScopeAggregate(); + CurrentScope->setIsUnion(); + return CurrentScope; + case dwarf::DW_TAG_enumeration_type: + CurrentScope = new LVScopeEnumeration(); + return CurrentScope; + case dwarf::DW_TAG_GNU_formal_parameter_pack: + CurrentScope = new LVScopeFormalPack(); + return CurrentScope; + case dwarf::DW_TAG_GNU_template_parameter_pack: + CurrentScope = new LVScopeTemplatePack(); + return CurrentScope; + default: + // Collect TAGs not implemented. + if (options().getInternalTag() && Tag) + CompileUnit->addDebugTag(Tag, CurrentOffset); + break; + } + return nullptr; +} + +void LVELFReader::processOneAttribute(const DWARFDie &Die, LVOffset *OffsetPtr, + const AttributeSpec &AttrSpec) { + uint64_t OffsetOnEntry = *OffsetPtr; + DWARFUnit *U = Die.getDwarfUnit(); + const DWARFFormValue &FormValue = + DWARFFormValue::createFromUnit(AttrSpec.Form, U, OffsetPtr); + + // We are processing .debug_info section, implicit_const attribute + // values are not really stored here, but in .debug_abbrev section. + auto GetAsUnsignedConstant = [&]() -> int64_t { + return AttrSpec.isImplicitConst() ? AttrSpec.getImplicitConstValue() + : *FormValue.getAsUnsignedConstant(); + }; + + auto GetFlag = [](const DWARFFormValue &FormValue) -> bool { + return FormValue.isFormClass(DWARFFormValue::FC_Flag); + }; + + auto GetBoundValue = [](const DWARFFormValue &FormValue) -> int64_t { + switch (FormValue.getForm()) { + case dwarf::DW_FORM_ref_addr: + case dwarf::DW_FORM_ref1: + case dwarf::DW_FORM_ref2: + case dwarf::DW_FORM_ref4: + case dwarf::DW_FORM_ref8: + case dwarf::DW_FORM_ref_udata: + case dwarf::DW_FORM_ref_sig8: + return *FormValue.getAsReferenceUVal(); + case dwarf::DW_FORM_data1: + case dwarf::DW_FORM_flag: + case dwarf::DW_FORM_data2: + case dwarf::DW_FORM_data4: + case dwarf::DW_FORM_data8: + case dwarf::DW_FORM_udata: + case dwarf::DW_FORM_ref_sup4: + case dwarf::DW_FORM_ref_sup8: + return *FormValue.getAsUnsignedConstant(); + case dwarf::DW_FORM_sdata: + return *FormValue.getAsSignedConstant(); + default: + return 0; + } + }; + + LLVM_DEBUG({ + dbgs() << " " << hexValue(OffsetOnEntry) + << formatv(" {0}", AttrSpec.Attr) << "\n"; + }); + + switch (AttrSpec.Attr) { + case dwarf::DW_AT_accessibility: + CurrentElement->setAccessibilityCode(*FormValue.getAsUnsignedConstant()); + break; + case dwarf::DW_AT_artificial: + CurrentElement->setIsArtificial(); + break; + case dwarf::DW_AT_bit_size: + CurrentElement->setBitSize(*FormValue.getAsUnsignedConstant()); + break; + case dwarf::DW_AT_call_file: + CurrentElement->setCallFilenameIndex(GetAsUnsignedConstant()); + break; + case dwarf::DW_AT_call_line: + CurrentElement->setCallLineNumber(IncrementFileIndex + ? GetAsUnsignedConstant() + 1 + : GetAsUnsignedConstant()); + break; + case dwarf::DW_AT_comp_dir: + CompileUnit->setCompilationDirectory(dwarf::toStringRef(FormValue)); + break; + case dwarf::DW_AT_const_value: + if (FormValue.isFormClass(DWARFFormValue::FC_Block)) { + ArrayRef Expr = *FormValue.getAsBlock(); + // Store the expression as a hexadecimal string. + CurrentElement->setValue( + llvm::toHex(llvm::toStringRef(Expr), /*LowerCase=*/true)); + } else if (FormValue.isFormClass(DWARFFormValue::FC_Constant)) { + // In the case of negative values, generate the string representation + // for a positive value prefixed with the negative sign. + if (FormValue.getForm() == dwarf::DW_FORM_sdata) { + std::stringstream Stream; + int64_t Value = *FormValue.getAsSignedConstant(); + if (Value < 0) { + Stream << "-"; + Value = std::abs(Value); + } + Stream << hexString(Value, 2); + CurrentElement->setValue(Stream.str()); + } else + CurrentElement->setValue( + hexString(*FormValue.getAsUnsignedConstant(), 2)); + } else + CurrentElement->setValue(dwarf::toStringRef(FormValue)); + break; + case dwarf::DW_AT_count: + CurrentElement->setCount(*FormValue.getAsUnsignedConstant()); + break; + case dwarf::DW_AT_decl_line: + CurrentElement->setLineNumber(GetAsUnsignedConstant()); + break; + case dwarf::DW_AT_decl_file: + CurrentElement->setFilenameIndex(IncrementFileIndex + ? GetAsUnsignedConstant() + 1 + : GetAsUnsignedConstant()); + break; + case dwarf::DW_AT_enum_class: + if (GetFlag(FormValue)) + CurrentElement->setIsEnumClass(); + break; + case dwarf::DW_AT_external: + if (GetFlag(FormValue)) + CurrentElement->setIsExternal(); + break; + case dwarf::DW_AT_GNU_discriminator: + CurrentElement->setDiscriminator(*FormValue.getAsUnsignedConstant()); + break; + case dwarf::DW_AT_inline: + CurrentElement->setInlineCode(*FormValue.getAsUnsignedConstant()); + break; + case dwarf::DW_AT_lower_bound: + CurrentElement->setLowerBound(GetBoundValue(FormValue)); + break; + case dwarf::DW_AT_name: + CurrentElement->setName(dwarf::toStringRef(FormValue)); + break; + case dwarf::DW_AT_linkage_name: + case dwarf::DW_AT_MIPS_linkage_name: + CurrentElement->setLinkageName(dwarf::toStringRef(FormValue)); + break; + case dwarf::DW_AT_producer: + if (options().getAttributeProducer()) + CurrentElement->setProducer(dwarf::toStringRef(FormValue)); + break; + case dwarf::DW_AT_upper_bound: + CurrentElement->setUpperBound(GetBoundValue(FormValue)); + break; + case dwarf::DW_AT_virtuality: + CurrentElement->setVirtualityCode(*FormValue.getAsUnsignedConstant()); + break; + + case dwarf::DW_AT_abstract_origin: + case dwarf::DW_AT_call_origin: + case dwarf::DW_AT_extension: + case dwarf::DW_AT_import: + case dwarf::DW_AT_specification: + case dwarf::DW_AT_type: + updateReference(AttrSpec.Attr, FormValue); + break; + + case dwarf::DW_AT_low_pc: + if (options().getGeneralCollectRanges()) { + FoundLowPC = true; + // For toolchains that support the removal of unused code, the linker + // marks functions that have been removed, by setting the value for the + // low_pc to the max address. + if (Optional Value = FormValue.getAsAddress()) { + CurrentLowPC = Value.value(); + } else { + uint64_t UValue = FormValue.getRawUValue(); + if (U->getAddrOffsetSectionItem(UValue)) { + CurrentLowPC = *FormValue.getAsAddress(); + } else { + FoundLowPC = false; + // We are dealing with an index into the .debug_addr section. + LLVM_DEBUG({ + dbgs() << format("indexed (%8.8x) address = ", (uint32_t)UValue); + }); + } + } + if (FoundLowPC) { + if (CurrentLowPC == MaxAddress) + CurrentElement->setIsDiscarded(); + if (CurrentElement->isCompileUnit()) + setCUBaseAddress(CurrentLowPC); + } + } + break; + + case dwarf::DW_AT_high_pc: + if (options().getGeneralCollectRanges()) { + FoundHighPC = true; + if (Optional Address = FormValue.getAsAddress()) + // High PC is an address. + CurrentHighPC = *Address; + if (Optional Offset = FormValue.getAsUnsignedConstant()) + // High PC is an offset from LowPC. + CurrentHighPC = CurrentLowPC + *Offset; + // Store the real upper limit for the address range. + if (UpdateHighAddress && CurrentHighPC > 0) + --CurrentHighPC; + if (CurrentElement->isCompileUnit()) + setCUHighAddress(CurrentHighPC); + } + break; + + case dwarf::DW_AT_ranges: + if (RangesDataAvailable && options().getGeneralCollectRanges()) { + auto GetRanges = [](const DWARFFormValue &FormValue, + DWARFUnit *U) -> Expected { + if (FormValue.getForm() == dwarf::DW_FORM_rnglistx) + return U->findRnglistFromIndex(*FormValue.getAsSectionOffset()); + return U->findRnglistFromOffset(*FormValue.getAsSectionOffset()); + }; + Expected RangesOrError = + GetRanges(FormValue, U); + if (!RangesOrError) { + LLVM_DEBUG({ + std::string TheError(toString(RangesOrError.takeError())); + dbgs() << format("error decoding address ranges = ", + TheError.c_str()); + }); + consumeError(RangesOrError.takeError()); + break; + } + // The address ranges are absolute. There is no need to add any addend. + DWARFAddressRangesVector Ranges = RangesOrError.get(); + for (DWARFAddressRange &Range : Ranges) { + // This seems to be a tombstone for empty ranges. + if (Range.LowPC == Range.HighPC) + continue; + // Store the real upper limit for the address range. + if (UpdateHighAddress && Range.HighPC > 0) + --Range.HighPC; + // Add the pair of addresses. + CurrentScope->addObject(Range.LowPC, Range.HighPC); + // If the scope is the CU, do not update the ranges set. + if (!CurrentElement->isCompileUnit()) + CurrentRanges.emplace_back(Range.LowPC, Range.HighPC); + } + } + break; + + // Get the location list for the symbol. + case dwarf::DW_AT_data_member_location: + if (options().getAttributeAnyLocation()) + processLocationMember(AttrSpec.Attr, FormValue, Die, OffsetOnEntry); + break; + + // Get the location list for the symbol. + case dwarf::DW_AT_location: + case dwarf::DW_AT_string_length: + case dwarf::DW_AT_use_location: + if (options().getAttributeAnyLocation() && CurrentSymbol) + processLocationList(AttrSpec.Attr, FormValue, Die, OffsetOnEntry); + break; + + case dwarf::DW_AT_call_data_value: + case dwarf::DW_AT_call_value: + case dwarf::DW_AT_GNU_call_site_data_value: + case dwarf::DW_AT_GNU_call_site_value: + if (options().getAttributeAnyLocation() && CurrentSymbol) + processLocationList(AttrSpec.Attr, FormValue, Die, OffsetOnEntry, + /*CallSiteLocation=*/true); + break; + + default: + break; + } +} + +LVScope *LVELFReader::processOneDie(const DWARFDie &InputDIE, LVScope *Parent, + DWARFDie &SkeletonDie) { + // If the input DIE corresponds to the compile unit, it can be: + // a) Simple DWARF: a standard DIE. Ignore the skeleton DIE (is empty). + // b) Split DWARF: the DIE for the split DWARF. The skeleton is the DIE + // for the skeleton DWARF. Process both DIEs. + const DWARFDie &DIE = SkeletonDie.isValid() ? SkeletonDie : InputDIE; + DWARFDataExtractor DebugInfoData = + DIE.getDwarfUnit()->getDebugInfoExtractor(); + LVOffset Offset = DIE.getOffset(); + + // Reset values for the current DIE. + CurrentLowPC = 0; + CurrentHighPC = 0; + CurrentOffset = Offset; + CurrentEndOffset = 0; + FoundLowPC = false; + FoundHighPC = false; + + // Process supported attributes. + if (DebugInfoData.isValidOffset(Offset)) { + + LLVM_DEBUG({ + dbgs() << "DIE: " << hexValue(Offset) << formatv(" {0}", DIE.getTag()) + << "\n"; + }); + + // Create the logical view element for the current DIE. + dwarf::Tag Tag = DIE.getTag(); + CurrentElement = createElement(Tag); + if (!CurrentElement) + return CurrentScope; + + CurrentElement->setTag(Tag); + CurrentElement->setOffset(Offset); + + if (options().getAttributeAnySource() && CurrentElement->isCompileUnit()) + addCompileUnitOffset(Offset, + static_cast(CurrentElement)); + + // Insert the newly created element into the element symbol table. If the + // element is in the list, it means there are previously created elements + // referencing this element. + if (ElementTable.find(Offset) == ElementTable.end()) { + // No previous references to this offset. + ElementTable.emplace( + std::piecewise_construct, std::forward_as_tuple(Offset), + std::forward_as_tuple(CurrentElement, LVElementSet())); + } else { + // There are previous references to this element. We need to update the + // element and all the references pointing to this element. + LVElementEntry &Reference = ElementTable[Offset]; + Reference.first = CurrentElement; + // Traverse the element set and update the elements (backtracking). + // Using the bit associated with 'type' or 'reference' allows us to set + // the correct target. + for (LVElement *Target : Reference.second) + Target->getHasReference() ? Target->setReference(CurrentElement) + : Target->setType(CurrentElement); + // Clear the pending elements. + Reference.second.clear(); + } + + // Add the current element to its parent as there are attributes + // (locations) that require the scope level. + if (CurrentScope) + Parent->addElement(CurrentScope); + else if (CurrentSymbol) + Parent->addElement(CurrentSymbol); + else if (CurrentType) + Parent->addElement(CurrentType); + + // Process the attributes for the given DIE. + auto ProcessAttributes = [&](const DWARFDie &TheDIE, + DWARFDataExtractor &DebugData) { + CurrentEndOffset = Offset; + uint32_t abbrCode = DebugData.getULEB128(&CurrentEndOffset); + if (abbrCode) { + if (const DWARFAbbreviationDeclaration *AbbrevDecl = + TheDIE.getAbbreviationDeclarationPtr()) + if (AbbrevDecl) + for (const DWARFAbbreviationDeclaration::AttributeSpec &AttrSpec : + AbbrevDecl->attributes()) + processOneAttribute(TheDIE, &CurrentEndOffset, AttrSpec); + } + }; + + ProcessAttributes(DIE, DebugInfoData); + + // If the input DIE is for a compile unit, process its attributes in + // the case of split DWARF, to override any common attribute values. + if (SkeletonDie.isValid()) { + DWARFDataExtractor DebugInfoData = + InputDIE.getDwarfUnit()->getDebugInfoExtractor(); + LVOffset Offset = InputDIE.getOffset(); + if (DebugInfoData.isValidOffset(Offset)) + ProcessAttributes(InputDIE, DebugInfoData); + } + } + + if (CurrentScope) { + if (CurrentScope->getCanHaveRanges()) { + // If the scope has ranges, they are already added to the scope. + // Add any collected LowPC/HighPC values. + bool IsCompileUnit = CurrentScope->getIsCompileUnit(); + if (FoundLowPC && FoundHighPC) { + CurrentScope->addObject(CurrentLowPC, CurrentHighPC); + if (!IsCompileUnit) { + // If the scope is a function, add it to the public names. + if ((options().getAttributePublics() || + options().getPrintAnyLine()) && + CurrentScope->getIsFunction() && + !CurrentScope->getIsInlinedFunction()) + CompileUnit->addPublicName(CurrentScope, CurrentLowPC, + CurrentHighPC); + } + } + + // Look for scopes with ranges and no linkage name information that + // are referencing another scopes via DW_AT_specification. They are + // possible candidates for a comdat scope. + if (CurrentScope->getHasRanges() && + !CurrentScope->getLinkageNameIndex() && + CurrentScope->getHasReferenceSpecification()) { + // Get the linkage name in order to search for a possible comdat. + Optional LinkageDIE = + DIE.findRecursively(dwarf::DW_AT_linkage_name); + if (LinkageDIE.has_value()) { + StringRef Name(dwarf::toStringRef(LinkageDIE)); + if (!Name.empty()) + CurrentScope->setLinkageName(Name); + } + } + + // If the current scope is in the 'LinkageNames' table, update its + // logical scope. For other scopes, always we will assume the default + // ".text" section index. + LVSectionIndex SectionIndex = updateSymbolTable(CurrentScope); + if (CurrentScope->getIsComdat()) + CompileUnit->setHasComdatScopes(); + + // Update section index contained ranges. + if (SectionIndex) { + if (!CurrentRanges.empty()) { + for (LVAddressRange &Range : CurrentRanges) + addSectionRange(SectionIndex, CurrentScope, Range.first, + Range.second); + CurrentRanges.clear(); + } + // If the scope is the CU, do not update the ranges set. + if (FoundLowPC && FoundHighPC && !IsCompileUnit) { + addSectionRange(SectionIndex, CurrentScope, CurrentLowPC, + CurrentHighPC); + } + } + } + // Mark member functions. + if (Parent->getIsAggregate()) + CurrentScope->setIsMember(); + } + + // Keep track of symbols with locations. + if (options().getAttributeAnyLocation() && CurrentSymbol && + CurrentSymbol->getHasLocation()) + SymbolsWithLocations.push_back(CurrentSymbol); + + // If we have template parameters, mark the parent as template. + if (CurrentType && CurrentType->getIsTemplateParam()) + Parent->setIsTemplate(); + + return CurrentScope; +} + +void LVELFReader::traverseDieAndChildren(DWARFDie &DIE, LVScope *Parent, + DWARFDie &SkeletonDie) { + // Process the current DIE. + LVScope *Scope = processOneDie(DIE, Parent, SkeletonDie); + if (Scope) { + LVOffset Lower = DIE.getOffset(); + LVOffset Upper = CurrentEndOffset; + DWARFDie DummyDie; + // Traverse the children chain. + DWARFDie Child = DIE.getFirstChild(); + while (Child) { + traverseDieAndChildren(Child, Scope, DummyDie); + Upper = Child.getOffset(); + Child = Child.getSibling(); + } + // Calculate contributions to the debug info section. + if (options().getPrintSizes() && Upper) + CompileUnit->addSize(Scope, Lower, Upper); + } +} + +void LVELFReader::processLocationGaps() { + if (options().getAttributeAnyLocation()) + for (LVSymbol *Symbol : SymbolsWithLocations) + Symbol->fillLocationGaps(); +} + +void LVELFReader::createLineAndFileRecords( + const DWARFDebugLine::LineTable *Lines) { + if (!Lines) + return; + + // Get the source filenames. + if (!Lines->Prologue.FileNames.empty()) + for (const DWARFDebugLine::FileNameEntry &Entry : + Lines->Prologue.FileNames) { + std::string Directory; + if (Lines->getDirectoryForEntry(Entry, Directory)) + Directory = transformPath(Directory); + if (Directory.empty()) + Directory = std::string(CompileUnit->getCompilationDirectory()); + std::string File = transformPath(dwarf::toStringRef(Entry.Name)); + std::string String; + raw_string_ostream(String) << Directory << "/" << File; + CompileUnit->addFilename(String); + } + + // In DWARF5 the file indexes start at 0; + bool IncrementIndex = Lines->Prologue.getVersion() >= 5; + + // Get the source lines. + if ((options().getAttributeRange() || options().getPrintLines()) && + Lines->Rows.size()) + for (const DWARFDebugLine::Row &Row : Lines->Rows) { + // Here we collect logical debug lines in CULines. Later on, + // the 'processLines()' function will move each created logical line + // to its enclosing logical scope, using the debug ranges information + // and they will be released when its scope parent is deleted. + LVLineDebug *Line = new LVLineDebug(); + CULines.push_back(Line); + Line->setAddress(Row.Address.Address); + Line->setFilename( + CompileUnit->getFilename(IncrementIndex ? Row.File + 1 : Row.File)); + Line->setLineNumber(Row.Line); + if (Row.Discriminator) + Line->setDiscriminator(Row.Discriminator); + if (Row.IsStmt) + Line->setIsNewStatement(); + if (Row.BasicBlock) + Line->setIsBasicBlock(); + if (Row.EndSequence) + Line->setIsEndSequence(); + if (Row.EpilogueBegin) + Line->setIsEpilogueBegin(); + if (Row.PrologueEnd) + Line->setIsPrologueEnd(); + LLVM_DEBUG({ + dbgs() << "Address: " << hexValue(Line->getAddress()) + << " Line: " << Line->lineNumberAsString(/*ShowZero=*/true) + << "\n"; + }); + } +} + +std::string LVELFReader::getRegisterName(LVSmall Opcode, uint64_t Operands[2]) { + // The 'prettyPrintRegisterOp' function uses the DWARFUnit to support + // DW_OP_regval_type. At this point we are operating on a logical view + // item, with no access to the underlying DWARF data used by LLVM. + // We do not support DW_OP_regval_type here. + if (Opcode == dwarf::DW_OP_regval_type) + return {}; + + std::string string; + raw_string_ostream Stream(string); + DIDumpOptions DumpOpts; + DWARFExpression::prettyPrintRegisterOp(/*U=*/nullptr, Stream, DumpOpts, + Opcode, Operands, MRI.get(), + /*isEH=*/false); + return Stream.str(); +} + +Error LVELFReader::createScopes() { + LLVM_DEBUG({ + W.startLine() << "\n"; + W.printString("File", Obj.getFileName().str()); + W.printString("Format", FileFormatName); + }); + + if (Error Err = LVReader::createScopes()) + return Err; + + // As the DwarfContext object is valid only during the scopes creation, + // we need to create our own Target information, to be used during the + // logical view printing, in the case of instructions being requested. + std::unique_ptr DwarfContext = DWARFContext::create(Obj); + if (!DwarfContext) + return createStringError(errc::invalid_argument, + "Could not create DWARF information: %s", + getFilename().str().c_str()); + + if (Error Err = loadTargetInfo(Obj)) + return Err; + + // Create a mapping for virtual addresses. + mapVirtualAddress(Obj); + + // Select the correct compile unit range, depending if we are dealing with + // a standard or split DWARF object. + DWARFContext::compile_unit_range CompileUnits = + DwarfContext->getNumCompileUnits() ? DwarfContext->compile_units() + : DwarfContext->dwo_compile_units(); + for (const std::unique_ptr &CU : CompileUnits) { + + // Deduction of index used for the line records. + // + // For the following test case: test.cpp + // void foo(void ParamPtr) { } + + // Both GCC and Clang generate DWARF-5 .debug_line layout. + + // * GCC (GNU C++17 11.3.0) - All DW_AT_decl_file use index 1. + // + // .debug_info: + // format = DWARF32, version = 0x0005 + // DW_TAG_compile_unit + // DW_AT_name ("test.cpp") + // DW_TAG_subprogram ("foo") + // DW_AT_decl_file (1) + // DW_TAG_formal_parameter ("ParamPtr") + // DW_AT_decl_file (1) + // .debug_line: + // Line table prologue: format (DWARF32), version (5) + // include_directories[0] = "..." + // file_names[0]: name ("test.cpp"), dir_index (0) + // file_names[1]: name ("test.cpp"), dir_index (0) + + // * Clang (14.0.6) - All DW_AT_decl_file use index 0. + // + // .debug_info: + // format = DWARF32, version = 0x0005 + // DW_AT_producer ("clang version 14.0.6") + // DW_AT_name ("test.cpp") + // + // DW_TAG_subprogram ("foo") + // DW_AT_decl_file (0) + // DW_TAG_formal_parameter ("ParamPtr") + // DW_AT_decl_file (0) + // .debug_line: + // Line table prologue: format (DWARF32), version (5) + // include_directories[0] = "..." + // file_names[0]: name ("test.cpp"), dir_index (0) + + // From DWARFDebugLine::getFileNameByIndex documentation: + // In Dwarf 4, the files are 1-indexed. + // In Dwarf 5, the files are 0-indexed. + // Additional discussions here: + // https://www.mail-archive.com/dwarf-discuss@lists.dwarfstd.org/msg00883.html + + // The ELF Reader is expecting the files are 1-indexed, so using + // the .debug_line header information decide if the indexed require + // an internal adjustment. + + // For the case of GCC (DWARF5), if the entries[0] and [1] are the + // same, do not perform any adjustment. + auto DeduceIncrementFileIndex = [&]() -> bool { + if (CU->getVersion() < 5) + // DWARF-4 or earlier -> Don't increment index. + return false; + + if (const DWARFDebugLine::LineTable *LT = + CU->getContext().getLineTableForUnit(CU.get())) { + // Check if there are at least 2 entries and if they are the same. + if (LT->hasFileAtIndex(0) && LT->hasFileAtIndex(1)) { + const DWARFDebugLine::FileNameEntry &EntryZero = + LT->Prologue.getFileNameEntry(0); + const DWARFDebugLine::FileNameEntry &EntryOne = + LT->Prologue.getFileNameEntry(1); + // Check directory indexes. + if (EntryZero.DirIdx != EntryOne.DirIdx) + // DWARF-5 -> Increment index. + return true; + // Check filename. + std::string FileZero; + std::string FileOne; + StringRef None; + LT->getFileNameByIndex( + 0, None, DILineInfoSpecifier::FileLineInfoKind::RawValue, + FileZero); + LT->getFileNameByIndex( + 1, None, DILineInfoSpecifier::FileLineInfoKind::RawValue, + FileOne); + return FileZero.compare(FileOne); + } + } + + // DWARF-5 -> Increment index. + return true; + }; + // The ELF reader expects the indexes as 1-indexed. + IncrementFileIndex = DeduceIncrementFileIndex(); + + DWARFDie UnitDie = CU->getUnitDIE(); + SmallString<16> DWOAlternativeLocation; + if (UnitDie) { + Optional DWOFileName = + CU->getVersion() >= 5 + ? dwarf::toString(UnitDie.find(dwarf::DW_AT_dwo_name)) + : dwarf::toString(UnitDie.find(dwarf::DW_AT_GNU_dwo_name)); + StringRef From(DWOFileName.has_value() ? DWOFileName.value() : ""); + DWOAlternativeLocation = createAlternativePath(From); + } + + // The current CU can be a normal compile unit (standard) or a skeleton + // compile unit (split). For both cases, the returned die, will be used + // to create the logical scopes. + DWARFDie CUDie = CU->getNonSkeletonUnitDIE( + /*ExtractUnitDIEOnly=*/false, + /*DWOAlternativeLocation=*/DWOAlternativeLocation); + if (!CUDie.isValid()) + continue; + + // The current unit corresponds to the .dwo file. We need to get the + // skeleton unit and query for any ranges that will enclose any ranges + // in the non-skeleton unit. + DWARFDie DummyDie; + DWARFDie SkeletonDie = + CUDie.getDwarfUnit()->isDWOUnit() ? CU->getUnitDIE(false) : DummyDie; + // Disable the ranges processing if we have just a single .dwo object, + // as any DW_AT_ranges will access not available range information. + RangesDataAvailable = + (!CUDie.getDwarfUnit()->isDWOUnit() || + (SkeletonDie.isValid() ? !SkeletonDie.getDwarfUnit()->isDWOUnit() + : true)); + + traverseDieAndChildren(CUDie, Root, SkeletonDie); + + createLineAndFileRecords(DwarfContext->getLineTableForUnit(CU.get())); + if (Error Err = createInstructions()) + return Err; + + // Process the compilation unit, as there are cases where enclosed + // functions have the same ranges values. Insert the compilation unit + // ranges at the end, to allow enclosing ranges to be first in the list. + LVSectionIndex SectionIndex = getSectionIndex(CompileUnit); + addSectionRange(SectionIndex, CompileUnit); + LVRange *ScopesWithRanges = getSectionRanges(SectionIndex); + ScopesWithRanges->sort(); + + processLines(&CULines, SectionIndex); + processLocationGaps(); + + // These are per compile unit. + ScopesWithRanges->clear(); + SymbolsWithLocations.clear(); + CULines.clear(); + } + + return Error::success(); +} + +// Get the location information for the associated attribute. +void LVELFReader::processLocationList(dwarf::Attribute Attr, + const DWARFFormValue &FormValue, + const DWARFDie &Die, + uint64_t OffsetOnEntry, + bool CallSiteLocation) { + + auto ProcessLocationExpression = [&](const DWARFExpression &Expression) { + // DW_OP_const_type is variable-length and has 3 + // operands. DWARFExpression thus far only supports 2. + uint64_t Operands[2] = {0}; + for (const DWARFExpression::Operation &Op : Expression) { + DWARFExpression::Operation::Description Description = Op.getDescription(); + for (unsigned Operand = 0; Operand < 2; ++Operand) { + if (Description.Op[Operand] == DWARFExpression::Operation::SizeNA) + break; + Operands[Operand] = Op.getRawOperand(Operand); + } + CurrentSymbol->addLocationOperands(Op.getCode(), Operands[0], + Operands[1]); + } + }; + + DWARFUnit *U = Die.getDwarfUnit(); + DWARFContext &DwarfContext = U->getContext(); + bool IsLittleEndian = DwarfContext.isLittleEndian(); + if (FormValue.isFormClass(DWARFFormValue::FC_Block) || + (DWARFAttribute::mayHaveLocationExpr(Attr) && + FormValue.isFormClass(DWARFFormValue::FC_Exprloc))) { + ArrayRef Expr = *FormValue.getAsBlock(); + DataExtractor Data(StringRef((const char *)Expr.data(), Expr.size()), + IsLittleEndian, 0); + DWARFExpression Expression(Data, U->getAddressByteSize(), + U->getFormParams().Format); + + // Add location and operation entries. + CurrentSymbol->addLocation(Attr, /*LowPC=*/0, /*HighPC=*/-1, + /*SectionOffset=*/0, OffsetOnEntry, + CallSiteLocation); + ProcessLocationExpression(Expression); + return; + } + + if (DWARFAttribute::mayHaveLocationList(Attr) && + FormValue.isFormClass(DWARFFormValue::FC_SectionOffset)) { + uint64_t Offset = *FormValue.getAsSectionOffset(); + if (FormValue.getForm() == dwarf::DW_FORM_loclistx) { + Optional LoclistOffset = U->getLoclistOffset(Offset); + if (!LoclistOffset) + return; + Offset = *LoclistOffset; + } + uint64_t BaseAddr = 0; + if (Optional BA = U->getBaseAddress()) + BaseAddr = BA->Address; + LVAddress LowPC = 0; + LVAddress HighPC = 0; + + auto ProcessLocationEntry = [&](const DWARFLocationEntry &Entry) { + if (Entry.Kind == dwarf::DW_LLE_base_address) { + BaseAddr = Entry.Value0; + return; + } + if (Entry.Kind == dwarf::DW_LLE_offset_pair) { + LowPC = BaseAddr + Entry.Value0; + HighPC = BaseAddr + Entry.Value1; + DWARFAddressRange Range{LowPC, HighPC, Entry.SectionIndex}; + if (Range.SectionIndex == SectionedAddress::UndefSection) + Range.SectionIndex = Entry.SectionIndex; + DWARFLocationExpression Loc{Range, Entry.Loc}; + DWARFDataExtractor Data(Loc.Expr, IsLittleEndian, + U->getAddressByteSize()); + DWARFExpression Expression(Data, U->getAddressByteSize()); + + // Store the real upper limit for the address range. + if (UpdateHighAddress && HighPC > 0) + --HighPC; + // Add location and operation entries. + CurrentSymbol->addLocation(Attr, LowPC, HighPC, Offset, OffsetOnEntry, + CallSiteLocation); + ProcessLocationExpression(Expression); + } + }; + Error E = U->getLocationTable().visitLocationList( + &Offset, [&](const DWARFLocationEntry &E) { + ProcessLocationEntry(E); + return true; + }); + if (E) + consumeError(std::move(E)); + } +} + +void LVELFReader::processLocationMember(dwarf::Attribute Attr, + const DWARFFormValue &FormValue, + const DWARFDie &Die, + uint64_t OffsetOnEntry) { + // Check if the value is an integer constant. + if (FormValue.isFormClass(DWARFFormValue::FC_Constant)) + // Add a record to hold a constant as location. + CurrentSymbol->addLocationConstant(Attr, *FormValue.getAsUnsignedConstant(), + OffsetOnEntry); + else + // This is a a location description, or a reference to one. + processLocationList(Attr, FormValue, Die, OffsetOnEntry); +} + +// Update the current element with the reference. +void LVELFReader::updateReference(dwarf::Attribute Attr, + const DWARFFormValue &FormValue) { + // We are assuming that DW_AT_specification, DW_AT_abstract_origin, + // DW_AT_type and DW_AT_extension do not appear at the same time + // in the same DIE. + uint64_t Reference = *FormValue.getAsReference(); + // Get target for the given reference, if already created. + LVElement *Target = getElementForOffset(Reference, CurrentElement); + // Check if we are dealing with cross CU references. + if (FormValue.getForm() == dwarf::DW_FORM_ref_addr) { + if (Target) { + // The global reference is ready. Mark it as global. + Target->setIsGlobalReference(); + // Remove global reference from the unseen list. + removeGlobalOffset(Reference); + } else + // Record the unseen cross CU reference. + addGlobalOffset(Reference); + } + + // At this point, 'Target' can be null, in the case of the target element + // not being seen. But the correct bit is set, to indicate that the target + // is being referenced by (abstract_origin, extension, specification) or + // (import, type). + // We must differentiate between the kind of reference. This is needed to + // complete inlined function instances with dropped abstract references, + // in order to facilitate a logical comparison. + switch (Attr) { + case dwarf::DW_AT_abstract_origin: + case dwarf::DW_AT_call_origin: + CurrentElement->setReference(Target); + CurrentElement->setHasReferenceAbstract(); + break; + case dwarf::DW_AT_extension: + CurrentElement->setReference(Target); + CurrentElement->setHasReferenceExtension(); + break; + case dwarf::DW_AT_specification: + CurrentElement->setReference(Target); + CurrentElement->setHasReferenceSpecification(); + break; + case dwarf::DW_AT_import: + case dwarf::DW_AT_type: + CurrentElement->setType(Target); + break; + default: + break; + } +} + +// Get an element given the DIE offset. +LVElement *LVELFReader::getElementForOffset(LVOffset Offset, + LVElement *Element) { + LVElement *Target = nullptr; + // Search offset in the cross references. + LVElementReference::iterator Iter = ElementTable.find(Offset); + if (Iter == ElementTable.end()) + // Reference to an unseen element. + ElementTable.emplace(std::piecewise_construct, + std::forward_as_tuple(Offset), + std::forward_as_tuple(nullptr, LVElementSet{Element})); + else { + // There are previous references to this element. We need to update the + // element and all the references pointing to this element. + LVElementEntry &Reference = Iter->second; + Target = Reference.first; + if (!Target) + // Add the element to the set. + Reference.second.insert(Element); + } + return Target; +} + +Error LVELFReader::loadTargetInfo(const ObjectFile &Obj) { + // Detect the architecture from the object file. We usually don't need OS + // info to lookup a target and create register info. + Triple TT; + TT.setArch(Triple::ArchType(Obj.getArch())); + TT.setVendor(Triple::UnknownVendor); + TT.setOS(Triple::UnknownOS); + + // Features to be passed to target/subtarget + SubtargetFeatures Features = Obj.getFeatures(); + + return loadGenericTargetInfo(TT.str(), Features.getString()); +} + +void LVELFReader::mapRangeAddress(const ObjectFile &Obj) { + for (auto Iter = Obj.symbol_begin(); Iter != Obj.symbol_end(); ++Iter) { + const SymbolRef &Symbol = *Iter; + + Expected TypeOrErr = Symbol.getType(); + if (!TypeOrErr) { + consumeError(TypeOrErr.takeError()); + continue; + } + + // Process only symbols that represent a function. + SymbolRef::Type Type = *TypeOrErr; + if (Type != SymbolRef::ST_Function) + continue; + + // In the case of a Mach-O STAB symbol, get its section only if + // the STAB symbol's section field refers to a valid section index. + // Otherwise the symbol may error trying to load a section that + // does not exist. + const MachOObjectFile *MachO = dyn_cast(&Obj); + bool IsSTAB = false; + if (MachO) { + DataRefImpl SymDRI = Symbol.getRawDataRefImpl(); + uint8_t NType = + (MachO->is64Bit() ? MachO->getSymbol64TableEntry(SymDRI).n_type + : MachO->getSymbolTableEntry(SymDRI).n_type); + if (NType & MachO::N_STAB) + IsSTAB = true; + } + + Expected IterOrErr = Symbol.getSection(); + if (!IterOrErr) { + consumeError(IterOrErr.takeError()); + continue; + } + section_iterator Section = IsSTAB ? Obj.section_end() : *IterOrErr; + if (Section == Obj.section_end()) + continue; + + // Get the symbol value. + Expected AddressOrErr = Symbol.getAddress(); + if (!AddressOrErr) { + consumeError(AddressOrErr.takeError()); + continue; + } + uint64_t Address = *AddressOrErr; + + // Get symbol name. + StringRef Name; + Expected NameOrErr = Symbol.getName(); + if (!NameOrErr) { + consumeError(NameOrErr.takeError()); + continue; + } + Name = *NameOrErr; + + // Check if the symbol is Comdat. + Expected FlagsOrErr = Symbol.getFlags(); + if (!FlagsOrErr) { + consumeError(FlagsOrErr.takeError()); + continue; + } + uint32_t Flags = *FlagsOrErr; + + // Mark the symbol as 'comdat' in any of the following cases: + // - Symbol has the SF_Weak flag or + // - Symbol section index different from the DotTextSectionIndex. + LVSectionIndex SectionIndex = Section->getIndex(); + bool IsComdat = + (Flags & SymbolRef::SF_Weak) || (SectionIndex != DotTextSectionIndex); + + // Record the symbol name (linkage) and its loading address. + addToSymbolTable(Name, Address, SectionIndex, IsComdat); + } +} + +void LVELFReader::sortScopes() { Root->sort(); } + +void LVELFReader::print(raw_ostream &OS) const { + OS << "LVType\n"; + LLVM_DEBUG(dbgs() << "CreateReaders\n"); +} Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-compare-logical-elements.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-compare-logical-elements.test @@ -0,0 +1,101 @@ +; Test case 1 - General options + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; Compare mode - Logical view. +; The output shows in view form the 'missing (-), added (+)' elements, +; giving more context by swapping the reference and target object files. + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --compare=types \ +; RUN: --report=view \ +; RUN: --print=symbols,types \ +; RUN: %p/Inputs/test-dwarf-clang.o \ +; RUN: %p/Inputs/test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Reference: 'test-dwarf-clang.o' +; ONE-NEXT: Target: 'test-dwarf-gcc.o' +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'test-dwarf-clang.o' +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; ONE-NEXT: +[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; ONE-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; ONE-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; ONE-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' +; ONE-NEXT: -[003] 4 {TypeAlias} 'INTEGER' -> 'int' + +; Compare mode - Logical elements. +; The output shows in tabular form the 'missing (-), added (+)' elements, +; giving more context by swapping the reference and target object files. + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --compare=types \ +; RUN: --report=list \ +; RUN: --print=symbols,types,summary \ +; RUN: %p/Inputs/test-dwarf-clang.o \ +; RUN: %p/Inputs/test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Reference: 'test-dwarf-clang.o' +; TWO-NEXT: Target: 'test-dwarf-gcc.o' +; TWO-EMPTY: +; TWO-NEXT: (1) Missing Types: +; TWO-NEXT: -[003] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: (1) Added Types: +; TWO-NEXT: +[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Element Expected Missing Added +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Scopes 4 0 0 +; TWO-NEXT: Symbols 0 0 0 +; TWO-NEXT: Types 2 1 1 +; TWO-NEXT: Lines 0 0 0 +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Total 6 1 1 + +; Changing the 'Reference' and 'Target' order: + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --compare=types \ +; RUN: --report=list \ +; RUN: --print=symbols,types,summary \ +; RUN: %p/Inputs/test-dwarf-gcc.o \ +; RUN: %p/Inputs/test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=THR %s + +; THR: Reference: 'test-dwarf-gcc.o' +; THR-NEXT: Target: 'test-dwarf-clang.o' +; THR-EMPTY: +; THR-NEXT: (1) Missing Types: +; THR-NEXT: -[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; THR-EMPTY: +; THR-NEXT: (1) Added Types: +; THR-NEXT: +[003] 4 {TypeAlias} 'INTEGER' -> 'int' +; THR-EMPTY: +; THR-NEXT: ---------------------------------------- +; THR-NEXT: Element Expected Missing Added +; THR-NEXT: ---------------------------------------- +; THR-NEXT: Scopes 4 0 0 +; THR-NEXT: Symbols 0 0 0 +; THR-NEXT: Types 2 1 1 +; THR-NEXT: Lines 0 0 0 +; THR-NEXT: ---------------------------------------- +; THR-NEXT: Total 6 1 1 Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-print-basic-details.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-print-basic-details.test @@ -0,0 +1,67 @@ +; Test case 1 - General options. + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; Print basic details. +; The following command prints basic details for all the logical elements +; sorted by the debug information internal offset; it includes its lexical +; level and debug info format. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=offset \ +; RUN: --print=scopes,symbols,types,lines,instructions \ +; RUN: %p/Inputs/test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=offset \ +; RUN: --print=elements \ +; RUN: %p/Inputs/test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'test-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; ONE-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; ONE-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' +; ONE-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; ONE-NEXT: [004] 5 {Line} +; ONE-NEXT: [004] {Code} 'movl $0x7, -0x1c(%rbp)' +; ONE-NEXT: [004] 6 {Line} +; ONE-NEXT: [004] {Code} 'movl $0x7, -0x4(%rbp)' +; ONE-NEXT: [004] {Code} 'jmp 0x6' +; ONE-NEXT: [004] 8 {Line} +; ONE-NEXT: [004] {Code} 'movl -0x14(%rbp), %eax' +; ONE-NEXT: [003] 4 {TypeAlias} 'INTEGER' -> 'int' +; ONE-NEXT: [003] 2 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'movb %dl, %al' +; ONE-NEXT: [003] {Code} 'movq %rdi, -0x10(%rbp)' +; ONE-NEXT: [003] {Code} 'movl %esi, -0x14(%rbp)' +; ONE-NEXT: [003] {Code} 'andb $0x1, %al' +; ONE-NEXT: [003] {Code} 'movb %al, -0x15(%rbp)' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'testb $0x1, -0x15(%rbp)' +; ONE-NEXT: [003] {Code} 'je 0x13' +; ONE-NEXT: [003] 8 {Line} +; ONE-NEXT: [003] {Code} 'movl %eax, -0x4(%rbp)' +; ONE-NEXT: [003] 9 {Line} +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [003] 9 {Line} +; ONE-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-select-logical-elements.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/01-dwarf-select-logical-elements.test @@ -0,0 +1,71 @@ +; Test case 1 - General options + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; Select logical elements. +; The following prints all 'instructions', 'symbols' and 'types' that +; contain 'inte' or 'movl' in their names or types, using a tab layout +; and given the number of matches. + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --select-nocase --select-regex \ +; RUN: --select=INTe --select=movl \ +; RUN: --report=list \ +; RUN: --print=symbols,types,instructions,summary \ +; RUN: %p/Inputs/test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'test-dwarf-clang.o' +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [004] {Code} 'movl $0x7, -0x1c(%rbp)' +; ONE-NEXT: [004] {Code} 'movl $0x7, -0x4(%rbp)' +; ONE-NEXT: [003] {Code} 'movl %eax, -0x4(%rbp)' +; ONE-NEXT: [003] {Code} 'movl %esi, -0x14(%rbp)' +; ONE-NEXT: [004] {Code} 'movl -0x14(%rbp), %eax' +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [003] 4 {TypeAlias} 'INTEGER' -> 'int' +; ONE-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; ONE-EMPTY: +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Element Total Printed +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Scopes 3 0 +; ONE-NEXT: Symbols 4 1 +; ONE-NEXT: Types 2 1 +; ONE-NEXT: Lines 17 6 +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Total 26 8 + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --select-regex --select-nocase \ +; RUN: --select=INTe \ +; RUN: --report=list \ +; RUN: --print=symbols,types \ +; RUN: %p/Inputs/test-dwarf-clang.o \ +; RUN: %p/Inputs/test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'test-dwarf-clang.o' +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'test.cpp' +; TWO-NEXT: [003] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; TWO-EMPTY: +; TWO-NEXT: Logical View: +; TWO-NEXT: [000] {File} 'test-dwarf-gcc.o' +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'test.cpp' +; TWO-NEXT: [004] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/02-dwarf-logical-lines.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/02-dwarf-logical-lines.test @@ -0,0 +1,63 @@ +; Test case 2 - Assembler instructions. + +; hello-world.cpp +; 1 extern int printf(const char * format, ... ); +; 2 +; 3 int main() +; 4 { +; 5 printf("Hello, World\n"); +; 6 return 0; +; 7 } + +; Logical lines. +; The logical views shows the intermixed lines and assembler instructions, +; allowing to compare the code generated by the different toolchains. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --print=lines,instructions \ +; RUN: %p/Inputs/hello-world-dwarf-clang.o \ +; RUN: %p/Inputs/hello-world-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'hello-world-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'hello-world.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 15.0.0 {{.*}}' +; ONE-NEXT: [002] 3 {Function} extern not_inlined 'main' -> 'int' +; ONE-NEXT: [003] 4 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'subq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'movl $0x0, -0x4(%rbp)' +; ONE-NEXT: [003] 5 {Line} +; ONE-NEXT: [003] {Code} 'leaq (%rip), %rdi' +; ONE-NEXT: [003] {Code} 'movb $0x0, %al' +; ONE-NEXT: [003] {Code} 'callq 0x0' +; ONE-NEXT: [003] 6 {Line} +; ONE-NEXT: [003] {Code} 'xorl %eax, %eax' +; ONE-NEXT: [003] {Code} 'addq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [003] 6 {Line} +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'hello-world-dwarf-gcc.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'hello-world.cpp' +; ONE-NEXT: [002] {Producer} 'GNU C++14 10.3.0 {{.*}}' +; ONE-NEXT: [002] 3 {Function} extern not_inlined 'main' -> 'int' +; ONE-NEXT: [003] 4 {Line} +; ONE-NEXT: [003] {Code} 'endbr64' +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] 5 {Line} +; ONE-NEXT: [003] {Code} 'leaq (%rip), %rdi' +; ONE-NEXT: [003] {Code} 'movl $0x0, %eax' +; ONE-NEXT: [003] {Code} 'callq 0x0' +; ONE-NEXT: [003] 6 {Line} +; ONE-NEXT: [003] {Code} 'movl $0x0, %eax' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [003] 7 {Line} Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/03-dwarf-incorrect-lexical-scope-typedef.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/03-dwarf-incorrect-lexical-scope-typedef.test @@ -0,0 +1,124 @@ +; Test case 3 - Incorrect lexical scope for typedef. + +; pr-44884.cpp +; 1 int bar(float Input) { return (int)Input; } +; 2 +; 3 unsigned foo(char Param) { +; 4 typedef int INT; // ** Definition for INT ** +; 5 INT Value = Param; +; 6 { +; 7 typedef float FLOAT; // ** Definition for FLOAT ** +; 8 { +; 9 FLOAT Added = Value + Param; +; 10 Value = bar(Added); +; 11 } +; 12 } +; 13 return Value + Param; +; 14 } + +; The lines 4 and 7 contains 2 typedefs, defined at different lexical +; scopes. + +; The above test is used to illustrates a scope issue found in the +; Clang compiler. +; PR44884: https://bugs.llvm.org/show_bug.cgi?id=44884 +; PR44229: https://github.com/llvm/llvm-project/issues/44229 + +; In the following logical views, we can see that the Clang compiler +; emits both typedefs at the same lexical scope (3), which is wrong. +; GCC emit correct lexical scope for both typedefs. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --output-sort=kind \ +; RUN: --print=symbols,types,lines \ +; RUN: %p/Inputs/pr-44884-dwarf-clang.o \ +; RUN: %p/Inputs/pr-44884-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'pr-44884-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-44884.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 15.0.0 {{.*}}' +; ONE-NEXT: [002] 1 {Function} extern not_inlined 'bar' -> 'int' +; ONE-NEXT: [003] 1 {Parameter} 'Input' -> 'float' +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [002] 3 {Function} extern not_inlined 'foo' -> 'unsigned int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 9 {Variable} 'Added' -> 'FLOAT' +; ONE-NEXT: [004] 9 {Line} +; ONE-NEXT: [004] 9 {Line} +; ONE-NEXT: [004] 9 {Line} +; ONE-NEXT: [004] 9 {Line} +; ONE-NEXT: [004] 9 {Line} +; ONE-NEXT: [004] 10 {Line} +; ONE-NEXT: [004] 10 {Line} +; ONE-NEXT: [004] 10 {Line} +; ONE-NEXT: [004] 13 {Line} +; ONE-NEXT: [003] 3 {Parameter} 'Param' -> 'char' +; ONE-NEXT: [003] 7 {TypeAlias} 'FLOAT' -> 'float' +; ONE-NEXT: [003] 4 {TypeAlias} 'INT' -> 'int' +; ONE-NEXT: [003] 5 {Variable} 'Value' -> 'INT' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] 5 {Line} +; ONE-NEXT: [003] 5 {Line} +; ONE-NEXT: [003] 13 {Line} +; ONE-NEXT: [003] 13 {Line} +; ONE-NEXT: [003] 13 {Line} +; ONE-NEXT: [003] 13 {Line} +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'pr-44884-dwarf-gcc.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-44884.cpp' +; ONE-NEXT: [002] {Producer} 'GNU C++14 10.3.0 {{.*}}' +; ONE-NEXT: [002] 1 {Function} extern not_inlined 'bar' -> 'int' +; ONE-NEXT: [003] 1 {Parameter} 'Input' -> 'float' +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [003] 1 {Line} +; ONE-NEXT: [002] 3 {Function} extern not_inlined 'foo' -> 'unsigned int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] {Block} +; ONE-NEXT: [005] 9 {Variable} 'Added' -> 'FLOAT' +; ONE-NEXT: [005] 9 {Line} +; ONE-NEXT: [005] 9 {Line} +; ONE-NEXT: [005] 9 {Line} +; ONE-NEXT: [005] 10 {Line} +; ONE-NEXT: [005] 13 {Line} +; ONE-NEXT: [004] 7 {TypeAlias} 'FLOAT' -> 'float' +; ONE-NEXT: [003] 3 {Parameter} 'Param' -> 'char' +; ONE-NEXT: [003] 4 {TypeAlias} 'INT' -> 'int' +; ONE-NEXT: [003] 5 {Variable} 'Value' -> 'INT' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] 5 {Line} +; ONE-NEXT: [003] 13 {Line} +; ONE-NEXT: [003] 14 {Line} +; ONE-NEXT: [003] 14 {Line} + +; Using the selection facilities, we can produce a simple tabular +; output showing just the logical types that are 'Typedef'. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=name \ +; RUN: --select-types=Typedef \ +; RUN: --report=list \ +; RUN: --print=types \ +; RUN: %p/Inputs/pr-44884-*.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'pr-44884-dwarf-clang.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-44884.cpp' +; TWO-NEXT: [003] 7 {TypeAlias} 'FLOAT' -> 'float' +; TWO-NEXT: [003] 4 {TypeAlias} 'INT' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: Logical View: +; TWO-NEXT: [000] {File} 'pr-44884-dwarf-gcc.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-44884.cpp' +; TWO-NEXT: [004] 7 {TypeAlias} 'FLOAT' -> 'float' +; TWO-NEXT: [003] 4 {TypeAlias} 'INT' -> 'int' Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/04-dwarf-missing-nested-enumerators.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/04-dwarf-missing-nested-enumerators.test @@ -0,0 +1,123 @@ +; Test case 4 - Missing nested enumerations. + +; pr-46466.cpp +; 1 struct Struct { +; 2 union Union { +; 3 enum NestedEnum { RED, BLUE }; +; 4 }; +; 5 Union U; +; 6 }; +; 7 +; 8 Struct S; +; 9 int test() { +; 10 return S.U.BLUE; +; 11 } + +; The above test is used to illustrate a scope issue found in the Clang +; compiler. +; PR46466: https://bugs.llvm.org/show_bug.cgi?id=46466 +; PR45811: https://github.com/llvm/llvm-project/issues/45811 + +; In the following logical views, we can see that the DWARF debug +; information generated by the Clang compiler does not include any +; references to the enumerators 'RED' and 'BLUE'. The DWARF generated +; by GCC, does include such references. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --output-sort=name \ +; RUN: --print=symbols,types \ +; RUN: %p/Inputs/pr-46466-dwarf-clang.o \ +; RUN: %p/Inputs/pr-46466-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 15.0.0 {{.*}}' +; ONE-NEXT: [002] 8 {Variable} extern 'S' -> 'Struct' +; ONE-NEXT: [002] 1 {Struct} 'Struct' +; ONE-NEXT: [003] 5 {Member} public 'U' -> 'Union' +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; ONE-NEXT: [002] {Producer} 'GNU C++14 10.3.0 {{.*}}' +; ONE-NEXT: [002] 8 {Variable} extern 'S' -> 'Struct' +; ONE-NEXT: [002] 1 {Struct} 'Struct' +; ONE-NEXT: [003] 5 {Member} public 'U' -> 'Union' +; ONE-NEXT: [003] 2 {Union} 'Union' +; ONE-NEXT: [004] 3 {Enumeration} 'NestedEnum' -> 'unsigned int' +; ONE-NEXT: [005] {Enumerator} 'BLUE' = '0x1' +; ONE-NEXT: [005] {Enumerator} 'RED' = '0x0' + +; Using the selection facilities, we can produce a logical view +; showing just the logical types that are 'Enumerator' and its +; parents. The logical view is sorted by the types name. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=name \ +; RUN: --select-types=Enumerator \ +; RUN: --report=parents \ +; RUN: --print=types \ +; RUN: %p/Inputs/pr-46466-*.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; TWO-EMPTY: +; TWO-NEXT: Logical View: +; TWO-NEXT: [000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; TWO-NEXT: [002] 1 {Struct} 'Struct' +; TWO-NEXT: [003] 2 {Union} 'Union' +; TWO-NEXT: [004] 3 {Enumeration} 'NestedEnum' -> 'unsigned int' +; TWO-NEXT: [005] {Enumerator} 'BLUE' = '0x1' +; TWO-NEXT: [005] {Enumerator} 'RED' = '0x0' + +; Using the selection facilities, we can produce a simple tabular output +; including a summary for the logical types that are 'Enumerator'. The +; logical view is sorted by the types name. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=name \ +; RUN: --select-types=Enumerator \ +; RUN: --print=types,summary \ +; RUN: %p/Inputs/pr-46466-*.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=THR %s + +; THR: Logical View: +; THR-NEXT: [000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64 +; THR-EMPTY: +; THR-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; THR-EMPTY: +; THR-NEXT: ----------------------------- +; THR-NEXT: Element Total Printed +; THR-NEXT: ----------------------------- +; THR-NEXT: Scopes 4 0 +; THR-NEXT: Symbols 0 0 +; THR-NEXT: Types 0 0 +; THR-NEXT: Lines 0 0 +; THR-NEXT: ----------------------------- +; THR-NEXT: Total 4 0 +; THR-EMPTY: +; THR-NEXT: Logical View: +; THR-NEXT: [000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64 +; THR-EMPTY: +; THR-NEXT: [001] {CompileUnit} 'pr-46466.cpp' +; THR-NEXT: [005] {Enumerator} 'BLUE' = '0x1' +; THR-NEXT: [005] {Enumerator} 'RED' = '0x0' +; THR-EMPTY: +; THR-NEXT: ----------------------------- +; THR-NEXT: Element Total Printed +; THR-NEXT: ----------------------------- +; THR-NEXT: Scopes 5 0 +; THR-NEXT: Symbols 0 0 +; THR-NEXT: Types 2 2 +; THR-NEXT: Lines 0 0 +; THR-NEXT: ----------------------------- +; THR-NEXT: Total 7 2 Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/05-dwarf-incorrect-lexical-scope-variable.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/05-dwarf-incorrect-lexical-scope-variable.test @@ -0,0 +1,108 @@ +; Test case 5 - Incorrect lexical scope variable. + +; pr-43860.cpp +; 1 #include "definitions.h" +; 2 forceinline int InlineFunction(int Param) { +; 3 int Var_1 = Param; +; 4 { +; 5 int Var_2 = Param + Var_1; +; 6 Var_1 = Var_2; +; 7 } +; 8 return Var_1; +; 9 } +; 10 +; 11 int test(int Param_1, int Param_2) { +; 12 int A = Param_1; +; 13 A += InlineFunction(Param_2); +; 14 return A; +; 15 } + +; The above test is used to illustrate a variable issue found in the +; Clang compiler. +; PR43860: https://bugs.llvm.org/show_bug.cgi?id=43860 +; PR43205: https://github.com/llvm/llvm-project/issues/43205 + +; In the following logical views, we can see that the DWARF debug +; information generated by the Clang compiler shows the variables +; 'Var_1' and 'Var_2' are at the same lexical scope (4) in the function +; 'InlineFuction'. +; The DWARF generated by GCC/Clang show those variables at the correct +; lexical scope: '3' and '4' respectively. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --output-sort=name \ +; RUN: --print=symbols \ +; RUN: %p/Inputs/pr-43860-dwarf-clang.o \ +; RUN: %p/Inputs/pr-43860-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'pr-43860-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-43860.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 15.0.0 {{.*}}' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'InlineFunction' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'Var_2' -> 'int' +; ONE-NEXT: [003] 2 {Parameter} 'Param' -> 'int' +; ONE-NEXT: [003] 3 {Variable} 'Var_1' -> 'int' +; ONE-NEXT: [002] 11 {Function} extern not_inlined 'test' -> 'int' +; ONE-NEXT: [003] 12 {Variable} 'A' -> 'int' +; ONE-NEXT: [003] 14 {InlinedFunction} not_inlined 'InlineFunction' -> 'int' +; ONE-NEXT: [004] {Block} +; ONE-NEXT: [005] {Variable} 'Var_2' -> 'int' +; ONE-NEXT: [004] {Parameter} 'Param' -> 'int' +; ONE-NEXT: [004] {Variable} 'Var_1' -> 'int' +; ONE-NEXT: [003] 11 {Parameter} 'Param_1' -> 'int' +; ONE-NEXT: [003] 11 {Parameter} 'Param_2' -> 'int' +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'pr-43860-dwarf-gcc.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-43860.cpp' +; ONE-NEXT: [002] {Producer} 'GNU C++14 10.3.0 {{.*}}' +; ONE-NEXT: [002] 2 {Function} extern declared_inlined 'InlineFunction' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'Var_2' -> 'int' +; ONE-NEXT: [003] 2 {Parameter} 'Param' -> 'int' +; ONE-NEXT: [003] 3 {Variable} 'Var_1' -> 'int' +; ONE-NEXT: [002] 11 {Function} extern not_inlined 'test' -> 'int' +; ONE-NEXT: [003] 12 {Variable} 'A' -> 'int' +; ONE-NEXT: [003] 13 {InlinedFunction} declared_inlined 'InlineFunction' -> 'int' +; ONE-NEXT: [004] {Block} +; ONE-NEXT: [005] {Variable} 'Var_2' -> 'int' +; ONE-NEXT: [004] {Parameter} 'Param' -> 'int' +; ONE-NEXT: [004] {Variable} 'Var_1' -> 'int' +; ONE-NEXT: [003] 11 {Parameter} 'Param_1' -> 'int' +; ONE-NEXT: [003] 11 {Parameter} 'Param_2' -> 'int' + +; Using the selection facilities, we can produce a simple tabular output +; showing just the logical elements that have in their name the 'var' +; pattern. The logical view is sorted by the variables name. + +; RUN: llvm-debuginfo-analyzer --attribute=level,format \ +; RUN: --output-sort=name \ +; RUN: --select-regex --select-nocase \ +; RUN: --select=Var \ +; RUN: --report=list \ +; RUN: --print=symbols \ +; RUN: %p/Inputs/pr-43860-*.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'pr-43860-dwarf-clang.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-43860.cpp' +; TWO-NEXT: [004] {Variable} 'Var_1' -> 'int' +; TWO-NEXT: [003] 3 {Variable} 'Var_1' -> 'int' +; TWO-NEXT: [005] {Variable} 'Var_2' -> 'int' +; TWO-NEXT: [004] 5 {Variable} 'Var_2' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: Logical View: +; TWO-NEXT: [000] {File} 'pr-43860-dwarf-gcc.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-43860.cpp' +; TWO-NEXT: [004] {Variable} 'Var_1' -> 'int' +; TWO-NEXT: [003] 3 {Variable} 'Var_1' -> 'int' +; TWO-NEXT: [005] {Variable} 'Var_2' -> 'int' +; TWO-NEXT: [004] 5 {Variable} 'Var_2' -> 'int' Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/06-dwarf-full-logical-view.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/06-dwarf-full-logical-view.test @@ -0,0 +1,105 @@ +; Test case 6 - Full logical view + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; Print low level details. +; The following command prints low level information that includes +; offsets within the debug information section, debug location +; operands, linkage names, etc. + +; RUN: llvm-debuginfo-analyzer --attribute=all \ +; RUN: --print=all \ +; RUN: %p/Inputs/test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [0x0000000000][000] {File} '{{.*}}test-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [0x000000000b][001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [0x000000000b][002] {Producer} 'clang version 15.0.0 {{.*}}' +; ONE-NEXT: {Directory} '/data/projects/tests/input/general' +; ONE-NEXT: {File} 'test.cpp' +; ONE-NEXT: {Public} 'foo' [0x0000000000:0x000000003a] +; ONE-NEXT: [0x000000000b][002] {Range} Lines 2:9 [0x0000000000:0x000000003a] +; ONE-NEXT: [0x00000000bc][002] {BaseType} 'bool' +; ONE-NEXT: [0x0000000099][002] {BaseType} 'int' +; ONE-NEXT: [0x00000000b5][002] {BaseType} 'unsigned int' +; ONE-EMPTY: +; ONE-NEXT: [0x00000000a0][002] {Source} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x00000000a0][002] 1 {TypeAlias} 'INTPTR' -> [0x00000000ab]'* const int' +; ONE-NEXT: [0x000000002a][002] 2 {Function} extern not_inlined 'foo' -> [0x0000000099]'int' +; ONE-NEXT: [0x000000002a][003] {Range} Lines 2:9 [0x0000000000:0x000000003a] +; ONE-NEXT: [0x000000002a][003] {Linkage} 0x2 '_Z3fooPKijb' +; ONE-NEXT: [0x0000000071][003] {Block} +; ONE-NEXT: [0x0000000071][004] {Range} Lines 5:8 [0x000000001c:0x000000002f] +; ONE-NEXT: [0x000000007e][004] 5 {Variable} 'CONSTANT' -> [0x00000000c3]'const INTEGER' +; ONE-NEXT: [0x000000007e][005] {Coverage} 100.00% +; ONE-NEXT: [0x000000007f][005] {Location} +; ONE-NEXT: [0x000000007f][006] {Entry} fbreg -28 +; ONE-NEXT: [0x000000001c][004] 5 {Line} {NewStatement} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x000000001c][004] {Code} 'movl $0x7, -0x1c(%rbp)' +; ONE-NEXT: [0x0000000023][004] 6 {Line} {NewStatement} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x0000000023][004] {Code} 'movl $0x7, -0x4(%rbp)' +; ONE-NEXT: [0x000000002a][004] {Code} 'jmp 0x6' +; ONE-NEXT: [0x000000002f][004] 8 {Line} {NewStatement} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x000000002f][004] {Code} 'movl -0x14(%rbp), %eax' +; ONE-NEXT: [0x0000000063][003] 2 {Parameter} 'ParamBool' -> [0x00000000bc]'bool' +; ONE-NEXT: [0x0000000063][004] {Coverage} 100.00% +; ONE-NEXT: [0x0000000064][004] {Location} +; ONE-NEXT: [0x0000000064][005] {Entry} fbreg -21 +; ONE-NEXT: [0x0000000047][003] 2 {Parameter} 'ParamPtr' -> [0x00000000a0]'INTPTR' +; ONE-NEXT: [0x0000000047][004] {Coverage} 100.00% +; ONE-NEXT: [0x0000000048][004] {Location} +; ONE-NEXT: [0x0000000048][005] {Entry} fbreg -16 +; ONE-NEXT: [0x0000000055][003] 2 {Parameter} 'ParamUnsigned' -> [0x00000000b5]'unsigned int' +; ONE-NEXT: [0x0000000055][004] {Coverage} 100.00% +; ONE-NEXT: [0x0000000056][004] {Location} +; ONE-NEXT: [0x0000000056][005] {Entry} fbreg -20 +; ONE-NEXT: [0x000000008d][003] 4 {TypeAlias} 'INTEGER' -> [0x0000000099]'int' +; ONE-NEXT: [0x0000000000][003] 2 {Line} {NewStatement} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x0000000000][003] {Code} 'pushq %rbp' +; ONE-NEXT: [0x0000000001][003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [0x0000000004][003] {Code} 'movb %dl, %al' +; ONE-NEXT: [0x0000000006][003] {Code} 'movq %rdi, -0x10(%rbp)' +; ONE-NEXT: [0x000000000a][003] {Code} 'movl %esi, -0x14(%rbp)' +; ONE-NEXT: [0x000000000d][003] {Code} 'andb $0x1, %al' +; ONE-NEXT: [0x000000000f][003] {Code} 'movb %al, -0x15(%rbp)' +; ONE-NEXT: [0x0000000012][003] 3 {Line} {NewStatement} {PrologueEnd} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x0000000012][003] {Code} 'testb $0x1, -0x15(%rbp)' +; ONE-NEXT: [0x0000000016][003] {Code} 'je 0x13' +; ONE-NEXT: [0x0000000032][003] 8 {Line} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x0000000032][003] {Code} 'movl %eax, -0x4(%rbp)' +; ONE-NEXT: [0x0000000035][003] 9 {Line} {NewStatement} '/data/projects/tests/input/general/test.cpp' +; ONE-NEXT: [0x0000000035][003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [0x0000000038][003] {Code} 'popq %rbp' +; ONE-NEXT: [0x0000000039][003] {Code} 'retq' +; ONE-NEXT: [0x000000003a][003] 9 {Line} {NewStatement} {EndSequence} '/data/projects/tests/input/general/test.cpp' +; ONE-EMPTY: +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Element Total Printed +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Scopes 3 3 +; ONE-NEXT: Symbols 4 4 +; ONE-NEXT: Types 5 5 +; ONE-NEXT: Lines 25 25 +; ONE-NEXT: ----------------------------- +; ONE-NEXT: Total 37 37 +; ONE-EMPTY: +; ONE-NEXT: Scope Sizes: +; ONE-NEXT: 189 (100.00%) : [0x000000000b][001] {CompileUnit} 'test.cpp' +; ONE-NEXT: 110 ( 58.20%) : [0x000000002a][002] 2 {Function} extern not_inlined 'foo' -> [0x0000000099]'int' +; ONE-NEXT: 27 ( 14.29%) : [0x0000000071][003] {Block} +; ONE-EMPTY: +; ONE-NEXT: Totals by lexical level: +; ONE-NEXT: [001]: 189 (100.00%) +; ONE-NEXT: [002]: 110 ( 58.20%) +; ONE-NEXT: [003]: 27 ( 14.29%) Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-57040-ignored-DW_FORM_implicit_const.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-57040-ignored-DW_FORM_implicit_const.test @@ -0,0 +1,65 @@ +; Ignored attributes with DW_FORM_implicit_const. +; https://github.com/llvm/llvm-project/issues/57040 + +; Output generated by g++ (Debian 11.3.0-3) 11.3.0 + +; .debug_abbrev contents: +; [1] DW_TAG_formal_parameter DW_CHILDREN_no +; DW_AT_decl_file DW_FORM_implicit_const 1 +; DW_AT_decl_line DW_FORM_implicit_const 2 + +; [2] DW_TAG_typedef DW_CHILDREN_no +; DW_AT_decl_file DW_FORM_implicit_const 1 +; DW_AT_decl_line DW_FORM_data1 + +; Attributes with DW_FORM_implicit_const being ignored by the ELFReader, +; causing {Parameter} and {TypeAlias} to omit line numbers. + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --print=scopes,symbols,types \ +; RUN: %p/Inputs/pr-57040-test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'pr-57040-test-dwarf-clang.o' -> elf64-x86-64 +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 14.0.6' +; ONE-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; ONE-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; ONE-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; ONE-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' +; ONE-NEXT: [003] 4 {TypeAlias} 'INTEGER' -> 'int' + +; RUN: llvm-debuginfo-analyzer --attribute=level,format,producer \ +; RUN: --print=scopes,symbols,types \ +; RUN: %p/Inputs/pr-57040-test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'pr-57040-test-dwarf-gcc.o' -> elf64-x86-64 +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'test.cpp' +; TWO-NEXT: [002] {Producer} 'GNU C++17 11.3.0 {{.*}}' +; TWO-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' +; TWO-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; TWO-NEXT: [003] {Block} +; TWO-NEXT: [004] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; TWO-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; TWO-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; TWO-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-57040-incorrect-function-compare.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-57040-incorrect-function-compare.test @@ -0,0 +1,142 @@ +; Incorrect function matching during comparison. +; https://github.com/llvm/llvm-project/issues/57040 + +; Output generated by g++ (Debian 11.3.0-3) 11.3.0 + +; .debug_info contents: +; format = DWARF32, version = 0x0005, unit_type = DW_UT_compile +; +; DW_TAG_compile_unit +; DW_TAG_subprogram ("foo") +; DW_AT_decl_file (1) +; +; DW_TAG_formal_parameter ("ParamPtr") +; DW_AT_decl_file (1) +; +; .debug_line contents: +; Line table prologue: +; format: DWARF32, version: 5 +; include_directories[0] = "/usr/local/google/home/aheejin/test/llvm-dva" +; file_names[0]: name: "test.cpp" dir_index: 0 +; file_names[1]: name: "test.cpp" dir_index: 0 + +; The values for DW_AT_decl_file are 1-indexed. + +; test.cpp +; 1 using INTPTR = const int *; +; 2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) { +; 3 if (ParamBool) { +; 4 typedef int INTEGER; +; 5 const INTEGER CONSTANT = 7; +; 6 return CONSTANT; +; 7 } +; 8 return ParamUnsigned; +; 9 } + +; RUN: llvm-debuginfo-analyzer --attribute=level,producer \ +; RUN: --compare=types \ +; RUN: --report=view \ +; RUN: --print=symbols,types \ +; RUN: %p/Inputs/pr-57040-test-dwarf-clang.o \ +; RUN: %p/Inputs/pr-57040-test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Reference: 'pr-57040-test-dwarf-clang.o' +; ONE-NEXT: Target: 'pr-57040-test-dwarf-gcc.o' +; ONE-EMPTY: +; ONE-NEXT: Logical View: +; ONE-NEXT: [000] {File} 'pr-57040-test-dwarf-clang.o' +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'test.cpp' +; ONE-NEXT: [002] {Producer} 'clang version 14.0.6' +; ONE-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; ONE-NEXT: +[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; ONE-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; ONE-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; ONE-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' +; ONE-NEXT: -[003] 4 {TypeAlias} 'INTEGER' -> 'int' + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --compare=types \ +; RUN: --report=list \ +; RUN: --print=symbols,types,summary \ +; RUN: %p/Inputs/pr-57040-test-dwarf-clang.o \ +; RUN: %p/Inputs/pr-57040-test-dwarf-gcc.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Reference: 'pr-57040-test-dwarf-clang.o' +; TWO-NEXT: Target: 'pr-57040-test-dwarf-gcc.o' +; TWO-EMPTY: +; TWO-NEXT: (1) Missing Types: +; TWO-NEXT: -[003] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: (1) Added Types: +; TWO-NEXT: +[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; TWO-EMPTY: +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Element Expected Missing Added +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Scopes 4 0 0 +; TWO-NEXT: Symbols 0 0 0 +; TWO-NEXT: Types 2 1 1 +; TWO-NEXT: Lines 0 0 0 +; TWO-NEXT: ---------------------------------------- +; TWO-NEXT: Total 6 1 1 + +; Changing the 'Reference' and 'Target' order: + +; RUN: llvm-debuginfo-analyzer --attribute=level,producer \ +; RUN: --compare=types \ +; RUN: --report=view \ +; RUN: --print=symbols,types \ +; RUN: %p/Inputs/pr-57040-test-dwarf-gcc.o \ +; RUN: %p/Inputs/pr-57040-test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=THR %s + +; THR: Reference: 'pr-57040-test-dwarf-gcc.o' +; THR-NEXT: Target: 'pr-57040-test-dwarf-clang.o' +; THR-EMPTY: +; THR-NEXT: Logical View: +; THR-NEXT: [000] {File} 'pr-57040-test-dwarf-gcc.o' +; THR-EMPTY: +; THR-NEXT: [001] {CompileUnit} 'test.cpp' +; THR-NEXT: [002] {Producer} 'GNU C++17 11.3.0 {{.*}}' +; THR-NEXT: [002] 1 {TypeAlias} 'INTPTR' -> '* const int' +; THR-NEXT: [002] 2 {Function} extern not_inlined 'foo' -> 'int' +; THR-NEXT: [003] {Block} +; THR-NEXT: -[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; THR-NEXT: [004] 5 {Variable} 'CONSTANT' -> 'const INTEGER' +; THR-NEXT: [003] 2 {Parameter} 'ParamBool' -> 'bool' +; THR-NEXT: [003] 2 {Parameter} 'ParamPtr' -> 'INTPTR' +; THR-NEXT: [003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int' +; THR-NEXT: +[003] 4 {TypeAlias} 'INTEGER' -> 'int' + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --compare=types \ +; RUN: --report=list \ +; RUN: --print=symbols,types,summary \ +; RUN: %p/Inputs/pr-57040-test-dwarf-gcc.o \ +; RUN: %p/Inputs/pr-57040-test-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=FOU %s + +; FOU: Reference: 'pr-57040-test-dwarf-gcc.o' +; FOU-NEXT: Target: 'pr-57040-test-dwarf-clang.o' +; FOU-EMPTY: +; FOU-NEXT: (1) Missing Types: +; FOU-NEXT: -[004] 4 {TypeAlias} 'INTEGER' -> 'int' +; FOU-EMPTY: +; FOU-NEXT: (1) Added Types: +; FOU-NEXT: +[003] 4 {TypeAlias} 'INTEGER' -> 'int' +; FOU-EMPTY: +; FOU-NEXT: ---------------------------------------- +; FOU-NEXT: Element Expected Missing Added +; FOU-NEXT: ---------------------------------------- +; FOU-NEXT: Scopes 4 0 0 +; FOU-NEXT: Symbols 0 0 0 +; FOU-NEXT: Types 2 1 1 +; FOU-NEXT: Lines 0 0 0 +; FOU-NEXT: ---------------------------------------- +; FOU-NEXT: Total 6 1 1 Index: llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-incorrect-logical-instructions.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-debuginfo-analyzer/DWARF/pr-incorrect-logical-instructions.test @@ -0,0 +1,205 @@ +; * Added incorrect logical instructions for: --print=lines,instructions +; 'bar' and 'foo' showing extra instruction from compiler generated functions: +; '_cxx_global_var_init' and '_GLOBAL_sub_l_suite_lexical_01.cpp' +; +; * Missing logical instructions for: --print=instructions +; Only 'foo' showing logical instructions. + +; pr-incorrect-instructions-dwarf-clang.cpp +; 1 int ABCDE = 56; int XYZ = ABCDE * 65; +; 2 int bar(int Param) { +; 3 return Param + 999999 * Param - 66; +; 4 } +; 5 +; 6 int foo(int Param) { +; 7 return Param - bar(Param) / Param * 66 + ABCDE; +; 8 } +; 9 +; 10 int test(int P1) { +; 11 int Local_1 = P1 - ABCDE; +; 12 { +; 13 int Local_A = 0; +; 14 Local_A = P1 + foo(Local_1); +; 15 ++Local_1; +; 16 } +; 17 return Local_1; +; 18 } +; 19 +; 20 int main() { +; 21 return 0; +; 22 } + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --print=lines,instructions \ +; RUN: %p/Inputs/pr-incorrect-instructions-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=ONE %s + +; ONE: Logical View: +; ONE-NEXT: [000] {File} 'pr-incorrect-instructions-dwarf-clang.o' +; ONE-EMPTY: +; ONE-NEXT: [001] {CompileUnit} 'pr-incorrect-instructions-dwarf-clang.cpp' +; ONE-NEXT: [002] 2 {Function} extern not_inlined 'bar' -> 'int' +; ONE-NEXT: [003] 2 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'imull $0xf423f, -0x4(%rbp), %ecx' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'addl %ecx, %eax' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'subl $0x42, %eax' +; ONE-NEXT: [003] 3 {Line} +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [002] 6 {Function} extern not_inlined 'foo' -> 'int' +; ONE-NEXT: [003] 6 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'subq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [003] {Code} 'movl %eax, -0x8(%rbp)' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %edi' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'callq 0x0' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'cltd' +; ONE-NEXT: [003] {Code} 'idivl -0x4(%rbp)' +; ONE-NEXT: [003] {Code} 'movl %eax, %ecx' +; ONE-NEXT: [003] {Code} 'movl -0x8(%rbp), %eax' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'imull $0x42, %ecx, %ecx' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'subl %ecx, %eax' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'addl (%rip), %eax' +; ONE-NEXT: [003] 7 {Line} +; ONE-NEXT: [003] {Code} 'addq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [003] {Code} 'data16' +; ONE-NEXT: [002] 10 {Function} extern not_inlined 'test' -> 'int' +; ONE-NEXT: [003] {Block} +; ONE-NEXT: [004] 13 {Line} +; ONE-NEXT: [004] {Code} 'movl $0x0, -0xc(%rbp)' +; ONE-NEXT: [004] 14 {Line} +; ONE-NEXT: [004] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [004] {Code} 'movl %eax, -0x10(%rbp)' +; ONE-NEXT: [004] 14 {Line} +; ONE-NEXT: [004] {Code} 'movl -0x8(%rbp), %edi' +; ONE-NEXT: [004] 14 {Line} +; ONE-NEXT: [004] {Code} 'callq 0x0' +; ONE-NEXT: [004] {Code} 'movl %eax, %ecx' +; ONE-NEXT: [004] {Code} 'movl -0x10(%rbp), %eax' +; ONE-NEXT: [004] 14 {Line} +; ONE-NEXT: [004] {Code} 'addl %ecx, %eax' +; ONE-NEXT: [004] 14 {Line} +; ONE-NEXT: [004] {Code} 'movl %eax, -0xc(%rbp)' +; ONE-NEXT: [004] 15 {Line} +; ONE-NEXT: [004] {Code} 'movl -0x8(%rbp), %eax' +; ONE-NEXT: [004] {Code} 'addl $0x1, %eax' +; ONE-NEXT: [004] {Code} 'movl %eax, -0x8(%rbp)' +; ONE-NEXT: [004] 17 {Line} +; ONE-NEXT: [004] {Code} 'movl -0x8(%rbp), %eax' +; ONE-NEXT: [003] 10 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'subq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; ONE-NEXT: [003] 11 {Line} +; ONE-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; ONE-NEXT: [003] 11 {Line} +; ONE-NEXT: [003] {Code} 'subl (%rip), %eax' +; ONE-NEXT: [003] 11 {Line} +; ONE-NEXT: [003] {Code} 'movl %eax, -0x8(%rbp)' +; ONE-NEXT: [003] 17 {Line} +; ONE-NEXT: [003] {Code} 'addq $0x10, %rsp' +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [002] 20 {Function} extern not_inlined 'main' -> 'int' +; ONE-NEXT: [003] 20 {Line} +; ONE-NEXT: [003] {Code} 'pushq %rbp' +; ONE-NEXT: [003] {Code} 'movq %rsp, %rbp' +; ONE-NEXT: [003] {Code} 'movl $0x0, -0x4(%rbp)' +; ONE-NEXT: [003] 21 {Line} +; ONE-NEXT: [003] {Code} 'xorl %eax, %eax' +; ONE-NEXT: [003] {Code} 'popq %rbp' +; ONE-NEXT: [003] {Code} 'retq' +; ONE-NEXT: [003] 21 {Line} + +; RUN: llvm-debuginfo-analyzer --attribute=level \ +; RUN: --print=instructions \ +; RUN: %p/Inputs/pr-incorrect-instructions-dwarf-clang.o 2>&1 | \ +; RUN: FileCheck --strict-whitespace -check-prefix=TWO %s + +; TWO: Logical View: +; TWO-NEXT: [000] {File} 'pr-incorrect-instructions-dwarf-clang.o' +; TWO-EMPTY: +; TWO-NEXT: [001] {CompileUnit} 'pr-incorrect-instructions-dwarf-clang.cpp' +; TWO-NEXT: [002] 2 {Function} extern not_inlined 'bar' -> 'int' +; TWO-NEXT: [003] {Code} 'pushq %rbp' +; TWO-NEXT: [003] {Code} 'movq %rsp, %rbp' +; TWO-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; TWO-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; TWO-NEXT: [003] {Code} 'imull $0xf423f, -0x4(%rbp), %ecx' +; TWO-NEXT: [003] {Code} 'addl %ecx, %eax' +; TWO-NEXT: [003] {Code} 'subl $0x42, %eax' +; TWO-NEXT: [003] {Code} 'popq %rbp' +; TWO-NEXT: [003] {Code} 'retq' +; TWO-NEXT: [002] 6 {Function} extern not_inlined 'foo' -> 'int' +; TWO-NEXT: [003] {Code} 'pushq %rbp' +; TWO-NEXT: [003] {Code} 'movq %rsp, %rbp' +; TWO-NEXT: [003] {Code} 'subq $0x10, %rsp' +; TWO-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; TWO-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; TWO-NEXT: [003] {Code} 'movl %eax, -0x8(%rbp)' +; TWO-NEXT: [003] {Code} 'movl -0x4(%rbp), %edi' +; TWO-NEXT: [003] {Code} 'callq 0x0' +; TWO-NEXT: [003] {Code} 'cltd' +; TWO-NEXT: [003] {Code} 'idivl -0x4(%rbp)' +; TWO-NEXT: [003] {Code} 'movl %eax, %ecx' +; TWO-NEXT: [003] {Code} 'movl -0x8(%rbp), %eax' +; TWO-NEXT: [003] {Code} 'imull $0x42, %ecx, %ecx' +; TWO-NEXT: [003] {Code} 'subl %ecx, %eax' +; TWO-NEXT: [003] {Code} 'addl (%rip), %eax' +; TWO-NEXT: [003] {Code} 'addq $0x10, %rsp' +; TWO-NEXT: [003] {Code} 'popq %rbp' +; TWO-NEXT: [003] {Code} 'retq' +; TWO-NEXT: [003] {Code} 'data16' +; TWO-NEXT: [002] 10 {Function} extern not_inlined 'test' -> 'int' +; TWO-NEXT: [003] {Block} +; TWO-NEXT: [004] {Code} 'movl $0x0, -0xc(%rbp)' +; TWO-NEXT: [004] {Code} 'movl -0x4(%rbp), %eax' +; TWO-NEXT: [004] {Code} 'movl %eax, -0x10(%rbp)' +; TWO-NEXT: [004] {Code} 'movl -0x8(%rbp), %edi' +; TWO-NEXT: [004] {Code} 'callq 0x0' +; TWO-NEXT: [004] {Code} 'movl %eax, %ecx' +; TWO-NEXT: [004] {Code} 'movl -0x10(%rbp), %eax' +; TWO-NEXT: [004] {Code} 'addl %ecx, %eax' +; TWO-NEXT: [004] {Code} 'movl %eax, -0xc(%rbp)' +; TWO-NEXT: [004] {Code} 'movl -0x8(%rbp), %eax' +; TWO-NEXT: [004] {Code} 'addl $0x1, %eax' +; TWO-NEXT: [004] {Code} 'movl %eax, -0x8(%rbp)' +; TWO-NEXT: [004] {Code} 'movl -0x8(%rbp), %eax' +; TWO-NEXT: [003] {Code} 'pushq %rbp' +; TWO-NEXT: [003] {Code} 'movq %rsp, %rbp' +; TWO-NEXT: [003] {Code} 'subq $0x10, %rsp' +; TWO-NEXT: [003] {Code} 'movl %edi, -0x4(%rbp)' +; TWO-NEXT: [003] {Code} 'movl -0x4(%rbp), %eax' +; TWO-NEXT: [003] {Code} 'subl (%rip), %eax' +; TWO-NEXT: [003] {Code} 'movl %eax, -0x8(%rbp)' +; TWO-NEXT: [003] {Code} 'addq $0x10, %rsp' +; TWO-NEXT: [003] {Code} 'popq %rbp' +; TWO-NEXT: [003] {Code} 'retq' +; TWO-NEXT: [002] 20 {Function} extern not_inlined 'main' -> 'int' +; TWO-NEXT: [003] {Code} 'pushq %rbp' +; TWO-NEXT: [003] {Code} 'movq %rsp, %rbp' +; TWO-NEXT: [003] {Code} 'movl $0x0, -0x4(%rbp)' +; TWO-NEXT: [003] {Code} 'xorl %eax, %eax' +; TWO-NEXT: [003] {Code} 'popq %rbp' +; TWO-NEXT: [003] {Code} 'retq' Index: llvm/tools/llvm-debuginfo-analyzer/llvm-debuginfo-analyzer.cpp =================================================================== --- llvm/tools/llvm-debuginfo-analyzer/llvm-debuginfo-analyzer.cpp +++ llvm/tools/llvm-debuginfo-analyzer/llvm-debuginfo-analyzer.cpp @@ -13,11 +13,10 @@ #include "Options.h" #include "llvm/DebugInfo/LogicalView/Core/LVOptions.h" -#include "llvm/Object/Archive.h" +#include "llvm/DebugInfo/LogicalView/LVReaderHandler.h" #include "llvm/Support/COM.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/InitLLVM.h" -#include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" @@ -39,6 +38,16 @@ exit(1); } +static void error(Error EC) { + if (!EC) + return; + handleAllErrors(std::move(EC), [&](const ErrorInfoBase &EI) { + errs() << "\n"; + WithColor::error(errs(), ToolName) << EI.message() << ".\n"; + exit(1); + }); +} + /// If the input path is a .dSYM bundle (as created by the dsymutil tool), /// replace it with individual entries for each of the object files inside the /// bundle otherwise return the input path. @@ -113,6 +122,7 @@ propagateOptions(); ScopedPrinter W(OutputFile.os()); + LVReaderHandler ReaderHandler(Objects, W, ReaderOptions); // Print the command line. if (options().getInternalCmdline()) { @@ -123,5 +133,9 @@ Stream << "\n"; } + // Create readers and perform requested tasks on them. + if (Error Err = ReaderHandler.process()) + error(std::move(Err)); + return EXIT_SUCCESS; } Index: llvm/unittests/DebugInfo/LogicalView/CMakeLists.txt =================================================================== --- llvm/unittests/DebugInfo/LogicalView/CMakeLists.txt +++ llvm/unittests/DebugInfo/LogicalView/CMakeLists.txt @@ -1,10 +1,15 @@ set(LLVM_LINK_COMPONENTS + AllTargetsDescs + AllTargetsInfos + AllTargetsDisassemblers DebugInfoLogicalView + MCDisassembler ) -add_llvm_unittest(DebugInfoLogicalViewTests +add_llvm_unittest_with_input_files(DebugInfoLogicalViewTests CommandLineOptionsTest.cpp CompareElementsTest.cpp + ELFReaderTest.cpp SelectElementsTest.cpp LocationRangesTest.cpp LogicalElementsTest.cpp Index: llvm/unittests/DebugInfo/LogicalView/ELFReaderTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/DebugInfo/LogicalView/ELFReaderTest.cpp @@ -0,0 +1,342 @@ +//===- llvm/unittest/DebugInfo/LogicalView/ELFReaderTest.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/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"; +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_EQ(CompileUnits->size(), 1u); + + LVScopes::const_iterator Iter = CompileUnits->begin(); + EXPECT_NE(Iter, nullptr); + LVScopeCompileUnit *CompileUnit = static_cast(*Iter); + EXPECT_NE(CompileUnit, nullptr); + return CompileUnit; +} + +// Helper function to create a reader. +LVReader *createReader(LVReaderHandler &ReaderHandler, + SmallString<128> &InputsDir, StringRef Filename) { + SmallString<128> ObjectName(InputsDir); + llvm::sys::path::append(ObjectName, Filename); + + Expected ReaderOrErr = + ReaderHandler.createReader(std::string(ObjectName)); + EXPECT_THAT_EXPECTED(ReaderOrErr, Succeeded()); + LVReader *Reader = *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().startswith("clang")); + EXPECT_EQ(CompileUnit->getName(), "test.cpp"); + + 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 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 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 SetScopes; + for (LVScope *Scope : MatchedScopes) + SetScopes.insert(Scope->getOffset()); + std::set::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.setAttributeLocation(); + ReaderOptions.setPrintAll(); + ReaderOptions.resolveDependencies(); + + std::vector Objects; + ScopedPrinter W(outs()); + LVReaderHandler ReaderHandler(Objects, W, ReaderOptions); + + // Check logical elements properties. + LVReader *Reader = createReader(ReaderHandler, InputsDir, DwarfClang); + checkElementProperties(Reader); + ReaderHandler.deleteReader(Reader); +} + +// 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 Objects; + ScopedPrinter W(outs()); + LVReaderHandler ReaderHandler(Objects, W, ReaderOptions); + + // Check logical elements selection. + LVReader *Reader = createReader(ReaderHandler, InputsDir, DwarfGcc); + checkElementSelection(Reader); + ReaderHandler.deleteReader(Reader); +} + +// 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 Objects; + ScopedPrinter W(outs()); + LVReaderHandler ReaderHandler(Objects, W, ReaderOptions); + + // Check logical comparison. + LVReader *Reference = createReader(ReaderHandler, InputsDir, DwarfClang); + LVReader *Target = createReader(ReaderHandler, InputsDir, DwarfGcc); + checkElementComparison(Reference, Target); + ReaderHandler.deleteReader(Reference); + ReaderHandler.deleteReader(Target); +} + +TEST(LogicalViewTest, ELFReader) { + // Initialize targets and assembly printers/parsers. + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + InitializeAllDisassemblers(); + + llvm::sys::InitializeCOMRAII COM(llvm::sys::COMThreadingMode::MultiThreaded); + + SmallString<128> InputsDir = unittest::getInputFileDirectory(TestMainArgv0); + + // Logical elements general properties and selection. + elementProperties(InputsDir); + elementSelection(InputsDir); + + // Compare logical elements. + compareElements(InputsDir); +} + +} // namespace