Add the ability to extract the unwind rows from DWARF Call Frame Information.

This patch adds the ability to evaluate the state machine for CIE and FDE unwind objects and produce a UnwindTable with all UnwindRow objects needed to unwind registers. It will also dump the UnwindTable for each CIE and FDE when dumping DWARF .debug_frame or .eh_frame sections in llvm-dwarfdump or llvm-objdump. This allows users to see what the unwind rows actually look like for a given CIE or FDE instead of just seeing a list of opcodes.

This patch adds new classes: UnwindLocation, RegisterLocations, UnwindRow, and UnwindTable.

UnwindLocation is a class that describes how to unwind a register or Call Frame Address (CFA).

RegisterLocations is a class that tracks registers and their UnwindLocations. It gets populated when parsing the DWARF call frame instruction opcodes for a unwind row. The registers are mapped from their register numbers to the UnwindLocation in a map.

UnwindRow contains the result of evaluating a row of DWARF call frame instructions for the CIE, or a row from a FDE. The CIE can produce a set of initial instructions that each FDE that points to that CIE will use as the seed for the state machine when parsing FDE opcodes. A UnwindRow for a CIE will not have a valid address, whille a UnwindRow for a FDE will have a valid address.

The UnwindTable is a class that contains a sorted (by address) vector of UnwindRow objects and is the result of parsing all opcodes in a CIE, or FDE. Parsing a CIE should produce a UnwindTable with a single row. Parsing a FDE will produce a UnwindTable with one or more UnwindRow objects where all UnwindRow objects have valid addresses. The rows in the UnwindTable will be sorted from lowest Address to highest after parsing the state machine, or an error will be returned if the table isn't sorted. To parse a UnwindTable clients can use the following methods:

    static Expected<UnwindTable> UnwindTable::create(const CIE *Cie);
    static Expected<UnwindTable> UnwindTable::create(const FDE *Fde);

A valid table will be returned if the DWARF call frame instruction opcodes have no encoding errors. There are a few things that can go wrong during the evaluation of the state machine and these create functions will catch and return them.

Differential Revision: https://reviews.llvm.org/D89845
This commit is contained in:
Greg Clayton 2020-12-07 15:36:45 -08:00
parent bacf9cf2c5
commit f8122d3532
8 changed files with 2121 additions and 10 deletions

View File

@ -10,12 +10,13 @@
#define LLVM_DEBUGINFO_DWARF_DWARFDEBUGFRAME_H
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/iterator.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Triple.h"
#include "llvm/ADT/iterator.h"
#include "llvm/DebugInfo/DWARF/DWARFDataExtractor.h"
#include "llvm/DebugInfo/DWARF/DWARFExpression.h"
#include "llvm/Support/Error.h"
#include <map>
#include <memory>
#include <vector>
@ -25,6 +26,359 @@ class raw_ostream;
namespace dwarf {
constexpr uint32_t InvalidRegisterNumber = UINT32_MAX;
/// A class that represents a location for the Call Frame Address (CFA) or a
/// register. This is decoded from the DWARF Call Frame Information
/// instructions and put into an UnwindRow.
class UnwindLocation {
public:
enum Location {
/// Not specified.
Unspecified,
/// Register is not available and can't be recovered.
Undefined,
/// Register value is in the register, nothing needs to be done to unwind
/// it:
/// reg = reg
Same,
/// Register is in or at the CFA plus an offset:
/// reg = CFA + offset
/// reg = defef(CFA + offset)
CFAPlusOffset,
/// Register it in or at a register plus offset:
/// reg = reg + offset
/// reg = deref(reg + offset)
RegPlusOffset,
/// Register value is in or at a value found by evaluating a DWARF
/// expression:
/// reg = eval(dwarf_expr)
/// reg = deref(eval(dwarf_expr))
DWARFExpr,
/// Value is a constant value contained in "Offset":
/// reg = Offset
Constant,
};
private:
Location Kind; /// The type of the location that describes how to unwind it.
uint32_t RegNum; /// The register number for Kind == RegPlusOffset.
int32_t Offset; /// The offset for Kind == CFAPlusOffset or RegPlusOffset.
Optional<DWARFExpression> Expr; /// The DWARF expression for Kind ==
/// DWARFExpression.
bool Dereference; /// If true, the resulting location must be dereferenced
/// after the location value is computed.
// Constructors are private to force people to use the create static
// functions.
UnwindLocation(Location K)
: Kind(K), RegNum(InvalidRegisterNumber), Offset(0), Dereference(false) {}
UnwindLocation(Location K, uint32_t Reg, int32_t Off, bool Deref)
: Kind(K), RegNum(Reg), Offset(Off), Dereference(Deref) {}
UnwindLocation(DWARFExpression E, bool Deref)
: Kind(DWARFExpr), RegNum(InvalidRegisterNumber), Offset(0), Expr(E),
Dereference(Deref) {}
public:
/// Create a location whose rule is set to Unspecified. This means the
/// register value might be in the same register but it wasn't specified in
/// the unwind opcodes.
static UnwindLocation createUnspecified();
/// Create a location where the value is undefined and not available. This can
/// happen when a register is volatile and can't be recovered.
static UnwindLocation createUndefined();
/// Create a location where the value is known to be in the register itself.
static UnwindLocation createSame();
/// Create a location that is in (Deref == false) or at (Deref == true) the
/// CFA plus an offset. Most registers that are spilled onto the stack use
/// this rule. The rule for the register will use this rule and specify a
/// unique offset from the CFA with \a Deref set to true. This value will be
/// relative to a CFA value which is typically defined using the register
/// plus offset location. \see createRegisterPlusOffset(...) for more
/// information.
static UnwindLocation createIsCFAPlusOffset(int32_t Off);
static UnwindLocation createAtCFAPlusOffset(int32_t Off);
/// Create a location where the saved value is in (Deref == false) or at
/// (Deref == true) a regiser plus an offset.
///
/// The CFA is usually defined using this rule by using the stack pointer or
/// frame pointer as the register, with an offset that accounts for all
/// spilled registers and all local variables in a function, and Deref ==
/// false.
static UnwindLocation createIsRegisterPlusOffset(uint32_t Reg, int32_t Off);
static UnwindLocation createAtRegisterPlusOffset(uint32_t Reg, int32_t Off);
/// Create a location whose value is the result of evaluating a DWARF
/// expression. This allows complex expressions to be evaluated in order to
/// unwind a register or CFA value.
static UnwindLocation createIsDWARFExpression(DWARFExpression Expr);
static UnwindLocation createAtDWARFExpression(DWARFExpression Expr);
static UnwindLocation createIsConstant(int32_t Value);
Location getLocation() const { return Kind; }
uint32_t getRegister() const { return RegNum; }
int32_t getOffset() const { return Offset; }
int32_t getConstant() const { return Offset; }
/// Some opcodes will modify the CFA location's register only, so we need
/// to be able to modify the CFA register when evaluating DWARF Call Frame
/// Information opcodes.
void setRegister(uint32_t NewRegNum) { RegNum = NewRegNum; }
/// Some opcodes will modify the CFA location's offset only, so we need
/// to be able to modify the CFA register when evaluating DWARF Call Frame
/// Information opcodes.
void setOffset(int32_t NewOffset) { Offset = NewOffset; }
/// Some opcodes modify a constant value and we need to be able to update
/// the constant value (DW_CFA_GNU_window_save which is also known as
// DW_CFA_AARCH64_negate_ra_state).
void setConstant(int32_t Value) { Offset = Value; }
Optional<DWARFExpression> getDWARFExpressionBytes() const { return Expr; }
/// Dump a location expression as text and use the register information if
/// some is provided.
///
/// \param OS the stream to use for output.
///
/// \param MRI register information that helps emit register names insteead
/// of raw register numbers.
///
/// \param IsEH true if the DWARF Call Frame Information is from .eh_frame
/// instead of from .debug_frame. This is needed for register number
/// conversion because some register numbers differ between the two sections
/// for certain architectures like x86.
void dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH) const;
bool operator==(const UnwindLocation &RHS) const;
};
raw_ostream &operator<<(raw_ostream &OS, const UnwindLocation &R);
/// A class that can track all registers with locations in a UnwindRow object.
///
/// Register locations use a map where the key is the register number and the
/// the value is a UnwindLocation.
///
/// The register maps are put into a class so that all register locations can
/// be copied when parsing the unwind opcodes DW_CFA_remember_state and
/// DW_CFA_restore_state.
class RegisterLocations {
std::map<uint32_t, UnwindLocation> Locations;
public:
/// Return the location for the register in \a RegNum if there is a location.
///
/// \param RegNum the register number to find a location for.
///
/// \returns A location if one is available for \a RegNum, or llvm::None
/// otherwise.
Optional<UnwindLocation> getRegisterLocation(uint32_t RegNum) const {
auto Pos = Locations.find(RegNum);
if (Pos == Locations.end())
return llvm::None;
return Pos->second;
}
/// Set the location for the register in \a RegNum to \a Location.
///
/// \param RegNum the register number to set the location for.
///
/// \param Location the UnwindLocation that describes how to unwind the value.
void setRegisterLocation(uint32_t RegNum, const UnwindLocation &Location) {
Locations.erase(RegNum);
Locations.insert(std::make_pair(RegNum, Location));
}
/// Removes any rule for the register in \a RegNum.
///
/// \param RegNum the register number to remove the location for.
void removeRegisterLocation(uint32_t RegNum) { Locations.erase(RegNum); }
/// Dump all registers + locations that are currently defined in this object.
///
/// \param OS the stream to use for output.
///
/// \param MRI register information that helps emit register names insteead
/// of raw register numbers.
///
/// \param IsEH true if the DWARF Call Frame Information is from .eh_frame
/// instead of from .debug_frame. This is needed for register number
/// conversion because some register numbers differ between the two sections
/// for certain architectures like x86.
void dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH) const;
/// Returns true if we have any register locations in this object.
bool hasLocations() const { return !Locations.empty(); }
size_t size() const { return Locations.size(); }
bool operator==(const RegisterLocations &RHS) const {
return Locations == RHS.Locations;
}
};
raw_ostream &operator<<(raw_ostream &OS, const RegisterLocations &RL);
/// A class that represents a single row in the unwind table that is decoded by
/// parsing the DWARF Call Frame Information opcodes.
///
/// The row consists of an optional address, the rule to unwind the CFA and all
/// rules to unwind any registers. If the address doesn't have a value, this
/// row represents the initial instructions for a CIE. If the address has a
/// value the UnwindRow represents a row in the UnwindTable for a FDE. The
/// address is the first address for which the CFA location and register rules
/// are valid within a function.
///
/// UnwindRow objects are created by parsing opcodes in the DWARF Call Frame
/// Information and UnwindRow objects are lazily populated and pushed onto a
/// stack in the UnwindTable when evaluating this state machine. Accessors are
/// needed for the address, CFA value, and register locations as the opcodes
/// encode a state machine that produces a sorted array of UnwindRow objects
/// \see UnwindTable.
class UnwindRow {
/// The address will be valid when parsing the instructions in a FDE. If
/// invalid, this object represents the initial instructions of a CIE.
Optional<uint64_t> Address; ///< Address for row in FDE, invalid for CIE.
UnwindLocation CFAValue; ///< How to unwind the Call Frame Address (CFA).
RegisterLocations RegLocs; ///< How to unwind all registers in this list.
public:
UnwindRow() : CFAValue(UnwindLocation::createUnspecified()) {}
/// Returns true if the address is valid in this object.
bool hasAddress() const { return Address.hasValue(); }
/// Get the address for this row.
///
/// Clients should only call this function after verifying it has a valid
/// address with a call to \see hasAddress().
uint64_t getAddress() const { return *Address; }
/// Set the address for this UnwindRow.
///
/// The address represents the first address for which the CFAValue and
/// RegLocs are valid within a function.
void setAddress(uint64_t Addr) { Address = Addr; }
/// Offset the address for this UnwindRow.
///
/// The address represents the first address for which the CFAValue and
/// RegLocs are valid within a function. Clients must ensure that this object
/// already has an address (\see hasAddress()) prior to calling this
/// function.
void slideAddress(uint64_t Offset) { *Address += Offset; }
UnwindLocation &getCFAValue() { return CFAValue; }
const UnwindLocation &getCFAValue() const { return CFAValue; }
RegisterLocations &getRegisterLocations() { return RegLocs; }
const RegisterLocations &getRegisterLocations() const { return RegLocs; }
/// Dump the UnwindRow to the stream.
///
/// \param OS the stream to use for output.
///
/// \param MRI register information that helps emit register names insteead
/// of raw register numbers.
///
/// \param IsEH true if the DWARF Call Frame Information is from .eh_frame
/// instead of from .debug_frame. This is needed for register number
/// conversion because some register numbers differ between the two sections
/// for certain architectures like x86.
///
/// \param IndentLevel specify the indent level as an integer. The UnwindRow
/// will be output to the stream preceded by 2 * IndentLevel number of spaces.
void dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH,
unsigned IndentLevel = 0) const;
};
raw_ostream &operator<<(raw_ostream &OS, const UnwindRow &Row);
class CFIProgram;
class CIE;
class FDE;
/// A class that contains all UnwindRow objects for an FDE or a single unwind
/// row for a CIE. To unwind an address the rows, which are sorted by start
/// address, can be searched to find the UnwindRow with the lowest starting
/// address that is greater than or equal to the address that is being looked
/// up.
class UnwindTable {
public:
using RowContainer = std::vector<UnwindRow>;
using iterator = RowContainer::iterator;
using const_iterator = RowContainer::const_iterator;
size_t size() const { return Rows.size(); }
iterator begin() { return Rows.begin(); }
const_iterator begin() const { return Rows.begin(); }
iterator end() { return Rows.end(); }
const_iterator end() const { return Rows.end(); }
const UnwindRow &operator[](size_t Index) const {
assert(Index < size());
return Rows[Index];
}
/// Dump the UnwindTable to the stream.
///
/// \param OS the stream to use for output.
///
/// \param MRI register information that helps emit register names insteead
/// of raw register numbers.
///
/// \param IsEH true if the DWARF Call Frame Information is from .eh_frame
/// instead of from .debug_frame. This is needed for register number
/// conversion because some register numbers differ between the two sections
/// for certain architectures like x86.
///
/// \param IndentLevel specify the indent level as an integer. The UnwindRow
/// will be output to the stream preceded by 2 * IndentLevel number of spaces.
void dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH,
unsigned IndentLevel = 0) const;
/// Create an UnwindTable from a Common Information Entry (CIE).
///
/// \param Cie The Common Information Entry to extract the table from. The
/// CFIProgram is retrieved from the \a Cie object and used to create the
/// UnwindTable.
///
/// \returns An error if the DWARF Call Frame Information opcodes have state
/// machine errors, or a valid UnwindTable otherwise.
static Expected<UnwindTable> create(const CIE *Cie);
/// Create an UnwindTable from a Frame Descriptor Entry (FDE).
///
/// \param Fde The Frame Descriptor Entry to extract the table from. The
/// CFIProgram is retrieved from the \a Fde object and used to create the
/// UnwindTable.
///
/// \returns An error if the DWARF Call Frame Information opcodes have state
/// machine errors, or a valid UnwindTable otherwise.
static Expected<UnwindTable> create(const FDE *Fde);
private:
RowContainer Rows;
/// The end address when data is extracted from a FDE. This value will be
/// invalid when a UnwindTable is extracted from a CIE.
Optional<uint64_t> EndAddress;
/// Parse the information in the CFIProgram and update the CurrRow object
/// that the state machine describes.
///
/// This is an internal implementation that emulates the state machine
/// described in the DWARF Call Frame Information opcodes and will push
/// CurrRow onto the Rows container when needed. \param CFID the CFI program
/// that contains the opcodes from a CIE or FDE.
///
/// \param CurrRow the current row to modify while parsing the state machine.
///
/// \param InitialLocs If non-NULL, we are parsing a FDE and this contains
/// the initial register locations from the CIE. If NULL, then a CIE's
/// opcodes are being parsed and this is not needed. This is used for the
/// DW_CFA_restore and DW_CFA_restore_extended opcodes.
Error parseRows(const CFIProgram &CFIP, UnwindRow &CurrRow,
const RegisterLocations *InitialLocs);
};
raw_ostream &operator<<(raw_ostream &OS, const UnwindTable &Rows);
/// Represent a sequence of Call Frame Information instructions that, when read
/// in order, construct a table mapping PC to frame state. This can also be
/// referred to as "CFI rules" in DWARF literature to avoid confusion with
@ -45,6 +399,12 @@ public:
Operands Ops;
// Associated DWARF expression in case this instruction refers to one
Optional<DWARFExpression> Expression;
Expected<uint64_t> getOperandAsUnsigned(const CFIProgram &CFIP,
uint32_t OperandIdx) const;
Expected<int64_t> getOperandAsSigned(const CFIProgram &CFIP,
uint32_t OperandIdx) const;
};
using InstrList = std::vector<Instruction>;
@ -58,6 +418,9 @@ public:
unsigned size() const { return (unsigned)Instructions.size(); }
bool empty() const { return Instructions.empty(); }
uint64_t codeAlign() const { return CodeAlignmentFactor; }
int64_t dataAlign() const { return DataAlignmentFactor; }
Triple::ArchType triple() const { return Arch; }
CFIProgram(uint64_t CodeAlignmentFactor, int64_t DataAlignmentFactor,
Triple::ArchType Arch)
@ -74,6 +437,11 @@ public:
void dump(raw_ostream &OS, DIDumpOptions DumpOpts, const MCRegisterInfo *MRI,
bool IsEH, unsigned IndentLevel = 1) const;
void addInstruction(const Instruction &I) { Instructions.push_back(I); }
/// Get a DWARF CFI call frame string for the given DW_CFA opcode.
StringRef callFrameString(unsigned Opcode) const;
private:
std::vector<Instruction> Instructions;
const uint64_t CodeAlignmentFactor;
@ -116,6 +484,9 @@ private:
OT_Expression
};
/// Get the OperandType as a "const char *".
static const char *operandTypeString(OperandType OT);
/// Retrieve the array describing the types of operands according to the enum
/// above. This is indexed by opcode.
static ArrayRef<OperandType[2]> getOperandTypes();

View File

@ -80,7 +80,7 @@ public:
friend class DWARFExpression::iterator;
uint8_t Opcode; ///< The Op Opcode, DW_OP_<something>.
Description Desc;
bool Error;
bool Error = false;
uint64_t EndOffset;
uint64_t Operands[2];
uint64_t OperandEndOffsets[2];
@ -157,6 +157,8 @@ public:
bool verify(DWARFUnit *U);
bool operator==(const DWARFExpression &RHS) const;
private:
DataExtractor Data;
uint8_t AddressSize;

View File

@ -42,6 +42,191 @@ static void printRegister(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH,
OS << "reg" << RegNum;
}
UnwindLocation UnwindLocation::createUnspecified() { return {Unspecified}; }
UnwindLocation UnwindLocation::createUndefined() { return {Undefined}; }
UnwindLocation UnwindLocation::createSame() { return {Same}; }
UnwindLocation UnwindLocation::createIsConstant(int32_t Value) {
return {Constant, InvalidRegisterNumber, Value, false};
}
UnwindLocation UnwindLocation::createIsCFAPlusOffset(int32_t Offset) {
return {CFAPlusOffset, InvalidRegisterNumber, Offset, false};
}
UnwindLocation UnwindLocation::createAtCFAPlusOffset(int32_t Offset) {
return {CFAPlusOffset, InvalidRegisterNumber, Offset, true};
}
UnwindLocation UnwindLocation::createIsRegisterPlusOffset(uint32_t RegNum,
int32_t Offset) {
return {RegPlusOffset, RegNum, Offset, false};
}
UnwindLocation UnwindLocation::createAtRegisterPlusOffset(uint32_t RegNum,
int32_t Offset) {
return {RegPlusOffset, RegNum, Offset, true};
}
UnwindLocation UnwindLocation::createIsDWARFExpression(DWARFExpression Expr) {
return {Expr, false};
}
UnwindLocation UnwindLocation::createAtDWARFExpression(DWARFExpression Expr) {
return {Expr, true};
}
void UnwindLocation::dump(raw_ostream &OS, const MCRegisterInfo *MRI,
bool IsEH) const {
if (Dereference)
OS << '[';
switch (Kind) {
case Unspecified:
OS << "unspecified";
break;
case Undefined:
OS << "undefined";
break;
case Same:
OS << "same";
break;
case CFAPlusOffset:
OS << "CFA";
if (Offset == 0)
break;
if (Offset > 0)
OS << "+";
OS << Offset;
break;
case RegPlusOffset:
printRegister(OS, MRI, IsEH, RegNum);
if (Offset == 0)
break;
if (Offset > 0)
OS << "+";
OS << Offset;
break;
case DWARFExpr:
Expr->print(OS, DIDumpOptions(), MRI, nullptr, IsEH);
break;
case Constant:
OS << Offset;
break;
}
if (Dereference)
OS << ']';
}
raw_ostream &llvm::dwarf::operator<<(raw_ostream &OS,
const UnwindLocation &UL) {
UL.dump(OS, nullptr, false);
return OS;
}
bool UnwindLocation::operator==(const UnwindLocation &RHS) const {
if (Kind != RHS.Kind)
return false;
switch (Kind) {
case Unspecified:
case Undefined:
case Same:
return true;
case CFAPlusOffset:
return Offset == RHS.Offset && Dereference == RHS.Dereference;
case RegPlusOffset:
return RegNum == RHS.RegNum && Offset == RHS.Offset &&
Dereference == RHS.Dereference;
case DWARFExpr:
return *Expr == *RHS.Expr && Dereference == RHS.Dereference;
case Constant:
return Offset == RHS.Offset;
}
return false;
}
void RegisterLocations::dump(raw_ostream &OS, const MCRegisterInfo *MRI,
bool IsEH) const {
bool First = true;
for (const auto &RegLocPair : Locations) {
if (First)
First = false;
else
OS << ", ";
printRegister(OS, MRI, IsEH, RegLocPair.first);
OS << '=';
RegLocPair.second.dump(OS, MRI, IsEH);
}
}
raw_ostream &llvm::dwarf::operator<<(raw_ostream &OS,
const RegisterLocations &RL) {
RL.dump(OS, nullptr, false);
return OS;
}
void UnwindRow::dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH,
unsigned IndentLevel) const {
OS.indent(2 * IndentLevel);
if (hasAddress())
OS << format("0x%" PRIx64 ": ", *Address);
OS << "CFA=";
CFAValue.dump(OS, MRI, IsEH);
if (RegLocs.hasLocations()) {
OS << ": ";
RegLocs.dump(OS, MRI, IsEH);
}
OS << "\n";
}
raw_ostream &llvm::dwarf::operator<<(raw_ostream &OS, const UnwindRow &Row) {
Row.dump(OS, nullptr, false, 0);
return OS;
}
void UnwindTable::dump(raw_ostream &OS, const MCRegisterInfo *MRI, bool IsEH,
unsigned IndentLevel) const {
for (const UnwindRow &Row : Rows)
Row.dump(OS, MRI, IsEH, IndentLevel);
}
raw_ostream &llvm::dwarf::operator<<(raw_ostream &OS, const UnwindTable &Rows) {
Rows.dump(OS, nullptr, false, 0);
return OS;
}
Expected<UnwindTable> UnwindTable::create(const FDE *Fde) {
UnwindTable UT;
UnwindRow Row;
Row.setAddress(Fde->getInitialLocation());
UT.EndAddress = Fde->getInitialLocation() + Fde->getAddressRange();
const CIE *Cie = Fde->getLinkedCIE();
if (Cie == nullptr)
return createStringError(errc::invalid_argument,
"unable to get CIE for FDE at offset 0x%" PRIx64,
Fde->getOffset());
if (Error CieError = UT.parseRows(Cie->cfis(), Row, nullptr))
return std::move(CieError);
// We need to save the initial locations of registers from the CIE parsing
// in case we run into DW_CFA_restore or DW_CFA_restore_extended opcodes.
const RegisterLocations InitialLocs = Row.getRegisterLocations();
if (Error FdeError = UT.parseRows(Fde->cfis(), Row, &InitialLocs))
return std::move(FdeError);
UT.Rows.push_back(Row);
return UT;
}
Expected<UnwindTable> UnwindTable::create(const CIE *Cie) {
UnwindTable UT;
UnwindRow Row;
if (Error CieError = UT.parseRows(Cie->cfis(), Row, nullptr))
return std::move(CieError);
UT.Rows.push_back(Row);
return UT;
}
// See DWARF standard v3, section 7.23
const uint8_t DWARF_CFI_PRIMARY_OPCODE_MASK = 0xc0;
const uint8_t DWARF_CFI_PRIMARY_OPERAND_MASK = 0x3f;
@ -174,10 +359,384 @@ Error CFIProgram::parse(DWARFDataExtractor Data, uint64_t *Offset,
return C.takeError();
}
namespace {
StringRef CFIProgram::callFrameString(unsigned Opcode) const {
return dwarf::CallFrameString(Opcode, Arch);
}
const char *CFIProgram::operandTypeString(CFIProgram::OperandType OT) {
#define ENUM_TO_CSTR(e) \
case e: \
return #e;
switch (OT) {
ENUM_TO_CSTR(OT_Unset);
ENUM_TO_CSTR(OT_None);
ENUM_TO_CSTR(OT_Address);
ENUM_TO_CSTR(OT_Offset);
ENUM_TO_CSTR(OT_FactoredCodeOffset);
ENUM_TO_CSTR(OT_SignedFactDataOffset);
ENUM_TO_CSTR(OT_UnsignedFactDataOffset);
ENUM_TO_CSTR(OT_Register);
ENUM_TO_CSTR(OT_Expression);
}
return "<unknown CFIProgram::OperandType>";
}
} // end anonymous namespace
llvm::Expected<uint64_t>
CFIProgram::Instruction::getOperandAsUnsigned(const CFIProgram &CFIP,
uint32_t OperandIdx) const {
if (OperandIdx >= 2)
return createStringError(errc::invalid_argument,
"operand index %" PRIu32 " is not valid",
OperandIdx);
OperandType Type = CFIP.getOperandTypes()[Opcode][OperandIdx];
uint64_t Operand = Ops[OperandIdx];
switch (Type) {
case OT_Unset:
case OT_None:
case OT_Expression:
return createStringError(errc::invalid_argument,
"op[%" PRIu32 "] has type %s which has no value",
OperandIdx, CFIProgram::operandTypeString(Type));
case OT_Offset:
case OT_SignedFactDataOffset:
case OT_UnsignedFactDataOffset:
return createStringError(
errc::invalid_argument,
"op[%" PRIu32 "] has OperandType OT_Offset which produces a signed "
"result, call getOperandAsSigned instead",
OperandIdx);
case OT_Address:
case OT_Register:
return Operand;
case OT_FactoredCodeOffset: {
const uint64_t CodeAlignmentFactor = CFIP.codeAlign();
if (CodeAlignmentFactor == 0)
return createStringError(
errc::invalid_argument,
"op[%" PRIu32 "] has type OT_FactoredCodeOffset but code alignment "
"is zero",
OperandIdx);
return Operand * CodeAlignmentFactor;
}
}
}
llvm::Expected<int64_t>
CFIProgram::Instruction::getOperandAsSigned(const CFIProgram &CFIP,
uint32_t OperandIdx) const {
if (OperandIdx >= 2)
return createStringError(errc::invalid_argument,
"operand index %" PRIu32 " is not valid",
OperandIdx);
OperandType Type = CFIP.getOperandTypes()[Opcode][OperandIdx];
uint64_t Operand = Ops[OperandIdx];
switch (Type) {
case OT_Unset:
case OT_None:
case OT_Expression:
return createStringError(errc::invalid_argument,
"op[%" PRIu32 "] has type %s which has no value",
OperandIdx, CFIProgram::operandTypeString(Type));
case OT_Address:
case OT_Register:
return createStringError(
errc::invalid_argument,
"op[%" PRIu32 "] has OperandType %s which produces an unsigned result, "
"call getOperandAsUnsigned instead",
OperandIdx, CFIProgram::operandTypeString(Type));
case OT_Offset:
return (int64_t)Operand;
case OT_FactoredCodeOffset:
case OT_SignedFactDataOffset: {
const int64_t DataAlignmentFactor = CFIP.dataAlign();
if (DataAlignmentFactor == 0)
return createStringError(errc::invalid_argument,
"op[%" PRIu32 "] has type %s but data "
"alignment is zero",
OperandIdx, CFIProgram::operandTypeString(Type));
return int64_t(Operand) * DataAlignmentFactor;
}
case OT_UnsignedFactDataOffset: {
const int64_t DataAlignmentFactor = CFIP.dataAlign();
if (DataAlignmentFactor == 0)
return createStringError(errc::invalid_argument,
"op[%" PRIu32
"] has type OT_UnsignedFactDataOffset but data "
"alignment is zero",
OperandIdx);
return Operand * DataAlignmentFactor;
}
}
}
Error UnwindTable::parseRows(const CFIProgram &CFIP, UnwindRow &Row,
const RegisterLocations *InitialLocs) {
std::vector<RegisterLocations> RegisterStates;
for (const CFIProgram::Instruction &Inst : CFIP) {
switch (Inst.Opcode) {
case dwarf::DW_CFA_set_loc: {
// The DW_CFA_set_loc instruction takes a single operand that
// represents a target address. The required action is to create a new
// table row using the specified address as the location. All other
// values in the new row are initially identical to the current row.
// The new location value is always greater than the current one. If
// the segment_size field of this FDE's CIE is non- zero, the initial
// location is preceded by a segment selector of the given length
llvm::Expected<uint64_t> NewAddress = Inst.getOperandAsUnsigned(CFIP, 0);
if (!NewAddress)
return NewAddress.takeError();
if (*NewAddress <= Row.getAddress())
return createStringError(
errc::invalid_argument,
"%s with adrress 0x%" PRIx64 " which must be greater than the "
"current row address 0x%" PRIx64,
CFIP.callFrameString(Inst.Opcode).str().c_str(), *NewAddress,
Row.getAddress());
Rows.push_back(Row);
Row.setAddress(*NewAddress);
break;
}
case dwarf::DW_CFA_advance_loc:
case dwarf::DW_CFA_advance_loc1:
case dwarf::DW_CFA_advance_loc2:
case dwarf::DW_CFA_advance_loc4: {
// The DW_CFA_advance instruction takes a single operand that
// represents a constant delta. The required action is to create a new
// table row with a location value that is computed by taking the
// current entrys location value and adding the value of delta *
// code_alignment_factor. All other values in the new row are initially
// identical to the current row.
Rows.push_back(Row);
llvm::Expected<uint64_t> Offset = Inst.getOperandAsUnsigned(CFIP, 0);
if (!Offset)
return Offset.takeError();
Row.slideAddress(*Offset);
break;
}
case dwarf::DW_CFA_restore:
case dwarf::DW_CFA_restore_extended: {
// The DW_CFA_restore instruction takes a single operand (encoded with
// the opcode) that represents a register number. The required action
// is to change the rule for the indicated register to the rule
// assigned it by the initial_instructions in the CIE.
if (InitialLocs == nullptr)
return createStringError(
errc::invalid_argument, "%s encountered while parsing a CIE",
CFIP.callFrameString(Inst.Opcode).str().c_str());
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
if (Optional<UnwindLocation> O =
InitialLocs->getRegisterLocation(*RegNum))
Row.getRegisterLocations().setRegisterLocation(*RegNum, *O);
else
Row.getRegisterLocations().removeRegisterLocation(*RegNum);
break;
}
case dwarf::DW_CFA_offset:
case dwarf::DW_CFA_offset_extended:
case dwarf::DW_CFA_offset_extended_sf: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
llvm::Expected<int64_t> Offset = Inst.getOperandAsSigned(CFIP, 1);
if (!Offset)
return Offset.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createAtCFAPlusOffset(*Offset));
break;
}
case dwarf::DW_CFA_nop:
break;
case dwarf::DW_CFA_remember_state:
RegisterStates.push_back(Row.getRegisterLocations());
break;
case dwarf::DW_CFA_restore_state:
if (RegisterStates.empty())
return createStringError(errc::invalid_argument,
"DW_CFA_restore_state without a matching "
"previous DW_CFA_remember_state");
Row.getRegisterLocations() = RegisterStates.back();
RegisterStates.pop_back();
break;
case dwarf::DW_CFA_GNU_window_save:
switch (CFIP.triple()) {
case Triple::aarch64:
case Triple::aarch64_be:
case Triple::aarch64_32: {
// DW_CFA_GNU_window_save is used for different things on different
// architectures. For aarch64 it is known as
// DW_CFA_AARCH64_negate_ra_state. The action is to toggle the
// value of the return address state between 1 and 0. If there is
// no rule for the AARCH64_DWARF_PAUTH_RA_STATE register, then it
// should be initially set to 1.
constexpr uint32_t AArch64DWARFPAuthRaState = 34;
auto LRLoc = Row.getRegisterLocations().getRegisterLocation(
AArch64DWARFPAuthRaState);
if (LRLoc) {
if (LRLoc->getLocation() == UnwindLocation::Constant) {
// Toggle the constant value from 0 to 1 or 1 to 0.
LRLoc->setConstant(LRLoc->getConstant() ^ 1);
} else {
return createStringError(
errc::invalid_argument,
"%s encountered when existing rule for this register is not "
"a constant",
CFIP.callFrameString(Inst.Opcode).str().c_str());
}
} else {
Row.getRegisterLocations().setRegisterLocation(
AArch64DWARFPAuthRaState, UnwindLocation::createIsConstant(1));
}
break;
}
case Triple::sparc:
case Triple::sparcv9:
case Triple::sparcel:
for (uint32_t RegNum = 16; RegNum < 32; ++RegNum) {
Row.getRegisterLocations().setRegisterLocation(
RegNum, UnwindLocation::createAtCFAPlusOffset((RegNum - 16) * 8));
}
break;
default: {
return createStringError(
errc::not_supported,
"DW_CFA opcode %#x is not supported for architecture %s",
Inst.Opcode, Triple::getArchTypeName(CFIP.triple()).str().c_str());
break;
}
}
break;
case dwarf::DW_CFA_undefined: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createUndefined());
break;
}
case dwarf::DW_CFA_same_value: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createSame());
break;
}
case dwarf::DW_CFA_GNU_args_size:
break;
case dwarf::DW_CFA_register: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
llvm::Expected<uint64_t> NewRegNum = Inst.getOperandAsUnsigned(CFIP, 1);
if (!NewRegNum)
return NewRegNum.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createIsRegisterPlusOffset(*NewRegNum, 0));
break;
}
case dwarf::DW_CFA_val_offset:
case dwarf::DW_CFA_val_offset_sf: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
llvm::Expected<int64_t> Offset = Inst.getOperandAsSigned(CFIP, 1);
if (!Offset)
return Offset.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createIsCFAPlusOffset(*Offset));
break;
}
case dwarf::DW_CFA_expression: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createAtDWARFExpression(*Inst.Expression));
break;
}
case dwarf::DW_CFA_val_expression: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
Row.getRegisterLocations().setRegisterLocation(
*RegNum, UnwindLocation::createIsDWARFExpression(*Inst.Expression));
break;
}
case dwarf::DW_CFA_def_cfa_register: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
if (Row.getCFAValue().getLocation() != UnwindLocation::RegPlusOffset)
Row.getCFAValue() =
UnwindLocation::createIsRegisterPlusOffset(*RegNum, 0);
else
Row.getCFAValue().setRegister(*RegNum);
break;
}
case dwarf::DW_CFA_def_cfa_offset:
case dwarf::DW_CFA_def_cfa_offset_sf: {
llvm::Expected<int64_t> Offset = Inst.getOperandAsSigned(CFIP, 0);
if (!Offset)
return Offset.takeError();
if (Row.getCFAValue().getLocation() != UnwindLocation::RegPlusOffset) {
return createStringError(
errc::invalid_argument,
"%s found when CFA rule was not RegPlusOffset",
CFIP.callFrameString(Inst.Opcode).str().c_str());
}
Row.getCFAValue().setOffset(*Offset);
break;
}
case dwarf::DW_CFA_def_cfa:
case dwarf::DW_CFA_def_cfa_sf: {
llvm::Expected<uint64_t> RegNum = Inst.getOperandAsUnsigned(CFIP, 0);
if (!RegNum)
return RegNum.takeError();
llvm::Expected<int64_t> Offset = Inst.getOperandAsSigned(CFIP, 1);
if (!Offset)
return Offset.takeError();
Row.getCFAValue() =
UnwindLocation::createIsRegisterPlusOffset(*RegNum, *Offset);
break;
}
case dwarf::DW_CFA_def_cfa_expression:
Row.getCFAValue() =
UnwindLocation::createIsDWARFExpression(*Inst.Expression);
break;
}
}
return Error::success();
}
ArrayRef<CFIProgram::OperandType[2]> CFIProgram::getOperandTypes() {
static OperandType OpTypes[DW_CFA_restore+1][2];
@ -244,7 +803,7 @@ void CFIProgram::printOperand(raw_ostream &OS, DIDumpOptions DumpOpts,
switch (Type) {
case OT_Unset: {
OS << " Unsupported " << (OperandIdx ? "second" : "first") << " operand to";
auto OpcodeName = CallFrameString(Opcode, Arch);
auto OpcodeName = callFrameString(Opcode);
if (!OpcodeName.empty())
OS << " " << OpcodeName;
else
@ -297,10 +856,8 @@ void CFIProgram::dump(raw_ostream &OS, DIDumpOptions DumpOpts,
unsigned IndentLevel) const {
for (const auto &Instr : Instructions) {
uint8_t Opcode = Instr.Opcode;
if (Opcode & DWARF_CFI_PRIMARY_OPCODE_MASK)
Opcode &= DWARF_CFI_PRIMARY_OPCODE_MASK;
OS.indent(2 * IndentLevel);
OS << CallFrameString(Opcode, Arch) << ":";
OS << callFrameString(Opcode) << ":";
for (unsigned i = 0; i < Instr.Ops.size(); ++i)
printOperand(OS, DumpOpts, MRI, IsEH, Instr, i, Instr.Ops[i]);
OS << '\n';
@ -354,6 +911,16 @@ void CIE::dump(raw_ostream &OS, DIDumpOptions DumpOpts,
OS << "\n";
CFIs.dump(OS, DumpOpts, MRI, IsEH);
OS << "\n";
if (Expected<UnwindTable> RowsOrErr = UnwindTable::create(this))
RowsOrErr->dump(OS, MRI, IsEH, 1);
else {
DumpOpts.RecoverableErrorHandler(joinErrors(
createStringError(errc::invalid_argument,
"decoding the CIE opcodes into rows failed"),
RowsOrErr.takeError()));
}
OS << "\n";
}
void FDE::dump(raw_ostream &OS, DIDumpOptions DumpOpts,
@ -373,6 +940,16 @@ void FDE::dump(raw_ostream &OS, DIDumpOptions DumpOpts,
OS << format(" LSDA Address: %016" PRIx64 "\n", *LSDAAddress);
CFIs.dump(OS, DumpOpts, MRI, IsEH);
OS << "\n";
if (Expected<UnwindTable> RowsOrErr = UnwindTable::create(this))
RowsOrErr->dump(OS, MRI, IsEH, 1);
else {
DumpOpts.RecoverableErrorHandler(joinErrors(
createStringError(errc::invalid_argument,
"decoding the FDE opcodes into rows failed"),
RowsOrErr.takeError()));
}
OS << "\n";
}
DWARFDebugFrame::DWARFDebugFrame(Triple::ArchType Arch,

View File

@ -501,4 +501,10 @@ bool DWARFExpression::printCompact(raw_ostream &OS, const MCRegisterInfo &MRI) {
return printCompactDWARFExpr(OS, begin(), end(), MRI);
}
bool DWARFExpression::operator==(const DWARFExpression &RHS) const {
if (AddressSize != RHS.AddressSize || Format != RHS.Format)
return false;
return Data.getData() == RHS.Data.getData();
}
} // namespace llvm

View File

@ -1,8 +1,10 @@
# RUN: llvm-mc -triple x86_64-unknown-linux %s -filetype=obj -o - | \
# RUN: llvm-dwarfdump -debug-frame - | \
# RUN: not llvm-dwarfdump -debug-frame - 2>&1 | \
# RUN: FileCheck %s
# CHECK: 00000000 {{.*}} FDE
# CHECK: error: decoding the FDE opcodes into rows failed
# CHECK: error: unable to get CIE for FDE at offset 0x0
.section .debug_frame,"",@progbits
## This FDE was formerly wrongly interpreted as a CIE because its CIE pointer

View File

@ -1,9 +1,11 @@
# RUN: llvm-mc -triple i386-unknown-linux %s -filetype=obj -o - | \
# RUN: llvm-dwarfdump -debug-frame - | \
# RUN: not llvm-dwarfdump -debug-frame - 2>&1 | \
# RUN: FileCheck %s
# CHECK: .debug_frame contents:
# CHECK: 00000000 0000000c 12345678 FDE cie=<invalid offset> pc=00010000...00010010
# CHECK: error: decoding the FDE opcodes into rows failed
# CHECK: error: unable to get CIE for FDE at offset 0x0
.section .debug_frame,"",@progbits
.long .LFDE0end-.LFDE0id # Length

View File

@ -14,6 +14,8 @@
# CHECK-EMPTY:
# CHECK-NEXT: DW_CFA_def_cfa: reg29 +0
# CHECK-EMPTY:
# CHECK-NEXT: CFA=reg29
# CHECK-EMPTY:
# CHECK-NEXT: 0000001c 00000018 00000020 FDE cie=00000000 pc=00400890...004008dc
# CHECK-NEXT: Format: DWARF32
# CHECK-NEXT: LSDA Address: 0000000000400a90
@ -23,5 +25,9 @@
# CHECK-NEXT: DW_CFA_offset: reg31 -4
# CHECK-NEXT: DW_CFA_nop:
# CHECK-EMPTY:
# CHECK-NEXT: 0x400890: CFA=reg29
# CHECK-NEXT: 0x400894: CFA=reg29+24
# CHECK-NEXT: 0x400898: CFA=reg29+24: reg31=[CFA-4]
# CHECK-EMPTY:
# CHECK-NEXT: 00000038 ZERO terminator
# CHECK-NOT: {{.}}

File diff suppressed because it is too large Load Diff