diff options
Diffstat (limited to 'bolt/unittests')
| -rw-r--r-- | bolt/unittests/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | bolt/unittests/Core/MCPlusBuilder.cpp | 55 | ||||
| -rw-r--r-- | bolt/unittests/Passes/CMakeLists.txt | 30 | ||||
| -rw-r--r-- | bolt/unittests/Passes/PointerAuthCFIFixup.cpp | 339 | ||||
| -rw-r--r-- | bolt/unittests/Profile/PerfSpeEvents.cpp | 88 |
5 files changed, 513 insertions, 0 deletions
diff --git a/bolt/unittests/CMakeLists.txt b/bolt/unittests/CMakeLists.txt index 64414b8..d47ddc4 100644 --- a/bolt/unittests/CMakeLists.txt +++ b/bolt/unittests/CMakeLists.txt @@ -7,3 +7,4 @@ endfunction() add_subdirectory(Core) add_subdirectory(Profile) +add_subdirectory(Passes) diff --git a/bolt/unittests/Core/MCPlusBuilder.cpp b/bolt/unittests/Core/MCPlusBuilder.cpp index bc37ced..7b6f162 100644 --- a/bolt/unittests/Core/MCPlusBuilder.cpp +++ b/bolt/unittests/Core/MCPlusBuilder.cpp @@ -143,6 +143,61 @@ TEST_P(MCPlusBuilderTester, AArch64_CmpJE) { ASSERT_EQ(Label, BB->getLabel()); } +TEST_P(MCPlusBuilderTester, AArch64_BTI) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock(); + + MCInst BTIjc; + BC->MIB->createBTI(BTIjc, true, true); + BB->addInstruction(BTIjc); + auto II = BB->begin(); + ASSERT_EQ(II->getOpcode(), AArch64::HINT); + ASSERT_EQ(II->getOperand(0).getImm(), 38); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); + BC->MIB->updateBTIVariant(*II, true, false); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + + MCInst BTIj; + BC->MIB->createBTI(BTIj, false, true); + II = BB->addInstruction(BTIj); + ASSERT_EQ(II->getOpcode(), AArch64::HINT); + ASSERT_EQ(II->getOperand(0).getImm(), 36); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true)); + BC->MIB->updateBTIVariant(*II, true, true); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); + + MCInst BTIc; + BC->MIB->createBTI(BTIc, true, false); + II = BB->addInstruction(BTIc); + ASSERT_EQ(II->getOpcode(), AArch64::HINT); + ASSERT_EQ(II->getOperand(0).getImm(), 34); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + BC->MIB->updateBTIVariant(*II, false, true); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true)); + +#ifndef NDEBUG + MCInst BTIinvalid; + ASSERT_DEATH(BC->MIB->createBTI(BTIinvalid, false, false), + "No target kinds!"); +#endif + + MCInst Paciasp = MCInstBuilder(AArch64::PACIASP); + II = BB->addInstruction(Paciasp); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, true, true)); + ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, false, true)); + ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II)); + + MCInst Pacibsp = MCInstBuilder(AArch64::PACIBSP); + II = BB->addInstruction(Pacibsp); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, true, true)); + ASSERT_FALSE(BC->MIB->isBTILandingPad(*II, false, true)); + ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II)); +} + TEST_P(MCPlusBuilderTester, AArch64_CmpJNE) { if (GetParam() != Triple::aarch64) GTEST_SKIP(); diff --git a/bolt/unittests/Passes/CMakeLists.txt b/bolt/unittests/Passes/CMakeLists.txt new file mode 100644 index 0000000..17ae880 --- /dev/null +++ b/bolt/unittests/Passes/CMakeLists.txt @@ -0,0 +1,30 @@ +set(LLVM_LINK_COMPONENTS + DebugInfoDWARF + Object + MC + ${BOLT_TARGETS_TO_BUILD} + ) + +add_bolt_unittest(PassTests + PointerAuthCFIFixup.cpp + + DISABLE_LLVM_LINK_LLVM_DYLIB + ) + +target_link_libraries(PassTests + PRIVATE + LLVMBOLTCore + LLVMBOLTRewrite + LLVMBOLTPasses + LLVMBOLTProfile + LLVMBOLTUtils + ) + +foreach (tgt ${BOLT_TARGETS_TO_BUILD}) + include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/${tgt} + ${LLVM_BINARY_DIR}/lib/Target/${tgt} + ) + string(TOUPPER "${tgt}" upper) + target_compile_definitions(PassTests PRIVATE "${upper}_AVAILABLE") +endforeach() diff --git a/bolt/unittests/Passes/PointerAuthCFIFixup.cpp b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp new file mode 100644 index 0000000..0d54b31 --- /dev/null +++ b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp @@ -0,0 +1,339 @@ +//===- bolt/unittest/Passes/PointerAuthCFIFixup.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 +// +//===----------------------------------------------------------------------===// + +#ifdef AARCH64_AVAILABLE +#include "AArch64Subtarget.h" +#include "MCTargetDesc/AArch64MCTargetDesc.h" +#endif // AARCH64_AVAILABLE + +#include "bolt/Core/BinaryBasicBlock.h" +#include "bolt/Core/BinaryFunction.h" +#include "bolt/Passes/PointerAuthCFIFixup.h" +#include "bolt/Rewrite/BinaryPassManager.h" +#include "bolt/Rewrite/RewriteInstance.h" +#include "bolt/Utils/CommandLineOpts.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/MC/MCDwarf.h" +#include "llvm/MC/MCInstBuilder.h" +#include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::ELF; +using namespace bolt; + +namespace opts { +extern cl::opt<bool> PrintPAuthCFIAnalyzer; +} // namespace opts + +namespace { +struct PassTester : public testing::TestWithParam<Triple::ArchType> { + void SetUp() override { + initalizeLLVM(); + prepareElf(); + initializeBolt(); + } + +protected: + void initalizeLLVM() { +#define BOLT_TARGET(target) \ + LLVMInitialize##target##TargetInfo(); \ + LLVMInitialize##target##TargetMC(); \ + LLVMInitialize##target##AsmParser(); \ + LLVMInitialize##target##Disassembler(); \ + LLVMInitialize##target##Target(); \ + LLVMInitialize##target##AsmPrinter(); + +#include "bolt/Core/TargetConfig.def" + } + +#define PREPARE_FUNC(name) \ + constexpr uint64_t FunctionAddress = 0x1000; \ + BinaryFunction *BF = BC->createBinaryFunction( \ + name, *TextSection, FunctionAddress, /*Size=*/0, /*SymbolSize=*/0, \ + /*Alignment=*/16); \ + /* Make sure the pass runs on the BF.*/ \ + BF->updateState(BinaryFunction::State::CFG); \ + BF->setContainedNegateRAState(); \ + /* All tests need at least one BB. */ \ + BinaryBasicBlock *BB = BF->addBasicBlock(); \ + BF->addEntryPoint(*BB); \ + BB->setCFIState(0); + + void prepareElf() { + memcpy(ElfBuf, "\177ELF", 4); + ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf); + EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; + EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; + EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64; + MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF"); + ObjFile = cantFail(ObjectFile::createObjectFile(Source)); + } + void initializeBolt() { + Relocation::Arch = ObjFile->makeTriple().getArch(); + BC = cantFail(BinaryContext::createBinaryContext( + ObjFile->makeTriple(), std::make_shared<orc::SymbolStringPool>(), + ObjFile->getFileName(), nullptr, true, DWARFContext::create(*ObjFile), + {llvm::outs(), llvm::errs()})); + ASSERT_FALSE(!BC); + BC->initializeTarget(std::unique_ptr<MCPlusBuilder>( + createMCPlusBuilder(GetParam(), BC->MIA.get(), BC->MII.get(), + BC->MRI.get(), BC->STI.get()))); + + PassManager = std::make_unique<BinaryFunctionPassManager>(*BC); + PassManager->registerPass( + std::make_unique<PointerAuthCFIFixup>(opts::PrintPAuthCFIAnalyzer)); + + TextSection = &BC->registerOrUpdateSection( + ".text", ELF::SHT_PROGBITS, ELF::SHF_ALLOC | ELF::SHF_EXECINSTR, + /*Data=*/nullptr, /*Size=*/0, + /*Alignment=*/16); + } + + std::vector<int> findCFIOffsets(BinaryFunction &BF) { + std::vector<int> Locations; + int Idx = 0; + int InstSize = 4; // AArch64 + for (BinaryBasicBlock &BB : BF) { + for (MCInst &Inst : BB) { + if (BC->MIB->isCFI(Inst)) { + const MCCFIInstruction *CFI = BF.getCFIFor(Inst); + if (CFI->getOperation() == MCCFIInstruction::OpNegateRAState) + Locations.push_back(Idx * InstSize); + } + Idx++; + } + } + return Locations; + } + + char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; + std::unique_ptr<ObjectFile> ObjFile; + std::unique_ptr<BinaryContext> BC; + std::unique_ptr<BinaryFunctionPassManager> PassManager; + BinarySection *TextSection; +}; +} // namespace + +TEST_P(PassTester, ExampleTest) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("ExampleFunction"); + + MCInst UnsignedInst = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(0) + .addImm(0); + BC->MIB->setRAState(UnsignedInst, false); + BB->addInstruction(UnsignedInst); + + MCInst SignedInst = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(1) + .addImm(0); + BC->MIB->setRAState(SignedInst, true); + BB->addInstruction(SignedInst); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + /* Expected layout of BF after the pass: + + .LBB0 (3 instructions, align : 1) + Entry Point + CFI State : 0 + 00000000: adds x0, x0, #0x0 + 00000004: !CFI $0 ; OpNegateRAState + 00000004: adds x0, x0, #0x1 + CFI State: 0 + */ + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 1u); + EXPECT_EQ(CFILoc[0], 4); +} + +TEST_P(PassTester, fillUnknownStateInBBTest) { + /* Check that a if BB starts with unknown RAState, we can fill the unknown + states based on following instructions with known RAStates. + * + * .LBB0 (1 instructions, align : 1) + Entry Point + CFI State : 0 + 00000000: adds x0, x0, #0x0 + CFI State: 0 + + .LBB1 (4 instructions, align : 1) + CFI State : 0 + 00000004: !CFI $0 ; OpNegateRAState + 00000004: adds x0, x0, #0x1 + 00000008: adds x0, x0, #0x2 + 0000000c: adds x0, x0, #0x3 + CFI State: 0 + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithUnknownStateInBB"); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + + MCInst Unsigned = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(0) + .addImm(0); + BC->MIB->setRAState(Unsigned, false); + BB->addInstruction(Unsigned); + + MCInst Unknown = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(1) + .addImm(0); + MCInst Unknown1 = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(2) + .addImm(0); + MCInst Signed = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(3) + .addImm(0); + BC->MIB->setRAState(Signed, true); + BB2->addInstruction(Unknown); + BB2->addInstruction(Unknown1); + BB2->addInstruction(Signed); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 1u); + EXPECT_EQ(CFILoc[0], 4); + // Check that the pass set Unknown and Unknown1 to signed. + // begin() is the CFI, begin() + 1 is Unknown, begin() + 2 is Unknown1. + std::optional<bool> RAState = BC->MIB->getRAState(*(BB2->begin() + 1)); + EXPECT_TRUE(RAState.has_value()); + EXPECT_TRUE(*RAState); + std::optional<bool> RAState1 = BC->MIB->getRAState(*(BB2->begin() + 2)); + EXPECT_TRUE(RAState1.has_value()); + EXPECT_TRUE(*RAState1); +} + +TEST_P(PassTester, fillUnknownStubs) { + /* + * Stubs that are not part of the function's CFG should inherit the RAState of + the BasicBlock before it. + * + * LBB1 is not part of the CFG: LBB0 jumps unconditionally to LBB2. + * LBB1 would be a stub inserted in LongJmp in real code. + * We do not add any NegateRAState CFIs, as other CFIs are not added either. + * See issue #160989 for more details. + * + * .LBB0 (1 instructions, align : 1) + Entry Point + 00000000: b .LBB2 + Successors: .LBB2 + + .LBB1 (1 instructions, align : 1) + 00000004: ret + + .LBB2 (1 instructions, align : 1) + Predecessors: .LBB0 + 00000008: ret + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithStub"); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + BinaryBasicBlock *BB3 = BF->addBasicBlock(); + BB3->setCFIState(0); + + BB->addSuccessor(BB3); + + // Jumping over BB2, to BB3. + MCInst Jump; + BC->MIB->createUncondBranch(Jump, BB3->getLabel(), BC->Ctx.get()); + BB->addInstruction(Jump); + BC->MIB->setRAState(Jump, false); + + // BB2, in real code it would be a ShortJmp. + // Unknown RAState. + MCInst StubInst; + BC->MIB->createReturn(StubInst); + BB2->addInstruction(StubInst); + + // Can be any instruction. + MCInst Ret; + BC->MIB->createReturn(Ret); + BB3->addInstruction(Ret); + BC->MIB->setRAState(Ret, false); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + // Check that we did not generate any NegateRAState CFIs. + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 0u); +} + +TEST_P(PassTester, fillUnknownStubsEmpty) { + /* + * This test checks that BOLT can set the RAState of unknown BBs, + * even if all previous BBs are empty, hence no PrevInst gets set. + * + * As this means that the current (empty) BB is the first with non-pseudo + * instructions, the function's initialRAState should be used. + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithStub"); + BF->setInitialRAState(false); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + + // BB is empty. + BB->addSuccessor(BB2); + + // BB2, in real code it would be a ShortJmp. + // Unknown RAState. + MCInst StubInst; + BC->MIB->createReturn(StubInst); + BB2->addInstruction(StubInst); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + // Check that BOLT added an RAState to BB2. + std::optional<bool> RAState = BC->MIB->getRAState(*(BB2->begin())); + EXPECT_TRUE(RAState.has_value()); + // BB2 should be set to BF.initialRAState (false). + EXPECT_FALSE(*RAState); +} + +#ifdef AARCH64_AVAILABLE +INSTANTIATE_TEST_SUITE_P(AArch64, PassTester, + ::testing::Values(Triple::aarch64)); +#endif diff --git a/bolt/unittests/Profile/PerfSpeEvents.cpp b/bolt/unittests/Profile/PerfSpeEvents.cpp index 8d023cd..4f060cd 100644 --- a/bolt/unittests/Profile/PerfSpeEvents.cpp +++ b/bolt/unittests/Profile/PerfSpeEvents.cpp @@ -161,4 +161,92 @@ TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstack) { parseAndCheckBrstackEvents(1234, ExpectedSamples); } +TEST_F(PerfSpeEventsTestHelper, SpeBranchesWithBrstackAndPbt) { + // Check perf input with SPE branch events as brstack format by + // combining with the previous branch target address (named as PBT). + // Example collection command: + // ``` + // perf record -e 'arm_spe_0/branch_filter=1/u' -- BINARY + // ``` + // How Bolt extracts the branch events: + // ``` + // perf script -F pid,brstack --itrace=bl + // ``` + + opts::ArmSPE = true; + opts::ReadPerfEvents = + // "<PID> <SRC>/<DEST>/PN/-/-/10/COND/- <NULL>/<PBT>/-/-/-/0//-\n" + " 4567 0xa002/0xa003/PN/-/-/10/COND/- 0x0/0xa001/-/-/-/0//-\n" + " 4567 0xb002/0xb003/P/-/-/4/RET/- 0x0/0xb001/-/-/-/0//-\n" + " 4567 0xc456/0xc789/P/-/-/13/-/- 0x0/0xc123/-/-/-/0//-\n" + " 4567 0xd456/0xd789/M/-/-/7/RET/- 0x0/0xd123/-/-/-/0//-\n" + " 4567 0xe005/0xe009/P/-/-/14/RET/- 0x0/0xe001/-/-/-/0//-\n" + " 4567 0xd456/0xd789/M/-/-/7/RET/- 0x0/0xd123/-/-/-/0//-\n" + " 4567 0xf002/0xf003/MN/-/-/8/COND/- 0x0/0xf001/-/-/-/0//-\n" + " 4567 0xc456/0xc789/P/-/-/13/-/- 0x0/0xc123/-/-/-/0//-\n"; + + // ExpectedSamples contains the aggregated information about + // a branch {{From, To, TraceTo}, {TakenCount, MispredCount}}. + // Where + // - From: is the source address of the sampled branch operation. + // - To: is the target address of the sampled branch operation. + // - TraceTo could be either + // - A 'Type = Trace::BR_ONLY', which means the trace only contains branch + // data. + // - Or an address, when the trace contains information about the previous + // branch. + // + // When FEAT_SPE_PBT is present, Arm SPE emits two records per sample: + // - the current branch (Spe.From/Spe.To), and + // - the previous taken branch target (PBT) (PBT.From, PBT.To). + // + // Together they behave like a depth-1 branch stack where: + // - the PBT entry is always taken + // - the current branch entry may represent a taken branch or a fall-through + // - the destination (Spe.To) is the architecturally executed target + // + // There can be fall-throughs to be inferred between the PBT entry and + // the current branch (Spe.From), but there cannot be between current + // branch's (Spe.From/Spe.To). + // + // PBT records only the target address (PBT.To), meaning we have no + // information as the branch source (PBT.From=0x0), branch type, and the + // prediction bit. + // + // Consider the trace pair: + // {{Spe.From, Spe.To, Type}, {TK, MP}}, + // {{PBT.From, PBT.To, TraceTo}, {TK, MP}} + // {{0xd456, 0xd789, Trace::BR_ONLY}, {2, 2}}, {{0x0, 0xd123, 0xd456}, {2, 0}} + // + // The first entry is the Spe record, which represents a trace from 0xd456 + // (Spe.From) to 0xd789 (Spe.To). Type = Trace::BR_ONLY, as Bolt processes the + // current branch event first. At this point we have no information about the + // previous trace (PBT). This entry has a TakenCount = 2, as we have two + // samples for (0xd456, 0xd789) in our input. It also has MispredsCount = 2, + // as 'M' misprediction flag appears in both cases. + // + // The second entry is the PBT record. TakenCount = 2 because the + // (PBT.From = 0x0, PBT.To = 0xd123) branch target appears twice in the input, + // and MispredsCount = 0 because prediction data is absent. There is no branch + // source information, so the PBT.From field is zero (0x0). TraceTo = 0xd456 + // connect the flow from the previous taken branch at 0xd123 (PBT.To) to the + // current source branch at 0xd456 (Spe.From), which then continues to 0xd789 + // (Spe.To). + std::vector<std::pair<Trace, TakenBranchInfo>> ExpectedSamples = { + {{0xa002, 0xa003, Trace::BR_ONLY}, {1, 0}}, + {{0x0, 0xa001, 0xa002}, {1, 0}}, + {{0xb002, 0xb003, Trace::BR_ONLY}, {1, 0}}, + {{0x0, 0xb001, 0xb002}, {1, 0}}, + {{0xc456, 0xc789, Trace::BR_ONLY}, {2, 0}}, + {{0x0, 0xc123, 0xc456}, {2, 0}}, + {{0xd456, 0xd789, Trace::BR_ONLY}, {2, 2}}, + {{0x0, 0xd123, 0xd456}, {2, 0}}, + {{0xe005, 0xe009, Trace::BR_ONLY}, {1, 0}}, + {{0x0, 0xe001, 0xe005}, {1, 0}}, + {{0xf002, 0xf003, Trace::BR_ONLY}, {1, 1}}, + {{0x0, 0xf001, 0xf002}, {1, 0}}}; + + parseAndCheckBrstackEvents(4567, ExpectedSamples); +} + #endif |
