Skip to content

Commit de2aa94

Browse files
committed
[BOLT] Support instrumentation hook via DT_FINI_ARRAY (llvm#67348)
BOLT currently hooks its its instrumentation finalization function via `DT_FINI`. However, this method of calling finalization routines is not supported anymore on newer ABIs like RISC-V. `DT_FINI_ARRAY` is preferred there. This patch adds support for hooking into `DT_FINI_ARRAY` instead if the binary does not have a `DT_FINI` entry. If it does, `DT_FINI` takes precedence so this patch should not change how the currently supported instrumentation targets behave. `DT_FINI_ARRAY` points to an array in memory of `DT_FINI_ARRAYSZ` bytes. It consists of pointer-length entries that contain the addresses of finalization functions. However, the addresses are only filled-in by the dynamic linker at load time using relative relocations. This makes hooking via `DT_FINI_ARRAY` a bit more complicated than via `DT_FINI`. The implementation works as follows: - While scanning the binary: find the section where `DT_FINI_ARRAY` points to, read its first dynamic relocation and use its addend to find the address of the fini function we will use to hook; - While writing the output file: overwrite the addend of the dynamic relocation with the address of the runtime library's fini function. Updating the dynamic relocation required a bit of boiler plate: since dynamic relocations are stored in a `std::multiset` which doesn't support getting mutable references to its items, functions were added to `BinarySection` to take an existing relocation and insert a new one. Note on testing: I can currently only test this on RISC-V, but its instrumentation support hasn't been upstreamed yet as it depends on this patch which I would like to review separately. I cannot get this patch to work on x86, even when forcing the linker to omit `DT_FINI`. The reason is that, even though `DT_FINI_ARRAY` exists and has valid entries, the functions do not show up in the symbols table which triggers some asserts later in BOLT. I would propose to not add tests right now but wait until RISC-V instrumentation is upstreamed which will automatically test this patch.
1 parent 3bc056d commit de2aa94

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

bolt/include/bolt/Core/BinaryContext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,15 @@ class BinaryContext {
680680
/// the execution of the binary is completed.
681681
std::optional<uint64_t> FiniFunctionAddress;
682682

683+
/// DT_FINI.
684+
std::optional<uint64_t> FiniAddress;
685+
686+
/// DT_FINI_ARRAY. Only used when DT_FINI is not set.
687+
std::optional<uint64_t> FiniArrayAddress;
688+
689+
/// DT_FINI_ARRAYSZ. Only used when DT_FINI is not set.
690+
std::optional<uint64_t> FiniArraySize;
691+
683692
/// Page alignment used for code layout.
684693
uint64_t PageAlign{HugePageSize};
685694

bolt/include/bolt/Core/BinarySection.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,12 @@ class BinarySection {
375375
/// Add a dynamic relocation at the given /p Offset.
376376
void addDynamicRelocation(uint64_t Offset, MCSymbol *Symbol, uint64_t Type,
377377
uint64_t Addend, uint64_t Value = 0) {
378-
assert(Offset < getSize() && "offset not within section bounds");
379-
DynamicRelocations.emplace(Relocation{Offset, Symbol, Type, Addend, Value});
378+
addDynamicRelocation(Relocation{Offset, Symbol, Type, Addend, Value});
379+
}
380+
381+
void addDynamicRelocation(const Relocation &Reloc) {
382+
assert(Reloc.Offset < getSize() && "offset not within section bounds");
383+
DynamicRelocations.emplace(Reloc);
380384
}
381385

382386
/// Add relocation against the original contents of this section.
@@ -410,6 +414,18 @@ class BinarySection {
410414
return Itr != DynamicRelocations.end() ? &*Itr : nullptr;
411415
}
412416

417+
std::optional<Relocation> takeDynamicRelocationAt(uint64_t Offset) {
418+
Relocation Key{Offset, 0, 0, 0, 0};
419+
auto Itr = DynamicRelocations.find(Key);
420+
421+
if (Itr == DynamicRelocations.end())
422+
return std::nullopt;
423+
424+
Relocation Reloc = *Itr;
425+
DynamicRelocations.erase(Itr);
426+
return Reloc;
427+
}
428+
413429
uint64_t hash(const BinaryData &BD) const {
414430
std::map<const BinaryData *, uint64_t> Cache;
415431
return hash(BD, Cache);

bolt/include/bolt/Rewrite/RewriteInstance.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ class RewriteInstance {
9595
/// from meta data in the file.
9696
void discoverFileObjects();
9797

98+
/// Check whether we should use DT_FINI or DT_FINI_ARRAY for instrumentation.
99+
/// DT_FINI is preferred; DT_FINI_ARRAY is only used when no DT_FINI entry was
100+
/// found.
101+
Error discoverRtFiniAddress();
102+
103+
/// If DT_FINI_ARRAY is used for instrumentation, update the relocation of its
104+
/// first entry to point to the instrumentation library's fini address.
105+
void updateRtFiniReloc();
106+
98107
/// Create and initialize metadata rewriters for this instance.
99108
void initializeMetadataManager();
100109

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ Error RewriteInstance::run() {
704704
adjustCommandLineOptions();
705705
discoverFileObjects();
706706

707+
if (opts::Instrument && !BC->IsStaticExecutable)
708+
if (Error E = discoverRtFiniAddress())
709+
return E;
710+
707711
preprocessProfileData();
708712

709713
// Skip disassembling if we have a translation table and we are running an
@@ -740,6 +744,9 @@ Error RewriteInstance::run() {
740744

741745
updateMetadata();
742746

747+
if (opts::Instrument && !BC->IsStaticExecutable)
748+
updateRtFiniReloc();
749+
743750
if (opts::LinuxKernelMode) {
744751
errs() << "BOLT-WARNING: not writing the output file for Linux Kernel\n";
745752
return Error::success();
@@ -1280,6 +1287,77 @@ void RewriteInstance::discoverFileObjects() {
12801287
registerFragments();
12811288
}
12821289

1290+
Error RewriteInstance::discoverRtFiniAddress() {
1291+
// Use DT_FINI if it's available.
1292+
if (BC->FiniAddress) {
1293+
BC->FiniFunctionAddress = BC->FiniAddress;
1294+
return Error::success();
1295+
}
1296+
1297+
if (!BC->FiniArrayAddress || !BC->FiniArraySize) {
1298+
return createStringError(
1299+
std::errc::not_supported,
1300+
"Instrumentation needs either DT_FINI or DT_FINI_ARRAY");
1301+
}
1302+
1303+
if (*BC->FiniArraySize < BC->AsmInfo->getCodePointerSize()) {
1304+
return createStringError(std::errc::not_supported,
1305+
"Need at least 1 DT_FINI_ARRAY slot");
1306+
}
1307+
1308+
ErrorOr<BinarySection &> FiniArraySection =
1309+
BC->getSectionForAddress(*BC->FiniArrayAddress);
1310+
if (auto EC = FiniArraySection.getError())
1311+
return errorCodeToError(EC);
1312+
1313+
if (const Relocation *Reloc = FiniArraySection->getDynamicRelocationAt(0)) {
1314+
BC->FiniFunctionAddress = Reloc->Addend;
1315+
return Error::success();
1316+
}
1317+
1318+
if (const Relocation *Reloc = FiniArraySection->getRelocationAt(0)) {
1319+
BC->FiniFunctionAddress = Reloc->Value;
1320+
return Error::success();
1321+
}
1322+
1323+
return createStringError(std::errc::not_supported,
1324+
"No relocation for first DT_FINI_ARRAY slot");
1325+
}
1326+
1327+
void RewriteInstance::updateRtFiniReloc() {
1328+
// Updating DT_FINI is handled by patchELFDynamic.
1329+
if (BC->FiniAddress)
1330+
return;
1331+
1332+
const RuntimeLibrary *RT = BC->getRuntimeLibrary();
1333+
if (!RT || !RT->getRuntimeFiniAddress())
1334+
return;
1335+
1336+
assert(BC->FiniArrayAddress && BC->FiniArraySize &&
1337+
"inconsistent .fini_array state");
1338+
1339+
ErrorOr<BinarySection &> FiniArraySection =
1340+
BC->getSectionForAddress(*BC->FiniArrayAddress);
1341+
assert(FiniArraySection && ".fini_array removed");
1342+
1343+
if (std::optional<Relocation> Reloc =
1344+
FiniArraySection->takeDynamicRelocationAt(0)) {
1345+
assert(Reloc->Addend == BC->FiniFunctionAddress &&
1346+
"inconsistent .fini_array dynamic relocation");
1347+
Reloc->Addend = RT->getRuntimeFiniAddress();
1348+
FiniArraySection->addDynamicRelocation(*Reloc);
1349+
}
1350+
1351+
// Update the static relocation by adding a pending relocation which will get
1352+
// patched when flushPendingRelocations is called in rewriteFile. Note that
1353+
// flushPendingRelocations will calculate the value to patch as
1354+
// "Symbol + Addend". Since we don't have a symbol, just set the addend to the
1355+
// desired value.
1356+
FiniArraySection->addPendingRelocation(Relocation{
1357+
/*Offset*/ 0, /*Symbol*/ nullptr, /*Type*/ Relocation::getAbs64(),
1358+
/*Addend*/ RT->getRuntimeFiniAddress(), /*Value*/ 0});
1359+
}
1360+
12831361
void RewriteInstance::registerFragments() {
12841362
if (!BC->HasSplitFunctions)
12851363
return;
@@ -5118,7 +5196,13 @@ Error RewriteInstance::readELFDynamic(ELFObjectFile<ELFT> *File) {
51185196
}
51195197
break;
51205198
case ELF::DT_FINI:
5121-
BC->FiniFunctionAddress = Dyn.getPtr();
5199+
BC->FiniAddress = Dyn.getPtr();
5200+
break;
5201+
case ELF::DT_FINI_ARRAY:
5202+
BC->FiniArrayAddress = Dyn.getPtr();
5203+
break;
5204+
case ELF::DT_FINI_ARRAYSZ:
5205+
BC->FiniArraySize = Dyn.getPtr();
51225206
break;
51235207
case ELF::DT_RELA:
51245208
DynamicRelocationsAddress = Dyn.getPtr();

bolt/lib/RuntimeLibs/InstrumentationRuntimeLibrary.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ void InstrumentationRuntimeLibrary::adjustCommandLineOptions(
5757
"the input binary\n";
5858
exit(1);
5959
}
60-
if (!BC.FiniFunctionAddress && !BC.IsStaticExecutable) {
61-
errs() << "BOLT-ERROR: input binary lacks DT_FINI entry in the dynamic "
62-
"section but instrumentation currently relies on patching "
63-
"DT_FINI to write the profile\n";
64-
exit(1);
65-
}
6660

6761
if ((opts::InstrumentationWaitForks || opts::InstrumentationSleepTime) &&
6862
opts::InstrumentationFileAppendPID) {

0 commit comments

Comments
 (0)