[llvm] [BOLT] Replace partial instructions with nops in patched entries (PR #205211)
Fabian Parzefall via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 23 17:01:05 PDT 2026
https://github.com/pzfl updated https://github.com/llvm/llvm-project/pull/205211
>From c6602d1ad10078a6b17ebad26170a7f8f0e468fb Mon Sep 17 00:00:00 2001
From: Fabian Parzefall <parzefall at meta.com>
Date: Mon, 22 Jun 2026 15:38:19 -0700
Subject: [PATCH] [BOLT] Replace partial instructions with nops in patched
entries
Overwriting a function entry with a jump is likely to not perfectly
align with the instruction stream. If the end of the patch does not
fall onto a instruction boundary, the bytes following the jump are
orphaned and will have nonsensical interpretations. This can leave
other tools confused, especially since these orphaned bytes can decode
to instructions that do not nicely rejoin the still intact part of the
instructions stream. Overwrite these bytes with nops in the PatchEntry
pass.
---
bolt/include/bolt/Core/BinaryFunction.h | 7 +++++
bolt/include/bolt/Core/MCPlusBuilder.h | 5 +++
bolt/include/bolt/Passes/PatchEntries.h | 1 +
bolt/lib/Core/BinaryFunction.cpp | 21 +++++++++++++
bolt/lib/Passes/PatchEntries.cpp | 39 ++++++++++++++++++++++--
bolt/lib/Target/X86/X86MCPlusBuilder.cpp | 5 +++
bolt/test/X86/patch-entries.test | 9 ++++++
7 files changed, 84 insertions(+), 3 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index 4ae8cfe372855..6acc6a467837c 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -1025,6 +1025,13 @@ class BinaryFunction {
std::optional<MCInst> disassembleInstructionAtOffset(uint64_t Offset) const;
+ /// Given a starting point \p Offset and a number of bytes \p MinLength,
+ /// returns the number of bytes \p MinLength + Tail such that the last
+ /// instruction in the sequence is not split apart. Returns std::nullopt if
+ /// disassembling fails. Assumes that offsets aligns with instruction stream.
+ std::optional<uint64_t>
+ getInstructionSequenceLength(uint64_t Offset, uint64_t MinLength) const;
+
/// Return offset for the first instruction. If there is data at the
/// beginning of a function then offset of the first instruction could
/// be different from 0
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 76b4a5fe778c0..84b44a9ab5483 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -1995,6 +1995,11 @@ class MCPlusBuilder {
llvm_unreachable("not implemented");
}
+ /// Creates a breakpoint instruction in Inst.
+ virtual void createBreakpoint(MCInst &Inst) const {
+ llvm_unreachable("not implemented");
+ }
+
/// Creates an instruction to bump the stack pointer just like a call.
virtual void createStackPointerIncrement(MCInst &Inst, int Size = 8,
bool NoFlagsClobber = false) const {
diff --git a/bolt/include/bolt/Passes/PatchEntries.h b/bolt/include/bolt/Passes/PatchEntries.h
index 04ec9165c2ff2..2f58ce08ea2e0 100644
--- a/bolt/include/bolt/Passes/PatchEntries.h
+++ b/bolt/include/bolt/Passes/PatchEntries.h
@@ -26,6 +26,7 @@ class PatchEntries : public BinaryFunctionPass {
struct Patch {
const MCSymbol *Symbol;
uint64_t Address;
+ uint32_t PaddingAfter = 0;
};
public:
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 79e92b79f6fee..e5d03bdabc8aa 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1279,6 +1279,27 @@ BinaryFunction::disassembleInstructionAtOffset(uint64_t Offset) const {
return std::nullopt;
}
+std::optional<uint64_t>
+BinaryFunction::getInstructionSequenceLength(uint64_t Offset,
+ uint64_t MinLength) const {
+ assert(Offset + MinLength <= MaxSize && "Invalid offset / min length");
+ ErrorOr<ArrayRef<unsigned char>> FunctionData = getData();
+ assert(FunctionData && "Cannot get function as data");
+ uint64_t Current = Offset;
+ const uint64_t Target = Offset + MinLength;
+ while (Current < Target) {
+ MCInst Instr;
+ uint64_t InstrSize = 0;
+ const uint64_t InstrAddress = getAddress() + Target;
+ if (BC.DisAsm->getInstruction(
+ Instr, InstrSize, FunctionData->slice(Current), InstrAddress,
+ nulls()) == MCDisassembler::DecodeStatus::Fail)
+ return std::nullopt;
+ Current += InstrSize;
+ }
+ return Current - Offset;
+}
+
Error BinaryFunction::disassemble() {
NamedRegionTimer T("disassemble", "Disassemble function", "buildfuncs",
"Build Binary Functions", opts::TimeBuild);
diff --git a/bolt/lib/Passes/PatchEntries.cpp b/bolt/lib/Passes/PatchEntries.cpp
index 9af9e2ca3bdee..2d5825461751c 100644
--- a/bolt/lib/Passes/PatchEntries.cpp
+++ b/bolt/lib/Passes/PatchEntries.cpp
@@ -63,6 +63,12 @@ Error PatchEntries::runOnFunctions(BinaryContext &BC) {
BC.MIB->createLongTailCall(Seq, BC.Ctx->createTempSymbol(), BC.Ctx.get());
PatchSize = BC.computeCodeSize(Seq.begin(), Seq.end());
}
+ static size_t FillerSize = 0;
+ if (BC.isX86() && FillerSize == 0) {
+ std::array<MCInst, 1> Seq;
+ BC.MIB->createBreakpoint(Seq[0]);
+ FillerSize = BC.computeCodeSize(Seq.begin(), Seq.end());
+ }
for (auto &BFI : BC.getBinaryFunctions()) {
BinaryFunction &Function = BFI.second;
@@ -91,8 +97,6 @@ Error PatchEntries::runOnFunctions(BinaryContext &BC) {
return false;
}
- PendingPatches.emplace_back(
- Patch{Symbol, Function.getAddress() + Offset});
NextValidByte = Offset + PatchSize;
if (NextValidByte > Function.getMaxSize()) {
if (opts::Verbosity >= 1)
@@ -101,6 +105,24 @@ Error PatchEntries::runOnFunctions(BinaryContext &BC) {
return false;
}
+ const uint64_t PatchAddress = Function.getAddress() + Offset;
+ Patch P{Symbol, PatchAddress};
+
+ if (BC.isX86()) {
+ std::optional<uint64_t> OverwriteLength =
+ Function.getInstructionSequenceLength(Offset, PatchSize);
+ assert(OverwriteLength.has_value() &&
+ "Disassembly of entry sequence cannot fail during opt passes");
+ P.PaddingAfter = *OverwriteLength - PatchSize;
+ assert(PendingPatches.empty() ||
+ (PendingPatches.back().Address + PatchSize +
+ PendingPatches.back().PaddingAfter <=
+ PatchAddress) &&
+ "Entry point cannot overlap with instruction stream of "
+ "previous entrypoint.");
+ }
+
+ PendingPatches.emplace_back(P);
return true;
});
@@ -117,6 +139,16 @@ Error PatchEntries::runOnFunctions(BinaryContext &BC) {
// Add instruction patch to the binary.
InstructionListType Instructions;
BC.MIB->createLongTailCall(Instructions, Patch.Symbol, BC.Ctx.get());
+
+ if (BC.isX86()) {
+ assert(Patch.PaddingAfter % FillerSize == 0 &&
+ "Padding must be multiple of filler size.");
+ llvm::MCInst Inst;
+ BC.MIB->createBreakpoint(Inst);
+ Instructions.resize(
+ Instructions.size() + Patch.PaddingAfter / FillerSize, Inst);
+ }
+
BinaryFunction *PatchFunction = BC.createInstructionPatch(
Patch.Address, Instructions,
NameResolver::append(Patch.Symbol->getName(), ".org.0"));
@@ -128,7 +160,8 @@ Error PatchEntries::runOnFunctions(BinaryContext &BC) {
uint64_t HotSize, ColdSize;
std::tie(HotSize, ColdSize) = BC.calculateEmittedSize(*PatchFunction);
assert(!ColdSize && "unexpected cold code");
- assert(HotSize <= PatchSize && "max patch size exceeded");
+ assert(HotSize <= PatchSize + Patch.PaddingAfter &&
+ "max patch size exceeded");
}
}
return Error::success();
diff --git a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
index 923de64be58c8..11a297f514530 100644
--- a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
+++ b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
@@ -2788,6 +2788,11 @@ class X86MCPlusBuilder : public MCPlusBuilder {
Inst.setOpcode(X86::TRAP);
}
+ void createBreakpoint(MCInst &Inst) const override {
+ Inst.clear();
+ Inst.setOpcode(X86::INT3);
+ }
+
void createCondBranch(MCInst &Inst, const MCSymbol *Target, unsigned CC,
MCContext *Ctx) const override {
Inst.setOpcode(X86::JCC_1);
diff --git a/bolt/test/X86/patch-entries.test b/bolt/test/X86/patch-entries.test
index bf31af342dc61..6a7f3af30e80e 100644
--- a/bolt/test/X86/patch-entries.test
+++ b/bolt/test/X86/patch-entries.test
@@ -29,3 +29,12 @@ CHECK-FOO: 0000000000[[#%x,ORG:]] [[#%x,ORGSIZE:]] t foo.org.0
CHECK-FOO: FDE {{.*}} pc=00[[#%x,ORG]]...00[[#%x,ORG+ORGSIZE]]
## original FDE comes second
CHECK-FOO: FDE {{.*}} pc=00[[#%x,ORG]]...00[[#%x,ORG+OPTSIZE]]
+
+## Check that incomplete instructions are replaced with int3:
+RUN: llvm-objdump %t.out --disassemble-symbols=main.org.0 \
+RUN: | FileCheck %s --check-prefix=CHECK-NOP
+CHECK-NOP: main.org.0
+CHECK-NOP-NEXT: jmp
+CHECK-NOP-NEXT: int3
+CHECK-NOP-NEXT: int3
+CHECK-NOP-NEXT: int3
More information about the llvm-commits
mailing list