Skip to content

Commit 64da33a

Browse files
authored
ELF: Introduce --randomize-section-padding option.
The --randomize-section-padding option randomly inserts padding between input sections using the given seed. It is intended to be used in A/B experiments to determine the average effect of a change on program performance, while controlling for effects such as false sharing in the cache which may introduce measurement bias. For more details, see the RFC: https://discourse.llvm.org/t/rfc-lld-feature-for-controlling-for-code-size-dependent-measurement-bias/83334 Reviewers: smithp35, MaskRay Reviewed By: MaskRay, smithp35 Pull Request: #117653
1 parent 2135bab commit 64da33a

File tree

9 files changed

+208
-2
lines changed

9 files changed

+208
-2
lines changed

lld/ELF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ struct Config {
320320
bool printGcSections;
321321
bool printIcfSections;
322322
bool printMemoryUsage;
323+
std::optional<uint64_t> randomizeSectionPadding;
323324
bool rejectMismatch;
324325
bool relax;
325326
bool relaxGP;

lld/ELF/Driver.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,9 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
14101410
ctx.arg.searchPaths = args::getStrings(args, OPT_library_path);
14111411
ctx.arg.sectionStartMap = getSectionStartMap(ctx, args);
14121412
ctx.arg.shared = args.hasArg(OPT_shared);
1413+
if (args.hasArg(OPT_randomize_section_padding))
1414+
ctx.arg.randomizeSectionPadding =
1415+
args::getInteger(args, OPT_randomize_section_padding, 0);
14131416
ctx.arg.singleRoRx = !args.hasFlag(OPT_rosegment, OPT_no_rosegment, true);
14141417
ctx.arg.soName = args.getLastArgValue(OPT_soname);
14151418
ctx.arg.sortSection = getSortSection(ctx, args);

lld/ELF/Options.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,9 @@ defm section_start: Eq<"section-start", "Set address of section">,
434434

435435
def shared: F<"shared">, HelpText<"Build a shared object">;
436436

437+
def randomize_section_padding: JJ<"randomize-section-padding=">,
438+
HelpText<"Randomly insert padding between input sections and at the start of each segment using given seed">;
439+
437440
defm soname: Eq<"soname", "Set DT_SONAME">;
438441

439442
defm sort_section:

lld/ELF/OutputSections.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,14 @@ class OutputSection final : public SectionBase {
124124
void sortInitFini();
125125
void sortCtorsDtors();
126126

127+
std::array<uint8_t, 4> getFiller(Ctx &);
128+
127129
// Used for implementation of --compress-debug-sections and
128130
// --compress-sections.
129131
CompressedData compressed;
130132

131133
private:
132134
SmallVector<InputSection *, 0> storage;
133-
134-
std::array<uint8_t, 4> getFiller(Ctx &);
135135
};
136136

137137
struct OutputDesc final : SectionCommand {

lld/ELF/SyntheticSections.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2753,6 +2753,21 @@ RelroPaddingSection::RelroPaddingSection(Ctx &ctx)
27532753
: SyntheticSection(ctx, ".relro_padding", SHT_NOBITS, SHF_ALLOC | SHF_WRITE,
27542754
1) {}
27552755

2756+
RandomizePaddingSection::RandomizePaddingSection(Ctx &ctx, uint64_t size,
2757+
OutputSection *parent)
2758+
: SyntheticSection(ctx, ".randomize_padding", SHT_PROGBITS, SHF_ALLOC, 1),
2759+
size(size) {
2760+
this->parent = parent;
2761+
}
2762+
2763+
void RandomizePaddingSection::writeTo(uint8_t *buf) {
2764+
std::array<uint8_t, 4> filler = getParent()->getFiller(ctx);
2765+
uint8_t *end = buf + size;
2766+
for (; buf + 4 <= end; buf += 4)
2767+
memcpy(buf, &filler[0], 4);
2768+
memcpy(buf, &filler[0], end - buf);
2769+
}
2770+
27562771
// The string hash function for .gdb_index.
27572772
static uint32_t computeGdbHash(StringRef s) {
27582773
uint32_t h = 0;

lld/ELF/SyntheticSections.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,15 @@ class RelroPaddingSection final : public SyntheticSection {
796796
void writeTo(uint8_t *buf) override {}
797797
};
798798

799+
class RandomizePaddingSection final : public SyntheticSection {
800+
uint64_t size;
801+
802+
public:
803+
RandomizePaddingSection(Ctx &ctx, uint64_t size, OutputSection *parent);
804+
size_t getSize() const override { return size; }
805+
void writeTo(uint8_t *buf) override;
806+
};
807+
799808
// Used by the merged DWARF32 .debug_names (a per-module index). If we
800809
// move to DWARF64, most of this data will need to be re-sized.
801810
class DebugNamesBaseSection : public SyntheticSection {

lld/ELF/Writer.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,40 @@ static void finalizeSynthetic(Ctx &ctx, SyntheticSection *sec) {
14491449
}
14501450
}
14511451

1452+
static bool canInsertPadding(OutputSection *sec) {
1453+
StringRef s = sec->name;
1454+
return s == ".bss" || s == ".data" || s == ".data.rel.ro" || s == ".lbss" ||
1455+
s == ".ldata" || s == ".lrodata" || s == ".ltext" || s == ".rodata" ||
1456+
s.starts_with(".text");
1457+
}
1458+
1459+
static void randomizeSectionPadding(Ctx &ctx) {
1460+
std::mt19937 g(*ctx.arg.randomizeSectionPadding);
1461+
PhdrEntry *curPtLoad = nullptr;
1462+
for (OutputSection *os : ctx.outputSections) {
1463+
if (!canInsertPadding(os))
1464+
continue;
1465+
for (SectionCommand *bc : os->commands) {
1466+
if (auto *isd = dyn_cast<InputSectionDescription>(bc)) {
1467+
SmallVector<InputSection *, 0> tmp;
1468+
if (os->ptLoad != curPtLoad) {
1469+
tmp.push_back(make<RandomizePaddingSection>(
1470+
ctx, g() % ctx.arg.maxPageSize, os));
1471+
curPtLoad = os->ptLoad;
1472+
}
1473+
for (InputSection *isec : isd->sections) {
1474+
// Probability of inserting padding is 1 in 16.
1475+
if (g() % 16 == 0)
1476+
tmp.push_back(
1477+
make<RandomizePaddingSection>(ctx, isec->addralign, os));
1478+
tmp.push_back(isec);
1479+
}
1480+
isd->sections = std::move(tmp);
1481+
}
1482+
}
1483+
}
1484+
}
1485+
14521486
// We need to generate and finalize the content that depends on the address of
14531487
// InputSections. As the generation of the content may also alter InputSection
14541488
// addresses we must converge to a fixed point. We do that here. See the comment
@@ -1475,6 +1509,9 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
14751509
if (ctx.arg.emachine == EM_HEXAGON)
14761510
hexagonTLSSymbolUpdate(ctx);
14771511

1512+
if (ctx.arg.randomizeSectionPadding)
1513+
randomizeSectionPadding(ctx);
1514+
14781515
uint32_t pass = 0, assignPasses = 0;
14791516
for (;;) {
14801517
bool changed = ctx.target->needsThunks

lld/docs/ld.lld.1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,19 @@ and
529529
.It Fl -pop-state
530530
Restore the states saved by
531531
.Fl -push-state.
532+
.It Fl -randomize-section-padding Ns = Ns Ar seed
533+
Randomly insert padding between input sections and at the start of each segment using the given seed.
534+
Padding is inserted into output sections with names matching the following patterns:
535+
.Cm .bss ,
536+
.Cm .data ,
537+
.Cm .data.rel.ro ,
538+
.Cm .lbss ,
539+
.Cm .ldata ,
540+
.Cm .lrodata ,
541+
.Cm .ltext ,
542+
.Cm .rodata
543+
and
544+
.Cm .text* .
532545
.It Fl --relax-gp
533546
Enable global pointer relaxation for RISC-V.
534547
.It Fl -relocatable , Fl r
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# REQUIRES: x86
2+
# RUN: split-file %s %t
3+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o
4+
5+
## --randomize-section-padding= inserts segment offset padding and pre-section
6+
## padding, and does not affect flags. Segment offset padding is only inserted
7+
## when PT_LOAD changes, as shown by .bss size (.data and .bss share a PT_LOAD).
8+
9+
# RUN: ld.lld --randomize-section-padding=6 %t/a.o -o %t/a.out
10+
# RUN: llvm-readelf -sS -x .rodata -x .text -x .data %t/a.out | FileCheck --check-prefix=PAD6 %s
11+
12+
# PAD6: .rodata PROGBITS 0000000000200158 000158 000b8d 00 A 0 0 1
13+
# PAD6-NEXT: .text PROGBITS 0000000000201ce8 000ce8 000270 00 AX 0 0 4
14+
# PAD6-NEXT: .data PROGBITS 0000000000202f58 000f58 000941 00 WA 0 0 1
15+
# PAD6-NEXT: .bss NOBITS 0000000000203899 001899 000003 00 WA 0 0 1
16+
17+
# PAD6: 0000000000203899 0 NOTYPE LOCAL DEFAULT 4 a
18+
# PAD6: 000000000020389a 0 NOTYPE LOCAL DEFAULT 4 b
19+
# PAD6: 000000000020389b 0 NOTYPE LOCAL DEFAULT 4 c
20+
21+
# PAD6: Hex dump of section '.rodata':
22+
# PAD6: 0x00200cd8 00000000 00000000 00000102 03
23+
# PAD6: Hex dump of section '.text':
24+
# PAD6: 0x00201f48 cccccccc cccccccc cccccccc 0405cc06
25+
# PAD6: Hex dump of section '.data':
26+
# PAD6: 0x00203888 00000000 00000000 00000000 00000708
27+
# PAD6: 0x00203898 09
28+
29+
## Size of segment offset padding and location of pre-section padding is
30+
## dependent on the seed.
31+
32+
# RUN: ld.lld --randomize-section-padding=46 %t/a.o -o %t/a.out
33+
# RUN: llvm-readelf -sS -x .rodata -x .text -x .data %t/a.out | FileCheck --check-prefix=PAD46 %s
34+
35+
# PAD46: .rodata PROGBITS 0000000000200158 000158 000cc0 00 A 0 0 1
36+
# PAD46-NEXT: .text PROGBITS 0000000000201e18 000e18 0009bf 00 AX 0 0 4
37+
# PAD46-NEXT: .data PROGBITS 00000000002037d7 0017d7 000540 00 WA 0 0 1
38+
# PAD46-NEXT: .bss NOBITS 0000000000203d17 001d17 000004 00 WA 0 0 1
39+
40+
# PAD46: 0000000000203d17 0 NOTYPE LOCAL DEFAULT 4 a
41+
# PAD46: 0000000000203d18 0 NOTYPE LOCAL DEFAULT 4 b
42+
# PAD46: 0000000000203d1a 0 NOTYPE LOCAL DEFAULT 4 c
43+
44+
# PAD46: Hex dump of section '.rodata':
45+
# PAD46: 0x00200e08 00000000 00000000 00000000 00010203
46+
# PAD46: Hex dump of section '.text':
47+
# PAD46: 0x002027c8 cccccccc cccccccc cccccccc 040506
48+
# PAD46: Hex dump of section '.data':
49+
# PAD46: 0x00203d07 00000000 00000000 00000000 07000809
50+
51+
## When there are multiple InputSectionDescriptions for an output section,
52+
## segment offset padding is inserted in the first InputSectionDescription.
53+
54+
# RUN: ld.lld --randomize-section-padding=46 %t/a.o %t/a.lds -o %t/a.out
55+
56+
# RUN: llvm-readelf -sS -x .rodata -x .text -x .data %t/a.out | FileCheck --check-prefix=PAD46-LDS %s
57+
58+
# PAD46-LDS: .rodata PROGBITS 0000000000000158 000158 000cc0 00 A 0 0 1
59+
# PAD46-LDS-NEXT: .text PROGBITS 0000000000001000 001000 0009c0 00 AX 0 0 4
60+
# PAD46-LDS-NEXT: .data PROGBITS 0000000000002000 002000 000540 00 WA 0 0 1
61+
# PAD46-LDS-NEXT: .bss NOBITS 0000000000002540 002540 000004 00 WA 0 0 1
62+
63+
# PAD46-LDS: 0000000000002543 0 NOTYPE LOCAL DEFAULT 4 a
64+
# PAD46-LDS: 0000000000002541 0 NOTYPE LOCAL DEFAULT 4 b
65+
# PAD46-LDS: 0000000000002540 0 NOTYPE LOCAL DEFAULT 4 c
66+
67+
# PAD46-LDS: Hex dump of section '.rodata':
68+
# PAD46-LDS: 0x00000e08 00000000 00000000 00000000 00030201 ................
69+
# PAD46-LDS: Hex dump of section '.text':
70+
# PAD46-LDS: 0x000019b0 cccccccc cccccccc cccc0605 04cccccc ................
71+
# PAD46-LDS: Hex dump of section '.data':
72+
# PAD46-LDS: 0x00002530 00000000 00000000 00000000 09000807 ................
73+
74+
#--- a.s
75+
76+
.section .rodata.a,"a",@progbits
77+
.byte 1
78+
79+
.section .rodata.b,"a",@progbits
80+
.byte 2
81+
82+
.section .rodata.c,"a",@progbits
83+
.byte 3
84+
85+
.section .text.a,"ax",@progbits
86+
.byte 4
87+
88+
.section .text.b,"ax",@progbits
89+
.byte 5
90+
91+
.section .text.c,"ax",@progbits
92+
.byte 6
93+
94+
.section .data.a,"aw",@progbits
95+
.byte 7
96+
97+
.section .data.b,"aw",@progbits
98+
.byte 8
99+
100+
.section .data.c,"aw",@progbits
101+
.byte 9
102+
103+
.section .bss.a,"a",@nobits
104+
a:
105+
.zero 1
106+
107+
.section .bss.b,"a",@nobits
108+
b:
109+
.zero 1
110+
111+
.section .bss.c,"a",@nobits
112+
c:
113+
.zero 1
114+
115+
#--- a.lds
116+
117+
SECTIONS {
118+
. = SIZEOF_HEADERS;
119+
.rodata : { *(.rodata.c) *(.rodata.b) *(.rodata.a) }
120+
. = ALIGN(CONSTANT(MAXPAGESIZE));
121+
.text : { *(.text.c) *(.text.b) *(.text.a) }
122+
. = ALIGN(CONSTANT(MAXPAGESIZE));
123+
.data : { *(.data.c) *(.data.b) *(.data.a) }
124+
.bss : { *(.bss.c) *(.bss.b) *(.bss.a) }
125+
}

0 commit comments

Comments
 (0)