aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/llvm/tools/llvm-dwarfdump
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/llvm/tools/llvm-dwarfdump')
-rw-r--r--contrib/llvm-project/llvm/tools/llvm-dwarfdump/Statistics.cpp413
-rw-r--r--contrib/llvm-project/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp646
2 files changed, 1059 insertions, 0 deletions
diff --git a/contrib/llvm-project/llvm/tools/llvm-dwarfdump/Statistics.cpp b/contrib/llvm-project/llvm/tools/llvm-dwarfdump/Statistics.cpp
new file mode 100644
index 000000000000..f26369b935cb
--- /dev/null
+++ b/contrib/llvm-project/llvm/tools/llvm-dwarfdump/Statistics.cpp
@@ -0,0 +1,413 @@
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/DebugInfo/DIContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
+#include "llvm/Object/ObjectFile.h"
+
+#define DEBUG_TYPE "dwarfdump"
+using namespace llvm;
+using namespace object;
+
+/// Holds statistics for one function (or other entity that has a PC range and
+/// contains variables, such as a compile unit).
+struct PerFunctionStats {
+ /// Number of inlined instances of this function.
+ unsigned NumFnInlined = 0;
+ /// Number of inlined instances that have abstract origins.
+ unsigned NumAbstractOrigins = 0;
+ /// Number of variables and parameters with location across all inlined
+ /// instances.
+ unsigned TotalVarWithLoc = 0;
+ /// Number of constants with location across all inlined instances.
+ unsigned ConstantMembers = 0;
+ /// List of all Variables and parameters in this function.
+ StringSet<> VarsInFunction;
+ /// Compile units also cover a PC range, but have this flag set to false.
+ bool IsFunction = false;
+ /// Verify function definition has PC addresses (for detecting when
+ /// a function has been inlined everywhere).
+ bool HasPCAddresses = false;
+ /// Function has source location information.
+ bool HasSourceLocation = false;
+ /// Number of function parameters.
+ unsigned NumParams = 0;
+ /// Number of function parameters with source location.
+ unsigned NumParamSourceLocations = 0;
+ /// Number of function parameters with type.
+ unsigned NumParamTypes = 0;
+ /// Number of function parameters with a DW_AT_location.
+ unsigned NumParamLocations = 0;
+ /// Number of variables.
+ unsigned NumVars = 0;
+ /// Number of variables with source location.
+ unsigned NumVarSourceLocations = 0;
+ /// Number of variables wtih type.
+ unsigned NumVarTypes = 0;
+ /// Number of variables wtih DW_AT_location.
+ unsigned NumVarLocations = 0;
+};
+
+/// Holds accumulated global statistics about DIEs.
+struct GlobalStats {
+ /// Total number of PC range bytes covered by DW_AT_locations.
+ unsigned ScopeBytesCovered = 0;
+ /// Total number of PC range bytes in each variable's enclosing scope,
+ /// starting from the first definition of the variable.
+ unsigned ScopeBytesFromFirstDefinition = 0;
+ /// Total number of call site entries (DW_TAG_call_site) or
+ /// (DW_AT_call_file & DW_AT_call_line).
+ unsigned CallSiteEntries = 0;
+ /// Total byte size of concrete functions. This byte size includes
+ /// inline functions contained in the concrete functions.
+ uint64_t FunctionSize = 0;
+ /// Total byte size of inlined functions. This is the total number of bytes
+ /// for the top inline functions within concrete functions. This can help
+ /// tune the inline settings when compiling to match user expectations.
+ uint64_t InlineFunctionSize = 0;
+};
+
+/// Extract the low pc from a Die.
+static uint64_t getLowPC(DWARFDie Die) {
+ auto RangesOrError = Die.getAddressRanges();
+ DWARFAddressRangesVector Ranges;
+ if (RangesOrError)
+ Ranges = RangesOrError.get();
+ else
+ llvm::consumeError(RangesOrError.takeError());
+ if (Ranges.size())
+ return Ranges[0].LowPC;
+ return dwarf::toAddress(Die.find(dwarf::DW_AT_low_pc), 0);
+}
+
+/// Collect debug info quality metrics for one DIE.
+static void collectStatsForDie(DWARFDie Die, std::string FnPrefix,
+ std::string VarPrefix, uint64_t ScopeLowPC,
+ uint64_t BytesInScope, uint32_t InlineDepth,
+ StringMap<PerFunctionStats> &FnStatMap,
+ GlobalStats &GlobalStats) {
+ bool HasLoc = false;
+ bool HasSrcLoc = false;
+ bool HasType = false;
+ bool IsArtificial = false;
+ uint64_t BytesCovered = 0;
+ uint64_t OffsetToFirstDefinition = 0;
+
+ if (Die.getTag() == dwarf::DW_TAG_call_site) {
+ GlobalStats.CallSiteEntries++;
+ return;
+ }
+
+ if (Die.getTag() != dwarf::DW_TAG_formal_parameter &&
+ Die.getTag() != dwarf::DW_TAG_variable &&
+ Die.getTag() != dwarf::DW_TAG_member) {
+ // Not a variable or constant member.
+ return;
+ }
+
+ if (Die.findRecursively(dwarf::DW_AT_decl_file) &&
+ Die.findRecursively(dwarf::DW_AT_decl_line))
+ HasSrcLoc = true;
+
+ if (Die.findRecursively(dwarf::DW_AT_type))
+ HasType = true;
+
+ if (Die.find(dwarf::DW_AT_artificial))
+ IsArtificial = true;
+
+ if (Die.find(dwarf::DW_AT_const_value)) {
+ // This catches constant members *and* variables.
+ HasLoc = true;
+ BytesCovered = BytesInScope;
+ } else {
+ if (Die.getTag() == dwarf::DW_TAG_member) {
+ // Non-const member.
+ return;
+ }
+ // Handle variables and function arguments.
+ auto FormValue = Die.find(dwarf::DW_AT_location);
+ HasLoc = FormValue.hasValue();
+ if (HasLoc) {
+ // Get PC coverage.
+ if (auto DebugLocOffset = FormValue->getAsSectionOffset()) {
+ auto *DebugLoc = Die.getDwarfUnit()->getContext().getDebugLoc();
+ if (auto List = DebugLoc->getLocationListAtOffset(*DebugLocOffset)) {
+ for (auto Entry : List->Entries)
+ BytesCovered += Entry.End - Entry.Begin;
+ if (List->Entries.size()) {
+ uint64_t FirstDef = List->Entries[0].Begin;
+ uint64_t UnitOfs = getLowPC(Die.getDwarfUnit()->getUnitDIE());
+ // Ranges sometimes start before the lexical scope.
+ if (UnitOfs + FirstDef >= ScopeLowPC)
+ OffsetToFirstDefinition = UnitOfs + FirstDef - ScopeLowPC;
+ // Or even after it. Count that as a failure.
+ if (OffsetToFirstDefinition > BytesInScope)
+ OffsetToFirstDefinition = 0;
+ }
+ }
+ assert(BytesInScope);
+ } else {
+ // Assume the entire range is covered by a single location.
+ BytesCovered = BytesInScope;
+ }
+ }
+ }
+
+ // Collect PC range coverage data.
+ auto &FnStats = FnStatMap[FnPrefix];
+ if (DWARFDie D =
+ Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin))
+ Die = D;
+ // By using the variable name + the path through the lexical block tree, the
+ // keys are consistent across duplicate abstract origins in different CUs.
+ std::string VarName = StringRef(Die.getName(DINameKind::ShortName));
+ FnStats.VarsInFunction.insert(VarPrefix + VarName);
+ if (BytesInScope) {
+ FnStats.TotalVarWithLoc += (unsigned)HasLoc;
+ // Adjust for the fact the variables often start their lifetime in the
+ // middle of the scope.
+ BytesInScope -= OffsetToFirstDefinition;
+ // Turns out we have a lot of ranges that extend past the lexical scope.
+ GlobalStats.ScopeBytesCovered += std::min(BytesInScope, BytesCovered);
+ GlobalStats.ScopeBytesFromFirstDefinition += BytesInScope;
+ assert(GlobalStats.ScopeBytesCovered <=
+ GlobalStats.ScopeBytesFromFirstDefinition);
+ } else if (Die.getTag() == dwarf::DW_TAG_member) {
+ FnStats.ConstantMembers++;
+ } else {
+ FnStats.TotalVarWithLoc += (unsigned)HasLoc;
+ }
+ if (!IsArtificial) {
+ if (Die.getTag() == dwarf::DW_TAG_formal_parameter) {
+ FnStats.NumParams++;
+ if (HasType)
+ FnStats.NumParamTypes++;
+ if (HasSrcLoc)
+ FnStats.NumParamSourceLocations++;
+ if (HasLoc)
+ FnStats.NumParamLocations++;
+ } else if (Die.getTag() == dwarf::DW_TAG_variable) {
+ FnStats.NumVars++;
+ if (HasType)
+ FnStats.NumVarTypes++;
+ if (HasSrcLoc)
+ FnStats.NumVarSourceLocations++;
+ if (HasLoc)
+ FnStats.NumVarLocations++;
+ }
+ }
+}
+
+/// Recursively collect debug info quality metrics.
+static void collectStatsRecursive(DWARFDie Die, std::string FnPrefix,
+ std::string VarPrefix, uint64_t ScopeLowPC,
+ uint64_t BytesInScope, uint32_t InlineDepth,
+ StringMap<PerFunctionStats> &FnStatMap,
+ GlobalStats &GlobalStats) {
+ // Handle any kind of lexical scope.
+ const dwarf::Tag Tag = Die.getTag();
+ const bool IsFunction = Tag == dwarf::DW_TAG_subprogram;
+ const bool IsBlock = Tag == dwarf::DW_TAG_lexical_block;
+ const bool IsInlinedFunction = Tag == dwarf::DW_TAG_inlined_subroutine;
+ if (IsFunction || IsInlinedFunction || IsBlock) {
+
+ // Reset VarPrefix when entering a new function.
+ if (Die.getTag() == dwarf::DW_TAG_subprogram ||
+ Die.getTag() == dwarf::DW_TAG_inlined_subroutine)
+ VarPrefix = "v";
+
+ // Ignore forward declarations.
+ if (Die.find(dwarf::DW_AT_declaration))
+ return;
+
+ // Check for call sites.
+ if (Die.find(dwarf::DW_AT_call_file) && Die.find(dwarf::DW_AT_call_line))
+ GlobalStats.CallSiteEntries++;
+
+ // PC Ranges.
+ auto RangesOrError = Die.getAddressRanges();
+ if (!RangesOrError) {
+ llvm::consumeError(RangesOrError.takeError());
+ return;
+ }
+
+ auto Ranges = RangesOrError.get();
+ uint64_t BytesInThisScope = 0;
+ for (auto Range : Ranges)
+ BytesInThisScope += Range.HighPC - Range.LowPC;
+ ScopeLowPC = getLowPC(Die);
+
+ // Count the function.
+ if (!IsBlock) {
+ StringRef Name = Die.getName(DINameKind::LinkageName);
+ if (Name.empty())
+ Name = Die.getName(DINameKind::ShortName);
+ FnPrefix = Name;
+ // Skip over abstract origins.
+ if (Die.find(dwarf::DW_AT_inline))
+ return;
+ // We've seen an (inlined) instance of this function.
+ auto &FnStats = FnStatMap[Name];
+ if (IsInlinedFunction) {
+ FnStats.NumFnInlined++;
+ if (Die.findRecursively(dwarf::DW_AT_abstract_origin))
+ FnStats.NumAbstractOrigins++;
+ }
+ FnStats.IsFunction = true;
+ if (BytesInThisScope && !IsInlinedFunction)
+ FnStats.HasPCAddresses = true;
+ std::string FnName = StringRef(Die.getName(DINameKind::ShortName));
+ if (Die.findRecursively(dwarf::DW_AT_decl_file) &&
+ Die.findRecursively(dwarf::DW_AT_decl_line))
+ FnStats.HasSourceLocation = true;
+ }
+
+ if (BytesInThisScope) {
+ BytesInScope = BytesInThisScope;
+ if (IsFunction)
+ GlobalStats.FunctionSize += BytesInThisScope;
+ else if (IsInlinedFunction && InlineDepth == 0)
+ GlobalStats.InlineFunctionSize += BytesInThisScope;
+ }
+ } else {
+ // Not a scope, visit the Die itself. It could be a variable.
+ collectStatsForDie(Die, FnPrefix, VarPrefix, ScopeLowPC, BytesInScope,
+ InlineDepth, FnStatMap, GlobalStats);
+ }
+
+ // Set InlineDepth correctly for child recursion
+ if (IsFunction)
+ InlineDepth = 0;
+ else if (IsInlinedFunction)
+ ++InlineDepth;
+
+ // Traverse children.
+ unsigned LexicalBlockIndex = 0;
+ DWARFDie Child = Die.getFirstChild();
+ while (Child) {
+ std::string ChildVarPrefix = VarPrefix;
+ if (Child.getTag() == dwarf::DW_TAG_lexical_block)
+ ChildVarPrefix += toHex(LexicalBlockIndex++) + '.';
+
+ collectStatsRecursive(Child, FnPrefix, ChildVarPrefix, ScopeLowPC,
+ BytesInScope, InlineDepth, FnStatMap, GlobalStats);
+ Child = Child.getSibling();
+ }
+}
+
+/// Print machine-readable output.
+/// The machine-readable format is single-line JSON output.
+/// \{
+static void printDatum(raw_ostream &OS, const char *Key, StringRef Value) {
+ OS << ",\"" << Key << "\":\"" << Value << '"';
+ LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
+}
+static void printDatum(raw_ostream &OS, const char *Key, uint64_t Value) {
+ OS << ",\"" << Key << "\":" << Value;
+ LLVM_DEBUG(llvm::dbgs() << Key << ": " << Value << '\n');
+}
+/// \}
+
+/// Collect debug info quality metrics for an entire DIContext.
+///
+/// Do the impossible and reduce the quality of the debug info down to a few
+/// numbers. The idea is to condense the data into numbers that can be tracked
+/// over time to identify trends in newer compiler versions and gauge the effect
+/// of particular optimizations. The raw numbers themselves are not particularly
+/// useful, only the delta between compiling the same program with different
+/// compilers is.
+bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
+ Twine Filename, raw_ostream &OS) {
+ StringRef FormatName = Obj.getFileFormatName();
+ GlobalStats GlobalStats;
+ StringMap<PerFunctionStats> Statistics;
+ for (const auto &CU : static_cast<DWARFContext *>(&DICtx)->compile_units())
+ if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(false))
+ collectStatsRecursive(CUDie, "/", "g", 0, 0, 0, Statistics, GlobalStats);
+
+ /// The version number should be increased every time the algorithm is changed
+ /// (including bug fixes). New metrics may be added without increasing the
+ /// version.
+ unsigned Version = 3;
+ unsigned VarParamTotal = 0;
+ unsigned VarParamUnique = 0;
+ unsigned VarParamWithLoc = 0;
+ unsigned NumFunctions = 0;
+ unsigned NumInlinedFunctions = 0;
+ unsigned NumFuncsWithSrcLoc = 0;
+ unsigned NumAbstractOrigins = 0;
+ unsigned ParamTotal = 0;
+ unsigned ParamWithType = 0;
+ unsigned ParamWithLoc = 0;
+ unsigned ParamWithSrcLoc = 0;
+ unsigned VarTotal = 0;
+ unsigned VarWithType = 0;
+ unsigned VarWithSrcLoc = 0;
+ unsigned VarWithLoc = 0;
+ for (auto &Entry : Statistics) {
+ PerFunctionStats &Stats = Entry.getValue();
+ unsigned TotalVars = Stats.VarsInFunction.size() * Stats.NumFnInlined;
+ // Count variables in concrete out-of-line functions and in global scope.
+ if (Stats.HasPCAddresses || !Stats.IsFunction)
+ TotalVars += Stats.VarsInFunction.size();
+ unsigned Constants = Stats.ConstantMembers;
+ VarParamWithLoc += Stats.TotalVarWithLoc + Constants;
+ VarParamTotal += TotalVars;
+ VarParamUnique += Stats.VarsInFunction.size();
+ LLVM_DEBUG(for (auto &V
+ : Stats.VarsInFunction) llvm::dbgs()
+ << Entry.getKey() << ": " << V.getKey() << "\n");
+ NumFunctions += Stats.IsFunction;
+ NumFuncsWithSrcLoc += Stats.HasSourceLocation;
+ NumInlinedFunctions += Stats.IsFunction * Stats.NumFnInlined;
+ NumAbstractOrigins += Stats.IsFunction * Stats.NumAbstractOrigins;
+ ParamTotal += Stats.NumParams;
+ ParamWithType += Stats.NumParamTypes;
+ ParamWithLoc += Stats.NumParamLocations;
+ ParamWithSrcLoc += Stats.NumParamSourceLocations;
+ VarTotal += Stats.NumVars;
+ VarWithType += Stats.NumVarTypes;
+ VarWithLoc += Stats.NumVarLocations;
+ VarWithSrcLoc += Stats.NumVarSourceLocations;
+ }
+
+ // Print summary.
+ OS.SetBufferSize(1024);
+ OS << "{\"version\":" << Version;
+ LLVM_DEBUG(llvm::dbgs() << "Variable location quality metrics\n";
+ llvm::dbgs() << "---------------------------------\n");
+ printDatum(OS, "file", Filename.str());
+ printDatum(OS, "format", FormatName);
+ printDatum(OS, "source functions", NumFunctions);
+ printDatum(OS, "source functions with location", NumFuncsWithSrcLoc);
+ printDatum(OS, "inlined functions", NumInlinedFunctions);
+ printDatum(OS, "inlined funcs with abstract origins", NumAbstractOrigins);
+ printDatum(OS, "unique source variables", VarParamUnique);
+ printDatum(OS, "source variables", VarParamTotal);
+ printDatum(OS, "variables with location", VarParamWithLoc);
+ printDatum(OS, "call site entries", GlobalStats.CallSiteEntries);
+ printDatum(OS, "scope bytes total",
+ GlobalStats.ScopeBytesFromFirstDefinition);
+ printDatum(OS, "scope bytes covered", GlobalStats.ScopeBytesCovered);
+ printDatum(OS, "total function size", GlobalStats.FunctionSize);
+ printDatum(OS, "total inlined function size", GlobalStats.InlineFunctionSize);
+ printDatum(OS, "total formal params", ParamTotal);
+ printDatum(OS, "formal params with source location", ParamWithSrcLoc);
+ printDatum(OS, "formal params with type", ParamWithType);
+ printDatum(OS, "formal params with binary location", ParamWithLoc);
+ printDatum(OS, "total vars", VarTotal);
+ printDatum(OS, "vars with source location", VarWithSrcLoc);
+ printDatum(OS, "vars with type", VarWithType);
+ printDatum(OS, "vars with binary location", VarWithLoc);
+ OS << "}\n";
+ LLVM_DEBUG(
+ llvm::dbgs() << "Total Availability: "
+ << (int)std::round((VarParamWithLoc * 100.0) / VarParamTotal)
+ << "%\n";
+ llvm::dbgs() << "PC Ranges covered: "
+ << (int)std::round((GlobalStats.ScopeBytesCovered * 100.0) /
+ GlobalStats.ScopeBytesFromFirstDefinition)
+ << "%\n");
+ return true;
+}
diff --git a/contrib/llvm-project/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/contrib/llvm-project/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
new file mode 100644
index 000000000000..05a7aef67ece
--- /dev/null
+++ b/contrib/llvm-project/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -0,0 +1,646 @@
+//===-- llvm-dwarfdump.cpp - Debug info dumping utility for llvm ----------===//
+//
+// 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 program is a utility that works like "dwarfdump".
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/DebugInfo/DIContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/Object/Archive.h"
+#include "llvm/Object/MachOUniversal.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace object;
+
+/// Parser for options that take an optional offest argument.
+/// @{
+struct OffsetOption {
+ uint64_t Val = 0;
+ bool HasValue = false;
+ bool IsRequested = false;
+};
+
+namespace llvm {
+namespace cl {
+template <>
+class parser<OffsetOption> final : public basic_parser<OffsetOption> {
+public:
+ parser(Option &O) : basic_parser(O) {}
+
+ /// Return true on error.
+ bool parse(Option &O, StringRef ArgName, StringRef Arg, OffsetOption &Val) {
+ if (Arg == "") {
+ Val.Val = 0;
+ Val.HasValue = false;
+ Val.IsRequested = true;
+ return false;
+ }
+ if (Arg.getAsInteger(0, Val.Val))
+ return O.error("'" + Arg + "' value invalid for integer argument!");
+ Val.HasValue = true;
+ Val.IsRequested = true;
+ return false;
+ }
+
+ enum ValueExpected getValueExpectedFlagDefault() const {
+ return ValueOptional;
+ }
+
+ void printOptionInfo(const Option &O, size_t GlobalWidth) const {
+ outs() << " -" << O.ArgStr;
+ Option::printHelpStr(O.HelpStr, GlobalWidth, getOptionWidth(O));
+ }
+
+ void printOptionDiff(const Option &O, OffsetOption V, OptVal Default,
+ size_t GlobalWidth) const {
+ printOptionName(O, GlobalWidth);
+ outs() << "[=offset]";
+ }
+
+ // An out-of-line virtual method to provide a 'home' for this class.
+ void anchor() override {};
+};
+} // cl
+} // llvm
+
+/// @}
+/// Command line options.
+/// @{
+
+namespace {
+using namespace cl;
+
+OptionCategory DwarfDumpCategory("Specific Options");
+static list<std::string>
+ InputFilenames(Positional, desc("<input object files or .dSYM bundles>"),
+ ZeroOrMore, cat(DwarfDumpCategory));
+
+cl::OptionCategory SectionCategory("Section-specific Dump Options",
+ "These control which sections are dumped. "
+ "Where applicable these parameters take an "
+ "optional =<offset> argument to dump only "
+ "the entry at the specified offset.");
+
+static opt<bool> DumpAll("all", desc("Dump all debug info sections"),
+ cat(SectionCategory));
+static alias DumpAllAlias("a", desc("Alias for -all"), aliasopt(DumpAll));
+
+// Options for dumping specific sections.
+static unsigned DumpType = DIDT_Null;
+static std::array<llvm::Optional<uint64_t>, (unsigned)DIDT_ID_Count>
+ DumpOffsets;
+#define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME) \
+ static opt<OffsetOption> Dump##ENUM_NAME( \
+ CMDLINE_NAME, desc("Dump the " ELF_NAME " section"), \
+ cat(SectionCategory));
+#include "llvm/BinaryFormat/Dwarf.def"
+#undef HANDLE_DWARF_SECTION
+
+static alias DumpDebugFrameAlias("eh-frame", desc("Alias for -debug-frame"),
+ NotHidden, cat(SectionCategory),
+ aliasopt(DumpDebugFrame));
+static list<std::string>
+ ArchFilters("arch",
+ desc("Dump debug information for the specified CPU "
+ "architecture only. Architectures may be specified by "
+ "name or by number. This option can be specified "
+ "multiple times, once for each desired architecture."),
+ cat(DwarfDumpCategory));
+static opt<bool>
+ Diff("diff",
+ desc("Emit diff-friendly output by omitting offsets and addresses."),
+ cat(DwarfDumpCategory));
+static list<std::string>
+ Find("find",
+ desc("Search for the exact match for <name> in the accelerator tables "
+ "and print the matching debug information entries. When no "
+ "accelerator tables are available, the slower but more complete "
+ "-name option can be used instead."),
+ value_desc("name"), cat(DwarfDumpCategory));
+static alias FindAlias("f", desc("Alias for -find."), aliasopt(Find));
+static opt<bool> IgnoreCase("ignore-case",
+ desc("Ignore case distinctions when searching."),
+ value_desc("i"), cat(DwarfDumpCategory));
+static alias IgnoreCaseAlias("i", desc("Alias for -ignore-case."),
+ aliasopt(IgnoreCase));
+static list<std::string> Name(
+ "name",
+ desc("Find and print all debug info entries whose name (DW_AT_name "
+ "attribute) matches the exact text in <pattern>. When used with the "
+ "the -regex option <pattern> is interpreted as a regular expression."),
+ value_desc("pattern"), cat(DwarfDumpCategory));
+static alias NameAlias("n", desc("Alias for -name"), aliasopt(Name));
+static opt<uint64_t>
+ Lookup("lookup",
+ desc("Lookup <address> in the debug information and print out any "
+ "available file, function, block and line table details."),
+ value_desc("address"), cat(DwarfDumpCategory));
+static opt<std::string>
+ OutputFilename("o", cl::init("-"),
+ cl::desc("Redirect output to the specified file."),
+ cl::value_desc("filename"), cat(DwarfDumpCategory));
+static alias OutputFilenameAlias("out-file", desc("Alias for -o."),
+ aliasopt(OutputFilename));
+static opt<bool>
+ UseRegex("regex",
+ desc("Treat any <pattern> strings as regular expressions when "
+ "searching instead of just as an exact string match."),
+ cat(DwarfDumpCategory));
+static alias RegexAlias("x", desc("Alias for -regex"), aliasopt(UseRegex));
+static opt<bool>
+ ShowChildren("show-children",
+ desc("Show a debug info entry's children when selectively "
+ "printing entries."),
+ cat(DwarfDumpCategory));
+static alias ShowChildrenAlias("c", desc("Alias for -show-children."),
+ aliasopt(ShowChildren));
+static opt<bool>
+ ShowParents("show-parents",
+ desc("Show a debug info entry's parents when selectively "
+ "printing entries."),
+ cat(DwarfDumpCategory));
+static alias ShowParentsAlias("p", desc("Alias for -show-parents."),
+ aliasopt(ShowParents));
+static opt<bool>
+ ShowForm("show-form",
+ desc("Show DWARF form types after the DWARF attribute types."),
+ cat(DwarfDumpCategory));
+static alias ShowFormAlias("F", desc("Alias for -show-form."),
+ aliasopt(ShowForm), cat(DwarfDumpCategory));
+static opt<unsigned>
+ ChildRecurseDepth("recurse-depth",
+ desc("Only recurse to a depth of N when displaying "
+ "children of debug info entries."),
+ cat(DwarfDumpCategory), init(-1U), value_desc("N"));
+static alias ChildRecurseDepthAlias("r", desc("Alias for -recurse-depth."),
+ aliasopt(ChildRecurseDepth));
+static opt<unsigned>
+ ParentRecurseDepth("parent-recurse-depth",
+ desc("Only recurse to a depth of N when displaying "
+ "parents of debug info entries."),
+ cat(DwarfDumpCategory), init(-1U), value_desc("N"));
+static opt<bool>
+ SummarizeTypes("summarize-types",
+ desc("Abbreviate the description of type unit entries."),
+ cat(DwarfDumpCategory));
+static cl::opt<bool>
+ Statistics("statistics",
+ cl::desc("Emit JSON-formatted debug info quality metrics."),
+ cat(DwarfDumpCategory));
+static opt<bool> Verify("verify", desc("Verify the DWARF debug info."),
+ cat(DwarfDumpCategory));
+static opt<bool> Quiet("quiet", desc("Use with -verify to not emit to STDOUT."),
+ cat(DwarfDumpCategory));
+static opt<bool> DumpUUID("uuid", desc("Show the UUID for each architecture."),
+ cat(DwarfDumpCategory));
+static alias DumpUUIDAlias("u", desc("Alias for -uuid."), aliasopt(DumpUUID));
+static opt<bool> Verbose("verbose",
+ desc("Print more low-level encoding details."),
+ cat(DwarfDumpCategory));
+static alias VerboseAlias("v", desc("Alias for -verbose."), aliasopt(Verbose),
+ cat(DwarfDumpCategory));
+static cl::extrahelp
+ HelpResponse("\nPass @FILE as argument to read options from FILE.\n");
+} // namespace
+/// @}
+//===----------------------------------------------------------------------===//
+
+static void error(StringRef Prefix, std::error_code EC) {
+ if (!EC)
+ return;
+ WithColor::error() << Prefix << ": " << EC.message() << "\n";
+ exit(1);
+}
+
+static DIDumpOptions getDumpOpts() {
+ DIDumpOptions DumpOpts;
+ DumpOpts.DumpType = DumpType;
+ DumpOpts.ChildRecurseDepth = ChildRecurseDepth;
+ DumpOpts.ParentRecurseDepth = ParentRecurseDepth;
+ DumpOpts.ShowAddresses = !Diff;
+ DumpOpts.ShowChildren = ShowChildren;
+ DumpOpts.ShowParents = ShowParents;
+ DumpOpts.ShowForm = ShowForm;
+ DumpOpts.SummarizeTypes = SummarizeTypes;
+ DumpOpts.Verbose = Verbose;
+ // In -verify mode, print DIEs without children in error messages.
+ if (Verify)
+ return DumpOpts.noImplicitRecursion();
+ return DumpOpts;
+}
+
+static uint32_t getCPUType(MachOObjectFile &MachO) {
+ if (MachO.is64Bit())
+ return MachO.getHeader64().cputype;
+ else
+ return MachO.getHeader().cputype;
+}
+
+/// Return true if the object file has not been filtered by an --arch option.
+static bool filterArch(ObjectFile &Obj) {
+ if (ArchFilters.empty())
+ return true;
+
+ if (auto *MachO = dyn_cast<MachOObjectFile>(&Obj)) {
+ for (auto Arch : ArchFilters) {
+ // Match architecture number.
+ unsigned Value;
+ if (!StringRef(Arch).getAsInteger(0, Value))
+ if (Value == getCPUType(*MachO))
+ return true;
+
+ // Match as name.
+ if (MachO->getArchTriple().getArch() == Triple(Arch).getArch())
+ return true;
+ }
+ }
+ return false;
+}
+
+using HandlerFn = std::function<bool(ObjectFile &, DWARFContext &DICtx, Twine,
+ raw_ostream &)>;
+
+/// Print only DIEs that have a certain name.
+static bool filterByName(const StringSet<> &Names, DWARFDie Die,
+ StringRef NameRef, raw_ostream &OS) {
+ std::string Name =
+ (IgnoreCase && !UseRegex) ? NameRef.lower() : NameRef.str();
+ if (UseRegex) {
+ // Match regular expression.
+ for (auto Pattern : Names.keys()) {
+ Regex RE(Pattern, IgnoreCase ? Regex::IgnoreCase : Regex::NoFlags);
+ std::string Error;
+ if (!RE.isValid(Error)) {
+ errs() << "error in regular expression: " << Error << "\n";
+ exit(1);
+ }
+ if (RE.match(Name)) {
+ Die.dump(OS, 0, getDumpOpts());
+ return true;
+ }
+ }
+ } else if (Names.count(Name)) {
+ // Match full text.
+ Die.dump(OS, 0, getDumpOpts());
+ return true;
+ }
+ return false;
+}
+
+/// Print only DIEs that have a certain name.
+static void filterByName(const StringSet<> &Names,
+ DWARFContext::unit_iterator_range CUs,
+ raw_ostream &OS) {
+ for (const auto &CU : CUs)
+ for (const auto &Entry : CU->dies()) {
+ DWARFDie Die = {CU.get(), &Entry};
+ if (const char *Name = Die.getName(DINameKind::ShortName))
+ if (filterByName(Names, Die, Name, OS))
+ continue;
+ if (const char *Name = Die.getName(DINameKind::LinkageName))
+ filterByName(Names, Die, Name, OS);
+ }
+}
+
+static void getDies(DWARFContext &DICtx, const AppleAcceleratorTable &Accel,
+ StringRef Name, SmallVectorImpl<DWARFDie> &Dies) {
+ for (const auto &Entry : Accel.equal_range(Name)) {
+ if (llvm::Optional<uint64_t> Off = Entry.getDIESectionOffset()) {
+ if (DWARFDie Die = DICtx.getDIEForOffset(*Off))
+ Dies.push_back(Die);
+ }
+ }
+}
+
+static DWARFDie toDie(const DWARFDebugNames::Entry &Entry,
+ DWARFContext &DICtx) {
+ llvm::Optional<uint64_t> CUOff = Entry.getCUOffset();
+ llvm::Optional<uint64_t> Off = Entry.getDIEUnitOffset();
+ if (!CUOff || !Off)
+ return DWARFDie();
+
+ DWARFCompileUnit *CU = DICtx.getCompileUnitForOffset(*CUOff);
+ if (!CU)
+ return DWARFDie();
+
+ if (llvm::Optional<uint64_t> DWOId = CU->getDWOId()) {
+ // This is a skeleton unit. Look up the DIE in the DWO unit.
+ CU = DICtx.getDWOCompileUnitForHash(*DWOId);
+ if (!CU)
+ return DWARFDie();
+ }
+
+ return CU->getDIEForOffset(CU->getOffset() + *Off);
+}
+
+static void getDies(DWARFContext &DICtx, const DWARFDebugNames &Accel,
+ StringRef Name, SmallVectorImpl<DWARFDie> &Dies) {
+ for (const auto &Entry : Accel.equal_range(Name)) {
+ if (DWARFDie Die = toDie(Entry, DICtx))
+ Dies.push_back(Die);
+ }
+}
+
+/// Print only DIEs that have a certain name.
+static void filterByAccelName(ArrayRef<std::string> Names, DWARFContext &DICtx,
+ raw_ostream &OS) {
+ SmallVector<DWARFDie, 4> Dies;
+ for (const auto &Name : Names) {
+ getDies(DICtx, DICtx.getAppleNames(), Name, Dies);
+ getDies(DICtx, DICtx.getAppleTypes(), Name, Dies);
+ getDies(DICtx, DICtx.getAppleNamespaces(), Name, Dies);
+ getDies(DICtx, DICtx.getDebugNames(), Name, Dies);
+ }
+ llvm::sort(Dies);
+ Dies.erase(std::unique(Dies.begin(), Dies.end()), Dies.end());
+
+ for (DWARFDie Die : Dies)
+ Die.dump(OS, 0, getDumpOpts());
+}
+
+/// Handle the --lookup option and dump the DIEs and line info for the given
+/// address.
+/// TODO: specified Address for --lookup option could relate for several
+/// different sections(in case not-linked object file). llvm-dwarfdump
+/// need to do something with this: extend lookup option with section
+/// information or probably display all matched entries, or something else...
+static bool lookup(ObjectFile &Obj, DWARFContext &DICtx, uint64_t Address,
+ raw_ostream &OS) {
+ auto DIEsForAddr = DICtx.getDIEsForAddress(Lookup);
+
+ if (!DIEsForAddr)
+ return false;
+
+ DIDumpOptions DumpOpts = getDumpOpts();
+ DumpOpts.ChildRecurseDepth = 0;
+ DIEsForAddr.CompileUnit->dump(OS, DumpOpts);
+ if (DIEsForAddr.FunctionDIE) {
+ DIEsForAddr.FunctionDIE.dump(OS, 2, DumpOpts);
+ if (DIEsForAddr.BlockDIE)
+ DIEsForAddr.BlockDIE.dump(OS, 4, DumpOpts);
+ }
+
+ // TODO: it is neccessary to set proper SectionIndex here.
+ // object::SectionedAddress::UndefSection works for only absolute addresses.
+ if (DILineInfo LineInfo = DICtx.getLineInfoForAddress(
+ {Lookup, object::SectionedAddress::UndefSection}))
+ LineInfo.dump(OS);
+
+ return true;
+}
+
+bool collectStatsForObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
+ Twine Filename, raw_ostream &OS);
+
+static bool dumpObjectFile(ObjectFile &Obj, DWARFContext &DICtx, Twine Filename,
+ raw_ostream &OS) {
+ logAllUnhandledErrors(DICtx.loadRegisterInfo(Obj), errs(),
+ Filename.str() + ": ");
+ // The UUID dump already contains all the same information.
+ if (!(DumpType & DIDT_UUID) || DumpType == DIDT_All)
+ OS << Filename << ":\tfile format " << Obj.getFileFormatName() << '\n';
+
+ // Handle the --lookup option.
+ if (Lookup)
+ return lookup(Obj, DICtx, Lookup, OS);
+
+ // Handle the --name option.
+ if (!Name.empty()) {
+ StringSet<> Names;
+ for (auto name : Name)
+ Names.insert((IgnoreCase && !UseRegex) ? StringRef(name).lower() : name);
+
+ filterByName(Names, DICtx.normal_units(), OS);
+ filterByName(Names, DICtx.dwo_units(), OS);
+ return true;
+ }
+
+ // Handle the --find option and lower it to --debug-info=<offset>.
+ if (!Find.empty()) {
+ filterByAccelName(Find, DICtx, OS);
+ return true;
+ }
+
+ // Dump the complete DWARF structure.
+ DICtx.dump(OS, getDumpOpts(), DumpOffsets);
+ return true;
+}
+
+static bool verifyObjectFile(ObjectFile &Obj, DWARFContext &DICtx,
+ Twine Filename, raw_ostream &OS) {
+ // Verify the DWARF and exit with non-zero exit status if verification
+ // fails.
+ raw_ostream &stream = Quiet ? nulls() : OS;
+ stream << "Verifying " << Filename.str() << ":\tfile format "
+ << Obj.getFileFormatName() << "\n";
+ bool Result = DICtx.verify(stream, getDumpOpts());
+ if (Result)
+ stream << "No errors.\n";
+ else
+ stream << "Errors detected.\n";
+ return Result;
+}
+
+static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
+ HandlerFn HandleObj, raw_ostream &OS);
+
+static bool handleArchive(StringRef Filename, Archive &Arch,
+ HandlerFn HandleObj, raw_ostream &OS) {
+ bool Result = true;
+ Error Err = Error::success();
+ for (auto Child : Arch.children(Err)) {
+ auto BuffOrErr = Child.getMemoryBufferRef();
+ error(Filename, errorToErrorCode(BuffOrErr.takeError()));
+ auto NameOrErr = Child.getName();
+ error(Filename, errorToErrorCode(NameOrErr.takeError()));
+ std::string Name = (Filename + "(" + NameOrErr.get() + ")").str();
+ Result &= handleBuffer(Name, BuffOrErr.get(), HandleObj, OS);
+ }
+ error(Filename, errorToErrorCode(std::move(Err)));
+
+ return Result;
+}
+
+static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
+ HandlerFn HandleObj, raw_ostream &OS) {
+ Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Buffer);
+ error(Filename, errorToErrorCode(BinOrErr.takeError()));
+
+ bool Result = true;
+ if (auto *Obj = dyn_cast<ObjectFile>(BinOrErr->get())) {
+ if (filterArch(*Obj)) {
+ std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
+ Result = HandleObj(*Obj, *DICtx, Filename, OS);
+ }
+ }
+ else if (auto *Fat = dyn_cast<MachOUniversalBinary>(BinOrErr->get()))
+ for (auto &ObjForArch : Fat->objects()) {
+ std::string ObjName =
+ (Filename + "(" + ObjForArch.getArchFlagName() + ")").str();
+ if (auto MachOOrErr = ObjForArch.getAsObjectFile()) {
+ auto &Obj = **MachOOrErr;
+ if (filterArch(Obj)) {
+ std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(Obj);
+ Result &= HandleObj(Obj, *DICtx, ObjName, OS);
+ }
+ continue;
+ } else
+ consumeError(MachOOrErr.takeError());
+ if (auto ArchiveOrErr = ObjForArch.getAsArchive()) {
+ error(ObjName, errorToErrorCode(ArchiveOrErr.takeError()));
+ Result &= handleArchive(ObjName, *ArchiveOrErr.get(), HandleObj, OS);
+ continue;
+ } else
+ consumeError(ArchiveOrErr.takeError());
+ }
+ else if (auto *Arch = dyn_cast<Archive>(BinOrErr->get()))
+ Result = handleArchive(Filename, *Arch, HandleObj, OS);
+ return Result;
+}
+
+static bool handleFile(StringRef Filename, HandlerFn HandleObj,
+ raw_ostream &OS) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
+ MemoryBuffer::getFileOrSTDIN(Filename);
+ error(Filename, BuffOrErr.getError());
+ std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
+ return handleBuffer(Filename, *Buffer, HandleObj, OS);
+}
+
+/// 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.
+static std::vector<std::string> expandBundle(const std::string &InputPath) {
+ std::vector<std::string> BundlePaths;
+ SmallString<256> BundlePath(InputPath);
+ // Normalize input path. This is necessary to accept `bundle.dSYM/`.
+ sys::path::remove_dots(BundlePath);
+ // Manually open up the bundle to avoid introducing additional dependencies.
+ if (sys::fs::is_directory(BundlePath) &&
+ sys::path::extension(BundlePath) == ".dSYM") {
+ std::error_code EC;
+ sys::path::append(BundlePath, "Contents", "Resources", "DWARF");
+ for (sys::fs::directory_iterator Dir(BundlePath, EC), DirEnd;
+ Dir != DirEnd && !EC; Dir.increment(EC)) {
+ const std::string &Path = Dir->path();
+ sys::fs::file_status Status;
+ EC = sys::fs::status(Path, Status);
+ error(Path, EC);
+ switch (Status.type()) {
+ case sys::fs::file_type::regular_file:
+ case sys::fs::file_type::symlink_file:
+ case sys::fs::file_type::type_unknown:
+ BundlePaths.push_back(Path);
+ break;
+ default: /*ignore*/;
+ }
+ }
+ error(BundlePath, EC);
+ }
+ if (!BundlePaths.size())
+ BundlePaths.push_back(InputPath);
+ return BundlePaths;
+}
+
+int main(int argc, char **argv) {
+ InitLLVM X(argc, argv);
+
+ llvm::InitializeAllTargetInfos();
+ llvm::InitializeAllTargetMCs();
+
+ HideUnrelatedOptions({&DwarfDumpCategory, &SectionCategory, &ColorCategory});
+ cl::ParseCommandLineOptions(
+ argc, argv,
+ "pretty-print DWARF debug information in object files"
+ " and debug info archives.\n");
+
+ // FIXME: Audit interactions between these two options and make them
+ // compatible.
+ if (Diff && Verbose) {
+ WithColor::error() << "incompatible arguments: specifying both -diff and "
+ "-verbose is currently not supported";
+ return 0;
+ }
+
+ std::error_code EC;
+ ToolOutputFile OutputFile(OutputFilename, EC, sys::fs::OF_None);
+ error("Unable to open output file" + OutputFilename, EC);
+ // Don't remove output file if we exit with an error.
+ OutputFile.keep();
+
+ bool OffsetRequested = false;
+
+ // Defaults to dumping all sections, unless brief mode is specified in which
+ // case only the .debug_info section in dumped.
+#define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME) \
+ if (Dump##ENUM_NAME.IsRequested) { \
+ DumpType |= DIDT_##ENUM_NAME; \
+ if (Dump##ENUM_NAME.HasValue) { \
+ DumpOffsets[DIDT_ID_##ENUM_NAME] = Dump##ENUM_NAME.Val; \
+ OffsetRequested = true; \
+ } \
+ }
+#include "llvm/BinaryFormat/Dwarf.def"
+#undef HANDLE_DWARF_SECTION
+ if (DumpUUID)
+ DumpType |= DIDT_UUID;
+ if (DumpAll)
+ DumpType = DIDT_All;
+ if (DumpType == DIDT_Null) {
+ if (Verbose)
+ DumpType = DIDT_All;
+ else
+ DumpType = DIDT_DebugInfo;
+ }
+
+ // Unless dumping a specific DIE, default to --show-children.
+ if (!ShowChildren && !Verify && !OffsetRequested && Name.empty() && Find.empty())
+ ShowChildren = true;
+
+ // Defaults to a.out if no filenames specified.
+ if (InputFilenames.empty())
+ InputFilenames.push_back("a.out");
+
+ // Expand any .dSYM bundles to the individual object files contained therein.
+ std::vector<std::string> Objects;
+ for (const auto &F : InputFilenames) {
+ auto Objs = expandBundle(F);
+ Objects.insert(Objects.end(), Objs.begin(), Objs.end());
+ }
+
+ if (Verify) {
+ // If we encountered errors during verify, exit with a non-zero exit status.
+ if (!all_of(Objects, [&](std::string Object) {
+ return handleFile(Object, verifyObjectFile, OutputFile.os());
+ }))
+ return 1;
+ } else if (Statistics)
+ for (auto Object : Objects)
+ handleFile(Object, collectStatsForObjectFile, OutputFile.os());
+ else
+ for (auto Object : Objects)
+ handleFile(Object, dumpObjectFile, OutputFile.os());
+
+ return EXIT_SUCCESS;
+}