[llvm] [BOLT][AArch64] Handle OpNegateRAState to enable optimizing binaries with pac-ret hardening (PR #120064)
Gergely Bálint via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 5 08:03:53 PDT 2025
https://github.com/bgergely0 updated https://github.com/llvm/llvm-project/pull/120064
>From 9d8186c1fe6edad92420e6a046875c6ac0c41eb5 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Tue, 19 Nov 2024 09:43:25 +0100
Subject: [PATCH 1/9] [BOLT] Recognize paciasp and autiasp instructions
---
bolt/include/bolt/Core/MCPlusBuilder.h | 7 +++++++
bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp | 6 ++++++
2 files changed, 13 insertions(+)
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index f902a8c43cd1d..0c25ba52ce05e 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -815,6 +815,13 @@ class MCPlusBuilder {
llvm_unreachable("not implemented");
return false;
}
+ virtual bool isPAuth(MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ }
+
+ virtual bool isPSign(MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ }
virtual bool isCleanRegXOR(const MCInst &Inst) const {
llvm_unreachable("not implemented");
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 973261765f951..2266733f956ee 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -894,6 +894,12 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
}
return false;
}
+ bool isPAuth(MCInst &Inst) const override {
+ return Inst.getOpcode() == AArch64::AUTIASP;
+ }
+ bool isPSign(MCInst &Inst) const override {
+ return Inst.getOpcode() == AArch64::PACIASP;
+ }
bool isRegToRegMove(const MCInst &Inst, MCPhysReg &From,
MCPhysReg &To) const override {
>From c22e4d9ad413fcb1997546aee4b9096ca113459a Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Mon, 16 Dec 2024 10:45:41 +0100
Subject: [PATCH 2/9] [BOLT] Support for OpNegateRAState
- save OpNegateRAState, RememberState and RestoreState locations
when parsing input
- determine the RA states from these before other optimizations
in MarkRAStates Pass
- after optimizations, we can insert OpNegateRAState at state
boundaries and other needed locations (e.g. split functions)
in InsertNegateRAStatePass
---
bolt/include/bolt/Core/BinaryFunction.h | 45 ++++++
bolt/include/bolt/Core/MCPlus.h | 9 +-
bolt/include/bolt/Core/MCPlusBuilder.h | 59 ++++++++
.../bolt/Passes/InsertNegateRAStatePass.h | 44 ++++++
bolt/include/bolt/Passes/MarkRAStates.h | 33 ++++
bolt/lib/Core/BinaryBasicBlock.cpp | 6 +-
bolt/lib/Core/BinaryFunction.cpp | 1 +
bolt/lib/Core/Exceptions.cpp | 24 ++-
bolt/lib/Core/MCPlusBuilder.cpp | 68 +++++++++
bolt/lib/Passes/CMakeLists.txt | 2 +
bolt/lib/Passes/InsertNegateRAStatePass.cpp | 142 ++++++++++++++++++
bolt/lib/Passes/MarkRAStates.cpp | 133 ++++++++++++++++
bolt/lib/Rewrite/BinaryPassManager.cpp | 7 +
13 files changed, 567 insertions(+), 6 deletions(-)
create mode 100644 bolt/include/bolt/Passes/InsertNegateRAStatePass.h
create mode 100644 bolt/include/bolt/Passes/MarkRAStates.h
create mode 100644 bolt/lib/Passes/InsertNegateRAStatePass.cpp
create mode 100644 bolt/lib/Passes/MarkRAStates.cpp
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index ae580520b9110..ebe12929c0973 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -1646,6 +1646,51 @@ class BinaryFunction {
void setHasInferredProfile(bool Inferred) { HasInferredProfile = Inferred; }
+ /// Find corrected offset the same way addCFIInstruction does it to skip NOPs.
+ std::optional<uint64_t> getCorrectedCFIOffset(uint64_t Offset) {
+ assert(!Instructions.empty());
+ auto I = Instructions.lower_bound(Offset);
+ if (Offset == getSize()) {
+ assert(I == Instructions.end() && "unexpected iterator value");
+ // Sometimes compiler issues restore_state after all instructions
+ // in the function (even after nop).
+ --I;
+ Offset = I->first;
+ }
+ assert(I->first == Offset && "CFI pointing to unknown instruction");
+ if (I == Instructions.begin())
+ return {};
+
+ --I;
+ while (I != Instructions.begin() && BC.MIB->isNoop(I->second)) {
+ Offset = I->first;
+ --I;
+ }
+ return Offset;
+ }
+
+ void setInstModifiesRAState(uint8_t CFIOpcode, uint64_t Offset) {
+ std::optional<uint64_t> CorrectedOffset = getCorrectedCFIOffset(Offset);
+ if (CorrectedOffset) {
+ auto I = Instructions.lower_bound(*CorrectedOffset);
+ I--;
+
+ switch (CFIOpcode) {
+ case dwarf::DW_CFA_AARCH64_negate_ra_state:
+ BC.MIB->setNegateRAState(I->second);
+ break;
+ case dwarf::DW_CFA_remember_state:
+ BC.MIB->setRememberState(I->second);
+ break;
+ case dwarf::DW_CFA_restore_state:
+ BC.MIB->setRestoreState(I->second);
+ break;
+ default:
+ assert(0 && "CFI Opcode not covered by function");
+ }
+ }
+ }
+
void addCFIInstruction(uint64_t Offset, MCCFIInstruction &&Inst) {
assert(!Instructions.empty());
diff --git a/bolt/include/bolt/Core/MCPlus.h b/bolt/include/bolt/Core/MCPlus.h
index 601d709712864..a95bba36c5a6e 100644
--- a/bolt/include/bolt/Core/MCPlus.h
+++ b/bolt/include/bolt/Core/MCPlus.h
@@ -72,7 +72,14 @@ class MCAnnotation {
kLabel, /// MCSymbol pointing to this instruction.
kSize, /// Size of the instruction.
kDynamicBranch, /// Jit instruction patched at runtime.
- kGeneric /// First generic annotation.
+ kSigning, /// Inst is a signing instruction (paciasp, etc.).
+ kSigned, /// Inst is in a range where RA is signed.
+ kAuthenticating, /// Authenticating inst (e.g. autiasp).
+ kUnsigned, /// Inst is in a range where RA is unsigned.
+ kRememberState, /// Inst has rememberState CFI.
+ kRestoreState, /// Inst has restoreState CFI.
+ kNegateState, /// Inst has OpNegateRAState CFI.
+ kGeneric, /// First generic annotation.
};
virtual void print(raw_ostream &OS) const = 0;
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 0c25ba52ce05e..05ae63cc63cb9 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -68,6 +68,20 @@ class MCPlusBuilder {
public:
using AllocatorIdTy = uint16_t;
+ std::optional<int64_t> getAnnotationAtOpIndex(const MCInst &Inst,
+ unsigned OpIndex) const {
+ std::optional<unsigned> FirstAnnotationOp = getFirstAnnotationOpIndex(Inst);
+ if (!FirstAnnotationOp)
+ return std::nullopt;
+
+ if (*FirstAnnotationOp > OpIndex || Inst.getNumOperands() < OpIndex)
+ return std::nullopt;
+
+ auto Op = Inst.begin() + OpIndex;
+ const int64_t ImmValue = Op->getImm();
+ return extractAnnotationIndex(ImmValue);
+ }
+
private:
/// A struct that represents a single annotation allocator
struct AnnotationAllocator {
@@ -1296,6 +1310,51 @@ class MCPlusBuilder {
/// Return true if the instruction is a tail call.
bool isTailCall(const MCInst &Inst) const;
+ /// Stores NegateRAState annotation on \p Inst.
+ void setNegateRAState(MCInst &Inst) const;
+
+ /// Return true if \p Inst has NegateRAState annotation.
+ bool hasNegateRAState(const MCInst &Inst) const;
+
+ /// Sets RememberState annotation on \p Inst.
+ void setRememberState(MCInst &Inst) const;
+
+ /// Return true if \p Inst has RememberState annotation.
+ bool hasRememberState(const MCInst &Inst) const;
+
+ /// Stores RestoreState annotation on \p Inst.
+ void setRestoreState(MCInst &Inst) const;
+
+ /// Return true if \p Inst has RestoreState annotation.
+ bool hasRestoreState(const MCInst &Inst) const;
+
+ /// Stores RA Signed annotation on \p Inst.
+ void setRASigned(MCInst &Inst) const;
+
+ /// Return true if \p Inst has Signed RA annotation.
+ bool isRASigned(const MCInst &Inst) const;
+
+ /// Stores RA Signing annotation on \p Inst.
+ void setRASigning(MCInst &Inst) const;
+
+ /// Return true if \p Inst has Signing RA annotation.
+ bool isRASigning(const MCInst &Inst) const;
+
+ /// Stores Authenticating annotation on \p Inst.
+ void setAuthenticating(MCInst &Inst) const;
+
+ /// Return true if \p Inst has Authenticating annotation.
+ bool isAuthenticating(const MCInst &Inst) const;
+
+ /// Stores RA Unsigned annotation on \p Inst.
+ void setRAUnsigned(MCInst &Inst) const;
+
+ /// Return true if \p Inst has Unsigned RA annotation.
+ bool isRAUnsigned(const MCInst &Inst) const;
+
+ /// Return true if \p Inst doesn't have any annotation related to RA state.
+ bool isRAStateUnknown(const MCInst &Inst) const;
+
/// Return true if the instruction is a call with an exception handling info.
virtual bool isInvoke(const MCInst &Inst) const {
return isCall(Inst) && getEHInfo(Inst);
diff --git a/bolt/include/bolt/Passes/InsertNegateRAStatePass.h b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h
new file mode 100644
index 0000000000000..e62006baa2eff
--- /dev/null
+++ b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h
@@ -0,0 +1,44 @@
+//===- bolt/Passes/InsertNegateRAStatePass.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the InsertNegateRAStatePass class.
+//
+//===----------------------------------------------------------------------===//
+#ifndef BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS
+#define BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS
+
+#include "bolt/Passes/BinaryPasses.h"
+#include <stack>
+
+namespace llvm {
+namespace bolt {
+
+class InsertNegateRAState : public BinaryFunctionPass {
+public:
+ explicit InsertNegateRAState() : BinaryFunctionPass(false) {}
+
+ const char *getName() const override { return "insert-negate-ra-state-pass"; }
+
+ /// Pass entry point
+ Error runOnFunctions(BinaryContext &BC) override;
+ void runOnFunction(BinaryFunction &BF);
+
+private:
+ /// Loops over all instructions and adds OpNegateRAState CFI
+ /// after any pointer signing or authenticating instructions.
+ /// Returns true, if any OpNegateRAState CFIs were added.
+ bool addNegateRAStateAfterPacOrAuth(BinaryFunction &BF);
+ /// 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.
+ void fixUnknownStates(BinaryFunction &BF);
+};
+
+} // namespace bolt
+} // namespace llvm
+#endif
diff --git a/bolt/include/bolt/Passes/MarkRAStates.h b/bolt/include/bolt/Passes/MarkRAStates.h
new file mode 100644
index 0000000000000..e7a49f813b6a7
--- /dev/null
+++ b/bolt/include/bolt/Passes/MarkRAStates.h
@@ -0,0 +1,33 @@
+//===- bolt/Passes/MarkRAStates.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the MarkRAStates class.
+//
+//===----------------------------------------------------------------------===//
+#ifndef BOLT_PASSES_MARK_RA_STATES
+#define BOLT_PASSES_MARK_RA_STATES
+
+#include "bolt/Passes/BinaryPasses.h"
+
+namespace llvm {
+namespace bolt {
+
+class MarkRAStates : public BinaryFunctionPass {
+public:
+ explicit MarkRAStates() : BinaryFunctionPass(false) {}
+
+ const char *getName() const override { return "mark-ra-states"; }
+
+ /// Pass entry point
+ Error runOnFunctions(BinaryContext &BC) override;
+ void runOnFunction(BinaryFunction &BF);
+};
+
+} // namespace bolt
+} // namespace llvm
+#endif
diff --git a/bolt/lib/Core/BinaryBasicBlock.cpp b/bolt/lib/Core/BinaryBasicBlock.cpp
index 311d5c15b8dca..22126eed67a6c 100644
--- a/bolt/lib/Core/BinaryBasicBlock.cpp
+++ b/bolt/lib/Core/BinaryBasicBlock.cpp
@@ -201,7 +201,11 @@ int32_t BinaryBasicBlock::getCFIStateAtInstr(const MCInst *Instr) const {
InstrSeen = (&Inst == Instr);
continue;
}
- if (Function->getBinaryContext().MIB->isCFI(Inst)) {
+ // Ignoring OpNegateRAState CFIs here, as they dont have a "State"
+ // number associated with them.
+ if (Function->getBinaryContext().MIB->isCFI(Inst) &&
+ (Function->getCFIFor(Inst)->getOperation() !=
+ MCCFIInstruction::OpNegateRAState)) {
LastCFI = &Inst;
break;
}
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index eec68ff5a5fce..7a044104f3b4e 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -2804,6 +2804,7 @@ struct CFISnapshot {
void advanceTo(int32_t State) {
for (int32_t I = CurState, E = State; I != E; ++I) {
const MCCFIInstruction &Instr = FDE[I];
+ assert(Instr.getOperation() != MCCFIInstruction::OpNegateRAState);
if (Instr.getOperation() != MCCFIInstruction::OpRestoreState) {
update(Instr, I);
continue;
diff --git a/bolt/lib/Core/Exceptions.cpp b/bolt/lib/Core/Exceptions.cpp
index 0b2e63b8ca6a7..63b7ad43b1dec 100644
--- a/bolt/lib/Core/Exceptions.cpp
+++ b/bolt/lib/Core/Exceptions.cpp
@@ -568,10 +568,21 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
case DW_CFA_remember_state:
Function.addCFIInstruction(
Offset, MCCFIInstruction::createRememberState(nullptr));
+
+ if (Function.getBinaryContext().isAArch64())
+ // Support for pointer authentication:
+ // We need to annotate instructions that modify the RA State, to work
+ // out the state of each instruction in MarkRAStates Pass.
+ Function.setInstModifiesRAState(DW_CFA_remember_state, Offset);
break;
case DW_CFA_restore_state:
Function.addCFIInstruction(Offset,
MCCFIInstruction::createRestoreState(nullptr));
+ if (Function.getBinaryContext().isAArch64())
+ // Support for pointer authentication:
+ // We need to annotate instructions that modify the RA State, to work
+ // out the state of each instruction in MarkRAStates Pass.
+ Function.setInstModifiesRAState(DW_CFA_restore_state, Offset);
break;
case DW_CFA_def_cfa:
Function.addCFIInstruction(
@@ -629,11 +640,16 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
BC.errs() << "BOLT-WARNING: DW_CFA_MIPS_advance_loc unimplemented\n";
return false;
case DW_CFA_GNU_window_save:
- // DW_CFA_GNU_window_save and DW_CFA_GNU_NegateRAState just use the same
- // id but mean different things. The latter is used in AArch64.
+ // DW_CFA_GNU_window_save and DW_CFA_AARCH64_negate_ra_state just use the
+ // same id but mean different things. The latter is used in AArch64.
if (Function.getBinaryContext().isAArch64()) {
- Function.addCFIInstruction(
- Offset, MCCFIInstruction::createNegateRAState(nullptr));
+ // The location OpNegateRAState CFIs are needed
+ // depends on the order of BasicBlocks, which changes during
+ // optimizations. Instead of adding OpNegateRAState CFIs, an annotation
+ // is added to the instruction, to mark that the instruction modifies
+ // the RA State. The actual state for instructions are worked out in
+ // MarkRAStates based on these annotations.
+ Function.setInstModifiesRAState(DW_CFA_AARCH64_negate_ra_state, Offset);
break;
}
if (opts::Verbosity >= 1)
diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp
index fa8f4d1df308b..aa2a784442a5f 100644
--- a/bolt/lib/Core/MCPlusBuilder.cpp
+++ b/bolt/lib/Core/MCPlusBuilder.cpp
@@ -149,6 +149,74 @@ bool MCPlusBuilder::isTailCall(const MCInst &Inst) const {
return false;
}
+void MCPlusBuilder::setNegateRAState(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kNegateState));
+ setAnnotationOpValue(Inst, MCAnnotation::kNegateState, true);
+}
+
+bool MCPlusBuilder::hasNegateRAState(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kNegateState);
+}
+
+void MCPlusBuilder::setRememberState(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kRememberState));
+ setAnnotationOpValue(Inst, MCAnnotation::kRememberState, true);
+}
+
+bool MCPlusBuilder::hasRememberState(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kRememberState);
+}
+
+void MCPlusBuilder::setRestoreState(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kRestoreState));
+ setAnnotationOpValue(Inst, MCAnnotation::kRestoreState, true);
+}
+
+bool MCPlusBuilder::hasRestoreState(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kRestoreState);
+}
+
+void MCPlusBuilder::setRASigned(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kSigned));
+ setAnnotationOpValue(Inst, MCAnnotation::kSigned, true);
+}
+
+bool MCPlusBuilder::isRASigned(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kSigned);
+}
+
+void MCPlusBuilder::setRASigning(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kSigning));
+ setAnnotationOpValue(Inst, MCAnnotation::kSigning, true);
+}
+
+bool MCPlusBuilder::isRASigning(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kSigning);
+}
+
+void MCPlusBuilder::setAuthenticating(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kAuthenticating));
+ setAnnotationOpValue(Inst, MCAnnotation::kAuthenticating, true);
+}
+
+bool MCPlusBuilder::isAuthenticating(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kAuthenticating);
+}
+
+void MCPlusBuilder::setRAUnsigned(MCInst &Inst) const {
+ assert(!hasAnnotation(Inst, MCAnnotation::kUnsigned));
+ setAnnotationOpValue(Inst, MCAnnotation::kUnsigned, true);
+}
+
+bool MCPlusBuilder::isRAUnsigned(const MCInst &Inst) const {
+ return hasAnnotation(Inst, MCAnnotation::kUnsigned);
+}
+
+bool MCPlusBuilder::isRAStateUnknown(const MCInst &Inst) const {
+ return !(isRAUnsigned(Inst) || isRASigned(Inst) || isRASigning(Inst) ||
+ isAuthenticating(Inst));
+}
+
std::optional<MCLandingPad> MCPlusBuilder::getEHInfo(const MCInst &Inst) const {
if (!isCall(Inst))
return std::nullopt;
diff --git a/bolt/lib/Passes/CMakeLists.txt b/bolt/lib/Passes/CMakeLists.txt
index 77d2bb9c2bcb5..d7519518f186f 100644
--- a/bolt/lib/Passes/CMakeLists.txt
+++ b/bolt/lib/Passes/CMakeLists.txt
@@ -17,12 +17,14 @@ add_llvm_library(LLVMBOLTPasses
IdenticalCodeFolding.cpp
IndirectCallPromotion.cpp
Inliner.cpp
+ InsertNegateRAStatePass.cpp
Instrumentation.cpp
JTFootprintReduction.cpp
LongJmp.cpp
LoopInversionPass.cpp
LivenessAnalysis.cpp
MCF.cpp
+ MarkRAStates.cpp
PatchEntries.cpp
PAuthGadgetScanner.cpp
PettisAndHansen.cpp
diff --git a/bolt/lib/Passes/InsertNegateRAStatePass.cpp b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
new file mode 100644
index 0000000000000..098ebf8f953b4
--- /dev/null
+++ b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
@@ -0,0 +1,142 @@
+//===- bolt/Passes/InsertNegateRAStatePass.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the InsertNegateRAStatePass class. It inserts
+// OpNegateRAState CFIs to places where the state of two consecutive
+// instructions are different.
+//
+//===----------------------------------------------------------------------===//
+#include "bolt/Passes/InsertNegateRAStatePass.h"
+#include "bolt/Core/BinaryFunction.h"
+#include "bolt/Core/ParallelUtilities.h"
+#include "bolt/Utils/CommandLineOpts.h"
+#include <cstdlib>
+#include <fstream>
+#include <iterator>
+
+using namespace llvm;
+
+namespace llvm {
+namespace bolt {
+
+void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
+ BinaryContext &BC = BF.getBinaryContext();
+
+ if (BF.getState() == BinaryFunction::State::Empty)
+ return;
+
+ if (BF.getState() != BinaryFunction::State::CFG &&
+ BF.getState() != BinaryFunction::State::CFG_Finalized) {
+ BC.outs() << "BOLT-INFO: No CFG for " << BF.getPrintName()
+ << " in InsertNegateRAStatePass\n";
+ return;
+ }
+
+ // If none is inserted, the function doesn't need more work.
+ if (!addNegateRAStateAfterPacOrAuth(BF))
+ return;
+
+ fixUnknownStates(BF);
+
+ bool FirstIter = true;
+ MCInst PrevInst;
+ BinaryBasicBlock *PrevBB = nullptr;
+ auto *Begin = BF.getLayout().block_begin();
+ auto *End = BF.getLayout().block_end();
+ for (auto *BB = Begin; BB != End; BB++) {
+
+ // Support for function splitting:
+ // if two consecutive BBs are going to end up in different functions,
+ // we have to negate the RA State, so the new function starts with a Signed
+ // state.
+ if (PrevBB != nullptr &&
+ PrevBB->getFragmentNum() != (*BB)->getFragmentNum() &&
+ BC.MIB->isRASigned(*((*BB)->begin()))) {
+ BF.addCFIInstruction(*BB, (*BB)->begin(),
+ MCCFIInstruction::createNegateRAState(nullptr));
+ }
+
+ for (auto It = (*BB)->begin(); It != (*BB)->end(); ++It) {
+
+ MCInst &Inst = *It;
+ if (BC.MIB->isCFI(Inst))
+ continue;
+
+ if (!FirstIter) {
+ if ((BC.MIB->isRASigned(PrevInst) && BC.MIB->isRAUnsigned(Inst)) ||
+ (BC.MIB->isRAUnsigned(PrevInst) && BC.MIB->isRASigned(Inst))) {
+
+ It = BF.addCFIInstruction(
+ *BB, It, MCCFIInstruction::createNegateRAState(nullptr));
+ }
+
+ } else {
+ FirstIter = false;
+ }
+ PrevInst = *It;
+ }
+ PrevBB = *BB;
+ }
+}
+
+bool InsertNegateRAState::addNegateRAStateAfterPacOrAuth(BinaryFunction &BF) {
+ BinaryContext &BC = BF.getBinaryContext();
+ bool FoundAny = false;
+ for (BinaryBasicBlock &BB : BF) {
+ for (auto Iter = BB.begin(); Iter != BB.end(); ++Iter) {
+ MCInst &Inst = *Iter;
+ if (BC.MIB->isPSign(Inst) || BC.MIB->isPAuth(Inst)) {
+ Iter = BF.addCFIInstruction(
+ &BB, Iter + 1, MCCFIInstruction::createNegateRAState(nullptr));
+ FoundAny = true;
+ }
+ }
+ }
+ return FoundAny;
+}
+
+void InsertNegateRAState::fixUnknownStates(BinaryFunction &BF) {
+ BinaryContext &BC = BF.getBinaryContext();
+ bool FirstIter = true;
+ MCInst PrevInst;
+ for (BinaryBasicBlock &BB : BF) {
+ for (auto It = BB.begin(); It != BB.end(); ++It) {
+
+ MCInst &Inst = *It;
+ if (BC.MIB->isCFI(Inst))
+ continue;
+
+ if (!FirstIter && BC.MIB->isRAStateUnknown(Inst)) {
+ if (BC.MIB->isRASigned(PrevInst) || BC.MIB->isRASigning(PrevInst)) {
+ BC.MIB->setRASigned(Inst);
+ } else if (BC.MIB->isRAUnsigned(PrevInst) ||
+ BC.MIB->isAuthenticating(PrevInst)) {
+ BC.MIB->setRAUnsigned(Inst);
+ }
+ } else {
+ FirstIter = false;
+ }
+ PrevInst = Inst;
+ }
+ }
+}
+
+Error InsertNegateRAState::runOnFunctions(BinaryContext &BC) {
+ ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) {
+ runOnFunction(BF);
+ };
+
+ ParallelUtilities::runOnEachFunction(
+ BC, ParallelUtilities::SchedulingPolicy::SP_TRIVIAL, WorkFun, nullptr,
+ "InsertNegateRAStatePass");
+
+ return Error::success();
+}
+
+} // end namespace bolt
+} // end namespace llvm
diff --git a/bolt/lib/Passes/MarkRAStates.cpp b/bolt/lib/Passes/MarkRAStates.cpp
new file mode 100644
index 0000000000000..adccf2090c36f
--- /dev/null
+++ b/bolt/lib/Passes/MarkRAStates.cpp
@@ -0,0 +1,133 @@
+//===- bolt/Passes/MarkRAStates.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the MarkRAStates class.
+// Three CFIs have an influence on the RA State of an instruction:
+// - NegateRAState flips the RA State,
+// - RememberState pushes the RA State to a stack,
+// - RestoreState pops the RA State from the stack.
+// These are saved as MCAnnotations on instructions they refer to at CFI
+// reading (in CFIReaderWriter::fillCFIInfoFor). In this pass, we can work out
+// the RA State of each instruction, and save it as new MCAnnotations. The new
+// annotations are Signing, Signed, Authenticating and Unsigned. After
+// optimizations, .cfi_negate_ra_state CFIs are added to the places where the
+// state changes in InsertNegateRAStatePass.
+//
+//===----------------------------------------------------------------------===//
+#include "bolt/Passes/MarkRAStates.h"
+#include "bolt/Core/BinaryFunction.h"
+#include "bolt/Core/ParallelUtilities.h"
+#include "bolt/Utils/CommandLineOpts.h"
+#include <cstdlib>
+#include <fstream>
+#include <iterator>
+
+#include <iostream>
+#include <optional>
+#include <stack>
+
+using namespace llvm;
+
+namespace llvm {
+namespace bolt {
+
+void MarkRAStates::runOnFunction(BinaryFunction &BF) {
+
+ if (BF.isIgnored())
+ return;
+
+ BinaryContext &BC = BF.getBinaryContext();
+
+ for (BinaryBasicBlock &BB : BF) {
+ for (auto It = BB.begin(); It != BB.end(); ++It) {
+ MCInst &Inst = *It;
+ if ((BC.MIB->isPSign(Inst) || BC.MIB->isPAuth(Inst)) &&
+ !BC.MIB->hasNegateRAState(Inst)) {
+ // no .cfi_negate_ra_state attached to signing or authenticating instr
+ // means, that this is a function with handwritten assembly, which might
+ // not respect Clang's conventions (e.g. tailcalls are always
+ // authenticated, so functions always start with unsigned RAState when
+ // working with compiler-generated code)
+ BF.setIgnored();
+ BC.outs() << "BOLT-INFO: inconsistent RAStates in function "
+ << BF.getPrintName() << "\n";
+ return;
+ }
+ }
+ }
+
+ bool RAState = false;
+ std::stack<bool> RAStateStack;
+
+ for (BinaryBasicBlock &BB : BF) {
+ for (auto It = BB.begin(); It != BB.end(); ++It) {
+
+ MCInst &Inst = *It;
+ if (BC.MIB->isCFI(Inst))
+ continue;
+
+ if (BC.MIB->isPSign(Inst)) {
+ if (RAState) {
+ // RA signing instructions should only follow unsigned RA state.
+ BC.outs() << "BOLT-INFO: inconsistent RAStates in function "
+ << BF.getPrintName() << "\n";
+ BF.setIgnored();
+ return;
+ }
+ BC.MIB->setRASigning(Inst);
+ } else if (BC.MIB->isPAuth(Inst)) {
+ if (!RAState) {
+ // RA authenticating instructions should only follow signed RA state.
+ BC.outs() << "BOLT-INFO: inconsistent RAStates in function "
+ << BF.getPrintName() << "\n";
+ BF.setIgnored();
+ return;
+ }
+ BC.MIB->setAuthenticating(Inst);
+ } else if (RAState) {
+ BC.MIB->setRASigned(Inst);
+ } else {
+ BC.MIB->setRAUnsigned(Inst);
+ }
+
+ // Updating RAState. All updates are valid from the next instruction.
+ // Because the same instruction can have remember and restore, the order
+ // here is relevant. This is the reason to loop over Annotations instead
+ // of just checking each in a predefined order.
+ for (unsigned int Idx = 0; Idx < Inst.getNumOperands(); Idx++) {
+ std::optional<int64_t> Annotation =
+ BC.MIB->getAnnotationAtOpIndex(Inst, Idx);
+ if (!Annotation)
+ continue;
+ if (Annotation == MCPlus::MCAnnotation::kNegateState)
+ RAState = !RAState;
+ else if (Annotation == MCPlus::MCAnnotation::kRememberState)
+ RAStateStack.push(RAState);
+ else if (Annotation == MCPlus::MCAnnotation::kRestoreState) {
+ RAState = RAStateStack.top();
+ RAStateStack.pop();
+ }
+ }
+ }
+ }
+}
+
+Error MarkRAStates::runOnFunctions(BinaryContext &BC) {
+ ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) {
+ runOnFunction(BF);
+ };
+
+ ParallelUtilities::runOnEachFunction(
+ BC, ParallelUtilities::SchedulingPolicy::SP_TRIVIAL, WorkFun, nullptr,
+ "MarkRAStates");
+
+ return Error::success();
+}
+
+} // end namespace bolt
+} // end namespace llvm
diff --git a/bolt/lib/Rewrite/BinaryPassManager.cpp b/bolt/lib/Rewrite/BinaryPassManager.cpp
index 996d2e972599d..8b412854dc701 100644
--- a/bolt/lib/Rewrite/BinaryPassManager.cpp
+++ b/bolt/lib/Rewrite/BinaryPassManager.cpp
@@ -19,11 +19,13 @@
#include "bolt/Passes/IdenticalCodeFolding.h"
#include "bolt/Passes/IndirectCallPromotion.h"
#include "bolt/Passes/Inliner.h"
+#include "bolt/Passes/InsertNegateRAStatePass.h"
#include "bolt/Passes/Instrumentation.h"
#include "bolt/Passes/JTFootprintReduction.h"
#include "bolt/Passes/LongJmp.h"
#include "bolt/Passes/LoopInversionPass.h"
#include "bolt/Passes/MCF.h"
+#include "bolt/Passes/MarkRAStates.h"
#include "bolt/Passes/PLTCall.h"
#include "bolt/Passes/PatchEntries.h"
#include "bolt/Passes/ProfileQualityStats.h"
@@ -350,6 +352,9 @@ Error BinaryFunctionPassManager::runPasses() {
Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
BinaryFunctionPassManager Manager(BC);
+ if (BC.isAArch64())
+ Manager.registerPass(std::make_unique<MarkRAStates>());
+
Manager.registerPass(
std::make_unique<EstimateEdgeCounts>(PrintEstimateEdgeCounts));
@@ -509,6 +514,8 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
// targets. No extra instructions after this pass, otherwise we may have
// relocations out of range and crash during linking.
Manager.registerPass(std::make_unique<LongJmpPass>(PrintLongJmp));
+
+ Manager.registerPass(std::make_unique<InsertNegateRAState>());
}
// This pass should always run last.*
>From 2f5a9240ffd2eeb462c1a589fb8d07aca5105170 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Mon, 7 Apr 2025 18:19:20 +0200
Subject: [PATCH 3/9] [BOLT][AArch64] Fix which PSign and PAuth variants are
used (#120064)
- only the ones operating on LR should be marked
with .cfi_cfi_negate_ra_state
- added support for fused PtrAuth and Ret instructions,
e.g. RETAA.
---
bolt/include/bolt/Core/MCPlusBuilder.h | 22 ++++++++++-----
.../bolt/Passes/InsertNegateRAStatePass.h | 4 ++-
bolt/lib/Passes/InsertNegateRAStatePass.cpp | 13 ++++++---
bolt/lib/Passes/MarkRAStates.cpp | 7 +++--
.../Target/AArch64/AArch64MCPlusBuilder.cpp | 28 +++++++++++++++----
5 files changed, 53 insertions(+), 21 deletions(-)
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 05ae63cc63cb9..6d41eaf45b03e 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -610,6 +610,21 @@ class MCPlusBuilder {
return std::nullopt;
}
+ virtual bool isPSignOnLR(const MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ return false;
+ }
+
+ virtual bool isPAuthOnLR(const MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ return false;
+ }
+
+ virtual bool isPAuthAndRet(const MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ return false;
+ }
+
/// Returns the register used as a return address. Returns std::nullopt if
/// not applicable, such as reading the return address from a system register
/// or from the stack.
@@ -829,13 +844,6 @@ class MCPlusBuilder {
llvm_unreachable("not implemented");
return false;
}
- virtual bool isPAuth(MCInst &Inst) const {
- llvm_unreachable("not implemented");
- }
-
- virtual bool isPSign(MCInst &Inst) const {
- llvm_unreachable("not implemented");
- }
virtual bool isCleanRegXOR(const MCInst &Inst) const {
llvm_unreachable("not implemented");
diff --git a/bolt/include/bolt/Passes/InsertNegateRAStatePass.h b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h
index e62006baa2eff..ce73b5a152d12 100644
--- a/bolt/include/bolt/Passes/InsertNegateRAStatePass.h
+++ b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h
@@ -30,7 +30,9 @@ class InsertNegateRAState : public BinaryFunctionPass {
private:
/// Loops over all instructions and adds OpNegateRAState CFI
- /// after any pointer signing or authenticating instructions.
+ /// after any pointer signing or authenticating instructions,
+ /// which operate on the LR, except fused ptrauth + ret instructions
+ /// (such as RETAA).
/// Returns true, if any OpNegateRAState CFIs were added.
bool addNegateRAStateAfterPacOrAuth(BinaryFunction &BF);
/// Because states are tracked as MCAnnotations on individual instructions,
diff --git a/bolt/lib/Passes/InsertNegateRAStatePass.cpp b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
index 098ebf8f953b4..05d4d58c7e4cf 100644
--- a/bolt/lib/Passes/InsertNegateRAStatePass.cpp
+++ b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
@@ -46,14 +46,16 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
bool FirstIter = true;
MCInst PrevInst;
BinaryBasicBlock *PrevBB = nullptr;
+ // We need to iterate on BBs in the Layout order
+ // not in the order they are stored in the BF class.
auto *Begin = BF.getLayout().block_begin();
auto *End = BF.getLayout().block_end();
for (auto *BB = Begin; BB != End; BB++) {
// Support for function splitting:
- // if two consecutive BBs are going to end up in different functions,
- // we have to negate the RA State, so the new function starts with a Signed
- // state.
+ // if two consecutive BBs with Signed state are going to end up in different
+ // functions, we have to add a OpNegateRAState to the beginning of the newly
+ // split function, so it starts with a Signed state.
if (PrevBB != nullptr &&
PrevBB->getFragmentNum() != (*BB)->getFragmentNum() &&
BC.MIB->isRASigned(*((*BB)->begin()))) {
@@ -68,6 +70,8 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
continue;
if (!FirstIter) {
+ // Consecutive instructions with different RAState means we need to add
+ // a OpNegateRAState.
if ((BC.MIB->isRASigned(PrevInst) && BC.MIB->isRAUnsigned(Inst)) ||
(BC.MIB->isRAUnsigned(PrevInst) && BC.MIB->isRASigned(Inst))) {
@@ -90,7 +94,8 @@ bool InsertNegateRAState::addNegateRAStateAfterPacOrAuth(BinaryFunction &BF) {
for (BinaryBasicBlock &BB : BF) {
for (auto Iter = BB.begin(); Iter != BB.end(); ++Iter) {
MCInst &Inst = *Iter;
- if (BC.MIB->isPSign(Inst) || BC.MIB->isPAuth(Inst)) {
+ if (BC.MIB->isPSignOnLR(Inst) ||
+ (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(Inst))) {
Iter = BF.addCFIInstruction(
&BB, Iter + 1, MCCFIInstruction::createNegateRAState(nullptr));
FoundAny = true;
diff --git a/bolt/lib/Passes/MarkRAStates.cpp b/bolt/lib/Passes/MarkRAStates.cpp
index adccf2090c36f..8121fffb93c9f 100644
--- a/bolt/lib/Passes/MarkRAStates.cpp
+++ b/bolt/lib/Passes/MarkRAStates.cpp
@@ -46,7 +46,8 @@ void MarkRAStates::runOnFunction(BinaryFunction &BF) {
for (BinaryBasicBlock &BB : BF) {
for (auto It = BB.begin(); It != BB.end(); ++It) {
MCInst &Inst = *It;
- if ((BC.MIB->isPSign(Inst) || BC.MIB->isPAuth(Inst)) &&
+ if ((BC.MIB->isPSignOnLR(Inst) ||
+ (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(Inst))) &&
!BC.MIB->hasNegateRAState(Inst)) {
// no .cfi_negate_ra_state attached to signing or authenticating instr
// means, that this is a function with handwritten assembly, which might
@@ -71,7 +72,7 @@ void MarkRAStates::runOnFunction(BinaryFunction &BF) {
if (BC.MIB->isCFI(Inst))
continue;
- if (BC.MIB->isPSign(Inst)) {
+ if (BC.MIB->isPSignOnLR(Inst)) {
if (RAState) {
// RA signing instructions should only follow unsigned RA state.
BC.outs() << "BOLT-INFO: inconsistent RAStates in function "
@@ -80,7 +81,7 @@ void MarkRAStates::runOnFunction(BinaryFunction &BF) {
return;
}
BC.MIB->setRASigning(Inst);
- } else if (BC.MIB->isPAuth(Inst)) {
+ } else if (BC.MIB->isPAuthOnLR(Inst)) {
if (!RAState) {
// RA authenticating instructions should only follow signed RA state.
BC.outs() << "BOLT-INFO: inconsistent RAStates in function "
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 2266733f956ee..e0061bd12857c 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -231,6 +231,28 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
}
}
+ bool isPSignOnLR(const MCInst &Inst) const override {
+ std::optional<MCPhysReg> SignReg = getSignedReg(Inst);
+ return SignReg && *SignReg == AArch64::LR;
+ }
+
+ bool isPAuthOnLR(const MCInst &Inst) const override {
+ // LDR(A|B) should not be covered.
+ bool IsChecked;
+ std::optional<MCPhysReg> AuthReg =
+ getWrittenAuthenticatedReg(Inst, IsChecked);
+ return !IsChecked && AuthReg && *AuthReg == AArch64::LR;
+ }
+
+ bool isPAuthAndRet(const MCInst &Inst) const override {
+ return Inst.getOpcode() == AArch64::RETAA ||
+ Inst.getOpcode() == AArch64::RETAB ||
+ Inst.getOpcode() == AArch64::RETAASPPCi ||
+ Inst.getOpcode() == AArch64::RETABSPPCi ||
+ Inst.getOpcode() == AArch64::RETAASPPCr ||
+ Inst.getOpcode() == AArch64::RETABSPPCr;
+ }
+
std::optional<MCPhysReg> getSignedReg(const MCInst &Inst) const override {
switch (Inst.getOpcode()) {
case AArch64::PACIA:
@@ -894,12 +916,6 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
}
return false;
}
- bool isPAuth(MCInst &Inst) const override {
- return Inst.getOpcode() == AArch64::AUTIASP;
- }
- bool isPSign(MCInst &Inst) const override {
- return Inst.getOpcode() == AArch64::PACIASP;
- }
bool isRegToRegMove(const MCInst &Inst, MCPhysReg &From,
MCPhysReg &To) const override {
>From 19ea611bdb90c22bd0451c96c7e60e4a55b4d9ff Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Fri, 4 Apr 2025 11:23:22 +0200
Subject: [PATCH 4/9] [BOLT] Add unit tests for negate_ra_state cfi handling
- also add match_dwarf.py, a tool used by the unit tests.
---
bolt/lib/Core/BinaryFunction.cpp | 24 +--
bolt/test/AArch64/negate-ra-state-incorrect.s | 44 ++++++
bolt/test/AArch64/negate-ra-state.s | 42 ++++++
bolt/test/lit.cfg.py | 7 +
bolt/test/match_dwarf.py | 137 ++++++++++++++++++
5 files changed, 233 insertions(+), 21 deletions(-)
create mode 100644 bolt/test/AArch64/negate-ra-state-incorrect.s
create mode 100644 bolt/test/AArch64/negate-ra-state.s
create mode 100755 bolt/test/match_dwarf.py
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 7a044104f3b4e..c4811888a58f9 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -2782,14 +2782,8 @@ struct CFISnapshot {
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
- llvm_unreachable("unsupported CFI opcode");
- break;
case MCCFIInstruction::OpNegateRAState:
- if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
- llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
- "as produced by '-mbranch-protection=pac-ret') are "
- "currently not supported by BOLT.");
- }
+ llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpRememberState:
case MCCFIInstruction::OpRestoreState:
@@ -2929,15 +2923,9 @@ struct CFISnapshotDiff : public CFISnapshot {
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
+ case MCCFIInstruction::OpNegateRAState:
llvm_unreachable("unsupported CFI opcode");
return false;
- case MCCFIInstruction::OpNegateRAState:
- if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
- llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
- "as produced by '-mbranch-protection=pac-ret') are "
- "currently not supported by BOLT.");
- }
- break;
case MCCFIInstruction::OpRememberState:
case MCCFIInstruction::OpRestoreState:
case MCCFIInstruction::OpGnuArgsSize:
@@ -3086,14 +3074,8 @@ BinaryFunction::unwindCFIState(int32_t FromState, int32_t ToState,
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
- llvm_unreachable("unsupported CFI opcode");
- break;
case MCCFIInstruction::OpNegateRAState:
- if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
- llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
- "as produced by '-mbranch-protection=pac-ret') are "
- "currently not supported by BOLT.");
- }
+ llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpGnuArgsSize:
// do not affect CFI state
diff --git a/bolt/test/AArch64/negate-ra-state-incorrect.s b/bolt/test/AArch64/negate-ra-state-incorrect.s
new file mode 100644
index 0000000000000..c6b8b36939f4d
--- /dev/null
+++ b/bolt/test/AArch64/negate-ra-state-incorrect.s
@@ -0,0 +1,44 @@
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q
+# RUN: llvm-bolt %t.exe -o %t.exe.bolt | FileCheck %s
+
+# check that the output is listing foo as incorrect
+# CHECK: BOLT-INFO: inconsistent RAStates in function foo
+
+# check that foo got Ignored, so it's not in the new .text section
+# RUN: llvm-objdump %t.exe.bolt -d -j .text > %t.exe.dump
+# RUN: not grep "<foo>:" %t.exe.dump
+
+
+# How is this test incorrect?
+# There is an extra .cfi_negate_ra_state in foo.
+# Because of this, we will get to the autiasp (hint #29)
+# in a (seemingly) unsigned state. That is incorrect.
+ .text
+ .globl foo
+ .p2align 2
+ .type foo, at function
+foo:
+ .cfi_startproc
+ hint #25
+ .cfi_negate_ra_state
+ sub sp, sp, #16
+ stp x29, x30, [sp, #16] // 16-byte Folded Spill
+ .cfi_def_cfa_offset 16
+ str w0, [sp, #12]
+ ldr w8, [sp, #12]
+ .cfi_negate_ra_state
+ add w0, w8, #1
+ ldp x29, x30, [sp, #16] // 16-byte Folded Reload
+ add sp, sp, #16
+ hint #29
+ .cfi_negate_ra_state
+ ret
+.Lfunc_end1:
+ .size foo, .Lfunc_end1-foo
+ .cfi_endproc
+
+ .global _start
+ .type _start, %function
+_start:
+ b foo
diff --git a/bolt/test/AArch64/negate-ra-state.s b/bolt/test/AArch64/negate-ra-state.s
new file mode 100644
index 0000000000000..11c511a254c71
--- /dev/null
+++ b/bolt/test/AArch64/negate-ra-state.s
@@ -0,0 +1,42 @@
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q
+
+# RUN: llvm-objdump %t.exe -d > %t.exe.dump
+# RUN: llvm-objdump --dwarf=frames %t.exe > %t.exe.dump-dwarf
+# RUN: match-dwarf %t.exe.dump %t.exe.dump-dwarf foo > %t.match-dwarf.txt
+
+# RUN: llvm-bolt %t.exe -o %t.exe.bolt
+
+# RUN: llvm-objdump %t.exe.bolt -d > %t.exe.bolt.dump
+# RUN: llvm-objdump --dwarf=frames %t.exe.bolt > %t.exe.bolt.dump-dwarf
+# RUN: match-dwarf %t.exe.bolt.dump %t.exe.bolt.dump-dwarf foo > %t.bolt.match-dwarf.txt
+
+# RUN: diff %t.match-dwarf.txt %t.bolt.match-dwarf.txt
+
+ .text
+ .globl foo
+ .p2align 2
+ .type foo, at function
+foo:
+ .cfi_startproc
+ hint #25
+ .cfi_negate_ra_state
+ sub sp, sp, #16
+ stp x29, x30, [sp, #16] // 16-byte Folded Spill
+ .cfi_def_cfa_offset 16
+ str w0, [sp, #12]
+ ldr w8, [sp, #12]
+ add w0, w8, #1
+ ldp x29, x30, [sp, #16] // 16-byte Folded Reload
+ add sp, sp, #16
+ hint #29
+ .cfi_negate_ra_state
+ ret
+.Lfunc_end1:
+ .size foo, .Lfunc_end1-foo
+ .cfi_endproc
+
+ .global _start
+ .type _start, %function
+_start:
+ b foo
diff --git a/bolt/test/lit.cfg.py b/bolt/test/lit.cfg.py
index 0d05229be2bf3..60617cd76f968 100644
--- a/bolt/test/lit.cfg.py
+++ b/bolt/test/lit.cfg.py
@@ -89,6 +89,7 @@
config.substitutions.append(("%cxxflags", ""))
link_fdata_cmd = os.path.join(config.test_source_root, "link_fdata.py")
+match_dwarf_cmd = os.path.join(config.test_source_root, "match_dwarf.py")
tool_dirs = [config.llvm_tools_dir, config.test_source_root]
@@ -131,6 +132,12 @@
ToolSubst("llvm-readobj", unresolved="fatal"),
ToolSubst("llvm-dwp", unresolved="fatal"),
ToolSubst("split-file", unresolved="fatal"),
+ ToolSubst(
+ "match-dwarf",
+ command=sys.executable,
+ unresolved="fatal",
+ extra_args=[match_dwarf_cmd],
+ ),
]
llvm_config.add_tool_substitutions(tools, tool_dirs)
diff --git a/bolt/test/match_dwarf.py b/bolt/test/match_dwarf.py
new file mode 100755
index 0000000000000..3d3ab22042d5c
--- /dev/null
+++ b/bolt/test/match_dwarf.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+
+# This tool helps matching dwarf dumps
+# (= the output from running llvm-objdump --dwarf=frames),
+# by address to function names (which are parsed from a normal objdump).
+# The script is used for checking if .cfi_negate_ra_state CFIs
+# are generated by BOLT the same way they are generated by LLVM.
+# The script is called twice in unittests: once with the objdumps of
+# the BOLT input binary, and once with the output binary from BOLT.
+# We output the offsets of .cfi_negate_ra_state instructions from the
+# function's start address to see that BOLT can generate them to the same
+# locations.
+# Because we check the location, this is only useful for testing without
+# optimization flags, so `llvm-bolt input.exe -o output.exe`
+
+
+import argparse
+import subprocess
+import sys
+import re
+
+
+class NameDwarfPair(object):
+ def __init__(self, name, body):
+ self.name = name
+ self.body = body
+ self.finalized = False
+
+ def append(self, body_line):
+ # only store elements into the body until the first whitespace line is encountered.
+ if body_line.isspace():
+ self.finalized = True
+ if not self.finalized:
+ self.body += body_line
+
+ def print(self):
+ print(self.name)
+ print(self.body)
+
+ def parse_negate_offsets(self):
+ """
+ Create a list of locations/offsets of the negate_ra_state CFIs in the
+ dwarf entry. To find offsets for each, we match the DW_CFA_advance_loc
+ entries, and sum up their values.
+ """
+ negate_offsets = []
+ loc = 0
+ # TODO: make sure this is not printed in hex
+ re_advloc = r"DW_CFA_advance_loc: (\d+)"
+
+ for line in self.body.splitlines():
+ # if line matches advance_loc int
+ match = re.search(re_advloc, line)
+ if match:
+ loc += int(match.group(1))
+ if "DW_CFA_AARCH64_negate_ra_state" in line:
+ negate_offsets.append(loc)
+
+ self.negate_offsets = negate_offsets
+
+ def __eq__(self, other):
+ return self.name == other.name and self.negate_offsets == other.negate_offsets
+
+
+def extract_function_addresses(objdump):
+ """
+ Parse and return address-to-name dictionary from objdump file.
+ Function names in the objdump look like this:
+ 000123abc <foo>:
+ We create a dict from the addr (000123abc), to the name (foo).
+ """
+ addr_name_dict = dict()
+ re_function = re.compile(r"^([0-9a-fA-F]+)\s<(.*)>:$")
+ with open(objdump, "r") as f:
+ for line in f.readlines():
+ match = re_function.match(line)
+ if not match:
+ continue
+ m_addr = match.groups()[0]
+ m_name = match.groups()[1]
+ addr_name_dict[int(m_addr, 16)] = m_name
+
+ return addr_name_dict
+
+
+def match_dwarf_to_name(dwarfdump, addr_name_dict):
+ """
+ Parse dwarf dump, and match names to blocks using the dict from the objdump.
+ Return a list of NameDwarfPairs.
+ The matched lines look like this:
+ 000123 000456 000789 FDE cie=000000 pc=0123abc...0456def
+ We do not have the function name for this, only the PC range it applies to.
+ We match the pc=0123abc (the start address), and find the matching name from
+ the addr_name_dict.
+ The resultint NameDwarfPair will hold the lines this header applied to, and
+ instead of the header with the addresses, it will just have the function name.
+ """
+ re_address_line = re.compile(r".*pc=([0-9a-fA-F]+)\.\.\.([0-9a-fA-F]+)")
+ with open(dwarfdump, "r") as dw:
+ functions = []
+ for line in dw.readlines():
+ match = re_address_line.match(line)
+ if not match:
+ if len(functions) > 0:
+ functions[-1].append(line)
+ continue
+ pc_start_address = match.groups()[0]
+ name = addr_name_dict.get(int(pc_start_address, 16))
+ functions.append(NameDwarfPair(name, ""))
+
+ return functions
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("objdump", help="Objdump file")
+ parser.add_argument(
+ "dwarfdump", help="dwarf dump file created with 'llvm-objdump --dwarf=frames'"
+ )
+ parser.add_argument("function", help="Function to search CFIs in.")
+
+ args = parser.parse_args()
+
+ addr_name_dict = extract_function_addresses(args.objdump)
+ functions = match_dwarf_to_name(args.dwarfdump, addr_name_dict)
+
+ for f in functions:
+ if f.name == args.function:
+ f.parse_negate_offsets()
+ print(f.negate_offsets)
+ break
+ else:
+ print(f"{args.function} not found")
+ exit(-1)
+
+
+main()
>From 9b53bc343a799e379a806287b764fc6197523d9e Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Fri, 23 May 2025 14:11:47 +0200
Subject: [PATCH 5/9] [BOLT] Basic exception unwinding test
---
bolt/test/runtime/AArch64/negate-ra-state.cpp | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 bolt/test/runtime/AArch64/negate-ra-state.cpp
diff --git a/bolt/test/runtime/AArch64/negate-ra-state.cpp b/bolt/test/runtime/AArch64/negate-ra-state.cpp
new file mode 100644
index 0000000000000..60b0b08950b58
--- /dev/null
+++ b/bolt/test/runtime/AArch64/negate-ra-state.cpp
@@ -0,0 +1,26 @@
+// REQUIRES: system-linux,bolt-runtime
+
+// RUN: %clangxx --target=aarch64-unknown-linux-gnu \
+// RUN: -mbranch-protection=pac-ret -Wl,-q %s -o %t.exe
+// RUN: llvm-bolt %t.exe -o %t.bolt.exe
+// RUN: %t.bolt.exe | FileCheck %s
+
+// CHECK: Exception caught: Exception from bar().
+
+#include <cstdio>
+#include <stdexcept>
+
+void bar() { throw std::runtime_error("Exception from bar()."); }
+
+void foo() {
+ try {
+ bar();
+ } catch (const std::exception &e) {
+ printf("Exception caught: %s\n", e.what());
+ }
+}
+
+int main() {
+ foo();
+ return 0;
+}
>From cf8684765c8d8b81f7a4553f2f12debede5f696f Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Tue, 29 Jul 2025 14:28:34 +0000
Subject: [PATCH 6/9] [BOLT] Add OpNegateRAState to printCFI
---
bolt/lib/Core/BinaryContext.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 84f1853469709..c8f623c2374bc 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1865,6 +1865,9 @@ void BinaryContext::printCFI(raw_ostream &OS, const MCCFIInstruction &Inst) {
case MCCFIInstruction::OpGnuArgsSize:
OS << "OpGnuArgsSize";
break;
+ case MCCFIInstruction::OpNegateRAState:
+ OS << "OpNegateRAState";
+ break;
default:
OS << "Op#" << Operation;
break;
>From f605baf0fd52184a57ebf41baefcfa7991b95ff8 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Tue, 29 Jul 2025 14:28:57 +0000
Subject: [PATCH 7/9] [BOLT] Improve function splitting at OpNegateRAState
handling
- Previous version used the Layout API which should not be used.
- This version iterates on Fragments to have the same effect.
- NegateRAState is added to the first *non-empty* BB of the cold fragment.
---
bolt/lib/Passes/InsertNegateRAStatePass.cpp | 44 +++++++++++----------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/bolt/lib/Passes/InsertNegateRAStatePass.cpp b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
index 05d4d58c7e4cf..0eb0e3aef00d4 100644
--- a/bolt/lib/Passes/InsertNegateRAStatePass.cpp
+++ b/bolt/lib/Passes/InsertNegateRAStatePass.cpp
@@ -43,27 +43,32 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
fixUnknownStates(BF);
- bool FirstIter = true;
- MCInst PrevInst;
- BinaryBasicBlock *PrevBB = nullptr;
- // We need to iterate on BBs in the Layout order
- // not in the order they are stored in the BF class.
- auto *Begin = BF.getLayout().block_begin();
- auto *End = BF.getLayout().block_end();
- for (auto *BB = Begin; BB != End; BB++) {
-
- // Support for function splitting:
- // if two consecutive BBs with Signed state are going to end up in different
- // functions, we have to add a OpNegateRAState to the beginning of the newly
- // split function, so it starts with a Signed state.
- if (PrevBB != nullptr &&
- PrevBB->getFragmentNum() != (*BB)->getFragmentNum() &&
- BC.MIB->isRASigned(*((*BB)->begin()))) {
- BF.addCFIInstruction(*BB, (*BB)->begin(),
+ // 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
+ // OpNegateRAState to the beginning of the newly split function, so it starts
+ // with a Signed state.
+ for (FunctionFragment &FF : BF.getLayout().fragments()) {
+ // Find the first BB in the FF which has Instructions.
+ // BOLT can generate empty BBs at function splitting which are only used as
+ // target labels. We should add the negate-ra-state CFI to the first
+ // non-empty BB.
+ auto FirstNonEmpty =
+ std::find_if(FF.begin(), FF.end(), [](BinaryBasicBlock *BB) {
+ // getFirstNonPseudo returns BB.end() if it does not find any
+ // Instructions.
+ return BB->getFirstNonPseudo() != BB->end();
+ });
+ if (BC.MIB->isRASigned(*((*FirstNonEmpty)->begin()))) {
+ BF.addCFIInstruction(*FirstNonEmpty, (*FirstNonEmpty)->begin(),
MCCFIInstruction::createNegateRAState(nullptr));
}
+ }
- for (auto It = (*BB)->begin(); It != (*BB)->end(); ++It) {
+ bool FirstIter = true;
+ MCInst PrevInst;
+ for (BinaryBasicBlock &BB : BF) {
+ for (auto It = BB.begin(); It != BB.end(); ++It) {
MCInst &Inst = *It;
if (BC.MIB->isCFI(Inst))
@@ -76,7 +81,7 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
(BC.MIB->isRAUnsigned(PrevInst) && BC.MIB->isRASigned(Inst))) {
It = BF.addCFIInstruction(
- *BB, It, MCCFIInstruction::createNegateRAState(nullptr));
+ &BB, It, MCCFIInstruction::createNegateRAState(nullptr));
}
} else {
@@ -84,7 +89,6 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
}
PrevInst = *It;
}
- PrevBB = *BB;
}
}
>From 12a04c734479b9eefa427ead5e6ec8ff2c892578 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Thu, 31 Jul 2025 08:42:06 +0000
Subject: [PATCH 8/9] [BOLT] Bugfix: CFIs can be placed before the first
Instruction
- This caused a crash when trying to Annotate RAState-changing CFIs
(RememberState, RestoreState, NegateRAState).
- The fix introduces an InitialRAState for each BinaryFunction.
- If we have a NegateRAState before the first Instr, we set that to
True.
- In MarkRAStates, we push the InitialRAState to the RAStateStack: as we
may have omitted the RememberState at the function start, its RestoreState
pair would try to pop an empty stack otherwise.
---
bolt/include/bolt/Core/BinaryFunction.h | 5 +++++
bolt/lib/Core/Exceptions.cpp | 21 ++++++++++++++++-----
bolt/lib/Passes/MarkRAStates.cpp | 3 ++-
3 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index ebe12929c0973..c797f9e2ac71a 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -147,6 +147,9 @@ class BinaryFunction {
PF_MEMEVENT = 4, /// Profile has mem events.
};
+ void setInitialRAState(bool State) { InitialRAState = State; }
+ bool getInitialRAState() { return InitialRAState; }
+
/// Struct for tracking exception handling ranges.
struct CallSite {
const MCSymbol *Start;
@@ -220,6 +223,8 @@ class BinaryFunction {
/// Current state of the function.
State CurrentState{State::Empty};
+ bool InitialRAState{false};
+
/// A list of symbols associated with the function entry point.
///
/// Multiple symbols would typically result from identical code-folding
diff --git a/bolt/lib/Core/Exceptions.cpp b/bolt/lib/Core/Exceptions.cpp
index 63b7ad43b1dec..954c21e14ba17 100644
--- a/bolt/lib/Core/Exceptions.cpp
+++ b/bolt/lib/Core/Exceptions.cpp
@@ -569,20 +569,24 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
Function.addCFIInstruction(
Offset, MCCFIInstruction::createRememberState(nullptr));
- if (Function.getBinaryContext().isAArch64())
+ if (Function.getBinaryContext().isAArch64()) {
// Support for pointer authentication:
// We need to annotate instructions that modify the RA State, to work
// out the state of each instruction in MarkRAStates Pass.
- Function.setInstModifiesRAState(DW_CFA_remember_state, Offset);
+ if (Offset != 0)
+ Function.setInstModifiesRAState(DW_CFA_remember_state, Offset);
+ }
break;
case DW_CFA_restore_state:
Function.addCFIInstruction(Offset,
MCCFIInstruction::createRestoreState(nullptr));
- if (Function.getBinaryContext().isAArch64())
+ if (Function.getBinaryContext().isAArch64()) {
// Support for pointer authentication:
// We need to annotate instructions that modify the RA State, to work
// out the state of each instruction in MarkRAStates Pass.
- Function.setInstModifiesRAState(DW_CFA_restore_state, Offset);
+ if (Offset != 0)
+ Function.setInstModifiesRAState(DW_CFA_restore_state, Offset);
+ }
break;
case DW_CFA_def_cfa:
Function.addCFIInstruction(
@@ -649,7 +653,14 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
// is added to the instruction, to mark that the instruction modifies
// the RA State. The actual state for instructions are worked out in
// MarkRAStates based on these annotations.
- Function.setInstModifiesRAState(DW_CFA_AARCH64_negate_ra_state, Offset);
+ if (Offset != 0)
+ Function.setInstModifiesRAState(DW_CFA_AARCH64_negate_ra_state,
+ Offset);
+ else
+ // We cannot Annotate an instruction at Offset == 0.
+ // Instead, we save the initial (Signed) state, and push it to
+ // MarkRAStates' RAStateStack.
+ Function.setInitialRAState(true);
break;
}
if (opts::Verbosity >= 1)
diff --git a/bolt/lib/Passes/MarkRAStates.cpp b/bolt/lib/Passes/MarkRAStates.cpp
index 8121fffb93c9f..d7db5532002bf 100644
--- a/bolt/lib/Passes/MarkRAStates.cpp
+++ b/bolt/lib/Passes/MarkRAStates.cpp
@@ -62,8 +62,9 @@ void MarkRAStates::runOnFunction(BinaryFunction &BF) {
}
}
- bool RAState = false;
+ bool RAState = BF.getInitialRAState();
std::stack<bool> RAStateStack;
+ RAStateStack.push(RAState);
for (BinaryBasicBlock &BB : BF) {
for (auto It = BB.begin(); It != BB.end(); ++It) {
>From 0cfab0c5590be356032dd4afed5f3fbce5d51067 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Fri, 1 Aug 2025 08:21:17 +0000
Subject: [PATCH 9/9] [BOLT] Add function-splitting test
---
.../runtime/AArch64/pacret-function-split.s | 333 ++++++++++++++++++
1 file changed, 333 insertions(+)
create mode 100644 bolt/test/runtime/AArch64/pacret-function-split.s
diff --git a/bolt/test/runtime/AArch64/pacret-function-split.s b/bolt/test/runtime/AArch64/pacret-function-split.s
new file mode 100644
index 0000000000000..58917e107a5db
--- /dev/null
+++ b/bolt/test/runtime/AArch64/pacret-function-split.s
@@ -0,0 +1,333 @@
+# This test check that the negate-ra-state CFIs are properly emitted in case of function splitting.
+# The test checks two things:
+# - we split at the correct location: to test the feature,
+# we need to split *before* the bl __cxa_throw at PLT call is made,
+# so the unwinder has to unwind from the split (cold) part.
+#
+# - the BOLTed binary runs, and returns the string from foo.
+
+# REQUIRES: system-linux,bolt-runtime
+
+# RUN: %clangxx --target=aarch64-unknown-linux-gnu %s -o %t.exe -Wl,-q
+# RUN: link_fdata %s %t.exe %t.fdata
+# RUN: llvm-bolt %t.exe -o %t.bolt --split-functions --split-eh --split-strategy=profile2 \
+# RUN: --split-all-cold --print-split --print-only=_Z3foov --data=%t.fdata 2>&1 | FileCheck --check-prefix=BOLT-CHECK %s
+# RUN: %t.bolt | FileCheck %s --check-prefix=RUN-CHECK
+
+# BOLT-CHECK: ------- HOT-COLD SPLIT POINT -------
+# BOLT-CHECK-EMPTY:
+# BOLT-CHECK-NEXT: .Ltmp6
+# BOLT-CHECK-NEXT: Exec Count
+# BOLT-CHECK-NEXT: CFI State
+# BOLT-CHECK-NEXT: Predecessors:
+# BOLT-CHECK-NEXT: ldr
+# BOLT-CHECK-NEXT: adrp
+# BOLT-CHECK-NEXT: ldr
+# BOLT-CHECK-NEXT: adrp
+# BOLT-CHECK-NEXT: ldr
+# BOLT-CHECK-NEXT: bl __cxa_throw at PLT
+
+# RUN-CHECK: Exception caught: Exception from foo().
+
+# Source for the assembly:
+#
+# #include <cstdio>
+# #include <stdexcept>
+#
+# void foo() { throw std::runtime_error("Exception from foo()."); }
+#
+# int main() {
+# try {
+# foo();
+# } catch (const std::exception &e) {
+# printf("Exception caught: %s\n", e.what());
+# }
+# return 0;
+# }
+
+ .text
+ .section .note.gnu.property,"a", at note
+ .p2align 3, 0x0
+ .word 4
+ .word 16
+ .word 5
+ .asciz "GNU"
+ .word 3221225472
+ .word 4
+ .word 2
+ .word 0
+.Lsec_end0:
+ .text
+ .globl _Z3foov // -- Begin function _Z3foov
+ .p2align 2
+ .type _Z3foov, at function
+_Z3foov: // @_Z3foov
+.Lfunc_begin0:
+ .cfi_startproc
+ .cfi_personality 156, DW.ref.__gxx_personality_v0
+ .cfi_lsda 28, .Lexception0
+// %bb.0:
+ hint #25
+ .cfi_negate_ra_state
+ sub sp, sp, #48
+ .cfi_def_cfa_offset 48
+ stp x29, x30, [sp, #32] // 16-byte Folded Spill
+ add x29, sp, #32
+ .cfi_def_cfa w29, 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ mov x0, #16 // =0x10
+ bl __cxa_allocate_exception
+ str x0, [sp, #8] // 8-byte Folded Spill
+.Ltmp0:
+ adrp x1, .L.str
+ add x1, x1, :lo12:.L.str
+ bl _ZNSt13runtime_errorC1EPKc
+.Ltmp1:
+ b .LBB0_1
+.LBB0_1:
+ ldr x0, [sp, #8] // 8-byte Folded Reload
+ adrp x1, :got:_ZTISt13runtime_error
+ ldr x1, [x1, :got_lo12:_ZTISt13runtime_error]
+ adrp x2, :got:_ZNSt13runtime_errorD1Ev
+ ldr x2, [x2, :got_lo12:_ZNSt13runtime_errorD1Ev]
+ bl __cxa_throw
+.LBB0_2:
+.Ltmp2:
+ mov x8, x0
+ ldr x0, [sp, #8] // 8-byte Folded Reload
+ stur x8, [x29, #-8]
+ mov w8, w1
+ stur w8, [x29, #-12]
+ bl __cxa_free_exception
+ b .LBB0_3
+.LBB0_3:
+ ldur x0, [x29, #-8]
+ bl _Unwind_Resume
+.Lfunc_end0:
+ .size _Z3foov, .Lfunc_end0-_Z3foov
+ .cfi_endproc
+ .section .gcc_except_table,"a", at progbits
+ .p2align 2, 0x0
+GCC_except_table0:
+.Lexception0:
+ .byte 255 // @LPStart Encoding = omit
+ .byte 255 // @TType Encoding = omit
+ .byte 1 // Call site Encoding = uleb128
+ .uleb128 .Lcst_end0-.Lcst_begin0
+.Lcst_begin0:
+ .uleb128 .Lfunc_begin0-.Lfunc_begin0 // >> Call Site 1 <<
+ .uleb128 .Ltmp0-.Lfunc_begin0 // Call between .Lfunc_begin0 and .Ltmp0
+ .byte 0 // has no landing pad
+ .byte 0 // On action: cleanup
+ .uleb128 .Ltmp0-.Lfunc_begin0 // >> Call Site 2 <<
+ .uleb128 .Ltmp1-.Ltmp0 // Call between .Ltmp0 and .Ltmp1
+ .uleb128 .Ltmp2-.Lfunc_begin0 // jumps to .Ltmp2
+ .byte 0 // On action: cleanup
+ .uleb128 .Ltmp1-.Lfunc_begin0 // >> Call Site 3 <<
+ .uleb128 .Lfunc_end0-.Ltmp1 // Call between .Ltmp1 and .Lfunc_end0
+ .byte 0 // has no landing pad
+ .byte 0 // On action: cleanup
+.Lcst_end0:
+ .p2align 2, 0x0
+ // -- End function
+ .text
+ .globl main // -- Begin function main
+ .p2align 2
+ .type main, at function
+main: // @main
+.Lfunc_begin1:
+ .cfi_startproc
+ .cfi_personality 156, DW.ref.__gxx_personality_v0
+ .cfi_lsda 28, .Lexception1
+// %bb.0:
+ hint #25
+ .cfi_negate_ra_state
+ sub sp, sp, #48
+ .cfi_def_cfa_offset 48
+ stp x29, x30, [sp, #32] // 16-byte Folded Spill
+ add x29, sp, #32
+ .cfi_def_cfa w29, 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ .cfi_remember_state
+ stur wzr, [x29, #-4]
+.Ltmp3:
+L3:
+# FDATA: 1 main #L3# 1 _Z3foov 0 0 1
+ bl _Z3foov
+.Ltmp4:
+ b .LBB1_1
+.LBB1_1:
+ b .LBB1_6
+.LBB1_2:
+.Ltmp5:
+ str x0, [sp, #16]
+ mov w8, w1
+ str w8, [sp, #12]
+ b .LBB1_3
+.LBB1_3:
+ ldr w8, [sp, #12]
+ subs w8, w8, #1
+ b.ne .LBB1_9
+ b .LBB1_4
+.LBB1_4:
+ ldr x0, [sp, #16]
+ bl __cxa_begin_catch
+ str x0, [sp]
+ ldr x0, [sp]
+ ldr x8, [x0]
+ ldr x8, [x8, #16]
+ blr x8
+ mov x1, x0
+.Ltmp6:
+ adrp x0, .L.str.1
+ add x0, x0, :lo12:.L.str.1
+ bl printf
+.Ltmp7:
+ b .LBB1_5
+.LBB1_5:
+ bl __cxa_end_catch
+ b .LBB1_6
+.LBB1_6:
+ mov w0, wzr
+ .cfi_def_cfa wsp, 48
+ ldp x29, x30, [sp, #32] // 16-byte Folded Reload
+ add sp, sp, #48
+ .cfi_def_cfa_offset 0
+ hint #29
+ .cfi_negate_ra_state
+ .cfi_restore w30
+ .cfi_restore w29
+ ret
+.LBB1_7:
+ .cfi_restore_state
+.Ltmp8:
+ str x0, [sp, #16]
+ mov w8, w1
+ str w8, [sp, #12]
+.Ltmp9:
+ bl __cxa_end_catch
+.Ltmp10:
+ b .LBB1_8
+.LBB1_8:
+ b .LBB1_9
+.LBB1_9:
+ ldr x0, [sp, #16]
+ bl _Unwind_Resume
+.LBB1_10:
+.Ltmp11:
+ bl __clang_call_terminate
+.Lfunc_end1:
+ .size main, .Lfunc_end1-main
+ .cfi_endproc
+ .section .gcc_except_table,"a", at progbits
+ .p2align 2, 0x0
+GCC_except_table1:
+.Lexception1:
+ .byte 255 // @LPStart Encoding = omit
+ .byte 156 // @TType Encoding = indirect pcrel sdata8
+ .uleb128 .Lttbase0-.Lttbaseref0
+.Lttbaseref0:
+ .byte 1 // Call site Encoding = uleb128
+ .uleb128 .Lcst_end1-.Lcst_begin1
+.Lcst_begin1:
+ .uleb128 .Ltmp3-.Lfunc_begin1 // >> Call Site 1 <<
+ .uleb128 .Ltmp4-.Ltmp3 // Call between .Ltmp3 and .Ltmp4
+ .uleb128 .Ltmp5-.Lfunc_begin1 // jumps to .Ltmp5
+ .byte 1 // On action: 1
+ .uleb128 .Ltmp4-.Lfunc_begin1 // >> Call Site 2 <<
+ .uleb128 .Ltmp6-.Ltmp4 // Call between .Ltmp4 and .Ltmp6
+ .byte 0 // has no landing pad
+ .byte 0 // On action: cleanup
+ .uleb128 .Ltmp6-.Lfunc_begin1 // >> Call Site 3 <<
+ .uleb128 .Ltmp7-.Ltmp6 // Call between .Ltmp6 and .Ltmp7
+ .uleb128 .Ltmp8-.Lfunc_begin1 // jumps to .Ltmp8
+ .byte 0 // On action: cleanup
+ .uleb128 .Ltmp7-.Lfunc_begin1 // >> Call Site 4 <<
+ .uleb128 .Ltmp9-.Ltmp7 // Call between .Ltmp7 and .Ltmp9
+ .byte 0 // has no landing pad
+ .byte 0 // On action: cleanup
+ .uleb128 .Ltmp9-.Lfunc_begin1 // >> Call Site 5 <<
+ .uleb128 .Ltmp10-.Ltmp9 // Call between .Ltmp9 and .Ltmp10
+ .uleb128 .Ltmp11-.Lfunc_begin1 // jumps to .Ltmp11
+ .byte 3 // On action: 2
+ .uleb128 .Ltmp10-.Lfunc_begin1 // >> Call Site 6 <<
+ .uleb128 .Lfunc_end1-.Ltmp10 // Call between .Ltmp10 and .Lfunc_end1
+ .byte 0 // has no landing pad
+ .byte 0 // On action: cleanup
+.Lcst_end1:
+ .byte 1 // >> Action Record 1 <<
+ // Catch TypeInfo 1
+ .byte 0 // No further actions
+ .byte 2 // >> Action Record 2 <<
+ // Catch TypeInfo 2
+ .byte 0 // No further actions
+ .p2align 2, 0x0
+ // >> Catch TypeInfos <<
+ .xword 0 // TypeInfo 2
+.Ltmp12: // TypeInfo 1
+ .xword .L_ZTISt9exception.DW.stub-.Ltmp12
+.Lttbase0:
+ .p2align 2, 0x0
+ // -- End function
+ .section .text.__clang_call_terminate,"axG", at progbits,__clang_call_terminate,comdat
+ .hidden __clang_call_terminate // -- Begin function __clang_call_terminate
+ .weak __clang_call_terminate
+ .p2align 2
+ .type __clang_call_terminate, at function
+__clang_call_terminate: // @__clang_call_terminate
+ .cfi_startproc
+// %bb.0:
+ hint #25
+ .cfi_negate_ra_state
+ stp x29, x30, [sp, #-16]! // 16-byte Folded Spill
+ .cfi_def_cfa_offset 16
+ mov x29, sp
+ .cfi_def_cfa w29, 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ bl __cxa_begin_catch
+ bl _ZSt9terminatev
+.Lfunc_end2:
+ .size __clang_call_terminate, .Lfunc_end2-__clang_call_terminate
+ .cfi_endproc
+ // -- End function
+ .type .L.str, at object // @.str
+ .section .rodata.str1.1,"aMS", at progbits,1
+.L.str:
+ .asciz "Exception from foo()."
+ .size .L.str, 22
+
+ .type .L.str.1, at object // @.str.1
+.L.str.1:
+ .asciz "Exception caught: %s\n"
+ .size .L.str.1, 22
+
+ .data
+ .p2align 3, 0x0
+.L_ZTISt9exception.DW.stub:
+ .xword _ZTISt9exception
+ .hidden DW.ref.__gxx_personality_v0
+ .weak DW.ref.__gxx_personality_v0
+ .section .data.DW.ref.__gxx_personality_v0,"awG", at progbits,DW.ref.__gxx_personality_v0,comdat
+ .p2align 3, 0x0
+ .type DW.ref.__gxx_personality_v0, at object
+ .size DW.ref.__gxx_personality_v0, 8
+DW.ref.__gxx_personality_v0:
+ .xword __gxx_personality_v0
+ .section ".note.GNU-stack","", at progbits
+ .addrsig
+ .addrsig_sym _Z3foov
+ .addrsig_sym __cxa_allocate_exception
+ .addrsig_sym __gxx_personality_v0
+ .addrsig_sym __cxa_free_exception
+ .addrsig_sym __cxa_throw
+ .addrsig_sym __cxa_begin_catch
+ .addrsig_sym printf
+ .addrsig_sym __cxa_end_catch
+ .addrsig_sym __clang_call_terminate
+ .addrsig_sym _ZSt9terminatev
+ .addrsig_sym _Unwind_Resume
+ .addrsig_sym _ZTISt13runtime_error
+ .addrsig_sym _ZTISt9exception
More information about the llvm-commits
mailing list