**Context** Follow-up to [#147460](https://github.com/llvm/llvm-project/pull/147460), which added the ability to surface register-resident variable locations. This PR moves the annotation logic out of `Instruction::Dump()` and into `Disassembler::PrintInstructions()`, and adds lightweight state tracking so we only print changes at range starts and when variables go out of scope. --- ## What this does While iterating the instructions for a function, we maintain a “live variable map” keyed by `lldb::user_id_t` (the `Variable`’s ID) to remember each variable’s last emitted location string. For each instruction: - **New (or newly visible) variable** → print `name = <location>` once at the start of its DWARF location range, cache it. - **Location changed** (e.g., DWARF range switched to a different register/const) → print the updated mapping. - **Out of scope** (was tracked previously but not found for the current PC) → print `name = <undef>` and drop it. This produces **concise, stateful annotations** that highlight variable lifetime transitions without spamming every line. --- ## Why in `PrintInstructions()`? - Keeps `Instruction` stateless and avoids changing the `Instruction::Dump()` virtual API. - Makes it straightforward to diff state across instructions (`prev → current`) inside the single driver loop. --- ## How it works (high-level) 1. For the current PC, get in-scope variables via `StackFrame::GetInScopeVariableList(/*get_parent=*/true)`. 2. For each `Variable`, query `DWARFExpressionList::GetExpressionEntryAtAddress(func_load_addr, current_pc)` (added in #144238). 3. If the entry exists, call `DumpLocation(..., eDescriptionLevelBrief, abi)` to get a short, ABI-aware location string (e.g., `DW_OP_reg3 RBX → RBX`). 4. Compare against the last emitted location in the live map: - If not present → emit `name = <location>` and record it. - If different → emit updated mapping and record it. 5. After processing current in-scope variables, compute the set difference vs. the previous map and emit `name = <undef>` for any that disappeared. Internally: - We respect file↔load address translation already provided by `DWARFExpressionList`. - We reuse the ABI to map LLVM register numbers to arch register names. --- ## Example output (x86_64, simplified) ``` -> 0x55c6f5f6a140 <+0>: cmpl $0x2, %edi ; argc = RDI, argv = RSI 0x55c6f5f6a143 <+3>: jl 0x55c6f5f6a176 ; <+54> at d_original_example.c:6:3 0x55c6f5f6a145 <+5>: pushq %r15 0x55c6f5f6a147 <+7>: pushq %r14 0x55c6f5f6a149 <+9>: pushq %rbx 0x55c6f5f6a14a <+10>: movq %rsi, %rbx 0x55c6f5f6a14d <+13>: movl %edi, %r14d 0x55c6f5f6a150 <+16>: movl $0x1, %r15d ; argc = R14 0x55c6f5f6a156 <+22>: nopw %cs:(%rax,%rax) ; i = R15, argv = RBX 0x55c6f5f6a160 <+32>: movq (%rbx,%r15,8), %rdi 0x55c6f5f6a164 <+36>: callq 0x55c6f5f6a030 ; symbol stub for: puts 0x55c6f5f6a169 <+41>: incq %r15 0x55c6f5f6a16c <+44>: cmpq %r15, %r14 0x55c6f5f6a16f <+47>: jne 0x55c6f5f6a160 ; <+32> at d_original_example.c:5:10 0x55c6f5f6a171 <+49>: popq %rbx ; i = <undef> 0x55c6f5f6a172 <+50>: popq %r14 ; argv = RSI 0x55c6f5f6a174 <+52>: popq %r15 ; argc = RDI 0x55c6f5f6a176 <+54>: xorl %eax, %eax 0x55c6f5f6a178 <+56>: retq ``` Only transitions are shown: the start of a location, changes, and end-of-lifetime. --- ## Scope & limitations (by design) - Handles **simple locations** first (registers, const-in-register cases surfaced by `DumpLocation`). - **Memory/composite locations** are out of scope for this PR. - Annotations appear **only at range boundaries** (start/change/end) to minimize noise. - Output is **target-independent**; register names come from the target ABI. ## Implementation notes - All annotation printing now happens in `Disassembler::PrintInstructions()`. - Uses `std::unordered_map<lldb::user_id_t, std::string>` as the live map. - No persistent state across calls; the map is rebuilt while walking instruction by instruction. - **No changes** to the `Instruction` interface. --- ## Requested feedback - Placement and wording of the `<undef>` marker. - Whether we should optionally gate this behind a setting (currently always on when disassembling with an `ExecutionContext`). - Preference for immediate inclusion of tests vs. follow-up patch. --- Thanks for reviewing! Happy to adjust behavior/format based on feedback. --------- Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com> Co-authored-by: Adrian Prantl <adrian.prantl@gmail.com>
354 lines
12 KiB
C++
354 lines
12 KiB
C++
//===-- DWARFExpression.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/DWARF/DWARFExpressionPrinter.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
|
|
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
|
|
using namespace llvm;
|
|
using namespace dwarf;
|
|
|
|
namespace llvm {
|
|
|
|
typedef DWARFExpression::Operation Op;
|
|
typedef Op::Description Desc;
|
|
|
|
static void prettyPrintBaseTypeRef(DWARFUnit *U, raw_ostream &OS,
|
|
DIDumpOptions DumpOpts,
|
|
ArrayRef<uint64_t> Operands,
|
|
unsigned Operand) {
|
|
assert(Operand < Operands.size() && "operand out of bounds");
|
|
if (!U) {
|
|
OS << format(" <base_type ref: 0x%" PRIx64 ">", Operands[Operand]);
|
|
return;
|
|
}
|
|
auto Die = U->getDIEForOffset(U->getOffset() + Operands[Operand]);
|
|
if (Die && Die.getTag() == dwarf::DW_TAG_base_type) {
|
|
OS << " (";
|
|
if (DumpOpts.Verbose)
|
|
OS << format("0x%08" PRIx64 " -> ", Operands[Operand]);
|
|
OS << format("0x%08" PRIx64 ")", U->getOffset() + Operands[Operand]);
|
|
if (auto Name = dwarf::toString(Die.find(dwarf::DW_AT_name)))
|
|
OS << " \"" << *Name << "\"";
|
|
} else {
|
|
OS << format(" <invalid base_type ref: 0x%" PRIx64 ">", Operands[Operand]);
|
|
}
|
|
}
|
|
|
|
static bool printOp(const DWARFExpression::Operation *Op, raw_ostream &OS,
|
|
DIDumpOptions DumpOpts, const DWARFExpression *Expr,
|
|
DWARFUnit *U) {
|
|
if (Op->isError()) {
|
|
if (!DumpOpts.PrintRegisterOnly)
|
|
OS << "<decoding error>";
|
|
return false;
|
|
}
|
|
|
|
// In "register-only" mode, still show simple constant-valued locations.
|
|
// This lets clients print annotations like "i = 0" when the location is
|
|
// a constant (e.g. DW_OP_constu/consts ... DW_OP_stack_value).
|
|
// We continue to suppress all other non-register ops in this mode.
|
|
if (DumpOpts.PrintRegisterOnly) {
|
|
// First, try pretty-printing registers (existing behavior below also does
|
|
// this, but we need to short-circuit here to avoid printing opcode names).
|
|
if ((Op->getCode() >= DW_OP_breg0 && Op->getCode() <= DW_OP_breg31) ||
|
|
(Op->getCode() >= DW_OP_reg0 && Op->getCode() <= DW_OP_reg31) ||
|
|
Op->getCode() == DW_OP_bregx || Op->getCode() == DW_OP_regx ||
|
|
Op->getCode() == DW_OP_regval_type) {
|
|
if (prettyPrintRegisterOp(U, OS, DumpOpts, Op->getCode(),
|
|
Op->getRawOperands()))
|
|
return true;
|
|
// If we couldn't pretty-print, fall through and suppress.
|
|
}
|
|
|
|
// Show constants (decimal), suppress everything else.
|
|
if (Op->getCode() == DW_OP_constu) {
|
|
OS << (uint64_t)Op->getRawOperand(0);
|
|
return true;
|
|
}
|
|
if (Op->getCode() == DW_OP_consts) {
|
|
OS << (int64_t)Op->getRawOperand(0);
|
|
return true;
|
|
}
|
|
if (Op->getCode() >= DW_OP_lit0 && Op->getCode() <= DW_OP_lit31) {
|
|
OS << (unsigned)(Op->getCode() - DW_OP_lit0);
|
|
return true;
|
|
}
|
|
if (Op->getCode() == DW_OP_stack_value)
|
|
return true; // metadata; don't print a token
|
|
|
|
return true; // suppress other opcodes silently in register-only mode
|
|
}
|
|
|
|
if (!DumpOpts.PrintRegisterOnly) {
|
|
StringRef Name = OperationEncodingString(Op->getCode());
|
|
assert(!Name.empty() && "DW_OP has no name!");
|
|
OS << Name;
|
|
}
|
|
|
|
if ((Op->getCode() >= DW_OP_breg0 && Op->getCode() <= DW_OP_breg31) ||
|
|
(Op->getCode() >= DW_OP_reg0 && Op->getCode() <= DW_OP_reg31) ||
|
|
Op->getCode() == DW_OP_bregx || Op->getCode() == DW_OP_regx ||
|
|
Op->getCode() == DW_OP_regval_type)
|
|
if (prettyPrintRegisterOp(U, OS, DumpOpts, Op->getCode(),
|
|
Op->getRawOperands()))
|
|
return true;
|
|
|
|
if (!DumpOpts.PrintRegisterOnly) {
|
|
for (unsigned Operand = 0; Operand < Op->getDescription().Op.size();
|
|
++Operand) {
|
|
unsigned Size = Op->getDescription().Op[Operand];
|
|
unsigned Signed = Size & DWARFExpression::Operation::SignBit;
|
|
|
|
if (Size == DWARFExpression::Operation::SizeSubOpLEB) {
|
|
StringRef SubName = SubOperationEncodingString(
|
|
Op->getCode(), Op->getRawOperand(Operand));
|
|
assert(!SubName.empty() && "DW_OP SubOp has no name!");
|
|
OS << " " << SubName;
|
|
} else if (Size == DWARFExpression::Operation::BaseTypeRef && U) {
|
|
// For DW_OP_convert the operand may be 0 to indicate that conversion to
|
|
// the generic type should be done. The same holds for
|
|
// DW_OP_reinterpret, which is currently not supported.
|
|
if (Op->getCode() == DW_OP_convert && Op->getRawOperand(Operand) == 0)
|
|
OS << " 0x0";
|
|
else
|
|
prettyPrintBaseTypeRef(U, OS, DumpOpts, Op->getRawOperands(),
|
|
Operand);
|
|
} else if (Size == DWARFExpression::Operation::WasmLocationArg) {
|
|
assert(Operand == 1);
|
|
switch (Op->getRawOperand(0)) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3: // global as uint32
|
|
case 4:
|
|
OS << format(" 0x%" PRIx64, Op->getRawOperand(Operand));
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
} else if (Size == DWARFExpression::Operation::SizeBlock) {
|
|
uint64_t Offset = Op->getRawOperand(Operand);
|
|
for (unsigned i = 0; i < Op->getRawOperand(Operand - 1); ++i)
|
|
OS << format(" 0x%02x",
|
|
static_cast<uint8_t>(Expr->getData()[Offset++]));
|
|
} else {
|
|
if (Signed)
|
|
OS << format(" %+" PRId64, (int64_t)Op->getRawOperand(Operand));
|
|
else if (Op->getCode() != DW_OP_entry_value &&
|
|
Op->getCode() != DW_OP_GNU_entry_value)
|
|
OS << format(" 0x%" PRIx64, Op->getRawOperand(Operand));
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void printDwarfExpression(const DWARFExpression *E, raw_ostream &OS,
|
|
DIDumpOptions DumpOpts, DWARFUnit *U, bool IsEH) {
|
|
uint32_t EntryValExprSize = 0;
|
|
uint64_t EntryValStartOffset = 0;
|
|
if (E->getData().empty())
|
|
OS << "<empty>";
|
|
|
|
for (auto &Op : *E) {
|
|
DumpOpts.IsEH = IsEH;
|
|
if (!printOp(&Op, OS, DumpOpts, E, U) && !DumpOpts.PrintRegisterOnly) {
|
|
uint64_t FailOffset = Op.getEndOffset();
|
|
while (FailOffset < E->getData().size())
|
|
OS << format(" %02x", static_cast<uint8_t>(E->getData()[FailOffset++]));
|
|
return;
|
|
}
|
|
if (!DumpOpts.PrintRegisterOnly) {
|
|
if (Op.getCode() == DW_OP_entry_value ||
|
|
Op.getCode() == DW_OP_GNU_entry_value) {
|
|
OS << "(";
|
|
EntryValExprSize = Op.getRawOperand(0);
|
|
EntryValStartOffset = Op.getEndOffset();
|
|
continue;
|
|
}
|
|
|
|
if (EntryValExprSize) {
|
|
EntryValExprSize -= Op.getEndOffset() - EntryValStartOffset;
|
|
if (EntryValExprSize == 0)
|
|
OS << ")";
|
|
}
|
|
|
|
if (Op.getEndOffset() < E->getData().size())
|
|
OS << ", ";
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A user-facing string representation of a DWARF expression. This might be an
|
|
/// Address expression, in which case it will be implicitly dereferenced, or a
|
|
/// Value expression.
|
|
struct PrintedExpr {
|
|
enum ExprKind {
|
|
Address,
|
|
Value,
|
|
};
|
|
ExprKind Kind;
|
|
SmallString<16> String;
|
|
|
|
PrintedExpr(ExprKind K = Address) : Kind(K) {}
|
|
};
|
|
|
|
static bool printCompactDWARFExpr(
|
|
raw_ostream &OS, DWARFExpression::iterator I,
|
|
const DWARFExpression::iterator E,
|
|
std::function<StringRef(uint64_t RegNum, bool IsEH)> GetNameForDWARFReg =
|
|
nullptr) {
|
|
SmallVector<PrintedExpr, 4> Stack;
|
|
|
|
while (I != E) {
|
|
const DWARFExpression::Operation &Op = *I;
|
|
uint8_t Opcode = Op.getCode();
|
|
switch (Opcode) {
|
|
case dwarf::DW_OP_regx: {
|
|
// DW_OP_regx: A register, with the register num given as an operand.
|
|
// Printed as the plain register name.
|
|
uint64_t DwarfRegNum = Op.getRawOperand(0);
|
|
auto RegName = GetNameForDWARFReg(DwarfRegNum, false);
|
|
if (RegName.empty())
|
|
return false;
|
|
raw_svector_ostream S(Stack.emplace_back(PrintedExpr::Value).String);
|
|
S << RegName;
|
|
break;
|
|
}
|
|
case dwarf::DW_OP_bregx: {
|
|
int DwarfRegNum = Op.getRawOperand(0);
|
|
int64_t Offset = Op.getRawOperand(1);
|
|
auto RegName = GetNameForDWARFReg(DwarfRegNum, false);
|
|
if (RegName.empty())
|
|
return false;
|
|
raw_svector_ostream S(Stack.emplace_back().String);
|
|
S << RegName;
|
|
if (Offset)
|
|
S << format("%+" PRId64, Offset);
|
|
break;
|
|
}
|
|
case dwarf::DW_OP_entry_value:
|
|
case dwarf::DW_OP_GNU_entry_value: {
|
|
// DW_OP_entry_value contains a sub-expression which must be rendered
|
|
// separately.
|
|
uint64_t SubExprLength = Op.getRawOperand(0);
|
|
DWARFExpression::iterator SubExprEnd = I.skipBytes(SubExprLength);
|
|
++I;
|
|
raw_svector_ostream S(Stack.emplace_back().String);
|
|
S << "entry(";
|
|
printCompactDWARFExpr(S, I, SubExprEnd, GetNameForDWARFReg);
|
|
S << ")";
|
|
I = SubExprEnd;
|
|
continue;
|
|
}
|
|
case dwarf::DW_OP_stack_value: {
|
|
// The top stack entry should be treated as the actual value of tne
|
|
// variable, rather than the address of the variable in memory.
|
|
assert(!Stack.empty());
|
|
Stack.back().Kind = PrintedExpr::Value;
|
|
break;
|
|
}
|
|
case dwarf::DW_OP_nop: {
|
|
break;
|
|
}
|
|
case dwarf::DW_OP_LLVM_user: {
|
|
assert(Op.getSubCode() == dwarf::DW_OP_LLVM_nop);
|
|
break;
|
|
}
|
|
default:
|
|
if (Opcode >= dwarf::DW_OP_reg0 && Opcode <= dwarf::DW_OP_reg31) {
|
|
// DW_OP_reg<N>: A register, with the register num implied by the
|
|
// opcode. Printed as the plain register name.
|
|
uint64_t DwarfRegNum = Opcode - dwarf::DW_OP_reg0;
|
|
auto RegName = GetNameForDWARFReg(DwarfRegNum, false);
|
|
if (RegName.empty())
|
|
return false;
|
|
raw_svector_ostream S(Stack.emplace_back(PrintedExpr::Value).String);
|
|
S << RegName;
|
|
} else if (Opcode >= dwarf::DW_OP_breg0 &&
|
|
Opcode <= dwarf::DW_OP_breg31) {
|
|
int DwarfRegNum = Opcode - dwarf::DW_OP_breg0;
|
|
int64_t Offset = Op.getRawOperand(0);
|
|
auto RegName = GetNameForDWARFReg(DwarfRegNum, false);
|
|
if (RegName.empty())
|
|
return false;
|
|
raw_svector_ostream S(Stack.emplace_back().String);
|
|
S << RegName;
|
|
if (Offset)
|
|
S << format("%+" PRId64, Offset);
|
|
} else {
|
|
// If we hit an unknown operand, we don't know its effect on the stack,
|
|
// so bail out on the whole expression.
|
|
OS << "<unknown op " << dwarf::OperationEncodingString(Opcode) << " ("
|
|
<< (int)Opcode << ")>";
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
++I;
|
|
}
|
|
|
|
if (Stack.size() != 1) {
|
|
OS << "<stack of size " << Stack.size() << ", expected 1>";
|
|
return false;
|
|
}
|
|
|
|
if (Stack.front().Kind == PrintedExpr::Address)
|
|
OS << "[" << Stack.front().String << "]";
|
|
else
|
|
OS << Stack.front().String;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool printDwarfExpressionCompact(
|
|
const DWARFExpression *E, raw_ostream &OS,
|
|
std::function<StringRef(uint64_t RegNum, bool IsEH)> GetNameForDWARFReg) {
|
|
return printCompactDWARFExpr(OS, E->begin(), E->end(), GetNameForDWARFReg);
|
|
}
|
|
|
|
bool prettyPrintRegisterOp(DWARFUnit *U, raw_ostream &OS,
|
|
DIDumpOptions DumpOpts, uint8_t Opcode,
|
|
ArrayRef<uint64_t> Operands) {
|
|
if (!DumpOpts.GetNameForDWARFReg)
|
|
return false;
|
|
|
|
uint64_t DwarfRegNum;
|
|
unsigned OpNum = 0;
|
|
|
|
if (Opcode == DW_OP_bregx || Opcode == DW_OP_regx ||
|
|
Opcode == DW_OP_regval_type)
|
|
DwarfRegNum = Operands[OpNum++];
|
|
else if (Opcode >= DW_OP_breg0 && Opcode < DW_OP_bregx)
|
|
DwarfRegNum = Opcode - DW_OP_breg0;
|
|
else
|
|
DwarfRegNum = Opcode - DW_OP_reg0;
|
|
|
|
auto RegName = DumpOpts.GetNameForDWARFReg(DwarfRegNum, DumpOpts.IsEH);
|
|
if (!RegName.empty()) {
|
|
if ((Opcode >= DW_OP_breg0 && Opcode <= DW_OP_breg31) ||
|
|
Opcode == DW_OP_bregx)
|
|
OS << ' ' << RegName << format("%+" PRId64, Operands[OpNum]);
|
|
else
|
|
OS << ' ' << RegName.data();
|
|
|
|
if (Opcode == DW_OP_regval_type)
|
|
prettyPrintBaseTypeRef(U, OS, DumpOpts, Operands, 1);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace llvm
|