Skip to content

Commit 5401c67

Browse files
authored
[BOLT][instr] Avoid WX segment (#128982)
BOLT instrumented binary today has a readable (R), writeable (W) and also executable (X) segment, which Android system won't load due to its WX attribute. Such RWX segment was produced because BOLT has a two step linking, first for everything in the updated or rewritten input binary and next for runtime library. Each linking will layout sections in the order of RX sections followed by RO sections and then followed by RW sections. So we could end up having a RW section `.bolt.instr.counters` surrounded by a number of RO and RX sections, and a new text segment was then formed by including all RX sections which includes the RW section in the middle, and hence the RWX segment. One way to fix this is to separate the RW `.bolt.instr.counters` section into its own segment by a). assigning the starting addresses for section `.bolt.instr.counters` and its following section with regular page aligned addresses and b). creating two extra program headers accordingly.
1 parent 30d7e21 commit 5401c67

File tree

4 files changed

+110
-18
lines changed

4 files changed

+110
-18
lines changed

bolt/include/bolt/Rewrite/RewriteInstance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,9 @@ class RewriteInstance {
505505
/// Number of local symbols in newly written symbol table.
506506
uint64_t NumLocalSymbols{0};
507507

508+
/// Flag indicating runtime library linking just started.
509+
bool StartLinkingRuntimeLib{false};
510+
508511
/// Information on special Procedure Linkage Table sections. There are
509512
/// multiple variants generated by different linkers.
510513
struct PLTSectionInfo {

bolt/lib/Passes/Instrumentation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ Error Instrumentation::runOnFunctions(BinaryContext &BC) {
604604
/*IsText=*/false,
605605
/*IsAllocatable=*/true);
606606
BC.registerOrUpdateSection(".bolt.instr.counters", ELF::SHT_PROGBITS, Flags,
607-
nullptr, 0, 1);
607+
nullptr, 0, BC.RegularPageSize);
608608

609609
BC.registerOrUpdateNoteSection(".bolt.instr.tables", nullptr, 0,
610610
/*Alignment=*/1,

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,11 @@ Error RewriteInstance::discoverStorage() {
628628
unsigned Phnum = Obj.getHeader().e_phnum;
629629
Phnum += 3;
630630

631+
// Reserve two more pheaders to avoid having writeable and executable
632+
// segment in instrumented binary.
633+
if (opts::Instrument)
634+
Phnum += 2;
635+
631636
NextAvailableAddress += Phnum * sizeof(ELF64LEPhdrTy);
632637
NextAvailableOffset += Phnum * sizeof(ELF64LEPhdrTy);
633638
}
@@ -2083,6 +2088,13 @@ void RewriteInstance::adjustCommandLineOptions() {
20832088
opts::HotText = false;
20842089
}
20852090

2091+
if (opts::Instrument && opts::UseGnuStack) {
2092+
BC->errs() << "BOLT-ERROR: cannot avoid having writeable and executable "
2093+
"segment in instrumented binary if program headers will be "
2094+
"updated in place\n";
2095+
exit(1);
2096+
}
2097+
20862098
if (opts::HotText && opts::HotTextMoveSections.getNumOccurrences() == 0) {
20872099
opts::HotTextMoveSections.addValue(".stub");
20882100
opts::HotTextMoveSections.addValue(".mover");
@@ -3612,11 +3624,13 @@ void RewriteInstance::emitAndLink() {
36123624
static_cast<MCObjectStreamer &>(*Streamer).getAssembler());
36133625
}
36143626

3615-
if (RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary())
3627+
if (RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary()) {
3628+
StartLinkingRuntimeLib = true;
36163629
RtLibrary->link(*BC, ToolPath, *Linker, [this](auto MapSection) {
36173630
// Map newly registered sections.
36183631
this->mapAllocatableSections(MapSection);
36193632
});
3633+
}
36203634

36213635
// Once the code is emitted, we can rename function sections to actual
36223636
// output sections and de-register sections used for emission.
@@ -4011,12 +4025,17 @@ void RewriteInstance::mapAllocatableSections(
40114025
Section.setOutputFileOffset(Section.getInputFileOffset());
40124026
MapSection(Section, Section.getAddress());
40134027
} else {
4014-
NextAvailableAddress =
4015-
alignTo(NextAvailableAddress, Section.getAlignment());
4028+
uint64_t Alignment = Section.getAlignment();
4029+
if (opts::Instrument && StartLinkingRuntimeLib) {
4030+
Alignment = BC->RegularPageSize;
4031+
StartLinkingRuntimeLib = false;
4032+
}
4033+
NextAvailableAddress = alignTo(NextAvailableAddress, Alignment);
4034+
40164035
LLVM_DEBUG({
4017-
dbgs() << "BOLT: mapping section " << Section.getName() << " (0x"
4018-
<< Twine::utohexstr(Section.getAllocAddress()) << ") to 0x"
4019-
<< Twine::utohexstr(NextAvailableAddress) << ":0x"
4036+
dbgs() << "BOLT-DEBUG: mapping section " << Section.getName()
4037+
<< " (0x" << Twine::utohexstr(Section.getAllocAddress())
4038+
<< ") to 0x" << Twine::utohexstr(NextAvailableAddress) << ":0x"
40204039
<< Twine::utohexstr(NextAvailableAddress +
40214040
Section.getOutputSize())
40224041
<< '\n';
@@ -4079,6 +4098,9 @@ void RewriteInstance::patchELFPHDRTable() {
40794098
}
40804099
}
40814100

4101+
if (opts::Instrument)
4102+
Phnum += 2;
4103+
40824104
// NOTE Currently .eh_frame_hdr appends to the last segment, recalculate
40834105
// last segments size based on the NextAvailableAddress variable.
40844106
if (!NewWritableSegmentSize) {
@@ -4093,7 +4115,8 @@ void RewriteInstance::patchELFPHDRTable() {
40934115
const uint64_t SavedPos = OS.tell();
40944116
OS.seek(PHDRTableOffset);
40954117

4096-
auto createNewTextPhdr = [&]() {
4118+
auto createNewPhdrs = [&]() {
4119+
SmallVector<ELF64LEPhdrTy, 3> NewPhdrs;
40974120
ELF64LEPhdrTy NewPhdr;
40984121
NewPhdr.p_type = ELF::PT_LOAD;
40994122
if (PHDRTableAddress) {
@@ -4108,20 +4131,67 @@ void RewriteInstance::patchELFPHDRTable() {
41084131
NewPhdr.p_filesz = NewTextSegmentSize;
41094132
NewPhdr.p_memsz = NewTextSegmentSize;
41104133
NewPhdr.p_flags = ELF::PF_X | ELF::PF_R;
4111-
if (opts::Instrument) {
4112-
// FIXME: Currently instrumentation is experimental and the runtime data
4113-
// is emitted with code, thus everything needs to be writable.
4114-
NewPhdr.p_flags |= ELF::PF_W;
4115-
}
41164134
NewPhdr.p_align = BC->PageAlign;
41174135

4118-
return NewPhdr;
4136+
if (!opts::Instrument) {
4137+
NewPhdrs.push_back(NewPhdr);
4138+
} else {
4139+
ErrorOr<BinarySection &> Sec =
4140+
BC->getUniqueSectionByName(".bolt.instr.counters");
4141+
assert(Sec && "expected one and only one `.bolt.instr.counters` section");
4142+
const uint64_t Addr = Sec->getOutputAddress();
4143+
const uint64_t Offset = Sec->getOutputFileOffset();
4144+
const uint64_t Size = Sec->getOutputSize();
4145+
assert(Addr > NewPhdr.p_vaddr &&
4146+
Addr + Size < NewPhdr.p_vaddr + NewPhdr.p_memsz &&
4147+
"`.bolt.instr.counters` section is expected to be included in the "
4148+
"new text sgement");
4149+
4150+
// Set correct size for the previous header since we are breaking the
4151+
// new text segment into three segments.
4152+
uint64_t Delta = Addr - NewPhdr.p_vaddr;
4153+
NewPhdr.p_filesz = Delta;
4154+
NewPhdr.p_memsz = Delta;
4155+
NewPhdrs.push_back(NewPhdr);
4156+
4157+
// Create a program header for a RW segment that includes the
4158+
// `.bolt.instr.counters` section only.
4159+
ELF64LEPhdrTy NewPhdrRWSegment;
4160+
NewPhdrRWSegment.p_type = ELF::PT_LOAD;
4161+
NewPhdrRWSegment.p_offset = Offset;
4162+
NewPhdrRWSegment.p_vaddr = Addr;
4163+
NewPhdrRWSegment.p_paddr = Addr;
4164+
NewPhdrRWSegment.p_filesz = Size;
4165+
NewPhdrRWSegment.p_memsz = Size;
4166+
NewPhdrRWSegment.p_flags = ELF::PF_R | ELF::PF_W;
4167+
NewPhdrRWSegment.p_align = BC->RegularPageSize;
4168+
NewPhdrs.push_back(NewPhdrRWSegment);
4169+
4170+
// Create a program header for a RX segment that includes all the RX
4171+
// sections from runtime library.
4172+
ELF64LEPhdrTy NewPhdrRXSegment;
4173+
NewPhdrRXSegment.p_type = ELF::PT_LOAD;
4174+
const uint64_t AddrRX = alignTo(Addr + Size, BC->RegularPageSize);
4175+
const uint64_t OffsetRX = alignTo(Offset + Size, BC->RegularPageSize);
4176+
const uint64_t SizeRX = NewTextSegmentSize - (AddrRX - NewPhdr.p_paddr);
4177+
NewPhdrRXSegment.p_offset = OffsetRX;
4178+
NewPhdrRXSegment.p_vaddr = AddrRX;
4179+
NewPhdrRXSegment.p_paddr = AddrRX;
4180+
NewPhdrRXSegment.p_filesz = SizeRX;
4181+
NewPhdrRXSegment.p_memsz = SizeRX;
4182+
NewPhdrRXSegment.p_flags = ELF::PF_X | ELF::PF_R;
4183+
NewPhdrRXSegment.p_align = BC->RegularPageSize;
4184+
NewPhdrs.push_back(NewPhdrRXSegment);
4185+
}
4186+
4187+
return NewPhdrs;
41194188
};
41204189

41214190
auto writeNewSegmentPhdrs = [&]() {
41224191
if (PHDRTableAddress || NewTextSegmentSize) {
4123-
ELF64LE::Phdr NewPhdr = createNewTextPhdr();
4124-
OS.write(reinterpret_cast<const char *>(&NewPhdr), sizeof(NewPhdr));
4192+
SmallVector<ELF64LE::Phdr, 3> NewPhdrs = createNewPhdrs();
4193+
OS.write(reinterpret_cast<const char *>(NewPhdrs.data()),
4194+
sizeof(ELF64LE::Phdr) * NewPhdrs.size());
41254195
}
41264196

41274197
if (NewWritableSegmentSize) {
@@ -4169,8 +4239,12 @@ void RewriteInstance::patchELFPHDRTable() {
41694239
}
41704240
case ELF::PT_GNU_STACK:
41714241
if (opts::UseGnuStack) {
4172-
// Overwrite the header with the new text segment header.
4173-
NewPhdr = createNewTextPhdr();
4242+
// Overwrite the header with the new segment header.
4243+
assert(!opts::Instrument);
4244+
SmallVector<ELF64LE::Phdr, 3> NewPhdrs = createNewPhdrs();
4245+
assert(NewPhdrs.size() == 1 &&
4246+
"expect exactly one program header was created");
4247+
NewPhdr = NewPhdrs[0];
41744248
ModdedGnuStack = true;
41754249
}
41764250
break;

bolt/test/avoid-wx-segment.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Test bolt instrumentation won't generate a binary with any segment that
2+
// is writable and executable. Basically we want to put `.bolt.instr.counters`
3+
// section into its own segment, separated from its surrounding RX sections.
4+
5+
// REQUIRES: system-linux
6+
7+
void foo() {}
8+
void bar() { foo(); }
9+
10+
// RUN: %clang %cflags -c %s -o %t.o
11+
// RUN: ld.lld -q -o %t.so %t.o -shared --init=foo --fini=foo
12+
// RUN: llvm-bolt --instrument %t.so -o %tt.so
13+
// RUN: llvm-readelf -l %tt.so | FileCheck %s
14+
// CHECK-NOT: RWE
15+
// CHECK: {{[0-9]*}} .bolt.instr.counters {{$}}

0 commit comments

Comments
 (0)