[llvm] [BOLT] Fix pwrite assertion failure when patching .dynsym at high offsets (PR #198569)
Jinjie Huang via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 2 06:38:39 PDT 2026
https://github.com/Jinjie-Huang updated https://github.com/llvm/llvm-project/pull/198569
>From 063aba38ea11eaf48abb19aac56ea178cfa7ee4a Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Tue, 19 May 2026 23:58:53 +0800
Subject: [PATCH 1/2] fix .dynsym pwrite problem
---
bolt/lib/Rewrite/RewriteInstance.cpp | 8 +++
bolt/test/X86/dynsym-pwrite.test | 89 ++++++++++++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 bolt/test/X86/dynsym-pwrite.test
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index cb5cba800a9d7..a6178bfff0ec8 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -5619,6 +5619,11 @@ void RewriteInstance::patchELFSymTabs(ELFObjectFile<ELFT> *File) {
assert((DynSymSection || BC->IsStaticExecutable) &&
"dynamic symbol table expected");
if (DynSymSection) {
+ uint64_t SavedPos = Out->os().tell();
+ uint64_t RequiredPos = DynSymSection->sh_offset + DynSymSection->sh_size;
+ if (SavedPos < RequiredPos)
+ Out->os().seek(RequiredPos);
+
updateELFSymbolTable(
File,
/*IsDynSym=*/true,
@@ -5630,6 +5635,9 @@ void RewriteInstance::patchELFSymTabs(ELFObjectFile<ELFT> *File) {
DynSymSection->sh_offset + Offset);
},
[](StringRef) -> size_t { return 0; });
+
+ if (SavedPos < RequiredPos)
+ Out->os().seek(SavedPos);
}
if (opts::RemoveSymtab)
diff --git a/bolt/test/X86/dynsym-pwrite.test b/bolt/test/X86/dynsym-pwrite.test
new file mode 100644
index 0000000000000..7885ba13fdfcc
--- /dev/null
+++ b/bolt/test/X86/dynsym-pwrite.test
@@ -0,0 +1,89 @@
+## Check that llvm-bolt can properly update the .dynsym section
+## when it is located at a high offset in the ELF file.
+
+
+# REQUIRES: system-linux
+
+# RUN: yaml2obj %s -o %t.so
+# RUN: llvm-bolt %t.so -o %t.so.bolt 2>&1 | FileCheck %s
+
+# CHECK-NOT: We don't support extending the stream
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_X86_64
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_R, PF_X ]
+ VAddr: 0x1000
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .eh_frame
+ - Type: PT_LOAD
+ Flags: [ PF_R, PF_W ]
+ VAddr: 0x2000
+ Align: 0x1000
+ FirstSec: .dynamic
+ LastSec: .dynsym
+ - Type: PT_DYNAMIC
+ Flags: [ PF_R, PF_W ]
+ VAddr: 0x2000
+ FirstSec: .dynamic
+ LastSec: .dynamic
+ - Type: PT_GNU_EH_FRAME
+ Flags: [ PF_R ]
+ VAddr: 0x1010
+ FirstSec: .eh_frame_hdr
+ LastSec: .eh_frame_hdr
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ AddressAlign: 0x1000
+ Content: 'C3' # ret
+ Size: 0x1
+ - Name: .eh_frame_hdr
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x1010
+ Content: '011B033B0C00000001000000F0FFFFFF28000000'
+ - Name: .eh_frame
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x1020
+ Content: '1400000000000000017A5200017810011B0C070890010000140000001C000000C0FFFFFF010000000000000000000000'
+ - Name: .dynamic
+ Type: SHT_DYNAMIC
+ Flags: [ SHF_ALLOC, SHF_WRITE ]
+ Address: 0x2000
+ AddressAlign: 0x1000
+ Entries:
+ - Tag: DT_SYMTAB
+ Value: 0x2100
+ - Tag: DT_NULL
+ Value: 0x0
+ - Name: .dynstr
+ Type: SHT_STRTAB
+ Flags: [ SHF_ALLOC ]
+ Address: 0x2050
+ - Name: .dynsym
+ Type: SHT_DYNSYM
+ Flags: [ SHF_ALLOC ]
+ Address: 0x2100
+ Link: .dynstr
+DynamicSymbols:
+ - Name: _DYNAMIC
+ Binding: STB_GLOBAL
+ Section: .dynamic
+Symbols:
+ - Name: _start
+ Section: .text
+ Value: 0x1000
+ Size: 0x1
+ Binding: STB_GLOBAL
+ Type: STT_FUNC
+...
>From ffa1abb1cb4b45bf87a895d0e518458e36eca476 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Tue, 2 Jun 2026 21:38:14 +0800
Subject: [PATCH 2/2] Implement safePWrite wrapper to generalize the usage of
pwrite in BOLT
---
bolt/include/bolt/Core/BinarySection.h | 2 +-
.../bolt/Rewrite/MachORewriteInstance.h | 3 +-
bolt/include/bolt/Utils/Utils.h | 3 ++
bolt/lib/Core/BinarySection.cpp | 12 ++---
bolt/lib/Rewrite/MachORewriteInstance.cpp | 14 +++---
bolt/lib/Rewrite/RewriteInstance.cpp | 45 +++++++------------
bolt/lib/Utils/Utils.cpp | 22 +++++++++
bolt/unittests/Core/BinaryContext.cpp | 31 +++++++++----
8 files changed, 79 insertions(+), 53 deletions(-)
diff --git a/bolt/include/bolt/Core/BinarySection.h b/bolt/include/bolt/Core/BinarySection.h
index 154a8d12de5ce..cafd55e7acf6c 100644
--- a/bolt/include/bolt/Core/BinarySection.h
+++ b/bolt/include/bolt/Core/BinarySection.h
@@ -478,7 +478,7 @@ class BinarySection {
/// Flush all pending relocations to patch original contents of sections
/// that were not emitted via MCStreamer.
- void flushPendingRelocations(raw_pwrite_stream &OS,
+ void flushPendingRelocations(raw_fd_ostream &OS,
SymbolResolverFuncTy Resolver);
/// Change contents of the section. Unless the section has a valid SectionID,
diff --git a/bolt/include/bolt/Rewrite/MachORewriteInstance.h b/bolt/include/bolt/Rewrite/MachORewriteInstance.h
index fc1dd12b7e06e..ef323aab6bc8d 100644
--- a/bolt/include/bolt/Rewrite/MachORewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/MachORewriteInstance.h
@@ -62,8 +62,7 @@ class MachORewriteInstance {
void runOptimizationPasses();
void emitAndLink();
- void writeInstrumentationSection(StringRef SectionName,
- raw_pwrite_stream &OS);
+ void writeInstrumentationSection(StringRef SectionName, raw_fd_ostream &OS);
void rewriteFile();
public:
diff --git a/bolt/include/bolt/Utils/Utils.h b/bolt/include/bolt/Utils/Utils.h
index 9baee7d94066d..3b4a17e919d00 100644
--- a/bolt/include/bolt/Utils/Utils.h
+++ b/bolt/include/bolt/Utils/Utils.h
@@ -76,6 +76,9 @@ std::optional<StringRef> getLTOCommonName(const StringRef Name);
// If the expression is defining the CFA, return std::nullopt.
std::optional<uint8_t> readDWARFExpressionTargetReg(StringRef ExprBytes);
+void safePWrite(raw_fd_ostream &OS, const char *Src, size_t Size,
+ uint64_t Offset);
+
} // namespace bolt
bool operator==(const llvm::MCCFIInstruction &L,
diff --git a/bolt/lib/Core/BinarySection.cpp b/bolt/lib/Core/BinarySection.cpp
index e803d17021f8b..a8620ba83ebfb 100644
--- a/bolt/lib/Core/BinarySection.cpp
+++ b/bolt/lib/Core/BinarySection.cpp
@@ -154,7 +154,7 @@ uint64_t BinarySection::write(raw_ostream &OS) const {
return getOutputSize();
}
-void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
+void BinarySection::flushPendingRelocations(raw_fd_ostream &OS,
SymbolResolverFuncTy Resolver) {
if (PendingRelocations.empty() && Patches.empty())
return;
@@ -174,8 +174,8 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
<< " offset: 0x" << Twine::utohexstr(SectionFileOffset) << '\n');
for (BinaryPatch &Patch : Patches)
- OS.pwrite(Patch.Bytes.data(), Patch.Bytes.size(),
- SectionFileOffset + Patch.Offset);
+ safePWrite(OS, Patch.Bytes.data(), Patch.Bytes.size(),
+ SectionFileOffset + Patch.Offset);
uint64_t SkippedPendingRelocations = 0;
for (Relocation &Reloc : PendingRelocations) {
@@ -194,9 +194,9 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
Value = Relocation::encodeValue(Reloc.Type, Value,
SectionAddress + Reloc.Offset);
- OS.pwrite(reinterpret_cast<const char *>(&Value),
- Relocation::getSizeForType(Reloc.Type),
- SectionFileOffset + Reloc.Offset);
+ safePWrite(OS, reinterpret_cast<const char *>(&Value),
+ Relocation::getSizeForType(Reloc.Type),
+ SectionFileOffset + Reloc.Offset);
LLVM_DEBUG(
dbgs() << "BOLT-DEBUG: writing value 0x" << Twine::utohexstr(Value)
diff --git a/bolt/lib/Rewrite/MachORewriteInstance.cpp b/bolt/lib/Rewrite/MachORewriteInstance.cpp
index 17f726ae13945..855ae437f0399 100644
--- a/bolt/lib/Rewrite/MachORewriteInstance.cpp
+++ b/bolt/lib/Rewrite/MachORewriteInstance.cpp
@@ -483,7 +483,7 @@ void MachORewriteInstance::emitAndLink() {
}
void MachORewriteInstance::writeInstrumentationSection(StringRef SectionName,
- raw_pwrite_stream &OS) {
+ raw_fd_ostream &OS) {
if (!opts::Instrument)
return;
ErrorOr<BinarySection &> Section = BC->getUniqueSectionByName(SectionName);
@@ -497,8 +497,8 @@ void MachORewriteInstance::writeInstrumentationSection(StringRef SectionName,
"Section input offset cannot be zero");
assert(Section->getAllocAddress() && "Section alloc address cannot be zero");
assert(Section->getOutputSize() && "Section output size cannot be zero");
- OS.pwrite(reinterpret_cast<char *>(Section->getAllocAddress()),
- Section->getOutputSize(), Section->getInputFileOffset());
+ safePWrite(OS, reinterpret_cast<char *>(Section->getAllocAddress()),
+ Section->getOutputSize(), Section->getInputFileOffset());
}
void MachORewriteInstance::rewriteFile() {
@@ -518,13 +518,13 @@ void MachORewriteInstance::rewriteFile() {
continue;
if (opts::Verbosity >= 2)
outs() << "BOLT: rewriting function \"" << Function << "\"\n";
- OS.pwrite(reinterpret_cast<char *>(Function.getImageAddress()),
- Function.getImageSize(), Function.getFileOffset());
+ safePWrite(OS, reinterpret_cast<char *>(Function.getImageAddress()),
+ Function.getImageSize(), Function.getFileOffset());
}
for (const BinaryFunction *Function : BC->getInjectedBinaryFunctions()) {
- OS.pwrite(reinterpret_cast<char *>(Function->getImageAddress()),
- Function->getImageSize(), Function->getFileOffset());
+ safePWrite(OS, reinterpret_cast<char *>(Function->getImageAddress()),
+ Function->getImageSize(), Function->getFileOffset());
}
writeInstrumentationSection("__counters", OS);
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index a6178bfff0ec8..9efae9d236f5f 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -5141,7 +5141,7 @@ void RewriteInstance::patchELFSectionHeaderTable(ELFObjectFile<ELFT> *File) {
NewEhdr.e_shoff = SHTOffset;
NewEhdr.e_shnum = OutputSections.size();
NewEhdr.e_shstrndx = NewSectionIndex[NewEhdr.e_shstrndx];
- OS.pwrite(reinterpret_cast<const char *>(&NewEhdr), sizeof(NewEhdr), 0);
+ safePWrite(OS, reinterpret_cast<const char *>(&NewEhdr), sizeof(NewEhdr), 0);
}
template <typename ELFT, typename WriteFuncTy, typename StrTabFuncTy>
@@ -5619,25 +5619,14 @@ void RewriteInstance::patchELFSymTabs(ELFObjectFile<ELFT> *File) {
assert((DynSymSection || BC->IsStaticExecutable) &&
"dynamic symbol table expected");
if (DynSymSection) {
- uint64_t SavedPos = Out->os().tell();
- uint64_t RequiredPos = DynSymSection->sh_offset + DynSymSection->sh_size;
- if (SavedPos < RequiredPos)
- Out->os().seek(RequiredPos);
-
updateELFSymbolTable(
File,
- /*IsDynSym=*/true,
- *DynSymSection,
- NewSectionIndex,
+ /*IsDynSym=*/true, *DynSymSection, NewSectionIndex,
[&](size_t Offset, const ELFSymTy &Sym) {
- Out->os().pwrite(reinterpret_cast<const char *>(&Sym),
- sizeof(ELFSymTy),
- DynSymSection->sh_offset + Offset);
+ safePWrite(Out->os(), reinterpret_cast<const char *>(&Sym),
+ sizeof(ELFSymTy), DynSymSection->sh_offset + Offset);
},
[](StringRef) -> size_t { return 0; });
-
- if (SavedPos < RequiredPos)
- Out->os().seek(SavedPos);
}
if (opts::RemoveSymtab)
@@ -5721,7 +5710,7 @@ void RewriteInstance::patchELFAllocatableRelrSection(
if (!Addend)
return;
- OS.pwrite(reinterpret_cast<const char *>(&Addend), PSize, FileOffset);
+ safePWrite(OS, reinterpret_cast<const char *>(&Addend), PSize, FileOffset);
};
// Fill new relative relocation offsets set
@@ -5759,8 +5748,8 @@ void RewriteInstance::patchELFAllocatableRelrSection(
exit(1);
}
- OS.pwrite(reinterpret_cast<const char *>(&Value), DynamicRelrEntrySize,
- RelrDynOffset);
+ safePWrite(OS, reinterpret_cast<const char *>(&Value), DynamicRelrEntrySize,
+ RelrDynOffset);
RelrDynOffset += DynamicRelrEntrySize;
};
@@ -5822,7 +5811,7 @@ RewriteInstance::patchELFAllocatableRelaSections(ELFObjectFile<ELFT> *File) {
DynamicRelativeRelocationsCount = 0;
auto writeRela = [&OS](const Elf_Rela *RelA, uint64_t &Offset) {
- OS.pwrite(reinterpret_cast<const char *>(RelA), sizeof(*RelA), Offset);
+ safePWrite(OS, reinterpret_cast<const char *>(RelA), sizeof(*RelA), Offset);
Offset += sizeof(*RelA);
};
@@ -5935,9 +5924,9 @@ void RewriteInstance::patchELFGOT(ELFObjectFile<ELFT> *File) {
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: patching GOT entry 0x"
<< Twine::utohexstr(*GOTEntry) << " with 0x"
<< Twine::utohexstr(NewAddress) << '\n');
- OS.pwrite(reinterpret_cast<const char *>(&NewAddress), sizeof(NewAddress),
- reinterpret_cast<const char *>(GOTEntry) -
- File->getData().data());
+ safePWrite(
+ OS, reinterpret_cast<const char *>(&NewAddress), sizeof(NewAddress),
+ reinterpret_cast<const char *>(GOTEntry) - File->getData().data());
}
}
}
@@ -6029,8 +6018,8 @@ void RewriteInstance::patchELFDynamic(ELFObjectFile<ELFT> *File) {
break;
}
if (ShouldPatch)
- OS.pwrite(reinterpret_cast<const char *>(&NewDE), sizeof(NewDE),
- DynamicOffset + (&Dyn - DTB) * sizeof(Dyn));
+ safePWrite(OS, reinterpret_cast<const char *>(&NewDE), sizeof(NewDE),
+ DynamicOffset + (&Dyn - DTB) * sizeof(Dyn));
}
if (BC->RequiresZNow && !ZNowSet) {
@@ -6232,8 +6221,8 @@ void RewriteInstance::rewriteFunctionsInPlace(raw_fd_ostream &OS) {
if (opts::Verbosity >= 2)
BC->outs() << "BOLT: rewriting function \"" << *Function << "\"\n";
- OS.pwrite(reinterpret_cast<char *>(Function->getImageAddress()),
- Function->getImageSize(), Function->getFileOffset());
+ safePWrite(OS, reinterpret_cast<char *>(Function->getImageAddress()),
+ Function->getImageSize(), Function->getFileOffset());
// Write nops at the end of the function.
if (Function->getMaxSize() != std::numeric_limits<uint64_t>::max()) {
@@ -6256,8 +6245,8 @@ void RewriteInstance::rewriteFunctionsInPlace(raw_fd_ostream &OS) {
for (const FunctionFragment &FF :
Function->getLayout().getSplitFragments()) {
- OS.pwrite(reinterpret_cast<char *>(FF.getImageAddress()),
- FF.getImageSize(), FF.getFileOffset());
+ safePWrite(OS,reinterpret_cast<char *>(FF.getImageAddress()),
+ FF.getImageSize(), FF.getFileOffset());
}
}
diff --git a/bolt/lib/Utils/Utils.cpp b/bolt/lib/Utils/Utils.cpp
index ecc2f1010a985..a9c8405d7f90b 100644
--- a/bolt/lib/Utils/Utils.cpp
+++ b/bolt/lib/Utils/Utils.cpp
@@ -97,6 +97,28 @@ std::optional<uint8_t> readDWARFExpressionTargetReg(StringRef ExprBytes) {
return Reg;
}
+void safePWrite(raw_fd_ostream &OS, const char *Src, size_t Size,
+ uint64_t Offset) {
+ if (Size == 0)
+ return;
+
+ const uint64_t SavedPos = OS.tell();
+ const uint64_t RequiredPos = Offset + Size;
+ const bool Extended = SavedPos < RequiredPos;
+
+ // raw_pwrite_stream::pwrite() expects the stream to be large enough
+ // to cover the write. If the target offset exceeds the current stream
+ // position, we must extend the stream by seeking to the required position
+ // first to avoid failures.
+ if (Extended)
+ OS.seek(RequiredPos);
+
+ OS.pwrite(Src, Size, Offset);
+
+ if (Extended)
+ OS.seek(SavedPos);
+}
+
} // namespace bolt
bool operator==(const llvm::MCCFIInstruction &L,
diff --git a/bolt/unittests/Core/BinaryContext.cpp b/bolt/unittests/Core/BinaryContext.cpp
index d7374b323c916..1713b56938391 100644
--- a/bolt/unittests/Core/BinaryContext.cpp
+++ b/bolt/unittests/Core/BinaryContext.cpp
@@ -10,6 +10,7 @@
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/TargetSelect.h"
#include "gtest/gtest.h"
@@ -104,18 +105,23 @@ TEST_P(BinaryContextTester, FlushPendingRelocCALL26) {
BS.addPendingRelocation(
Relocation{12, RelSymbol2, ELF::R_AARCH64_CALL26, 0, 0});
- SmallVector<char> Vect(DataSize);
- raw_svector_ostream OS(Vect);
-
+ SmallString<64> TempPath;
+ int FD;
+ sys::fs::createTemporaryFile("bolt-test-call26", "bin", FD, TempPath);
+ raw_fd_ostream OS(FD, true);
BS.flushPendingRelocations(OS, [&](const MCSymbol *S) {
return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0;
});
+ auto MBOrErr = MemoryBuffer::getFile(TempPath);
+ ASSERT_TRUE(MBOrErr);
+ const char *Vect = MBOrErr.get()->getBufferStart();
const uint8_t Func1Call[4] = {255, 255, 255, 151};
const uint8_t Func2Call[4] = {1, 0, 0, 148};
EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4)) << "Wrong backward call value\n";
EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) << "Wrong forward call value\n";
+ sys::fs::remove(TempPath);
}
TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
@@ -146,12 +152,16 @@ TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
BS.addPendingRelocation(
Relocation{12, RelSymbol2, ELF::R_AARCH64_JUMP26, 0, 0});
- SmallVector<char> Vect(Size);
- raw_svector_ostream OS(Vect);
-
+ SmallString<64> TempPath;
+ int FD;
+ sys::fs::createTemporaryFile("bolt-test-jump26", "bin", FD, TempPath);
+ raw_fd_ostream OS(FD, true);
BS.flushPendingRelocations(OS, [&](const MCSymbol *S) {
return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0;
});
+ auto MBOrErr = MemoryBuffer::getFile(TempPath);
+ ASSERT_TRUE(MBOrErr);
+ const char *Vect = MBOrErr.get()->getBufferStart();
const uint8_t Func1Call[4] = {255, 255, 255, 23};
const uint8_t Func2Call[4] = {1, 0, 0, 20};
@@ -160,6 +170,7 @@ TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
<< "Wrong backward branch value\n";
EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4))
<< "Wrong forward branch value\n";
+ sys::fs::remove(TempPath);
}
TEST_P(BinaryContextTester,
@@ -182,15 +193,17 @@ TEST_P(BinaryContextTester,
Reloc.setOptional();
BS.addPendingRelocation(Reloc);
- SmallVector<char> Vect;
- raw_svector_ostream OS(Vect);
-
+ SmallString<64> TempPath;
+ int FD;
+ sys::fs::createTemporaryFile("bolt-test-outofrange", "bin", FD, TempPath);
+ raw_fd_ostream OS(FD, true);
// Resolve relocation symbol to a high value so encoding will be out of range.
BS.flushPendingRelocations(OS, [&](const MCSymbol *S) { return 0x800000F; });
outs().flush();
std::string CapturedStdOut = testing::internal::GetCapturedStdout();
EXPECT_EQ(CapturedStdOut,
"BOLT-INFO: skipped 1 out-of-range optional relocations\n");
+ sys::fs::remove(TempPath);
}
#endif
More information about the llvm-commits
mailing list