1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 10:42:39 +01:00
llvm-mirror/unittests/DebugInfo/DWARF/DWARFDebugFrameTest.cpp

1471 lines
62 KiB
C++
Raw Normal View History

//===- llvm/unittest/DebugInfo/DWARFDebugFrameTest.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/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugFrame.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace llvm;
namespace {
dwarf::CIE createCIE(bool IsDWARF64, uint64_t Offset, uint64_t Length) {
return dwarf::CIE(IsDWARF64, Offset, Length,
/*Version=*/3,
/*Augmentation=*/StringRef(),
/*AddressSize=*/8,
/*SegmentDescriptorSize=*/0,
/*CodeAlignmentFactor=*/1,
/*DataAlignmentFactor=*/-8,
/*ReturnAddressRegister=*/16,
/*AugmentationData=*/StringRef(),
/*FDEPointerEncoding=*/dwarf::DW_EH_PE_absptr,
/*LSDAPointerEncoding=*/dwarf::DW_EH_PE_omit,
/*Personality=*/None,
/*PersonalityEnc=*/None,
/*Arch=*/Triple::x86_64);
}
void expectDumpResult(const dwarf::CIE &TestCIE, bool IsEH,
StringRef ExpectedFirstLine) {
std::string Output;
raw_string_ostream OS(Output);
TestCIE.dump(OS, DIDumpOptions(), /*MRI=*/nullptr, IsEH);
OS.flush();
StringRef FirstLine = StringRef(Output).split('\n').first;
EXPECT_EQ(FirstLine, ExpectedFirstLine);
}
void expectDumpResult(const dwarf::FDE &TestFDE, bool IsEH,
StringRef ExpectedFirstLine) {
std::string Output;
raw_string_ostream OS(Output);
TestFDE.dump(OS, DIDumpOptions(), /*MRI=*/nullptr, IsEH);
OS.flush();
StringRef FirstLine = StringRef(Output).split('\n').first;
EXPECT_EQ(FirstLine, ExpectedFirstLine);
}
TEST(DWARFDebugFrame, DumpDWARF32CIE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x1111abcd,
/*Length=*/0x2222abcd);
expectDumpResult(TestCIE, /*IsEH=*/false, "1111abcd 2222abcd ffffffff CIE");
}
TEST(DWARFDebugFrame, DumpDWARF64CIE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/true,
/*Offset=*/0x1111abcdabcd,
/*Length=*/0x2222abcdabcd);
expectDumpResult(TestCIE, /*IsEH=*/false,
"1111abcdabcd 00002222abcdabcd ffffffffffffffff CIE");
}
TEST(DWARFDebugFrame, DumpEHCIE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x1000,
/*Length=*/0x20);
expectDumpResult(TestCIE, /*IsEH=*/true, "00001000 00000020 00000000 CIE");
}
TEST(DWARFDebugFrame, DumpEH64CIE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/true,
/*Offset=*/0x1000,
/*Length=*/0x20);
expectDumpResult(TestCIE, /*IsEH=*/true,
"00001000 0000000000000020 00000000 CIE");
}
TEST(DWARFDebugFrame, DumpDWARF64FDE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/true,
/*Offset=*/0x1111abcdabcd,
/*Length=*/0x2222abcdabcd);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x5555abcdabcd,
/*AddressRange=*/0x111111111111,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
expectDumpResult(TestFDE, /*IsEH=*/false,
"3333abcdabcd 00004444abcdabcd 00001111abcdabcd FDE "
"cie=1111abcdabcd pc=5555abcdabcd...6666bcdebcde");
}
TEST(DWARFDebugFrame, DumpEH64FDE) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/true,
/*Offset=*/0x1111ab9a000c,
/*Length=*/0x20);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x1111abcdabcd,
/*Length=*/0x2222abcdabcd,
/*CIEPointer=*/0x33abcd,
/*InitialLocation=*/0x4444abcdabcd,
/*AddressRange=*/0x111111111111,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
expectDumpResult(TestFDE, /*IsEH=*/true,
"1111abcdabcd 00002222abcdabcd 0033abcd FDE "
"cie=1111ab9a000c pc=4444abcdabcd...5555bcdebcde");
}
static Error parseCFI(dwarf::CIE &C, ArrayRef<uint8_t> Instructions,
Optional<uint64_t> Size = None) {
DWARFDataExtractor Data(Instructions, /*IsLittleEndian=*/true,
/*AddressSize=*/8);
uint64_t Offset = 0;
const uint64_t EndOffset = Size ? *Size : (uint64_t)Instructions.size();
return C.cfis().parse(Data, &Offset, EndOffset);
}
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
2020-12-08 00:36:45 +01:00
static Error parseCFI(dwarf::FDE &FDE, ArrayRef<uint8_t> Instructions) {
DWARFDataExtractor Data(Instructions, /*IsLittleEndian=*/true,
/*AddressSize=*/8);
uint64_t Offset = 0;
return FDE.cfis().parse(Data, &Offset, Instructions.size());
}
TEST(DWARFDebugFrame, InvalidCFIOpcodesTest) {
llvm::DenseSet<uint8_t> ValidExtendedOpcodes = {
dwarf::DW_CFA_nop,
dwarf::DW_CFA_advance_loc,
dwarf::DW_CFA_offset,
dwarf::DW_CFA_restore,
dwarf::DW_CFA_set_loc,
dwarf::DW_CFA_advance_loc1,
dwarf::DW_CFA_advance_loc2,
dwarf::DW_CFA_advance_loc4,
dwarf::DW_CFA_offset_extended,
dwarf::DW_CFA_restore_extended,
dwarf::DW_CFA_undefined,
dwarf::DW_CFA_same_value,
dwarf::DW_CFA_register,
dwarf::DW_CFA_remember_state,
dwarf::DW_CFA_restore_state,
dwarf::DW_CFA_def_cfa,
dwarf::DW_CFA_def_cfa_register,
dwarf::DW_CFA_def_cfa_offset,
dwarf::DW_CFA_def_cfa_expression,
dwarf::DW_CFA_expression,
dwarf::DW_CFA_offset_extended_sf,
dwarf::DW_CFA_def_cfa_sf,
dwarf::DW_CFA_def_cfa_offset_sf,
dwarf::DW_CFA_val_offset,
dwarf::DW_CFA_val_offset_sf,
dwarf::DW_CFA_val_expression,
dwarf::DW_CFA_MIPS_advance_loc8,
dwarf::DW_CFA_GNU_window_save,
dwarf::DW_CFA_AARCH64_negate_ra_state,
dwarf::DW_CFA_GNU_args_size};
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
// See DWARF standard v3, section 7.23: low 6 bits are used to encode an
// extended opcode.
for (uint8_t Code = 0; Code <= 63; ++Code) {
if (ValidExtendedOpcodes.count(Code))
continue;
EXPECT_THAT_ERROR(parseCFI(TestCIE, Code),
FailedWithMessage(("invalid extended CFI opcode 0x" +
Twine::utohexstr(Code))
.str()
.c_str()));
}
}
// Here we test how truncated Call Frame Instructions are parsed.
TEST(DWARFDebugFrame, ParseTruncatedCFITest) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
// Having an empty instructions list is fine.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {}), Succeeded());
// Unable to read an opcode, because the instructions list is empty, but we
// say to the parser that it is not.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {}, /*Size=*/1),
FailedWithMessage(
"unexpected end of data at offset 0x0 while reading [0x0, 0x1)"));
// Unable to read a truncated DW_CFA_offset instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_offset}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
// Unable to read a truncated DW_CFA_set_loc instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_set_loc}),
FailedWithMessage(
"unexpected end of data at offset 0x1 while reading [0x1, 0x9)"));
// Unable to read a truncated DW_CFA_advance_loc1 instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_advance_loc1}),
FailedWithMessage(
"unexpected end of data at offset 0x1 while reading [0x1, 0x2)"));
// Unable to read a truncated DW_CFA_advance_loc2 instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_advance_loc2}),
FailedWithMessage(
"unexpected end of data at offset 0x1 while reading [0x1, 0x3)"));
// Unable to read a truncated DW_CFA_advance_loc4 instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_advance_loc4}),
FailedWithMessage(
"unexpected end of data at offset 0x1 while reading [0x1, 0x5)"));
// A test for an instruction with a single ULEB128 operand.
auto CheckOp_ULEB128 = [&](uint8_t Inst) {
EXPECT_THAT_ERROR(
parseCFI(TestCIE, Inst),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
};
for (uint8_t Inst :
{dwarf::DW_CFA_restore_extended, dwarf::DW_CFA_undefined,
dwarf::DW_CFA_same_value, dwarf::DW_CFA_def_cfa_register,
dwarf::DW_CFA_def_cfa_offset, dwarf::DW_CFA_GNU_args_size})
CheckOp_ULEB128(Inst);
// Unable to read a truncated DW_CFA_def_cfa_offset_sf instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa_offset_sf}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed sleb128, extends past end"));
// A test for an instruction with two ULEB128 operands.
auto CheckOp_ULEB128_ULEB128 = [&](uint8_t Inst) {
EXPECT_THAT_ERROR(
parseCFI(TestCIE, Inst),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {Inst, /*Op1=*/0}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000002: "
"malformed uleb128, extends past end"));
};
for (uint8_t Inst : {dwarf::DW_CFA_offset_extended, dwarf::DW_CFA_register,
dwarf::DW_CFA_def_cfa, dwarf::DW_CFA_val_offset})
CheckOp_ULEB128_ULEB128(Inst);
// A test for an instruction with two operands: ULEB128, SLEB128.
auto CheckOp_ULEB128_SLEB128 = [&](uint8_t Inst) {
EXPECT_THAT_ERROR(
parseCFI(TestCIE, Inst),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {Inst, /*Op1=*/0}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000002: "
"malformed sleb128, extends past end"));
};
for (uint8_t Inst : {dwarf::DW_CFA_offset_extended_sf,
dwarf::DW_CFA_def_cfa_sf, dwarf::DW_CFA_val_offset_sf})
CheckOp_ULEB128_SLEB128(Inst);
// Unable to read a truncated DW_CFA_def_cfa_expression instruction.
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa_expression}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa_expression,
/*expression length=*/0x1}),
FailedWithMessage(
"unexpected end of data at offset 0x2 while reading [0x2, 0x3)"));
// The DW_CFA_def_cfa_expression can contain a zero length expression.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa_expression,
/*ExprLen=*/0}),
Succeeded());
// A test for an instruction with three operands: ULEB128, expression length
// (ULEB128) and expression bytes.
auto CheckOp_ULEB128_Expr = [&](uint8_t Inst) {
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {Inst}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000001: "
"malformed uleb128, extends past end"));
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {Inst, /*Op1=*/0}),
FailedWithMessage("unable to decode LEB128 at offset 0x00000002: "
"malformed uleb128, extends past end"));
// A zero length expression is fine
EXPECT_THAT_ERROR(parseCFI(TestCIE, {Inst,
/*Op1=*/0, /*ExprLen=*/0}),
Succeeded());
EXPECT_THAT_ERROR(
parseCFI(TestCIE, {Inst,
/*Op1=*/0, /*ExprLen=*/1}),
FailedWithMessage(
"unexpected end of data at offset 0x3 while reading [0x3, 0x4)"));
};
for (uint8_t Inst : {dwarf::DW_CFA_expression, dwarf::DW_CFA_val_expression})
CheckOp_ULEB128_Expr(Inst);
}
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
2020-12-08 00:36:45 +01:00
void expectDumpResult(const dwarf::UnwindLocation &Loc,
StringRef ExpectedFirstLine) {
std::string Output;
raw_string_ostream OS(Output);
OS << Loc;
OS.flush();
StringRef FirstLine = StringRef(Output).split('\n').first;
EXPECT_EQ(FirstLine, ExpectedFirstLine);
}
TEST(DWARFDebugFrame, DumpUnwindLocations) {
// Test constructing unwind locations and dumping each kind.
constexpr int32_t PlusOff = 8;
constexpr int32_t MinusOff = -8;
constexpr uint8_t RegNum = 12;
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
2020-12-08 00:36:45 +01:00
expectDumpResult(dwarf::UnwindLocation::createUnspecified(), "unspecified");
expectDumpResult(dwarf::UnwindLocation::createUndefined(), "undefined");
expectDumpResult(dwarf::UnwindLocation::createSame(), "same");
expectDumpResult(dwarf::UnwindLocation::createIsCFAPlusOffset(PlusOff),
"CFA+8");
expectDumpResult(dwarf::UnwindLocation::createIsCFAPlusOffset(MinusOff),
"CFA-8");
expectDumpResult(dwarf::UnwindLocation::createAtCFAPlusOffset(PlusOff),
"[CFA+8]");
expectDumpResult(dwarf::UnwindLocation::createAtCFAPlusOffset(MinusOff),
"[CFA-8]");
expectDumpResult(
dwarf::UnwindLocation::createIsRegisterPlusOffset(RegNum, PlusOff),
"reg12+8");
expectDumpResult(
dwarf::UnwindLocation::createIsRegisterPlusOffset(RegNum, MinusOff),
"reg12-8");
expectDumpResult(
dwarf::UnwindLocation::createAtRegisterPlusOffset(RegNum, PlusOff),
"[reg12+8]");
expectDumpResult(
dwarf::UnwindLocation::createAtRegisterPlusOffset(RegNum, MinusOff),
"[reg12-8]");
expectDumpResult(dwarf::UnwindLocation::createIsConstant(12), "12");
expectDumpResult(dwarf::UnwindLocation::createIsConstant(-32), "-32");
}
void expectDumpResult(const dwarf::RegisterLocations &Locs,
StringRef ExpectedFirstLine) {
std::string Output;
raw_string_ostream OS(Output);
OS << Locs;
OS.flush();
StringRef FirstLine = StringRef(Output).split('\n').first;
EXPECT_EQ(FirstLine, ExpectedFirstLine);
}
TEST(DWARFDebugFrame, RegisterLocations) {
// Test the functionality of the RegisterLocations class.
dwarf::RegisterLocations Locs;
expectDumpResult(Locs, "");
EXPECT_FALSE(Locs.hasLocations());
// Set a register location for reg12 to unspecified and verify it dumps
// correctly.
Locs.setRegisterLocation(12, dwarf::UnwindLocation::createUnspecified());
EXPECT_TRUE(Locs.hasLocations());
expectDumpResult(Locs, "reg12=unspecified");
// Replace the register location for reg12 to "same" and verify it dumps
// correctly after it is modified
Locs.setRegisterLocation(12, dwarf::UnwindLocation::createSame());
EXPECT_TRUE(Locs.hasLocations());
expectDumpResult(Locs, "reg12=same");
// Remove the register location for reg12 verify it dumps correctly after it
// is removed.
Locs.removeRegisterLocation(12);
EXPECT_FALSE(Locs.hasLocations());
expectDumpResult(Locs, "");
// Verify multiple registers added to the list dump correctly.
auto Reg12Loc = dwarf::UnwindLocation::createAtCFAPlusOffset(4);
auto Reg13Loc = dwarf::UnwindLocation::createAtCFAPlusOffset(8);
auto Reg14Loc = dwarf::UnwindLocation::createSame();
Locs.setRegisterLocation(12, Reg12Loc);
Locs.setRegisterLocation(13, Reg13Loc);
Locs.setRegisterLocation(14, Reg14Loc);
EXPECT_TRUE(Locs.hasLocations());
expectDumpResult(Locs, "reg12=[CFA+4], reg13=[CFA+8], reg14=same");
// Verify RegisterLocations::getRegisterLocation() works as expected.
Optional<dwarf::UnwindLocation> OptionalLoc;
OptionalLoc = Locs.getRegisterLocation(0);
EXPECT_FALSE(OptionalLoc.hasValue());
OptionalLoc = Locs.getRegisterLocation(12);
EXPECT_TRUE(OptionalLoc.hasValue());
EXPECT_EQ(*OptionalLoc, Reg12Loc);
OptionalLoc = Locs.getRegisterLocation(13);
EXPECT_TRUE(OptionalLoc.hasValue());
EXPECT_EQ(*OptionalLoc, Reg13Loc);
OptionalLoc = Locs.getRegisterLocation(14);
EXPECT_TRUE(OptionalLoc.hasValue());
EXPECT_EQ(*OptionalLoc, Reg14Loc);
// Verify registers are correctly removed when multiple exist in the list.
Locs.removeRegisterLocation(13);
EXPECT_FALSE(Locs.getRegisterLocation(13).hasValue());
EXPECT_TRUE(Locs.hasLocations());
expectDumpResult(Locs, "reg12=[CFA+4], reg14=same");
Locs.removeRegisterLocation(14);
EXPECT_FALSE(Locs.getRegisterLocation(14).hasValue());
EXPECT_TRUE(Locs.hasLocations());
expectDumpResult(Locs, "reg12=[CFA+4]");
Locs.removeRegisterLocation(12);
EXPECT_FALSE(Locs.getRegisterLocation(12).hasValue());
EXPECT_FALSE(Locs.hasLocations());
expectDumpResult(Locs, "");
}
TEST(DWARFDebugFrame, UnwindTableErrorNonAscendingFDERows) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition.
constexpr uint8_t Reg = 12;
constexpr uint8_t Offset = 32;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, Reg, Offset}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode two DW_CFA_set_loc opcodes:
// DW_CFA_set_loc(0x1100)
// DW_CFA_set_loc(0x1000)
// These opcodes cause a new row to be appended to the rows in a UnwindTable
// and the resulting rows are not in ascending address order and should cause
// a state machine error.
EXPECT_THAT_ERROR(
parseCFI(TestFDE, {dwarf::DW_CFA_set_loc, 0x00, 0x11, 0, 0, 0, 0, 0, 0,
dwarf::DW_CFA_set_loc, 0x00, 0x10, 0, 0, 0, 0, 0, 0}),
Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(),
FailedWithMessage("DW_CFA_set_loc with adrress 0x1000 which"
" must be greater than the current row "
"address 0x1100"));
}
TEST(DWARFDebugFrame, UnwindTableError_DW_CFA_restore_state) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition.
constexpr uint8_t Reg = 12;
constexpr uint8_t Offset = 32;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, Reg, Offset}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode a DW_CFA_restore_state opcode that was not preceded by a
// DW_CFA_remember_state, and an error should be returned.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_restore_state}),
Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(),
FailedWithMessage("DW_CFA_restore_state without a matching "
"previous DW_CFA_remember_state"));
}
TEST(DWARFDebugFrame, UnwindTableError_DW_CFA_GNU_window_save) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition.
constexpr uint8_t Reg = 12;
constexpr uint8_t Offset = 32;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, Reg, Offset}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode a DW_CFA_GNU_window_save that is not supported. I have not
// found any documentation that describes what this does after some brief
// searching.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_GNU_window_save}),
Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(),
FailedWithMessage("DW_CFA opcode 0x2d is not supported for "
"architecture x86_64"));
}
TEST(DWARFDebugFrame, UnwindTableError_DW_CFA_def_cfa_offset) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has an invalid CFA definition. We do this so we can try
// and use a DW_CFA_def_cfa_register opcode in the FDE and get an appropriate
// error back.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {}), Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode a DW_CFA_def_cfa_offset with a offset of 16, but our CIE
// didn't define the CFA in terms of a register plus offset, so this should
// cause an error.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_def_cfa_offset, 16}),
Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(),
FailedWithMessage("DW_CFA_def_cfa_offset found when CFA "
"rule was not RegPlusOffset"));
}
TEST(DWARFDebugFrame, UnwindTableDefCFAOffsetSFCFAError) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has an invalid CFA definition. We do this so we can try
// and use a DW_CFA_def_cfa_offset_sf opcode in the FDE and get an
// appropriate error back.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {}), Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode a DW_CFA_def_cfa_offset_sf with a offset of 4, but our CIE
// didn't define the CFA in terms of a register plus offset, so this should
// cause an error.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_def_cfa_offset_sf, 4}),
Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(),
FailedWithMessage("DW_CFA_def_cfa_offset_sf found when CFA "
"rule was not RegPlusOffset"));
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_def_cfa_register) {
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has only defines the CFA register with no offset. Some
// architectures do this and we must ensure that we set the CFA value to be
// equal to that register with no offset.
constexpr uint8_t CFAReg = 12;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa_register, CFAReg}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that have valid
// syntax, but will cause an error when we parse them into a UnwindTable.
// Here we encode a DW_CFA_def_cfa_register with a register number of 12, but
// our CIE didn't define the CFA in terms of a register plus offset, so this
// should cause an error.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {}), Succeeded());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg, 0));
}
TEST(DWARFDebugFrame, UnwindTableRowPushingOpcodes) {
// Test all opcodes that should end up pushing a UnwindRow into a UnwindTable.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
constexpr uint8_t CFAReg = 12;
constexpr uint8_t CFAOffset = 32;
constexpr uint8_t Reg = 13;
constexpr uint8_t InReg = 14;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, CFAReg, CFAOffset,
dwarf::DW_CFA_register, Reg, InReg}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that use all of the
// row pushing opcodes. This will verify that all opcodes that should create
// a row are correctly working. Each opcode will push a row prior to
// advancing the address, and then a row will be automatically pushed at the
// end of the parsing, so we should end up with 6 rows starting at address
// 0x1000 (from the FDE) and incrementing each one by 4 * CodeAlignmentFactor
// from the CIE.
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_advance_loc1,
4,
dwarf::DW_CFA_advance_loc2,
4,
0,
dwarf::DW_CFA_advance_loc4,
4,
0,
0,
0,
dwarf::DW_CFA_set_loc,
0x14,
0x10,
0,
0,
0,
0,
0,
0}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsRegisterPlusOffset(InReg, 0));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
ASSERT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 6u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[1].getAddress(), 0x1004u);
EXPECT_EQ(Rows[1].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[1].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[2].getAddress(), 0x1008u);
EXPECT_EQ(Rows[2].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[2].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[3].getAddress(), 0x100cu);
EXPECT_EQ(Rows[3].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[3].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[4].getAddress(), 0x1010u);
EXPECT_EQ(Rows[4].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[4].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[5].getAddress(), 0x1014u);
EXPECT_EQ(Rows[5].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[5].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_restore) {
// Test that DW_CFA_restore works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
constexpr uint8_t CFAReg = 12;
constexpr uint8_t CFAOffset = 32;
constexpr uint8_t Reg = 13;
constexpr uint8_t InReg = 14;
constexpr int32_t RegCFAOffset = -8;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, CFAReg, CFAOffset,
dwarf::DW_CFA_register, Reg, InReg}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that changes the rule
// for register "Reg" to be [CFA-8], then push a row, and then restore the
// register unwind rule for "Reg" using DW_CFA_restore. We should end up with
// two rows:
// - one with Reg = [CFA-8]
// - one with Reg = InReg
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_offset | Reg, 1,
dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_restore | Reg}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs1;
VerifyLocs1.setRegisterLocation(
Reg, dwarf::UnwindLocation::createAtCFAPlusOffset(RegCFAOffset));
dwarf::RegisterLocations VerifyLocs2;
VerifyLocs2.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsRegisterPlusOffset(InReg, 0));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 2u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs1);
EXPECT_EQ(Rows[1].getAddress(), 0x1004u);
EXPECT_EQ(Rows[1].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[1].getRegisterLocations(), VerifyLocs2);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_restore_extended) {
// Test that DW_CFA_restore works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
constexpr uint8_t CFAReg = 12;
constexpr uint8_t CFAOffset = 32;
constexpr uint8_t Reg = 13;
constexpr uint8_t InReg = 14;
constexpr int32_t RegCFAOffset = -8;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, CFAReg, CFAOffset,
dwarf::DW_CFA_register, Reg, InReg}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that changes the rule
// for register "Reg" to be [CFA-8], then push a row, and then restore the
// register unwind rule for "Reg" using DW_CFA_restore_extended. We should
// end up with two rows:
// - one with Reg = [CFA-8]
// - one with Reg = InReg
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_offset | Reg, 1,
dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_restore_extended, Reg}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs1;
VerifyLocs1.setRegisterLocation(
Reg, dwarf::UnwindLocation::createAtCFAPlusOffset(RegCFAOffset));
dwarf::RegisterLocations VerifyLocs2;
VerifyLocs2.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsRegisterPlusOffset(InReg, 0));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 2u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs1);
EXPECT_EQ(Rows[1].getAddress(), 0x1004u);
EXPECT_EQ(Rows[1].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[1].getRegisterLocations(), VerifyLocs2);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_offset) {
// Test that DW_CFA_offset, DW_CFA_offset_extended and
// DW_CFA_offset_extended_sf work as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that changes the
// unwind rules for the follwing registers:
// Reg1 = [CFA-8]
// Reg2 = [CFA-16]
// Reg3 = [CFA+8]
constexpr uint8_t Reg1 = 14;
constexpr uint8_t Reg2 = 15;
constexpr uint8_t Reg3 = 16;
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
2020-12-08 00:36:45 +01:00
constexpr uint8_t Neg1SLEB = 0x7f;
EXPECT_THAT_ERROR(
parseCFI(TestFDE,
{dwarf::DW_CFA_offset | Reg1, 1, dwarf::DW_CFA_offset_extended,
Reg2, 2, dwarf::DW_CFA_offset_extended_sf, Reg3, Neg1SLEB}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createAtCFAPlusOffset(-8));
VerifyLocs.setRegisterLocation(
Reg2, dwarf::UnwindLocation::createAtCFAPlusOffset(-16));
VerifyLocs.setRegisterLocation(
Reg3, dwarf::UnwindLocation::createAtCFAPlusOffset(8));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_val_offset) {
// Test that DW_CFA_val_offset and DW_CFA_val_offset_sf work as expected when
// parsed in the state machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that changes the
// unwind rules for the follwing registers:
// Reg1 = [CFA-8]
// Reg2 = [CFA-16]
// Reg3 = [CFA+8]
constexpr uint8_t Reg1 = 14;
constexpr uint8_t Reg2 = 15;
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
2020-12-08 00:36:45 +01:00
constexpr uint8_t Neg1SLEB = 0x7f;
EXPECT_THAT_ERROR(
parseCFI(TestFDE, {dwarf::DW_CFA_val_offset, Reg1, 1,
dwarf::DW_CFA_val_offset_sf, Reg2, Neg1SLEB}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createIsCFAPlusOffset(-8));
VerifyLocs.setRegisterLocation(
Reg2, dwarf::UnwindLocation::createIsCFAPlusOffset(8));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_nop) {
// Test that DW_CFA_nop works as expected when parsed in the state machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that changes the
// unwind rules for the follwing registers:
// Reg1 = [CFA-8]
// The opcodes for setting Reg1 are preceded by a DW_CFA_nop.
constexpr uint8_t Reg1 = 14;
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
2020-12-08 00:36:45 +01:00
EXPECT_THAT_ERROR(
parseCFI(TestFDE, {dwarf::DW_CFA_nop, dwarf::DW_CFA_offset | Reg1, 1}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createAtCFAPlusOffset(-8));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_remember_state) {
// Test that DW_CFA_remember_state and DW_CFA_restore_state work as expected
// when parsed in the state machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=[CFA-8]
// 0x1004: CFA=reg12+32: Reg1=[CFA-8] Reg2=[CFA-16]
// 0x1008: CFA=reg12+32: Reg1=[CFA-8] Reg2=[CFA-16] Reg3=[CFA-24]
// 0x100C: CFA=reg12+32: Reg1=[CFA-8] Reg2=[CFA-16]
// 0x1010: CFA=reg12+32: Reg1=[CFA-8]
// This state machine will:
// - set Reg1 location
// - push a row (from DW_CFA_advance_loc)
// - remember the state
// - set Reg2 location
// - push a row (from DW_CFA_advance_loc)
// - remember the state
// - set Reg3 location
// - push a row (from DW_CFA_advance_loc)
// - remember the state where Reg1 and Reg2 were set
// - push a row (from DW_CFA_advance_loc)
// - remember the state where only Reg1 was set
// - push a row (automatically at the end of instruction parsing)
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg1 = 14;
constexpr uint8_t Reg2 = 15;
constexpr uint8_t Reg3 = 16;
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
2020-12-08 00:36:45 +01:00
EXPECT_THAT_ERROR(
parseCFI(TestFDE,
{dwarf::DW_CFA_offset | Reg1, 1, dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_remember_state, dwarf::DW_CFA_offset | Reg2, 2,
dwarf::DW_CFA_advance_loc | 4, dwarf::DW_CFA_remember_state,
dwarf::DW_CFA_offset | Reg3, 3, dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_restore_state, dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_restore_state}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs1;
VerifyLocs1.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createAtCFAPlusOffset(-8));
dwarf::RegisterLocations VerifyLocs2;
VerifyLocs2.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createAtCFAPlusOffset(-8));
VerifyLocs2.setRegisterLocation(
Reg2, dwarf::UnwindLocation::createAtCFAPlusOffset(-16));
dwarf::RegisterLocations VerifyLocs3;
VerifyLocs3.setRegisterLocation(
Reg1, dwarf::UnwindLocation::createAtCFAPlusOffset(-8));
VerifyLocs3.setRegisterLocation(
Reg2, dwarf::UnwindLocation::createAtCFAPlusOffset(-16));
VerifyLocs3.setRegisterLocation(
Reg3, dwarf::UnwindLocation::createAtCFAPlusOffset(-24));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 5u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs1);
EXPECT_EQ(Rows[1].getAddress(), 0x1004u);
EXPECT_EQ(Rows[1].getRegisterLocations(), VerifyLocs2);
EXPECT_EQ(Rows[2].getAddress(), 0x1008u);
EXPECT_EQ(Rows[2].getRegisterLocations(), VerifyLocs3);
EXPECT_EQ(Rows[3].getAddress(), 0x100Cu);
EXPECT_EQ(Rows[3].getRegisterLocations(), VerifyLocs2);
EXPECT_EQ(Rows[4].getAddress(), 0x1010u);
EXPECT_EQ(Rows[4].getRegisterLocations(), VerifyLocs1);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_undefined) {
// Test that DW_CFA_undefined works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=undefined
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg1 = 14;
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
2020-12-08 00:36:45 +01:00
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_undefined, Reg1}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(Reg1,
dwarf::UnwindLocation::createUndefined());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_same_value) {
// Test that DW_CFA_same_value works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=same
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg1 = 14;
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
2020-12-08 00:36:45 +01:00
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_same_value, Reg1}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(Reg1, dwarf::UnwindLocation::createSame());
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_register) {
// Test that DW_CFA_register works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=same
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg = 13;
constexpr uint8_t InReg = 14;
EXPECT_THAT_ERROR(parseCFI(TestFDE, {dwarf::DW_CFA_register, Reg, InReg}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsRegisterPlusOffset(InReg, 0));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_expression) {
// Test that DW_CFA_expression works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=DWARFExpr(DW_OP_reg12)
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg = 13;
constexpr uint8_t AddrSize = 8;
std::vector<uint8_t> CFIBytes = {dwarf::DW_CFA_expression, Reg, 1,
dwarf::DW_OP_reg12};
EXPECT_THAT_ERROR(parseCFI(TestFDE, CFIBytes), Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
std::vector<uint8_t> ExprBytes = {dwarf::DW_OP_reg12};
DataExtractor ExprData(ExprBytes, true, AddrSize);
DWARFExpression Expr(ExprData, AddrSize);
VerifyLocs.setRegisterLocation(
Reg, dwarf::UnwindLocation::createAtDWARFExpression(Expr));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_val_expression) {
// Test that DW_CFA_val_expression works as expected when parsed in the state
// machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, 12, 32}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that encodes the
// follwing rows:
// 0x1000: CFA=reg12+32: Reg1=DWARFExpr(DW_OP_reg12)
// Then we verify that all registers are correct in all generated rows.
constexpr uint8_t Reg = 13;
constexpr uint8_t AddrSize = 8;
std::vector<uint8_t> CFIBytes = {dwarf::DW_CFA_val_expression, Reg, 1,
dwarf::DW_OP_reg12};
EXPECT_THAT_ERROR(parseCFI(TestFDE, CFIBytes), Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
std::vector<uint8_t> ExprBytes = {dwarf::DW_OP_reg12};
DataExtractor ExprData(ExprBytes, true, AddrSize);
DWARFExpression Expr(ExprData, AddrSize);
VerifyLocs.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsDWARFExpression(Expr));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 1u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
}
TEST(DWARFDebugFrame, UnwindTable_DW_CFA_def_cfa) {
// Test that DW_CFA_def_cfa, DW_CFA_def_cfa_sf, DW_CFA_def_cfa_register,
// DW_CFA_def_cfa_offset, and DW_CFA_def_cfa_offset_sf works as expected when
// parsed in the state machine.
dwarf::CIE TestCIE = createCIE(/*IsDWARF64=*/false,
/*Offset=*/0x0,
/*Length=*/0xff);
dwarf::FDE TestFDE(/*IsDWARF64=*/true,
/*Offset=*/0x3333abcdabcd,
/*Length=*/0x4444abcdabcd,
/*CIEPointer=*/0x1111abcdabcd,
/*InitialLocation=*/0x1000,
/*AddressRange=*/0x1000,
/*Cie=*/&TestCIE,
/*LSDAAddress=*/None,
/*Arch=*/Triple::x86_64);
// Make a CIE that has a valid CFA definition and a single register unwind
// rule for register that we will verify is in all of the pushed rows.
constexpr uint8_t CFAReg1 = 12;
constexpr uint8_t CFAOff1 = 32;
constexpr uint8_t CFAReg2 = 13;
constexpr uint8_t CFAOff2 = 48;
constexpr uint8_t Reg = 13;
constexpr uint8_t InReg = 14;
EXPECT_THAT_ERROR(parseCFI(TestCIE, {dwarf::DW_CFA_def_cfa, CFAReg1, CFAOff1,
dwarf::DW_CFA_register, Reg, InReg}),
Succeeded());
// Make a FDE with DWARF call frame instruction opcodes that use all of the
// DW_CFA_def_cfa* opcodes. This will verify that all opcodes that should
// create a row are correctly working.
EXPECT_THAT_ERROR(
parseCFI(
TestFDE,
{
dwarf::DW_CFA_advance_loc | 4, dwarf::DW_CFA_def_cfa_register,
CFAReg2, dwarf::DW_CFA_advance_loc | 4,
dwarf::DW_CFA_def_cfa_offset, CFAOff2,
dwarf::DW_CFA_advance_loc | 4, dwarf::DW_CFA_def_cfa_offset_sf,
0x7c, // -4 SLEB to make offset = 32 (CFAOff1)
dwarf::DW_CFA_advance_loc | 4, dwarf::DW_CFA_def_cfa_sf, CFAReg1,
0x7a, // -6 SLEB to make CFA offset 48 (CFAOff2)
}),
Succeeded());
// Create locations that we expect the UnwindRow objects to contain after
// parsing the DWARF call frame instructions.
dwarf::RegisterLocations VerifyLocs;
VerifyLocs.setRegisterLocation(
Reg, dwarf::UnwindLocation::createIsRegisterPlusOffset(InReg, 0));
// Verify we catch state machine error.
Expected<dwarf::UnwindTable> RowsOrErr = dwarf::UnwindTable::create(&TestFDE);
EXPECT_THAT_ERROR(RowsOrErr.takeError(), Succeeded());
const dwarf::UnwindTable &Rows = RowsOrErr.get();
EXPECT_EQ(Rows.size(), 5u);
EXPECT_EQ(Rows[0].getAddress(), 0x1000u);
EXPECT_EQ(
Rows[0].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg1, CFAOff1));
EXPECT_EQ(Rows[0].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[0].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[1].getAddress(), 0x1004u);
EXPECT_EQ(
Rows[1].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg2, CFAOff1));
EXPECT_EQ(Rows[1].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[1].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[2].getAddress(), 0x1008u);
EXPECT_EQ(
Rows[2].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg2, CFAOff2));
EXPECT_EQ(Rows[2].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[2].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[3].getAddress(), 0x100cu);
EXPECT_EQ(
Rows[3].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg2, CFAOff1));
EXPECT_EQ(Rows[3].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[3].getRegisterLocations(), VerifyLocs);
EXPECT_EQ(Rows[4].getAddress(), 0x1010u);
EXPECT_EQ(
Rows[4].getCFAValue(),
dwarf::UnwindLocation::createIsRegisterPlusOffset(CFAReg1, CFAOff2));
EXPECT_EQ(Rows[4].getRegisterLocations().size(), 1u);
EXPECT_EQ(Rows[4].getRegisterLocations(), VerifyLocs);
}
} // end anonymous namespace