These are defined in the user range until standard versions of them get adopted into dwarf, which is expected in DWARF6. Some of these amount to reservations currently as no code to use them is included. It would be very helpful to get them committed to avoid conflicts necessitating encoding changes while we are in the process of upstreaming. --------- Co-authored-by: Juan Martinez Fernandez <juamarti@amd.com> Co-authored-by: Emma Pilkington <Emma.Pilkington@amd.com>
380 lines
13 KiB
C++
380 lines
13 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;
|
|
}
|
|
|
|
std::optional<unsigned> SubOpcode = Op->getSubCode();
|
|
|
|
// 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 ||
|
|
SubOpcode == DW_OP_LLVM_call_frame_entry_reg ||
|
|
SubOpcode == DW_OP_LLVM_aspace_bregx) {
|
|
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 (SubOpcode) {
|
|
StringRef SubName = SubOperationEncodingString(Op->getCode(), *SubOpcode);
|
|
assert(!SubName.empty() && "DW_OP SubOp has no name!");
|
|
OS << ' ' << SubName;
|
|
}
|
|
}
|
|
|
|
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 ||
|
|
SubOpcode == DW_OP_LLVM_call_frame_entry_reg ||
|
|
SubOpcode == DW_OP_LLVM_aspace_bregx)
|
|
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) {
|
|
assert(Operand == 0 && "DW_OP SubOp must be the first operand");
|
|
assert(SubOpcode && "DW_OP SubOp description is inconsistent");
|
|
} 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;
|
|
|
|
auto UnknownOpcode = [](raw_ostream &OS, uint8_t Opcode,
|
|
std::optional<unsigned> SubOpcode) -> bool {
|
|
// 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;
|
|
if (SubOpcode)
|
|
OS << ") subop " << dwarf::SubOperationEncodingString(Opcode, *SubOpcode)
|
|
<< " (" << *SubOpcode;
|
|
OS << ")>";
|
|
return false;
|
|
};
|
|
|
|
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: {
|
|
std::optional<unsigned> SubOpcode = Op.getSubCode();
|
|
if (SubOpcode == dwarf::DW_OP_LLVM_nop)
|
|
break;
|
|
return UnknownOpcode(OS, Opcode, SubOpcode);
|
|
}
|
|
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 {
|
|
return UnknownOpcode(OS, Opcode, std::nullopt);
|
|
}
|
|
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;
|
|
|
|
std::optional<unsigned> SubOpcode;
|
|
if (Opcode == DW_OP_LLVM_user)
|
|
SubOpcode = Operands[OpNum++];
|
|
|
|
if (Opcode == DW_OP_bregx || Opcode == DW_OP_regx ||
|
|
Opcode == DW_OP_regval_type || SubOpcode == DW_OP_LLVM_aspace_bregx ||
|
|
SubOpcode == DW_OP_LLVM_call_frame_entry_reg)
|
|
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 || SubOpcode == DW_OP_LLVM_aspace_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
|