From 5163e27c2afcb9800aa12562e3562a382433009a Mon Sep 17 00:00:00 2001 From: Andrija Date: Tue, 13 May 2025 14:25:12 +0200 Subject: [PATCH] [LLD] Scatter nops Enable pseudo-randomly adding nops to beginning of instruction sections. Percentage of sections that are affected can be set. This is used in nanoMIPS primarily to try analyzing instruction cache influence on execution time, as well as normalize the results. --- lld/ELF/Arch/NanoMips.cpp | 3 ++ lld/ELF/Config.h | 2 + lld/ELF/Driver.cpp | 5 ++ lld/ELF/NanoMipsTransformations.cpp | 75 +++++++++++++++++++++++------ lld/ELF/NanoMipsTransformations.h | 9 ++-- lld/ELF/Options.td | 14 ++++++ lld/ELF/Target.h | 4 ++ lld/ELF/Writer.cpp | 8 +++ 8 files changed, 101 insertions(+), 19 deletions(-) diff --git a/lld/ELF/Arch/NanoMips.cpp b/lld/ELF/Arch/NanoMips.cpp index 38f862326591d..fecc098ff0999 100644 --- a/lld/ELF/Arch/NanoMips.cpp +++ b/lld/ELF/Arch/NanoMips.cpp @@ -178,6 +178,9 @@ template class NanoMips final : public TargetInfo { bool relaxOnce(int pass) const override { return this->transformController.relaxOnce(pass); } + + void scatterNops() const override { this->transformController.scatterNops(); } + void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override; uint32_t calcEFlags() const override; diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index 9a57baf2a4a77..fe2085dccbaa8 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -256,6 +256,8 @@ struct Config { bool relrGlibc = false; bool relrPackDynRelocs = false; llvm::DenseSet saveTempsArgs; + uint32_t scatterNopsDensity; + uint32_t scatterNopsSeed; llvm::SmallVector, 0> shuffleSections; bool singleRoRx; bool shared; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index c6a7f40602217..8c6810bb1f960 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1236,6 +1236,11 @@ static void readConfigs(opt::InputArgList &args) { } config->searchPaths = args::getStrings(args, OPT_library_path); + config->scatterNopsDensity = + args.hasArg(OPT_relocatable) + ? 0 + : args::getInteger(args, OPT_scatter_nops_density, 0); + config->scatterNopsSeed = args::getInteger(args, OPT_scatter_nops_seed, 0); config->sectionStartMap = getSectionStartMap(args); config->shared = args.hasArg(OPT_shared); config->singleRoRx = !args.hasFlag(OPT_rosegment, OPT_no_rosegment, true); diff --git a/lld/ELF/NanoMipsTransformations.cpp b/lld/ELF/NanoMipsTransformations.cpp index 5806e0fcf1c2b..7710bcfb520d7 100644 --- a/lld/ELF/NanoMipsTransformations.cpp +++ b/lld/ELF/NanoMipsTransformations.cpp @@ -21,6 +21,7 @@ #include "llvm/Object/ELFTypes.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/xxhash.h" #define DEBUG_TYPE "lld-nanomips" using namespace lld; @@ -362,19 +363,16 @@ void NanoMipsTransform::changeBytes(InputSection *isec, uint64_t location, } } -void NanoMipsTransform::updateSectionContent(InputSection *isec, - uint64_t location, int32_t delta, - bool align) { - +bool NanoMipsTransform::updateSectionContentInner(InputSection *isec, + uint64_t location, + int32_t delta, bool align) { // Other than increasing/decreasing byte size of isec, it also // allocates new section content if delta is 0 and section // hasn't yet been changed, as those content would be readonly - changeBytes(isec, location, delta); + NanoMipsTransform::changeBytes(isec, location, delta); if (delta == 0) - return; + return false; - this->changed = true; - this->changedThisIteration = true; LLVM_DEBUG(llvm::dbgs() << "Changed size of input section " << (isec->file ? isec->file->getName() : "nofile") << "(" << isec->name << ") by " << delta @@ -395,7 +393,7 @@ void NanoMipsTransform::updateSectionContent(InputSection *isec, // TODO: Check whether sections without a corresponding file // may have symbols that should be changed during transformations if (!isec->file) - return; + return true; for (auto &symAnchor : isec->nanoMipsRelaxAux->anchors) { Defined *dSym = symAnchor.d; @@ -413,6 +411,19 @@ void NanoMipsTransform::updateSectionContent(InputSection *isec, } } } + + return true; +} + +void NanoMipsTransform::updateSectionContent(InputSection *isec, + uint64_t location, int32_t delta, + bool align) { + bool changed = NanoMipsTransform::updateSectionContentInner(isec, location, + delta, align); + if (changed) { + this->changed = true; + this->changedThisIteration = true; + } } SmallVector NanoMipsTransform::getTransformInsns( @@ -1140,6 +1151,37 @@ void NanoMipsTransformController::changeState(int pass) { LLVM_DEBUG(llvm::dbgs() << "Changed transform state to None\n";); } +template +void NanoMipsTransformController::scatterNops() const { + if (!this->mayRelax()) + return; + + NanoMipsTransformController::initTransformAuxInfo(); + + for (OutputSection *osec : outputSections) { + if (!isOutputSecTransformable(osec)) + continue; + + SmallVector storage; + for (InputSection *sec : getInputSections(*osec, storage)) { + if (!NanoMipsTransformController::safeToModify(sec) || + !sec->relocs().size() || !(sec->flags & SHF_EXECINSTR)) + continue; + + uint64_t hash = llvm::xxHash64(sec->name) + config->scatterNopsSeed; + if ((hash % 100) >= config->scatterNopsDensity) + continue; + + const uint32_t nop16 = 0x9008; + const uint32_t nop16Size = 2; + NanoMipsTransform::updateSectionContentInner(sec, 0, 2, false); + writeInsn(nop16, sec->content(), 0, nop16Size); + LLVM_DEBUG(llvm::dbgs() << sec->name + << " updated with a nop in the beginning!\n";); + } + } +} + // relaxOnce is used for both relaxations and expansions template bool NanoMipsTransformController::relaxOnce(int pass) const { @@ -1148,10 +1190,10 @@ bool NanoMipsTransformController::relaxOnce(int pass) const { LLVM_DEBUG(llvm::dbgs() << "Transformation Pass num: " << pass << "\n";); bool shouldRunAgain = false; if (this->mayRelax()) { - if (pass == 0) { + if (pass == 0 && config->scatterNopsDensity == 0) { // Initialization of additional info that are needed for // relaxations/expansions - initTransformAuxInfo(); + NanoMipsTransformController::initTransformAuxInfo(); } for (OutputSection *osec : outputSections) { if (!isOutputSecTransformable(osec)) @@ -1159,7 +1201,7 @@ bool NanoMipsTransformController::relaxOnce(int pass) const { SmallVector storage; for (InputSection *sec : getInputSections(*osec, storage)) { - if (!this->safeToModify(sec)) + if (!NanoMipsTransformController::safeToModify(sec)) continue; if (sec->relocs().size()) this->scanAndTransform(sec); @@ -1175,8 +1217,8 @@ bool NanoMipsTransformController::relaxOnce(int pass) const { } template -inline bool lld::elf::NanoMipsTransformController::safeToModify( - InputSection *sec) const { +inline bool +lld::elf::NanoMipsTransformController::safeToModify(InputSection *sec) { bool modifiable = false; if (auto *obj = sec->getFile()) { modifiable = @@ -1186,14 +1228,15 @@ inline bool lld::elf::NanoMipsTransformController::safeToModify( } template -void NanoMipsTransformController::initTransformAuxInfo() const { +void NanoMipsTransformController::initTransformAuxInfo() { SmallVector storage; for (OutputSection *osec : outputSections) { if (!isOutputSecTransformable(osec)) continue; for (InputSection *sec : getInputSections(*osec, storage)) { - if (!this->safeToModify(sec) || sec->relocs().size() == 0) + if (!NanoMipsTransformController::safeToModify(sec) || + sec->relocs().size() == 0) continue; sec->nanoMipsRelaxAux = make(); sec->nanoMipsRelaxAux->isAlreadyTransformed = false; diff --git a/lld/ELF/NanoMipsTransformations.h b/lld/ELF/NanoMipsTransformations.h index 524e4d0fb7862..2b5d0c4099f83 100644 --- a/lld/ELF/NanoMipsTransformations.h +++ b/lld/ELF/NanoMipsTransformations.h @@ -352,6 +352,8 @@ class NanoMipsTransform { getTransformTemplate(const NanoMipsInsProperty *insProperty, uint32_t relNum, uint64_t valueToRelocate, uint64_t insn, const InputSection *isec) const = 0; + static bool updateSectionContentInner(InputSection *isec, uint64_t location, + int32_t delta, bool aligned); virtual void updateSectionContent(InputSection *isec, uint64_t location, int32_t delta, bool align = false); bool getChanged() { return changed; } @@ -375,7 +377,7 @@ class NanoMipsTransform { static uint32_t newSkipBcSymCount; private: - void changeBytes(InputSection *isec, uint64_t location, int32_t count); + static void changeBytes(InputSection *isec, uint64_t location, int32_t count); }; class NanoMipsTransformExpand : public NanoMipsTransform { @@ -450,6 +452,7 @@ template class NanoMipsTransformController { void initState(); bool relaxOnce(int pass) const; + void scatterNops() const; // should be called before change state bool shouldRunAgain() const { return this->currentState->getChanged(); } @@ -467,8 +470,8 @@ template class NanoMipsTransformController { NanoMipsTransform::TransformKind getType() const { return this->currentState->getType(); } - bool safeToModify(InputSection *sec) const; - void initTransformAuxInfo() const; + static bool safeToModify(InputSection *sec); + static void initTransformAuxInfo(); void scanAndTransform(InputSection *sec) const; void align(InputSection *sec, Relocation &reloc, uint32_t relNum) const; void changeState(int pass); diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td index 42fb6eb7642c2..d5f161d2d00b9 100644 --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -371,6 +371,20 @@ defm retain_symbols_file: Eq<"retain-symbols-file", "Retain only the symbols listed in the file">, MetaVarName<"">; +defm scatter_nops_density: EEq<"scatter-nops-density", + "Approximate percentage of instruction sections that will have nops " + "inserted at their beginnings. If 0, scatter nops doesn't do anything. " + "Doesn't do anything for relocatable outputs (default=0)(nanoMIPS only)">, + Flags<[HelpHidden]>; + + +defm scatter_nops_seed: EEq<"scatter-nops-seed", + "Use a different seed for the pseudo-random determination of " + "section that will be inserted with a nop. If scatter-nops-density is " + "0 or the requested output is relocatable, than this option doesn't have " + "effect (default=0)(nanoMIPS only)">, + Flags<[HelpHidden]>; + defm script: Eq<"script", "Read linker script">; defm section_start: Eq<"section-start", "Set address of section">, diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h index d1c4cf63d2662..4b071d4d956cc 100644 --- a/lld/ELF/Target.h +++ b/lld/ELF/Target.h @@ -94,6 +94,10 @@ class TargetInfo { // Do a linker relaxation pass and return true if we changed something. virtual bool relaxOnce(int pass) const { return false; } + // Insert nops randomly on the beginning of every instruction section. + // Currently only implemented on nanoMIPS. + virtual void scatterNops() const {} + virtual void applyJumpInstrMod(uint8_t *loc, JumpModType type, JumpModType val) const {} diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp index 1373734b65b7f..2cb0656c71298 100644 --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1657,6 +1657,14 @@ template void Writer::finalizeAddressDependentContent() { ThunkCreator tc; AArch64Err843419Patcher a64p; ARMErr657417Patcher a32p; + + // Put scatter nops here, everything is finished with input sections + // till this point, except some instruction transformations. Putting + // scatter nops may trigger those transformations, so we have to put + // it before these transformations. + if (config->scatterNopsDensity != 0) + target->scatterNops(); + script->assignAddresses(); // .ARM.exidx and SHF_LINK_ORDER do not require precise addresses, but they // do require the relative addresses of OutputSections because linker scripts