Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions llvm/docs/CommandGuide/llvm-debuginfo-analyzer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ to make the output easier to understand.
=list: Elements are displayed in a tabular format.
=parents: Elements and parents are displayed in a tree format.
=view: Elements, parents and children are displayed in a tree format.
=debugger: Lines, and optionally variables and instructions are
displayed in a way to simulate stepping through a debugger.

The **list** layout presents the logical elements in a tabular form
without any parent-child relationship. This may be the preferred way to
Expand All @@ -417,6 +419,10 @@ The combined **view** layout includes the elements that match any given
criteria (:option:`--select`) or (:option:`--compare`), its parents
and children.

The combined **debugger** layout prints each statement line in order and
variables live at each line (if `--print=symbols` given), as well as
instructions (if `--print=instructions` given).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a section between SELECT LOGICAL ELEMENTS and COMPARISON MODE that shows the debugger view, using one of the input files located in llvm/test/tools/llvm-debuginfo-analyzer. First using the standard logical view and then the debugger view.
Something like:

DEBUGGER VIEW
"""""""""""""
The following prints ... logical view ...

.. code-block:: none

  llvm-debuginfo-analyzer --print=symbols test-dwarf-clang.o

  the generated logical view ...  

The following prints ... debugger view ...

.. code-block:: none

  llvm-debuginfo-analyzer --report=debugger --print=symbols test-dwarf-clang.o

  the generated debugger view ...  

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this section.

**Notes**:

1. When a selection criteria (:option:`--select`) is specified with no
Expand Down Expand Up @@ -855,6 +861,73 @@ layout and given the number of matches.
-----------------------------
Total 26 8

DEBUGGER VIEW
"""""""""""""
In debugger view, :program:`llvm-debuginfo-analyzer` prints out
debug-info in a manner that emulates a debugger. For each function, each
statement line is printed out in order, complete with the inlined
callstack. This is useful to verify the specific orders of lines, as
well as verifying inline callstacks.

Copy link
Member

@CarlosAlbertoEnciso CarlosAlbertoEnciso Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with other View layouts, the Debugger View should support additional attributes such as --attribute=offset,level.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added support for them. In fact the offsets and levels are controlled by the same mechanism for printing them as the other options.

.. code-block:: none

llvm-debuginfo-analyzer --report=debugger
test-dwarf-clang.o test-dwarf-gcc.o

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the another general printing layouts, include:
Logical View title follow by {File and CompileUnit entries

Logical View:
 [000]           {File} 'test-dwarf-clang.o'
 [001]           {CompileUnit} 'test.cpp'

{Function}: foo
{Line}: [0x0000000000] test.cpp:2 [foo]
{Line}: [0x0000000012] test.cpp:3 [foo]
{Line}: [0x000000001c] test.cpp:5 [foo]
{Line}: [0x0000000023] test.cpp:6 [foo]
{Line}: [0x000000002f] test.cpp:8 [foo]
{Line}: [0x0000000035] test.cpp:9 [foo]
{Function}: foo
{Line}: [0x0000000000] test.cpp:2 [foo]
{Line}: [0x0000000014] test.cpp:3 [foo]
{Line}: [0x000000001a] test.cpp:5 [foo]
{Line}: [0x0000000021] test.cpp:6 [foo]
{Line}: [0x0000000028] test.cpp:8 [foo]
{Line}: [0x000000002b] test.cpp:9 [foo]

Optionally, by adding `--print=symbols`, live variables for each line is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Optionally, by adding `--print=symbols`, live variables for each line is
Optionally, by adding `--print=symbols`, live variables for each line are

printed out.

.. code-block:: none

llvm-debuginfo-analyzer --report=debugger
test-dwarf-clang.o

{Function}: foo
{Line}: [0x0000000000] test.cpp:2 [foo]
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)
{Line}: [0x0000000012] test.cpp:3 [foo]
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)
{Line}: [0x000000001c] test.cpp:5 [foo]
{Variable}: CONSTANT: const INTEGER : fbreg -28 (line 5)
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)
{Line}: [0x0000000023] test.cpp:6 [foo]
{Variable}: CONSTANT: const INTEGER : fbreg -28 (line 5)
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)
{Line}: [0x000000002f] test.cpp:8 [foo]
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)
{Line}: [0x0000000035] test.cpp:9 [foo]
{Variable}: ParamBool: bool : fbreg -21 (line 2)
{Variable}: ParamPtr: INTPTR : fbreg -16 (line 2)
{Variable}: ParamUnsigned: unsigned int : fbreg -20 (line 2)

Optionally, `--print=instructions`, the lines are interleaved with the
instructions. Combined with the output of `--print=symbols`, tests can
verify specific expressions for live variables.

COMPARISON MODE
^^^^^^^^^^^^^^^
In this mode :program:`llvm-debuginfo-analyzer` compares logical views
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/DebugInfo/LogicalView/Core/LVLocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class LLVM_ABI LVLocation : public LVObject {
void printRaw(raw_ostream &OS, bool Full = true) const;
virtual void printRawExtra(raw_ostream &OS, bool Full = true) const {}

virtual void printLocations(raw_ostream &OS) const {}
void print(raw_ostream &OS, bool Full = true) const override;
void printExtra(raw_ostream &OS, bool Full = true) const override;
};
Expand All @@ -177,6 +178,7 @@ class LLVM_ABI LVLocationSymbol final : public LVLocation {
uint64_t LocDescOffset) override;
void addObject(LVSmall Opcode, ArrayRef<LVUnsigned> Operands) override;

void printLocations(raw_ostream &OS) const override;
void printRawExtra(raw_ostream &OS, bool Full = true) const override;
void printExtra(raw_ostream &OS, bool Full = true) const override;
};
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/DebugInfo/LogicalView/Core/LVOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ using LVPrintKindSet = std::set<LVPrintKind>;
enum class LVReportKind {
All, // --report=all
Children, // --report=children
Debugger, // --report=debugger
List, // --report=list
Parents, // --report=parents
View // --report=view
Expand Down Expand Up @@ -406,6 +407,7 @@ class LVOptions {
// --report.
REPORT_OPTION(All);
REPORT_OPTION(Children);
REPORT_OPTION(Debugger);
REPORT_OPTION(List);
REPORT_OPTION(Parents);
REPORT_OPTION(View);
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/DebugInfo/LogicalView/Core/LVReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class LLVM_ABI LVReader {
return std::string(Path);
}

Error printDebugger();
virtual Error printScopes();
virtual Error printMatchedElements(bool UseMatchedElements);
virtual void sortScopes() {}
Expand Down
25 changes: 16 additions & 9 deletions llvm/lib/DebugInfo/LogicalView/Core/LVLocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,19 @@ void LVLocation::print(LVLocations *Locations, raw_ostream &OS, bool Full) {
Location->print(OS, Full);
}

void LVLocationSymbol::printLocations(raw_ostream &OS) const {
if (Entries) {
bool CodeViewLocation = getParentSymbol()->getHasCodeViewLocation();
std::string Leading;
for (LVOperation *Operation : *Entries) {
OS << Leading
<< (CodeViewLocation ? Operation->getOperandsCodeViewInfo()
: Operation->getOperandsDWARFInfo());
Leading = ", ";
}
Comment on lines +654 to +660
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I see that this code was moved from this original location, but perhaps we could take the chance to adopt llvm::interleave() to print to OS w/ interleaved ,s.

}
}

void LVLocationSymbol::printExtra(raw_ostream &OS, bool Full) const {
OS << "{Location}";
if (getIsCallSite())
Expand All @@ -657,15 +670,9 @@ void LVLocationSymbol::printExtra(raw_ostream &OS, bool Full) const {

// Print location entries.
if (Full && Entries) {
bool CodeViewLocation = getParentSymbol()->getHasCodeViewLocation();
std::stringstream Stream;
std::string Leading;
for (LVOperation *Operation : *Entries) {
Stream << Leading
<< (CodeViewLocation ? Operation->getOperandsCodeViewInfo()
: Operation->getOperandsDWARFInfo());
Leading = ", ";
}
std::string Str;
raw_string_ostream Stream(Str);
printLocations(Stream);
printAttributes(OS, Full, "{Entry} ", const_cast<LVLocationSymbol *>(this),
StringRef(Stream.str()),
/*UseQuotes=*/false,
Expand Down
11 changes: 10 additions & 1 deletion llvm/lib/DebugInfo/LogicalView/Core/LVOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ void LVOptions::resolveDependencies() {
setPrintWarnings();
}

if (getReportDebugger()) {
// Must include at least the lines, otherwise there's nothing to print
setPrintLines();
// Printing symbols in debugger report requires the symbol ranges
if (getPrintSymbols())
setAttributeRange();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a very minor points. Missing full stops in the comments.

// '--warning=all' settings.
if (getWarningAll()) {
setWarningCoverages();
Expand Down Expand Up @@ -186,6 +194,7 @@ void LVOptions::resolveDependencies() {
// '--reports=all' settings.
if (getReportAll()) {
setReportChildren();
setReportDebugger();
setReportList();
setReportParents();
setReportView();
Expand All @@ -202,7 +211,7 @@ void LVOptions::resolveDependencies() {
setReportAnyView();

// The report will include: List or Parents or Children.
if (getReportList() || getReportAnyView())
if (getReportList() || getReportAnyView() || getReportDebugger())
setReportExecute();

// If a view or element comparison has been requested, the following options
Expand Down
175 changes: 174 additions & 1 deletion llvm/lib/DebugInfo/LogicalView/Core/LVReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

#include "llvm/DebugInfo/LogicalView/Core/LVReader.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/DebugInfo/LogicalView/Core/LVScope.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatAdapters.h"
Expand Down Expand Up @@ -516,13 +517,185 @@ Error LVReader::doPrint() {
if (options().getReportParents() || options().getReportView())
if (Error Err = printScopes())
return Err;

// Requested debugger report.
if (options().getReportDebugger())
if (Error Err = printDebugger())
return Err;
return Error::success();
}

return printScopes();
}

namespace {

struct DebuggerViewPrinter {
std::vector<const LVLine *> Lines;
std::unordered_map<LVAddress, std::vector<const LVLocation *>> LifetimeBegins;
std::unordered_map<LVAddress, std::vector<const LVLocation *>>
LifetimeEndsExclusive;
raw_ostream &OS;

const bool IncludeRanges = false;

void walk(raw_ostream &OS, const LVScope *Scope) {
if (Scope->scopeCount()) {
for (const LVScope *ChildScope : *Scope->getScopes())
walk(OS, ChildScope);
}
if (Scope->lineCount()) {
for (const LVLine *Line : *Scope->getLines()) {
Lines.push_back(Line);
}
}
if (Scope->symbolCount()) {
for (const LVSymbol *Symbol : *Scope->getSymbols()) {
LVLocations SymbolLocations;
Symbol->getLocations(SymbolLocations);
if (SymbolLocations.empty())
continue;

if (IncludeRanges)
OS << "{Range}: " << Symbol->getName() << " (line "
<< Symbol->getLineNumber() << ")" << ": ";

for (const LVLocation *Loc : SymbolLocations) {
if (Loc->getIsGapEntry())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loc --> Location

continue;

LVAddress Begin = Loc->getLowerAddress();
LVAddress End = Loc->getUpperAddress();
LifetimeBegins[Begin].push_back(Loc);
LifetimeEndsExclusive[End].push_back(Loc);

if (IncludeRanges)
OS << "[" << hexValue(Begin) << ":" << hexValue(End) << "] ";
}

if (IncludeRanges)
OS << "\n";
}
}
}

DebuggerViewPrinter(raw_ostream &OS, const LVScopeFunction *Fn) : OS(OS) {
walk(OS, Fn);
std::sort(Lines.begin(), Lines.end(),
[](const LVLine *a, const LVLine *b) -> bool {
if (a->getAddress() != b->getAddress())
return a->getAddress() < b->getAddress();
if (a->getIsLineDebug() != b->getIsLineDebug())
return a->getIsLineDebug();
return a->getID() < b->getID();
});
}

static void printIndent(raw_ostream &OS, int Indent) {
for (int i = 0; i < Indent; i++)
OS << " ";
}

static void printCallstack(raw_ostream &OS, const LVScope *Scope) {
const LVScope *PrevScope = nullptr;
while (Scope) {
if (Scope->getIsFunction() || Scope->getIsInlinedFunction()) {
OS << "[" << Scope->getName();
if (PrevScope && PrevScope->getIsInlinedFunction()) {
OS << ":"
<< cast<LVScopeFunctionInlined>(PrevScope)->getCallLineNumber();
}
OS << "]";
PrevScope = Scope;
}
Scope = Scope->getParentScope();
}
}

static bool isChildScopeOf(const LVScope *A, const LVScope *B) {
while (A) {
A = A->getParentScope();
if (A == B)
return true;
}
return false;
}

void print() {
const bool IncludeVars = options().getPrintSymbols();
const bool IncludeCode = options().getPrintInstructions();
SetVector<const LVLocation *>
LiveSymbols; // This needs to be ordered since we're iterating over it.
for (const LVLine *Line : Lines) {
const LVScope *Scope = Line->getParentScope();
// Update live list: Add lives
for (auto Loc : LifetimeBegins[Line->getAddress()])
LiveSymbols.insert(Loc);
// Update live list: remove dead
for (auto Loc : LifetimeEndsExclusive[Line->getAddress()])
LiveSymbols.remove(Loc);

if (Line->getIsNewStatement() && Line->getIsLineDebug() &&
Line->getLineNumber() != 0) {
auto LineDebug = cast<LVLineDebug>(Line);

printIndent(OS, 1);
OS << "{Line}: " << " [" << hexValue(LineDebug->getAddress()) << "] "
<< LineDebug->getPathname() << ":" << LineDebug->getLineNumber()
<< " ";
printCallstack(OS, Scope);
OS << "\n";
if (IncludeVars) {
for (auto SymLoc : LiveSymbols) {
const LVSymbol *Sym = SymLoc->getParentSymbol();
auto SymScope = Sym->getParentScope();
auto LineScope = LineDebug->getParentScope();
if (SymScope != LineScope && !isChildScopeOf(LineScope, SymScope))
continue;
printIndent(OS, 2);
OS << "{Variable}: " << Sym->getName() << ": "
<< Sym->getType()->getName() << " : ";
SymLoc->printLocations(OS);
OS << " (line " << Sym->getLineNumber() << ")";
OS << "\n";
}
}

} else if (IncludeCode && Line->getIsLineAssembler()) {
OS << " {Code}: " << " [" << hexValue(Line->getAddress()) << "] "
<< Line->getName() << "\n";
}
}
}
};

} // namespace

Error LVReader::printDebugger() {
if (!Root || !Root->scopeCount())
return Error::success();

for (auto *Scope : *Root->getScopes()) {
auto *CU = dyn_cast<LVScopeCompileUnit>(Scope);
Copy link
Member

@CarlosAlbertoEnciso CarlosAlbertoEnciso Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Root has only CompileUnit children. Use the inheritance relationships to invoke the needed printing code. Have a look at LVScopeRoot::doPrintMatches to see how to handle the output split feature.

Error LVScopeRoot::doPrintDebugger(bool Split, raw_ostream &OS, ....) const {
  if (Scopes) {
    ...
    for (LVScope *Scope : *Scopes) {
      // Virtual function in LVScope and defined for:
      // LVScopeCompileUnit, LVScopeFunction and LVScopeFunctionInlined
      Scope->printDebugger(.....);
    }
    ...
}
void LVScopeCompileUnit::printDebugger(raw_ostream &OS, ...) {
  ...
}
LVScopeFunction::printDebugger(raw_ostream &OS, ...) {
  ...
}
LVScopeFunctionInlined::printDebugger(raw_ostream &OS, ...) {
  ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The bulk of the logic is still in one place though, under LVFunction::printDebugger, but much of the specifics have been moved to be handled by virtual functions.

if (!CU)
continue;
for (const LVScope *ChildScope : *CU->getScopes()) {
auto *Fn = dyn_cast<LVScopeFunction>(ChildScope);
if (Fn) {
const LVLines *Lines = Fn->getLines();
// If there's no lines, this function has no body.
if (!Lines)
continue;
outs() << "{Function}: " << ChildScope->getName() << "\n";

DebuggerViewPrinter P(OS, Fn);
P.print();
}
}
}

return Error::success();
}

Error LVReader::printScopes() {
if (bool DoPrint =
(options().getPrintExecute() || options().getComparePrint())) {
Expand Down
Loading