Skip to content

Commit d2c8f20

Browse files
committed
[LLD][COFF] Add support for custom section layout
1 parent 45b4f1b commit d2c8f20

File tree

8 files changed

+220
-0
lines changed

8 files changed

+220
-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> sectionLayout;
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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,38 @@ 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);
236+
while (!content.empty() && (content[0] == '\r' || content[0] == '\n'))
237+
content = content.substr(1);
238+
}
239+
240+
line = line.trim();
241+
if (line.empty())
242+
continue;
243+
244+
StringRef sectionName = line.split(' ').first;
245+
ctx.config.sectionLayout[sectionName.str()] = index++;
246+
}
247+
}
248+
217249
void LinkerDriver::parseDosStub(StringRef path) {
218250
std::unique_ptr<MemoryBuffer> stub =
219251
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: 30 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,34 @@ 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.sectionLayout.empty())
1421+
return;
1422+
1423+
std::unordered_map<const OutputSection *, size_t> originalOrder;
1424+
for (size_t i = 0; i < ctx.outputSections.size(); ++i)
1425+
originalOrder[ctx.outputSections[i]] = i;
1426+
1427+
std::stable_sort(
1428+
ctx.outputSections.begin(), ctx.outputSections.end(),
1429+
[this, &originalOrder](const OutputSection *a, const OutputSection *b) {
1430+
auto itA = ctx.config.sectionLayout.find(a->name.str());
1431+
auto itB = ctx.config.sectionLayout.find(b->name.str());
1432+
1433+
if (itA != ctx.config.sectionLayout.end() &&
1434+
itB != ctx.config.sectionLayout.end())
1435+
return itA->second < itB->second;
1436+
1437+
if (itA == ctx.config.sectionLayout.end() &&
1438+
itB == ctx.config.sectionLayout.end())
1439+
return originalOrder[a] < originalOrder[b];
1440+
1441+
// Not found in layout file; respect the original order
1442+
return originalOrder[a] < originalOrder[b];
1443+
});
1444+
}
1445+
14161446
// The Windows loader doesn't seem to like empty sections,
14171447
// so we remove them if any.
14181448
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: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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+
CHECK1: Sections [
18+
CHECK1: Section {
19+
CHECK1: Number: 1
20+
CHECK1: Name: .text1
21+
CHECK1: }
22+
CHECK1: Section {
23+
CHECK1: Number: 2
24+
CHECK1: Name: .text3
25+
CHECK1: }
26+
CHECK1: Section {
27+
CHECK1: Number: 3
28+
CHECK1: Name: .text2
29+
CHECK1: }
30+
CHECK1: ]
31+
32+
## Order in 3 -> 2 -> 1
33+
RUN: echo ".text3" > %t.layout.txt
34+
RUN: echo ".text2" >> %t.layout.txt
35+
RUN: echo ".text1" >> %t.layout.txt
36+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
37+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s
38+
39+
CHECK2: Sections [
40+
CHECK2: Section {
41+
CHECK2: Number: 1
42+
CHECK2: Name: .text3
43+
CHECK2: }
44+
CHECK2: Section {
45+
CHECK2: Number: 2
46+
CHECK2: Name: .text2
47+
CHECK2: }
48+
CHECK2: Section {
49+
CHECK2: Number: 3
50+
CHECK2: Name: .text1
51+
CHECK2: }
52+
CHECK2: ]
53+
54+
## Put non-exisist section in layout file has no effect; original order is respected
55+
RUN: echo "notexist" > %t.layout.txt
56+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
57+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
58+
59+
## Empty layout file has no effect
60+
RUN: echo "" > %t.layout.txt
61+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
62+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
63+
64+
## Empty layout file has no effect
65+
RUN: echo " " > %t.layout.txt
66+
RUN: echo " " >> %t.layout.txt
67+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
68+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
69+
70+
CHECK3: Sections [
71+
CHECK3: Section {
72+
CHECK3: Number: 1
73+
CHECK3: Name: .text1
74+
CHECK3: }
75+
CHECK3: Section {
76+
CHECK3: Number: 2
77+
CHECK3: Name: .text2
78+
CHECK3: }
79+
CHECK3: Section {
80+
CHECK3: Number: 3
81+
CHECK3: Name: .text3
82+
CHECK3: }
83+
CHECK3: ]
84+
85+
## Order in 3 -> 1, but 2 remains unspecified
86+
RUN: echo ".text3" > %t.layout.txt
87+
RUN: echo ".text1" >> %t.layout.txt
88+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
89+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK4 %s
90+
91+
CHECK4: Sections [
92+
CHECK4: Section {
93+
CHECK4: Number: 1
94+
CHECK4: Name: .text3
95+
CHECK4: }
96+
CHECK4: Section {
97+
CHECK4: Number: 2
98+
CHECK4: Name: .text1
99+
CHECK4: }
100+
CHECK4: Section {
101+
CHECK4: Number: 3
102+
CHECK4: Name: .text2
103+
CHECK4: }
104+
CHECK4: ]
105+
106+
## Order in 3 -> 2, but 1 remains unspecified.
107+
## 1 should be the first, as the original order (1 -> 2 -> 3) is respected
108+
RUN: echo ".text3" > %t.layout.txt
109+
RUN: echo ".text2" >> %t.layout.txt
110+
RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
111+
RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK5 %s
112+
113+
CHECK5: Sections [
114+
CHECK5: Section {
115+
CHECK5: Number: 1
116+
CHECK5: Name: .text1
117+
CHECK5: }
118+
CHECK5: Section {
119+
CHECK5: Number: 2
120+
CHECK5: Name: .text3
121+
CHECK5: }
122+
CHECK5: Section {
123+
CHECK5: Number: 3
124+
CHECK5: Name: .text2
125+
CHECK5: }
126+
CHECK5: ]

0 commit comments

Comments
 (0)