Skip to content

Commit c737198

Browse files
committed
[𝘀𝗽𝗿] initial version
Created using spr 1.3.6-beta.1
2 parents f6641e2 + 56a495b commit c737198

38 files changed

+667
-50
lines changed

compiler-rt/test/cfi/mfcall.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ int main(int argc, char **argv) {
6363
switch (argv[1][0]) {
6464
case 'a':
6565
// A: runtime error: control flow integrity check for type 'int (S::*)()' failed during non-virtual pointer to member function call
66-
// A: note: S::f1() defined here
66+
// A: note: S::f1() {{.*}}defined here
6767
(s.*bitcast<S_int>(&S::f1))();
6868
break;
6969
case 'b':
7070
// B: runtime error: control flow integrity check for type 'int (T::*)()' failed during non-virtual pointer to member function call
71-
// B: note: S::f2() defined here
71+
// B: note: S::f2() {{.*}}defined here
7272
(t.*bitcast<T_int>(&S::f2))();
7373
break;
7474
case 'c':

lld/ELF/Arch/X86_64.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class X86_64 : public TargetInfo {
5050
bool deleteFallThruJmpInsn(InputSection &is, InputFile *file,
5151
InputSection *nextIS) const override;
5252
bool relaxOnce(int pass) const override;
53+
void relaxCFIJumpTables() const override;
5354
void applyBranchToBranchOpt() const override;
5455

5556
private:
@@ -317,6 +318,177 @@ bool X86_64::deleteFallThruJmpInsn(InputSection &is, InputFile *file,
317318
return true;
318319
}
319320

321+
void X86_64::relaxCFIJumpTables() const {
322+
// Relax CFI jump tables.
323+
// - Split jump table into pieces and place target functions inside the jump
324+
// table if small enough.
325+
// - Move jump table before last called function and delete last branch
326+
// instruction.
327+
std::map<InputSection *, std::vector<InputSection *>> sectionReplacements;
328+
SmallVector<InputSection *, 0> storage;
329+
for (OutputSection *osec : ctx.outputSections) {
330+
if (!(osec->flags & SHF_EXECINSTR))
331+
continue;
332+
for (InputSection *sec : getInputSections(*osec, storage)) {
333+
if (sec->type != SHT_LLVM_CFI_JUMP_TABLE || sec->entsize == 0 ||
334+
sec->size % sec->entsize != 0)
335+
continue;
336+
337+
// We're going to replace the jump table with this list of sections. This
338+
// list will be made up of slices of the original section and function
339+
// bodies that were moved into the jump table.
340+
std::vector<InputSection *> replacements;
341+
342+
// First, push the original jump table section. This is only so that it
343+
// can act as a relocation target. Later on, we will set the size of the
344+
// jump table section to 0 so that the slices and moved function bodies
345+
// become the actual relocation targets.
346+
replacements.push_back(sec);
347+
348+
// Add the slice [begin, end) of the original section to the replacement
349+
// list. [rbegin, rend) is the slice of the relocation list that covers
350+
// [begin, end).
351+
auto addSectionSlice = [&](size_t begin, size_t end, Relocation *rbegin,
352+
Relocation *rend) {
353+
auto *slice = make<InputSection>(
354+
sec->file, sec->name, sec->type, sec->flags, sec->entsize,
355+
sec->entsize,
356+
sec->contentMaybeDecompress().slice(begin, end - begin));
357+
// Ensure that --preferred-function-alignment does not mess with the
358+
// placement of this section.
359+
slice->retainAlignment = true;
360+
for (const Relocation &r : ArrayRef<Relocation>(rbegin, rend)) {
361+
slice->relocations.push_back(
362+
Relocation{r.expr, r.type, r.offset - begin, r.addend, r.sym});
363+
}
364+
replacements.push_back(slice);
365+
};
366+
367+
// r is the only relocation in a jump table entry. Figure out whether it
368+
// is a branch pointing to the start of a statically known section that
369+
// hasn't already been moved while processing a different jump table
370+
// section, and if so return it.
371+
auto getMovableSection = [&](Relocation &r) -> InputSection * {
372+
if (r.type != R_X86_64_PC32 && r.type != R_X86_64_PLT32)
373+
return nullptr;
374+
auto *sym = dyn_cast_or_null<Defined>(r.sym);
375+
if (!sym || sym->isPreemptible || sym->isGnuIFunc() ||
376+
sym->value + r.addend != -4ull)
377+
return nullptr;
378+
auto *target = dyn_cast_or_null<InputSection>(sym->section);
379+
if (!target || target->addralign > sec->entsize ||
380+
sectionReplacements.count(target))
381+
return nullptr;
382+
return target;
383+
};
384+
385+
// Figure out the movable section for the last entry. We do this first
386+
// because the last entry controls which output section the jump table is
387+
// placed into, which affects move eligibility for other sections.
388+
auto *lastSec = [&]() -> InputSection * {
389+
Relocation *lastReloc = sec->relocs().end();
390+
while (lastReloc != sec->relocs().begin() &&
391+
(lastReloc - 1)->offset >= sec->size - sec->entsize)
392+
--lastReloc;
393+
if (lastReloc + 1 != sec->relocs().end())
394+
return nullptr;
395+
return getMovableSection(*lastReloc);
396+
}();
397+
OutputSection *targetOutputSec;
398+
if (lastSec) {
399+
// We've already decided to move the output section so make sure that we
400+
// don't try to move it again.
401+
sectionReplacements[lastSec] = replacements;
402+
targetOutputSec = lastSec->getParent();
403+
} else {
404+
targetOutputSec = sec->getParent();
405+
}
406+
407+
// Walk the jump table entries other than the last one looking for sections
408+
// that are small enough to be moved into the jump table and in the same
409+
// section as the jump table's destination.
410+
size_t begin = 0;
411+
Relocation *rbegin = sec->relocs().begin();
412+
size_t cur = begin;
413+
Relocation *rcur = rbegin;
414+
while (cur != sec->size - sec->entsize) {
415+
size_t next = cur + sec->entsize;
416+
Relocation *rnext = rcur;
417+
while (rnext != sec->relocs().end() && rnext->offset < next)
418+
++rnext;
419+
if (rcur + 1 == rnext) {
420+
InputSection *target = getMovableSection(*rcur);
421+
if (target && target->size <= sec->entsize &&
422+
target->getParent() == targetOutputSec) {
423+
// Okay, we found a small enough section. Move it into the jump
424+
// table. First add a slice for the unmodified jump table entries
425+
// before this one.
426+
addSectionSlice(begin, cur, rbegin, rcur);
427+
// Ensure that --preferred-function-alignment does not mess with the
428+
// placement of this section.
429+
target->retainAlignment = true;
430+
// Add the target to our replacement list, and set the target's
431+
// replacement list to the empty list. This removes it from its
432+
// original position and adds it here, as well as causing
433+
// future getMovableSection() queries to return nullptr.
434+
replacements.push_back(target);
435+
sectionReplacements[target] = {};
436+
begin = next;
437+
rbegin = rnext;
438+
}
439+
}
440+
cur = next;
441+
rcur = rnext;
442+
}
443+
444+
// Finally, process the last entry. If it is movable, move the entire
445+
// jump table behind it and delete the last entry (so that the last
446+
// function's body acts as the last jump table entry), otherwise leave the
447+
// jump table where it is and keep the last entry.
448+
if (lastSec) {
449+
addSectionSlice(begin, cur, rbegin, rcur);
450+
lastSec->retainAlignment = true;
451+
replacements.push_back(lastSec);
452+
sectionReplacements[sec] = {};
453+
sectionReplacements[lastSec] = replacements;
454+
for (auto *s : replacements)
455+
s->parent = lastSec->parent;
456+
} else {
457+
addSectionSlice(begin, sec->size, rbegin, sec->relocs().end());
458+
sectionReplacements[sec] = replacements;
459+
for (auto *s : replacements)
460+
s->parent = sec->parent;
461+
}
462+
463+
// Everything from the original section has been recreated, so delete the
464+
// original contents.
465+
sec->relocations.clear();
466+
sec->size = 0;
467+
}
468+
}
469+
470+
// Now that we have the complete mapping of replacements, go through the input
471+
// section lists and apply the replacements.
472+
for (OutputSection *osec : ctx.outputSections) {
473+
if (!(osec->flags & SHF_EXECINSTR))
474+
continue;
475+
for (SectionCommand *cmd : osec->commands) {
476+
auto *isd = dyn_cast<InputSectionDescription>(cmd);
477+
if (!isd)
478+
continue;
479+
SmallVector<InputSection *> newSections;
480+
for (auto *sec : isd->sections) {
481+
auto i = sectionReplacements.find(sec);
482+
if (i == sectionReplacements.end())
483+
newSections.push_back(sec);
484+
else
485+
newSections.append(i->second.begin(), i->second.end());
486+
}
487+
isd->sections = std::move(newSections);
488+
}
489+
}
490+
}
491+
320492
bool X86_64::relaxOnce(int pass) const {
321493
uint64_t minVA = UINT64_MAX, maxVA = 0;
322494
for (OutputSection *osec : ctx.outputSections) {

lld/ELF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ struct Config {
358358
bool optRemarksWithHotness;
359359
bool picThunk;
360360
bool pie;
361+
std::optional<uint64_t> preferredFunctionAlignment;
361362
bool printGcSections;
362363
bool printIcfSections;
363364
bool printMemoryUsage;

lld/ELF/Driver.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,9 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
15081508
if (auto *arg = args.getLastArg(OPT_package_metadata))
15091509
parsePackageMetadata(ctx, *arg);
15101510
ctx.arg.pie = args.hasFlag(OPT_pie, OPT_no_pie, false);
1511+
if (args.hasArg(OPT_preferred_function_alignment))
1512+
ctx.arg.preferredFunctionAlignment =
1513+
args::getInteger(args, OPT_preferred_function_alignment, 0);
15111514
ctx.arg.printIcfSections =
15121515
args.hasFlag(OPT_print_icf_sections, OPT_no_print_icf_sections, false);
15131516
ctx.arg.printGcSections =

lld/ELF/InputSection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ InputSectionBase::InputSectionBase(InputFile *file, StringRef name,
5858
Kind sectionKind)
5959
: SectionBase(sectionKind, file, name, type, flags, link, info, addralign,
6060
entsize),
61-
bss(0), decodedCrel(0), keepUnique(0), nopFiller(0),
61+
bss(0), decodedCrel(0), keepUnique(0), nopFiller(0), retainAlignment(0),
6262
content_(data.data()), size(data.size()) {
6363
// In order to reduce memory allocation, we assume that mergeable
6464
// sections are smaller than 4 GiB, which is not an unreasonable

lld/ELF/InputSection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ class InputSectionBase : public SectionBase {
183183
LLVM_PREFERRED_TYPE(bool)
184184
uint8_t nopFiller : 1;
185185

186+
// If true, --preferred-function-alignment has no effect on this section.
187+
// Set by the CFI jump table relaxation pass.
188+
LLVM_PREFERRED_TYPE(bool)
189+
uint8_t retainAlignment : 1;
190+
186191
mutable bool compressed = false;
187192

188193
// Input sections are part of an output section. Special sections

lld/ELF/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ def pop_state: F<"pop-state">,
406406
def push_state: F<"push-state">,
407407
HelpText<"Save the current state of --as-needed, -static and --whole-archive">;
408408

409+
def preferred_function_alignment: JJ<"preferred-function-alignment=">,
410+
HelpText<"Align functions to the given alignment if possible">;
411+
409412
def print_map: F<"print-map">,
410413
HelpText<"Print a link map to the standard output">;
411414

lld/ELF/OutputSections.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ static bool canMergeToProgbits(Ctx &ctx, unsigned type) {
9191
return type == SHT_NOBITS || type == SHT_PROGBITS || type == SHT_INIT_ARRAY ||
9292
type == SHT_PREINIT_ARRAY || type == SHT_FINI_ARRAY ||
9393
type == SHT_NOTE ||
94-
(type == SHT_X86_64_UNWIND && ctx.arg.emachine == EM_X86_64);
94+
(type == SHT_X86_64_UNWIND && ctx.arg.emachine == EM_X86_64) ||
95+
type == SHT_LLVM_CFI_JUMP_TABLE;
9596
}
9697

9798
// Record that isec will be placed in the OutputSection. isec does not become

lld/ELF/Relocations.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1675,7 +1675,7 @@ void RelocationScanner::scan(Relocs<RelTy> rels) {
16751675
// branch-to-branch optimization.
16761676
if (is_contained({EM_RISCV, EM_LOONGARCH}, ctx.arg.emachine) ||
16771677
(ctx.arg.emachine == EM_PPC64 && sec->name == ".toc") ||
1678-
ctx.arg.branchToBranch)
1678+
ctx.arg.branchToBranch || sec->type == SHT_LLVM_CFI_JUMP_TABLE)
16791679
llvm::stable_sort(sec->relocs(),
16801680
[](const Relocation &lhs, const Relocation &rhs) {
16811681
return lhs.offset < rhs.offset;

lld/ELF/Target.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ class TargetInfo {
9696

9797
// Do a linker relaxation pass and return true if we changed something.
9898
virtual bool relaxOnce(int pass) const { return false; }
99+
// Relax CFI jump tables if implemented by target.
100+
virtual void relaxCFIJumpTables() const {}
99101
// Do finalize relaxation after collecting relaxation infos.
100102
virtual void finalizeRelax(int passes) const {}
101103

0 commit comments

Comments
 (0)