Skip to content

Commit 119d507

Browse files
authored
[LLD][COFF] Add support for custom section layout (#152779)
MS link.exe provides the `/sectionlayout:@` option to specify the order of output sections at the granularity of individual sections. LLD/COFF currently does not have capability for user-controlled ordering of one or more output sections (as LLD/COFF does not support linker scripts), and this PR adds the option to align with MS link.exe. The option accepts only a file that specifies the order of sections, one per line. For example, `mylayout.txt` could emit the `.text` section after all other sections while preserving the original relative order of the remaining sections. ``` .data .rdata .pdata .rsrc .reloc .text ``` ```bash echo 'int main() { return 0; }' > main.c cl main.c /link /entry:main /sectionlayout:@mylayout.txt llvm-readobj --sections main.exe ```
1 parent f88eadd commit 119d507

File tree

8 files changed

+227
-0
lines changed

8 files changed

+227
-0
lines changed

lld/COFF/Config.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ struct Configuration {
212212

213213
// Used for /section=.name,{DEKPRSW} to set section attributes.
214214
std::map<StringRef, uint32_t> section;
215+
// Used for /sectionlayout: to layout sections in specified order.
216+
std::map<std::string, int> sectionOrder;
215217

216218
// Options for manifest files.
217219
ManifestKind manifest = Default;

lld/COFF/Driver.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,6 +2049,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
20492049
// Handle /section
20502050
for (auto *arg : args.filtered(OPT_section))
20512051
parseSection(arg->getValue());
2052+
// Handle /sectionlayout
2053+
if (auto *arg = args.getLastArg(OPT_sectionlayout))
2054+
parseSectionLayout(arg->getValue());
20522055

20532056
// Handle /align
20542057
if (auto *arg = args.getLastArg(OPT_align)) {

lld/COFF/Driver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ class LinkerDriver {
213213
void parseMerge(StringRef);
214214
void parsePDBPageSize(StringRef);
215215
void parseSection(StringRef);
216+
void parseSectionLayout(StringRef);
216217

217218
void parseSameAddress(StringRef);
218219

lld/COFF/DriverUtils.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,43 @@ void LinkerDriver::parseSection(StringRef s) {
214214
ctx.config.section[name] = parseSectionAttributes(ctx, attrs);
215215
}
216216

217+
// Parses /sectionlayout: option argument.
218+
void LinkerDriver::parseSectionLayout(StringRef path) {
219+
if (path.starts_with("@"))
220+
path = path.substr(1);
221+
std::unique_ptr<MemoryBuffer> layoutFile =
222+
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
223+
StringRef content = layoutFile->getBuffer();
224+
int index = 0;
225+
226+
while (!content.empty()) {
227+
size_t pos = content.find_first_of("\r\n");
228+
StringRef line;
229+
230+
if (pos == StringRef::npos) {
231+
line = content;
232+
content = StringRef();
233+
} else {
234+
line = content.substr(0, pos);
235+
content = content.substr(pos).ltrim("\r\n");
236+
}
237+
238+
line = line.trim();
239+
if (line.empty())
240+
continue;
241+
242+
StringRef sectionName = line.split(' ').first;
243+
244+
if (ctx.config.sectionOrder.count(sectionName.str())) {
245+
Warn(ctx) << "duplicate section '" << sectionName.str()
246+
<< "' in section layout file, ignoring";
247+
continue;
248+
}
249+
250+
ctx.config.sectionOrder[sectionName.str()] = index++;
251+
}
252+
}
253+
217254
void LinkerDriver::parseDosStub(StringRef path) {
218255
std::unique_ptr<MemoryBuffer> stub =
219256
CHECK(MemoryBuffer::getFile(path), "could not open " + path);

lld/COFF/Options.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">,
102102
MetaVarName<"<name>=<file>">,
103103
HelpText<"Embed the contents of <file> in the PDB as named stream <name>">;
104104
def section : P<"section", "Specify section attributes">;
105+
def sectionlayout : P<"sectionlayout", "Specifies the layout strategy for output sections">;
105106
def stack : P<"stack", "Size of the stack">;
106107
def stub : P<"stub", "Specify DOS stub file">;
107108
def subsystem : P<"subsystem", "Specify subsystem">;

lld/COFF/Writer.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ class Writer {
219219
void sortECChunks();
220220
void appendECImportTables();
221221
void removeUnusedSections();
222+
void layoutSections();
222223
void assignAddresses();
223224
bool isInRange(uint16_t relType, uint64_t s, uint64_t p, int margin,
224225
MachineTypes machine);
@@ -783,6 +784,7 @@ void Writer::run() {
783784
appendECImportTables();
784785
createDynamicRelocs();
785786
removeUnusedSections();
787+
layoutSections();
786788
finalizeAddresses();
787789
removeEmptySections();
788790
assignOutputSectionIndices();
@@ -1413,6 +1415,33 @@ void Writer::removeUnusedSections() {
14131415
llvm::erase_if(ctx.outputSections, isUnused);
14141416
}
14151417

1418+
void Writer::layoutSections() {
1419+
llvm::TimeTraceScope timeScope("Layout sections");
1420+
if (ctx.config.sectionOrder.empty())
1421+
return;
1422+
1423+
llvm::stable_sort(ctx.outputSections,
1424+
[this](const OutputSection *a, const OutputSection *b) {
1425+
auto itA = ctx.config.sectionOrder.find(a->name.str());
1426+
auto itB = ctx.config.sectionOrder.find(b->name.str());
1427+
bool aInOrder = itA != ctx.config.sectionOrder.end();
1428+
bool bInOrder = itB != ctx.config.sectionOrder.end();
1429+
1430+
// Put unspecified sections after all specified sections
1431+
if (aInOrder && bInOrder) {
1432+
return itA->second < itB->second;
1433+
} else if (aInOrder && !bInOrder) {
1434+
return true; // ordered sections come before unordered
1435+
} else {
1436+
// (!aInOrder && bInOrder): unordered comes after
1437+
// ordered
1438+
// (!aInOrder && !bInOrder): both unspecified, preserve
1439+
// the original order
1440+
return false;
1441+
}
1442+
});
1443+
}
1444+
14161445
// The Windows loader doesn't seem to like empty sections,
14171446
// so we remove them if any.
14181447
void Writer::removeEmptySections() {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--- !COFF
2+
header:
3+
Machine: IMAGE_FILE_MACHINE_AMD64
4+
Characteristics: []
5+
sections:
6+
- Name: '.text1'
7+
Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
8+
Alignment: 16
9+
SectionData: B82A000000C3
10+
- Name: '.text2'
11+
Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
12+
Alignment: 16
13+
SectionData: CC
14+
- Name: '.text3'
15+
Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
16+
Alignment: 16
17+
SectionData: CC
18+
symbols:
19+
- Name: main
20+
Value: 0
21+
SectionNumber: 1
22+
SimpleType: IMAGE_SYM_TYPE_NULL
23+
ComplexType: IMAGE_SYM_DTYPE_FUNCTION
24+
StorageClass: IMAGE_SYM_CLASS_EXTERNAL
25+
...

lld/test/COFF/sectionlayout.test

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
RUN: yaml2obj %p/Inputs/sectionlayout.yaml -o %t.obj
2+
3+
## Error on non-exist input layout file
4+
RUN: not lld-link /entry:main /sectionlayout:doesnotexist.txt %t.obj
5+
6+
## Order in 1 -> 3 -> 2
7+
RUN: echo ".text1" > %t.layout.txt
8+
RUN: echo ".text3" >> %t.layout.txt
9+
RUN: echo ".text2" >> %t.layout.txt
10+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
11+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK1 %s
12+
13+
## While /sectionlayout:abc is valid, /sectionlayout:@abc is also accepted (to align with MS link.exe)
14+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:@%t.layout.txt %t.obj
15+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK1 %s
16+
17+
## Ensure tokens after section name is ignored (for now, to align with MS link.exe)
18+
RUN: echo ".text1 ALIGN=1" > %t.layout.txt
19+
RUN: echo ".text3" >> %t.layout.txt
20+
RUN: echo ".text2" >> %t.layout.txt
21+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:@%t.layout.txt %t.obj
22+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK1 %s
23+
24+
CHECK1: Sections [
25+
CHECK1: Section {
26+
CHECK1: Number: 1
27+
CHECK1: Name: .text1
28+
CHECK1: }
29+
CHECK1: Section {
30+
CHECK1: Number: 2
31+
CHECK1: Name: .text3
32+
CHECK1: }
33+
CHECK1: Section {
34+
CHECK1: Number: 3
35+
CHECK1: Name: .text2
36+
CHECK1: }
37+
CHECK1: ]
38+
39+
## Order in 3 -> 2 -> 1
40+
RUN: echo ".text3" > %t.layout.txt
41+
RUN: echo ".text2" >> %t.layout.txt
42+
RUN: echo ".text1" >> %t.layout.txt
43+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
44+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s
45+
46+
CHECK2: Sections [
47+
CHECK2: Section {
48+
CHECK2: Number: 1
49+
CHECK2: Name: .text3
50+
CHECK2: }
51+
CHECK2: Section {
52+
CHECK2: Number: 2
53+
CHECK2: Name: .text2
54+
CHECK2: }
55+
CHECK2: Section {
56+
CHECK2: Number: 3
57+
CHECK2: Name: .text1
58+
CHECK2: }
59+
CHECK2: ]
60+
61+
## Put non-exisist section in layout file has no effect; original order is respected
62+
RUN: echo "notexist" > %t.layout.txt
63+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
64+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
65+
66+
## Empty layout file has no effect
67+
RUN: echo "" > %t.layout.txt
68+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
69+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
70+
71+
## Empty layout file has no effect
72+
RUN: echo " " > %t.layout.txt
73+
RUN: echo " " >> %t.layout.txt
74+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
75+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
76+
77+
CHECK3: Sections [
78+
CHECK3: Section {
79+
CHECK3: Number: 1
80+
CHECK3: Name: .text1
81+
CHECK3: }
82+
CHECK3: Section {
83+
CHECK3: Number: 2
84+
CHECK3: Name: .text2
85+
CHECK3: }
86+
CHECK3: Section {
87+
CHECK3: Number: 3
88+
CHECK3: Name: .text3
89+
CHECK3: }
90+
CHECK3: ]
91+
92+
## Order in 3 -> 1, but 2 remains unspecified
93+
RUN: echo ".text3" > %t.layout.txt
94+
RUN: echo ".text1" >> %t.layout.txt
95+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
96+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK4 %s
97+
98+
CHECK4: Sections [
99+
CHECK4: Section {
100+
CHECK4: Number: 1
101+
CHECK4: Name: .text3
102+
CHECK4: }
103+
CHECK4: Section {
104+
CHECK4: Number: 2
105+
CHECK4: Name: .text1
106+
CHECK4: }
107+
CHECK4: Section {
108+
CHECK4: Number: 3
109+
CHECK4: Name: .text2
110+
CHECK4: }
111+
CHECK4: ]
112+
113+
## Order in 3 -> 2, but 1 remains unspecified.
114+
## 1 should be the first, as the original order (1 -> 2 -> 3) is respected
115+
RUN: echo ".text3" > %t.layout.txt
116+
RUN: echo ".text2" >> %t.layout.txt
117+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
118+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s
119+
120+
## Order in 3 -> 2 -> 1, multiple specification has no effect (the first one is used)
121+
RUN: echo ".text3" > %t.layout.txt
122+
RUN: echo ".text3" >> %t.layout.txt
123+
RUN: echo ".text3" >> %t.layout.txt
124+
RUN: echo ".text2" >> %t.layout.txt
125+
RUN: echo ".text2" >> %t.layout.txt
126+
RUN: echo ".text1" >> %t.layout.txt
127+
RUN: echo ".text3" >> %t.layout.txt
128+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
129+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s

0 commit comments

Comments
 (0)