diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h index a52998564ee1b..97bcde39e5e5f 100644 --- a/bolt/include/bolt/Core/BinaryFunction.h +++ b/bolt/include/bolt/Core/BinaryFunction.h @@ -1621,6 +1621,51 @@ class BinaryFunction { void setHasInferredProfile(bool Inferred) { HasInferredProfile = Inferred; } + /// Find corrected offset the same way addCFIInstruction does it to skip NOPs. + std::optional 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 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 cf37a984da93f..14a327d3c4fb4 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -67,6 +67,20 @@ class MCPlusBuilder { public: using AllocatorIdTy = uint16_t; + std::optional getAnnotationAtOpIndex(const MCInst &Inst, + unsigned OpIndex) const { + std::optional 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 { @@ -561,11 +575,30 @@ class MCPlusBuilder { return {}; } + virtual ErrorOr getSignedReg(const MCInst &Inst) const { + llvm_unreachable("not implemented"); + return getNoRegister(); + } virtual ErrorOr getAuthenticatedReg(const MCInst &Inst) const { llvm_unreachable("not implemented"); return getNoRegister(); } + 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; + } + virtual bool isAuthenticationOfReg(const MCInst &Inst, MCPhysReg AuthenticatedReg) const { llvm_unreachable("not implemented"); @@ -1200,6 +1233,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..ce73b5a152d12 --- /dev/null +++ b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h @@ -0,0 +1,46 @@ +//===- 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 + +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, + /// 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, + /// 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/include/bolt/Utils/CommandLineOpts.h b/bolt/include/bolt/Utils/CommandLineOpts.h index 3de945f6a1507..34e96dadf33d1 100644 --- a/bolt/include/bolt/Utils/CommandLineOpts.h +++ b/bolt/include/bolt/Utils/CommandLineOpts.h @@ -53,6 +53,7 @@ extern llvm::cl::opt OutputFilename; extern llvm::cl::opt PerfData; extern llvm::cl::opt PrintCacheMetrics; extern llvm::cl::opt PrintSections; +extern llvm::cl::opt AllowPacret; // The format to use with -o in aggregation mode (perf2bolt) enum ProfileFormatKind { PF_Fdata, PF_YAML }; 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 9773e21aa7522..fa0f171a8b207 100644 --- a/bolt/lib/Core/BinaryFunction.cpp +++ b/bolt/lib/Core/BinaryFunction.cpp @@ -64,6 +64,7 @@ extern cl::opt Instrument; extern cl::opt StrictMode; extern cl::opt UpdateDebugSections; extern cl::opt Verbosity; +extern cl::opt AllowPacret; extern bool BinaryAnalysisMode; extern bool HeatmapMode; @@ -183,6 +184,15 @@ template static bool emptyRange(const R &Range) { return Range.begin() == Range.end(); } +static void checkFlagsForPacRet() { + if (!(opts::BinaryAnalysisMode || opts::HeatmapMode || opts::AllowPacret)) { + llvm_unreachable( + "BOLT-ERROR: support for binaries using pac-ret hardening (e.g. as " + "produced by '-mbranch-protection=pac-ret') is experimental\n" + "BOLT-ERROR: set --allow-experimental-pacret to allow processing"); + } +} + /// Gets debug line information for the instruction located at the given /// address in the original binary. The SMLoc's pointer is used /// to point to this information, which is represented by a @@ -2769,11 +2779,7 @@ struct CFISnapshot { 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."); - } + checkFlagsForPacRet(); break; case MCCFIInstruction::OpRememberState: case MCCFIInstruction::OpRestoreState: @@ -2915,11 +2921,7 @@ struct CFISnapshotDiff : public CFISnapshot { 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."); - } + checkFlagsForPacRet(); break; case MCCFIInstruction::OpRememberState: case MCCFIInstruction::OpRestoreState: @@ -3072,11 +3074,7 @@ BinaryFunction::unwindCFIState(int32_t FromState, int32_t ToState, 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."); - } + checkFlagsForPacRet(); break; case MCCFIInstruction::OpGnuArgsSize: // do not affect CFI state diff --git a/bolt/lib/Core/Exceptions.cpp b/bolt/lib/Core/Exceptions.cpp index 0b2e63b8ca6a7..d2cdb7b28272c 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,9 +640,22 @@ 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()) { + // 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); + // To have the --allow-experimental-pacret flag, we have to add the + // OpNegateRAState CFI, and remove it later in MarkRAStates. Unittests + // on AArch64 would be broken otherwise, as some AArch64 platforms will + // have pac-ret for linker inserted functions, e.g. + // __do_global_dtors_aux. The user cannot remove the + // .cfi_negate_ra_state from such functions. Function.addCFIInstruction( Offset, MCCFIInstruction::createNegateRAState(nullptr)); break; diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp index 7752079b61538..4b23f1056c511 100644 --- a/bolt/lib/Core/MCPlusBuilder.cpp +++ b/bolt/lib/Core/MCPlusBuilder.cpp @@ -147,6 +147,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 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..05d4d58c7e4cf --- /dev/null +++ b/bolt/lib/Passes/InsertNegateRAStatePass.cpp @@ -0,0 +1,147 @@ +//===- 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 +#include +#include + +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; + // 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(), + MCCFIInstruction::createNegateRAState(nullptr)); + } + + for (auto It = (*BB)->begin(); It != (*BB)->end(); ++It) { + + MCInst &Inst = *It; + if (BC.MIB->isCFI(Inst)) + 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))) { + + 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->isPSignOnLR(Inst) || + (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(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..4cdeb42240f7f --- /dev/null +++ b/bolt/lib/Passes/MarkRAStates.cpp @@ -0,0 +1,159 @@ +//===- 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 +#include +#include + +#include +#include +#include + +using namespace llvm; + +namespace llvm { +namespace bolt { + +void MarkRAStates::runOnFunction(BinaryFunction &BF) { + + if (BF.isIgnored()) + return; + + BinaryContext &BC = BF.getBinaryContext(); + + // Because of the --allow-experimental-pacret flag, + // we cannot remove all OpNegateRAStates at FillCFIInfoFor, + // but we still need to remove them here, because their pre-optimization + // locations would be incorrect after optimizations. + std::vector Blocks(BF.pbegin(), BF.pend()); + for (BinaryBasicBlock *BB : Blocks) { + for (auto II = BB->begin(); II != BB->end();) { + MCInst &Instr = *II; + if (BC.MIB->isCFI(Instr)) { + const MCCFIInstruction *CFI = BF.getCFIFor(Instr); + if (CFI->getOperation() == MCCFIInstruction::OpNegateRAState) { + II = BB->erasePseudoInstruction(II); + continue; + } + } + ++II; + } + } + + for (BinaryBasicBlock &BB : BF) { + for (auto It = BB.begin(); It != BB.end(); ++It) { + MCInst &Inst = *It; + 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 + // 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"; + BC.outs() + << "BOLT-INFO: ptr sign/auth inst without .cfi_negate_ra_state\n"; + return; + } + } + } + + bool RAState = false; + std::stack 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->isPSignOnLR(Inst)) { + if (RAState) { + // RA signing instructions should only follow unsigned RA state. + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() << "\n"; + BC.outs() << "BOLT-INFO: ptr signing inst encountered in Signed RA " + "state.\n"; + BF.setIgnored(); + return; + } + BC.MIB->setRASigning(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 " + << BF.getPrintName() << "\n"; + BC.outs() << "BOLT-INFO: ptr authenticating inst encountered in " + "Unsigned RA state.\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 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 dd48653931eb9..ac50168ff67a8 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()); + Manager.registerPass( std::make_unique(PrintEstimateEdgeCounts)); @@ -505,6 +510,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(PrintLongJmp)); + + Manager.registerPass(std::make_unique()); } // This pass should always run last.* diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp index 1c24200059035..2aed0560b4acc 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -700,6 +700,12 @@ Error RewriteInstance::run() { adjustCommandLineOptions(); discoverFileObjects(); + if (opts::Instrument && opts::AllowPacret) { + BC->errs() << "BOLT-ERROR: Instrumenting binaries with pac-ret hardening " + "is not supported.\n"; + exit(1); + } + if (opts::Instrument && !BC->IsStaticExecutable) if (Error E = discoverRtFiniAddress()) return E; diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index e00d6a18b0f6c..e911c2835857b 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -196,6 +196,37 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { return {AArch64::LR}; } + ErrorOr getSignedReg(const MCInst &Inst) const override { + switch (Inst.getOpcode()) { + case AArch64::PACIAZ: + case AArch64::PACIBZ: + case AArch64::PACIASP: + case AArch64::PACIBSP: + case AArch64::PACIASPPC: + case AArch64::PACIBSPPC: + return AArch64::LR; + + case AArch64::PACIA1716: + case AArch64::PACIB1716: + case AArch64::PACIA171615: + case AArch64::PACIB171615: + return AArch64::X17; + + case AArch64::PACIA: + case AArch64::PACIB: + case AArch64::PACDA: + case AArch64::PACDB: + case AArch64::PACIZA: + case AArch64::PACIZB: + case AArch64::PACDZA: + case AArch64::PACDZB: + return Inst.getOperand(0).getReg(); + + default: + return getNoRegister(); + } + } + ErrorOr getAuthenticatedReg(const MCInst &Inst) const override { switch (Inst.getOpcode()) { case AArch64::AUTIAZ: @@ -250,6 +281,25 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { } } + bool isPSignOnLR(const MCInst &Inst) const override { + ErrorOr SignReg = getSignedReg(Inst); + return SignReg && *SignReg != getNoRegister() && *SignReg == AArch64::LR; + } + + bool isPAuthOnLR(const MCInst &Inst) const override { + ErrorOr AutReg = getAuthenticatedReg(Inst); + return AutReg && *AutReg != getNoRegister() && *AutReg == 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; + } + bool isAuthenticationOfReg(const MCInst &Inst, MCPhysReg Reg) const override { if (Reg == getNoRegister()) return false; diff --git a/bolt/lib/Utils/CommandLineOpts.cpp b/bolt/lib/Utils/CommandLineOpts.cpp index ad714371436e0..4d90917e8c164 100644 --- a/bolt/lib/Utils/CommandLineOpts.cpp +++ b/bolt/lib/Utils/CommandLineOpts.cpp @@ -219,6 +219,11 @@ cl::opt cl::init(0), cl::ZeroOrMore, cl::cat(BoltCategory), cl::sub(cl::SubCommand::getAll())); +cl::opt AllowPacret( + "allow-experimental-pacret", + cl::desc("Enable processing binaries with pac-ret (experimental)"), + cl::init(false), cl::cat(BoltOptCategory)); + bool processAllFunctions() { if (opts::AggregateOnly) return false; 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 ":" %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,@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,@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/AArch64/pacret-instrument-flags.s b/bolt/test/AArch64/pacret-instrument-flags.s new file mode 100644 index 0000000000000..4abb149aa5d7a --- /dev/null +++ b/bolt/test/AArch64/pacret-instrument-flags.s @@ -0,0 +1,40 @@ +# Instrumenting binaries with pac-ret hardening is not supported. +# This test makes sure that BOLT will fail when ran with both the +# --allow-experimental-pacret and --instrument flags. + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q + +# RUN: not llvm-bolt %t.exe -o %t.bolt --allow-experimental-pacret --instrument 2>&1 | FileCheck %s + +# CHECK: BOLT-ERROR: Instrumenting binaries with pac-ret hardening is not supported. + + .text + .globl foo + .p2align 2 + .type foo,@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 + +.reloc 0, R_AARCH64_NONE 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 : + 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()