diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h index 794e859bfe72c..1bb4a311be189 100644 --- a/llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h +++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFExpression.h @@ -159,6 +159,8 @@ class DWARFExpression { bool operator==(const DWARFExpression &RHS) const; + StringRef getData() const { return Data.getData(); } + private: DataExtractor Data; uint8_t AddressSize; diff --git a/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt b/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt index 29cdec9064f0b..9fa24ee4cb9c9 100644 --- a/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt +++ b/llvm/unittests/DebugInfo/DWARF/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_unittest(DebugInfoDWARFTests DWARFDebugLineTest.cpp DWARFDieTest.cpp DWARFDieManualExtractTest.cpp + DWARFExpressionCopyBytesTest.cpp DWARFExpressionCompactPrinterTest.cpp DWARFFormValueTest.cpp DWARFListTableTest.cpp diff --git a/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCopyBytesTest.cpp b/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCopyBytesTest.cpp new file mode 100644 index 0000000000000..ead650bced854 --- /dev/null +++ b/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCopyBytesTest.cpp @@ -0,0 +1,241 @@ +//===- llvm/unittest/DebugInfo/DWARFExpressionRawDataTest.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/ArrayRef.h" +#include "llvm/ADT/Triple.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/DebugInfo/DWARF/DWARFDebugFrame.h" +#include "llvm/DebugInfo/DWARF/DWARFDie.h" +#include "llvm/DebugInfo/DWARF/DWARFExpression.h" +#include "llvm/MC/MCAsmBackend.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCCodeEmitter.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectWriter.h" +#include "llvm/MC/MCStreamer.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/Object/Binary.h" +#include "llvm/Support/DataExtractor.h" +#include "llvm/Support/LEB128.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace dwarf; + +namespace { + +/// Tests that a client of DebugInfo/DWARF is able to read raw data bytes of a +/// DWARFExpression parsed from CFI with the intent of writing them back as is +/// via MC layer / cfi_escape. +/// This is relevant for binary tools that need to rewrite/copy unwind and +/// debug info from input to output binary. +class DWARFExpressionCopyBytesTest : public ::testing::Test { +public: + const char *TripleName = "x86_64-pc-linux"; + std::unique_ptr MRI; + std::unique_ptr MAI; + std::unique_ptr STI; + const Target *TheTarget; + + DWARFExpressionCopyBytesTest() { + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmPrinters(); + + std::string ErrorStr; + TheTarget = TargetRegistry::lookupTarget(TripleName, ErrorStr); + if (!TheTarget) + return; + + MRI.reset(TheTarget->createMCRegInfo(TripleName)); + MAI.reset(TheTarget->createMCAsmInfo(*MRI, TripleName, MCTargetOptions())); + STI.reset(TheTarget->createMCSubtargetInfo(TripleName, "", "")); + } + + struct StreamerContext { + std::unique_ptr MOFI; + std::unique_ptr Ctx; + std::unique_ptr MII; + std::unique_ptr Streamer; + }; + + /// Create all data structures necessary to operate an assembler + StreamerContext createStreamer(raw_pwrite_stream &OS); + /// Emit a dummy obj file with a single CFI instruction, + /// DW_CFA_def_cfa_expression, encoding as its operand the DWARF expression + /// represented by ExprBytes + SmallString<0> emitObjFile(StringRef ExprBytes); + /// Peruse the object file looking for the encoded DWARF expression, and check + /// that its operand was encoded correctly + void parseCFIsAndCheckExpression(const llvm::object::ObjectFile &E, + ArrayRef Expected); + /// Open the in-memory relocatable object file and verify that it contains + /// the expected DWARF expression bytes + void readAndCheckObjFile(StringRef ObjFileData, ArrayRef Expected); + /// Run this test on the DWARF expression represented by the bytes in + /// ExprData. Check that the getData() API retrieves these original bytes and + /// that we can use them to encode a CFI with those bytes as operands (via + /// cfi_escape). + void testExpr(ArrayRef ExprData); +}; + +} // namespace + +DWARFExpressionCopyBytesTest::StreamerContext +DWARFExpressionCopyBytesTest::createStreamer(raw_pwrite_stream &OS) { + StreamerContext Res; + Res.Ctx = + std::make_unique(Triple(TripleName), MAI.get(), MRI.get(), + /*MSTI=*/nullptr); + Res.MOFI.reset(TheTarget->createMCObjectFileInfo(*Res.Ctx.get(), + /*PIC=*/false)); + Res.Ctx->setObjectFileInfo(Res.MOFI.get()); + + Res.MII.reset(TheTarget->createMCInstrInfo()); + MCCodeEmitter *MCE = TheTarget->createMCCodeEmitter(*Res.MII, *MRI, *Res.Ctx); + MCAsmBackend *MAB = + TheTarget->createMCAsmBackend(*STI, *MRI, MCTargetOptions()); + std::unique_ptr OW = MAB->createObjectWriter(OS); + Res.Streamer.reset(TheTarget->createMCObjectStreamer( + Triple(TripleName), *Res.Ctx, std::unique_ptr(MAB), + std::move(OW), std::unique_ptr(MCE), *STI, + /* RelaxAll */ false, + /* IncrementalLinkerCompatible */ false, + /* DWARFMustBeAtTheEnd */ false)); + return Res; +} + +SmallString<0> DWARFExpressionCopyBytesTest::emitObjFile(StringRef ExprBytes) { + auto EncodeDefCfaExpr = [&](StringRef Bytes) { + std::string Str; + raw_string_ostream OS(Str); + OS << static_cast(dwarf::DW_CFA_def_cfa_expression); + encodeULEB128(Bytes.size(), OS); + OS << Bytes; + return Str; + }; + + SmallString<0> Storage; + raw_svector_ostream VecOS(Storage); + StreamerContext C = createStreamer(VecOS); + C.Streamer->InitSections(false); + MCSection *Section = C.MOFI->getTextSection(); + Section->setHasInstructions(true); + C.Streamer->SwitchSection(Section); + C.Streamer->emitCFIStartProc(true); + auto Str = EncodeDefCfaExpr(ExprBytes); + C.Streamer->emitCFIEscape(Str); + C.Streamer->emitNops(4, 1, SMLoc()); + C.Streamer->emitCFIEndProc(); + C.Streamer->Finish(); + return Storage; +} + +void DWARFExpressionCopyBytesTest::parseCFIsAndCheckExpression( + const llvm::object::ObjectFile &E, ArrayRef Expected) { + auto FetchFirstCfaExpression = + [](const DWARFDebugFrame &EHFrame) -> Optional { + for (const dwarf::FrameEntry &Entry : EHFrame.entries()) { + const auto *CurFDE = dyn_cast(&Entry); + if (!CurFDE) + continue; + for (const CFIProgram::Instruction &Instr : CurFDE->cfis()) { + if (Instr.Opcode != dwarf::DW_CFA_def_cfa_expression) + continue; + return Instr; + } + } + return NoneType(); + }; + + std::unique_ptr Ctx = DWARFContext::create(E); + const DWARFDebugFrame *EHFrame = cantFail(Ctx->getEHFrame()); + ASSERT_NE(nullptr, EHFrame); + auto CfiInstr = FetchFirstCfaExpression(*EHFrame); + ASSERT_TRUE(CfiInstr); + DWARFExpression Expr = *(CfiInstr->Expression); + StringRef ExprData = Expr.getData(); + EXPECT_EQ(ExprData.size(), Expected.size()); + for (unsigned I = 0, E = ExprData.size(); I != E; ++I) { + EXPECT_EQ(static_cast(ExprData[I]), Expected[I]); + } +} + +void DWARFExpressionCopyBytesTest::readAndCheckObjFile( + StringRef ObjFileData, ArrayRef Expected) { + std::unique_ptr MB = + MemoryBuffer::getMemBuffer(ObjFileData, "", false); + std::unique_ptr Bin = + cantFail(llvm::object::createBinary(MB->getMemBufferRef())); + if (auto *E = dyn_cast(&*Bin)) { + parseCFIsAndCheckExpression(*E, Expected); + } +} + +void DWARFExpressionCopyBytesTest::testExpr(ArrayRef ExprData) { + // If we didn't build x86, do not run the test. + if (!MRI) + return; + + DataExtractor DE(ExprData, true, 8); + DWARFExpression Expr(DE, 8); + + // Copy this expression into the CFI of a binary and check that we are able to + // get it back correctly from this binary. + const SmallString<0> EmittedBinContents = emitObjFile(Expr.getData()); + readAndCheckObjFile(EmittedBinContents.str(), ExprData); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_reg0) { testExpr({DW_OP_reg0}); } + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_reg10) { testExpr({DW_OP_reg10}); } + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_regx) { + testExpr({DW_OP_regx, 0x80, 0x02}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_breg0) { + testExpr({DW_OP_breg0, 0x04}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_breg0_large_offset) { + testExpr({DW_OP_breg0, 0x80, 0x02}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_breg13) { + testExpr({DW_OP_breg13, 0x10}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_breg13_zero_offset) { + testExpr({DW_OP_breg13, 0x00}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_breg0_negative) { + testExpr({DW_OP_breg0, 0x70}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_bregx) { + testExpr({DW_OP_bregx, 0x0d, 0x28}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_stack_value) { + testExpr({DW_OP_breg13, 0x04, DW_OP_stack_value}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_entry_value) { + testExpr({DW_OP_entry_value, 0x01, DW_OP_reg0, DW_OP_stack_value}); +} + +TEST_F(DWARFExpressionCopyBytesTest, Test_OP_entry_value_mem) { + testExpr({DW_OP_entry_value, 0x02, DW_OP_breg13, 0x10, DW_OP_stack_value}); +}