mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
[BOLT] Improve DWARF CFI generation for pac-ret binaries (#163381)
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.
This commit is contained in:
@@ -200,15 +200,22 @@ This pass runs after optimizations. It performns the _inverse_ of MarkRAState pa
|
||||
Some BOLT passes can add new Instructions. In InsertNegateRAStatePass, we have
|
||||
to know what RA state these have.
|
||||
|
||||
The current solution has the `inferUnknownStates` function to cover these, using
|
||||
a fairly simple strategy: unknown states inherit the last known state.
|
||||
|
||||
This will be updated to a more robust solution.
|
||||
|
||||
> [!important]
|
||||
> As issue #160989 describes, unwind info is incorrect in stubs with multiple callers.
|
||||
> For this same reason, we cannot generate correct pac-specific unwind info: the signess
|
||||
> of the _incorrect_ return address is meaningless.
|
||||
> As issue #160989 explains, unwind info is missing from stubs.
|
||||
> For this same reason, we cannot generate correct pac-specific unwind info: the
|
||||
> signedness of the _incorrect_ return address is meaningless.
|
||||
|
||||
Assignment of RAStates to newly generated instructions is done in `inferUnknownStates`.
|
||||
We have two different cases to cover:
|
||||
|
||||
1. If a BasicBlock has some instructions with known RA state, and some without, we
|
||||
can copy the RAState of known instructions to the unknown ones. As the control
|
||||
flow only changes between BasicBlocks, instructions in the same BasicBlock have
|
||||
the same return address. (The exception is noreturn calls, but these would only
|
||||
cause problems, if the newly inserted instruction is right after the call.)
|
||||
|
||||
2. If a BasicBlock has no instructions with known RAState, we have to copy the
|
||||
RAState of the previous BasicBlock in layout order.
|
||||
|
||||
### Optimizations requiring special attention
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//===- bolt/Passes/InsertNegateRAStatePass.cpp ----------------------------===//
|
||||
//===- bolt/Passes/InsertNegateRAStatePass.h ------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
@@ -30,9 +30,30 @@ public:
|
||||
private:
|
||||
/// Because states are tracked as MCAnnotations on individual instructions,
|
||||
/// newly inserted instructions do not have a state associated with them.
|
||||
/// New states are "inherited" from the last known state.
|
||||
/// Uses fillUnknownStateInBB and fillUnknownStubs.
|
||||
void inferUnknownStates(BinaryFunction &BF);
|
||||
|
||||
/// Simple case: copy RAStates to unknown insts from previous inst.
|
||||
/// If the first inst has unknown state, copy set it to the first known state.
|
||||
/// Accounts for signing and authenticating insts.
|
||||
void fillUnknownStateInBB(BinaryContext &BC, BinaryBasicBlock &BB);
|
||||
|
||||
/// Fill in RAState in BasicBlocks consisting entirely of new instructions.
|
||||
/// As of #160989, we have to copy the RAState from the previous BB in the
|
||||
/// layout, because CFIs are already incorrect here.
|
||||
void fillUnknownStubs(BinaryFunction &BF);
|
||||
|
||||
/// Returns the first known RAState from \p BB, or std::nullopt if all are
|
||||
/// unknown.
|
||||
std::optional<bool> getFirstKnownRAState(BinaryContext &BC,
|
||||
BinaryBasicBlock &BB);
|
||||
|
||||
/// \p Return true if all instructions have unknown RAState.
|
||||
bool isUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB);
|
||||
|
||||
/// Set all instructions in \p BB to \p State.
|
||||
void markUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB, bool State);
|
||||
|
||||
/// Support for function splitting:
|
||||
/// if two consecutive BBs with Signed state are going to end up in different
|
||||
/// functions (so are held by different FunctionFragments), we have to add a
|
||||
|
||||
@@ -52,8 +52,8 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
|
||||
MCInst &Inst = *It;
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
auto RAState = BC.MIB->getRAState(Inst);
|
||||
if (!RAState) {
|
||||
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
|
||||
if (!RAState.has_value()) {
|
||||
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
|
||||
<< " in function " << BF.getPrintName() << "\n";
|
||||
PassFailed = true;
|
||||
@@ -74,6 +74,20 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
|
||||
}
|
||||
}
|
||||
|
||||
void InsertNegateRAState::inferUnknownStates(BinaryFunction &BF) {
|
||||
BinaryContext &BC = BF.getBinaryContext();
|
||||
|
||||
// Fill in missing RAStates in simple cases (inside BBs).
|
||||
for (BinaryBasicBlock &BB : BF) {
|
||||
fillUnknownStateInBB(BC, BB);
|
||||
}
|
||||
// BasicBlocks which are made entirely of "new instructions" (instructions
|
||||
// without RAState annotation) are stubs, and do not have correct unwind info.
|
||||
// We should iterate in layout order and fill them based on previous known
|
||||
// RAState.
|
||||
fillUnknownStubs(BF);
|
||||
}
|
||||
|
||||
void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF,
|
||||
FunctionFragment &FF) {
|
||||
BinaryContext &BC = BF.getBinaryContext();
|
||||
@@ -92,8 +106,8 @@ void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF,
|
||||
// If a function is already split in the input, the first FF can also start
|
||||
// with Signed state. This covers that scenario as well.
|
||||
auto II = (*FirstNonEmpty)->getFirstNonPseudo();
|
||||
auto RAState = BC.MIB->getRAState(*II);
|
||||
if (!RAState) {
|
||||
std::optional<bool> RAState = BC.MIB->getRAState(*II);
|
||||
if (!RAState.has_value()) {
|
||||
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
|
||||
<< " in function " << BF.getPrintName() << "\n";
|
||||
PassFailed = true;
|
||||
@@ -104,32 +118,119 @@ void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF,
|
||||
MCCFIInstruction::createNegateRAState(nullptr));
|
||||
}
|
||||
|
||||
void InsertNegateRAState::inferUnknownStates(BinaryFunction &BF) {
|
||||
std::optional<bool>
|
||||
InsertNegateRAState::getFirstKnownRAState(BinaryContext &BC,
|
||||
BinaryBasicBlock &BB) {
|
||||
for (const MCInst &Inst : BB) {
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
|
||||
if (RAState.has_value())
|
||||
return RAState;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool InsertNegateRAState::isUnknownBlock(BinaryContext &BC,
|
||||
BinaryBasicBlock &BB) {
|
||||
std::optional<bool> FirstRAState = getFirstKnownRAState(BC, BB);
|
||||
return !FirstRAState.has_value();
|
||||
}
|
||||
|
||||
void InsertNegateRAState::fillUnknownStateInBB(BinaryContext &BC,
|
||||
BinaryBasicBlock &BB) {
|
||||
|
||||
auto First = BB.getFirstNonPseudo();
|
||||
if (First == BB.end())
|
||||
return;
|
||||
// If the first instruction has unknown RAState, we should copy the first
|
||||
// known RAState.
|
||||
std::optional<bool> RAState = BC.MIB->getRAState(*First);
|
||||
if (!RAState.has_value()) {
|
||||
std::optional<bool> FirstRAState = getFirstKnownRAState(BC, BB);
|
||||
if (!FirstRAState.has_value())
|
||||
// We fill unknown BBs later.
|
||||
return;
|
||||
|
||||
BC.MIB->setRAState(*First, *FirstRAState);
|
||||
}
|
||||
|
||||
// At this point we know the RAState of the first instruction,
|
||||
// so we can propagate the RAStates to all subsequent unknown instructions.
|
||||
MCInst Prev = *First;
|
||||
for (auto It = First + 1; It != BB.end(); ++It) {
|
||||
MCInst &Inst = *It;
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
|
||||
// No need to check for nullopt: we only entered this loop after the first
|
||||
// instruction had its RAState set, and RAState is always set for the
|
||||
// previous instruction in the previous iteration of the loop.
|
||||
std::optional<bool> PrevRAState = BC.MIB->getRAState(Prev);
|
||||
|
||||
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
|
||||
if (!RAState.has_value()) {
|
||||
if (BC.MIB->isPSignOnLR(Prev))
|
||||
PrevRAState = true;
|
||||
else if (BC.MIB->isPAuthOnLR(Prev))
|
||||
PrevRAState = false;
|
||||
BC.MIB->setRAState(Inst, *PrevRAState);
|
||||
}
|
||||
Prev = Inst;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertNegateRAState::markUnknownBlock(BinaryContext &BC,
|
||||
BinaryBasicBlock &BB, bool State) {
|
||||
// If we call this when an Instruction has either kRASigned or kRAUnsigned
|
||||
// annotation, setRASigned or setRAUnsigned would fail.
|
||||
assert(isUnknownBlock(BC, BB) &&
|
||||
"markUnknownBlock should only be called on unknown blocks");
|
||||
for (MCInst &Inst : BB) {
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
BC.MIB->setRAState(Inst, State);
|
||||
}
|
||||
}
|
||||
|
||||
void InsertNegateRAState::fillUnknownStubs(BinaryFunction &BF) {
|
||||
BinaryContext &BC = BF.getBinaryContext();
|
||||
bool FirstIter = true;
|
||||
MCInst PrevInst;
|
||||
for (BinaryBasicBlock &BB : BF) {
|
||||
for (MCInst &Inst : BB) {
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
|
||||
auto RAState = BC.MIB->getRAState(Inst);
|
||||
if (!FirstIter && !RAState) {
|
||||
if (BC.MIB->isPSignOnLR(PrevInst))
|
||||
RAState = true;
|
||||
else if (BC.MIB->isPAuthOnLR(PrevInst))
|
||||
RAState = false;
|
||||
else {
|
||||
auto PrevRAState = BC.MIB->getRAState(PrevInst);
|
||||
RAState = PrevRAState ? *PrevRAState : false;
|
||||
}
|
||||
BC.MIB->setRAState(Inst, *RAState);
|
||||
} else {
|
||||
for (FunctionFragment &FF : BF.getLayout().fragments()) {
|
||||
for (BinaryBasicBlock *BB : FF) {
|
||||
if (FirstIter) {
|
||||
FirstIter = false;
|
||||
if (!RAState)
|
||||
BC.MIB->setRAState(Inst, BF.getInitialRAState());
|
||||
if (isUnknownBlock(BC, *BB))
|
||||
// If the first BasicBlock is unknown, the function's entry RAState
|
||||
// should be used.
|
||||
markUnknownBlock(BC, *BB, BF.getInitialRAState());
|
||||
} else if (isUnknownBlock(BC, *BB)) {
|
||||
// As explained in issue #160989, the unwind info is incorrect for
|
||||
// stubs. Indicating the correct RAState without the rest of the unwind
|
||||
// info being correct is not useful. Instead, we copy the RAState from
|
||||
// the previous instruction.
|
||||
std::optional<bool> PrevRAState = BC.MIB->getRAState(PrevInst);
|
||||
if (!PrevRAState.has_value()) {
|
||||
// No non-cfi instruction encountered in the function yet.
|
||||
// This means the RAState is the same as at the function entry.
|
||||
markUnknownBlock(BC, *BB, BF.getInitialRAState());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BC.MIB->isPSignOnLR(PrevInst))
|
||||
PrevRAState = true;
|
||||
else if (BC.MIB->isPAuthOnLR(PrevInst))
|
||||
PrevRAState = false;
|
||||
markUnknownBlock(BC, *BB, *PrevRAState);
|
||||
}
|
||||
PrevInst = Inst;
|
||||
// This function iterates on BasicBlocks, so the PrevInst has to be
|
||||
// updated to the last instruction of the current BasicBlock. If the
|
||||
// BasicBlock is empty, or only has PseudoInstructions, PrevInst will not
|
||||
// be updated.
|
||||
auto Last = BB->getLastNonPseudo();
|
||||
if (Last != BB->rend())
|
||||
PrevInst = *Last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@ endfunction()
|
||||
|
||||
add_subdirectory(Core)
|
||||
add_subdirectory(Profile)
|
||||
add_subdirectory(Passes)
|
||||
|
||||
30
bolt/unittests/Passes/CMakeLists.txt
Normal file
30
bolt/unittests/Passes/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
DebugInfoDWARF
|
||||
Object
|
||||
MC
|
||||
${BOLT_TARGETS_TO_BUILD}
|
||||
)
|
||||
|
||||
add_bolt_unittest(PassTests
|
||||
InsertNegateRAState.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()
|
||||
333
bolt/unittests/Passes/InsertNegateRAState.cpp
Normal file
333
bolt/unittests/Passes/InsertNegateRAState.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
//===- 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
|
||||
Reference in New Issue
Block a user