mirror of
https://github.com/intel/llvm.git
synced 2026-01-16 05:32:28 +08:00
During InsertNegateRAState pass we check the annotations on instructions, to decide where to generate the OpNegateRAState CFIs in the output binary. As only instructions in the input binary were annotated, we have to make a judgement on instructions generated by other BOLT passes. Incorrect placement may cause issues when an (async) unwind request is received during the new "unknown" instructions. This patch adds more logic to make a more informed decision on by taking into account: - unknown instructions in a BasicBlock with other instruction have the same RAState. Previously, if the BasicBlock started with an unknown instruction, the RAState was copied from the preceding block. Now, the RAState is copied from the succeeding instructions in the same block. - Some BasicBlocks may only contain instructions with unknown RAState, As explained in issue #160989, these blocks already have incorrect unwind info. Because of this, the last known RAState based on the layout order is copied. Updated bolt/docs/PacRetDesign.md to reflect changes.
334 lines
11 KiB
C++
334 lines
11 KiB
C++
//===- bolt/unittest/Passes/InsertNegateRAState.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/InsertNegateRAStatePass.h"
|
|
#include "bolt/Rewrite/BinaryPassManager.h"
|
|
#include "bolt/Rewrite/RewriteInstance.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 {
|
|
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<InsertNegateRAState>());
|
|
|
|
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
|