[lld] [LLD][COFF] Add support for custom section layout (PR #152779)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 8 13:52:09 PDT 2025
https://github.com/kkent030315 updated https://github.com/llvm/llvm-project/pull/152779
>From d2c8f20d6700932a7c073ef742f70849b88f19b7 Mon Sep 17 00:00:00 2001
From: kkent030315 <hrn832 at protonmail.com>
Date: Sat, 9 Aug 2025 03:56:53 +0900
Subject: [PATCH 1/3] [LLD][COFF] Add support for custom section layout
---
lld/COFF/Config.h | 2 +
lld/COFF/Driver.cpp | 3 +
lld/COFF/Driver.h | 1 +
lld/COFF/DriverUtils.cpp | 32 ++++++
lld/COFF/Options.td | 1 +
lld/COFF/Writer.cpp | 30 ++++++
lld/test/COFF/Inputs/sectionlayout.yaml | 25 +++++
lld/test/COFF/sectionlayout.test | 126 ++++++++++++++++++++++++
8 files changed, 220 insertions(+)
create mode 100644 lld/test/COFF/Inputs/sectionlayout.yaml
create mode 100644 lld/test/COFF/sectionlayout.test
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index a03bb57641670..bb9bcb957cab5 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -212,6 +212,8 @@ struct Configuration {
// Used for /section=.name,{DEKPRSW} to set section attributes.
std::map<StringRef, uint32_t> section;
+ // Used for /sectionlayout:@ to layout sections in specified order.
+ std::map<std::string, int> sectionLayout;
// Options for manifest files.
ManifestKind manifest = Default;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index ff616d0ce2bff..852c509a5c77d 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2049,6 +2049,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
// Handle /section
for (auto *arg : args.filtered(OPT_section))
parseSection(arg->getValue());
+ // Handle /sectionlayout
+ if (auto *arg = args.getLastArg(OPT_sectionlayout))
+ parseSectionLayout(arg->getValue());
// Handle /align
if (auto *arg = args.getLastArg(OPT_align)) {
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index b500ac8bba569..14710d5853bcf 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -213,6 +213,7 @@ class LinkerDriver {
void parseMerge(StringRef);
void parsePDBPageSize(StringRef);
void parseSection(StringRef);
+ void parseSectionLayout(StringRef);
void parseSameAddress(StringRef);
diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp
index dc4039f116f25..a31521eccbb0b 100644
--- a/lld/COFF/DriverUtils.cpp
+++ b/lld/COFF/DriverUtils.cpp
@@ -214,6 +214,38 @@ void LinkerDriver::parseSection(StringRef s) {
ctx.config.section[name] = parseSectionAttributes(ctx, attrs);
}
+// Parses /sectionlayout:@ option argument.
+void LinkerDriver::parseSectionLayout(StringRef path) {
+ if (path.starts_with("@"))
+ path = path.substr(1);
+ std::unique_ptr<MemoryBuffer> layoutFile =
+ CHECK(MemoryBuffer::getFile(path), "could not open " + path);
+ StringRef content = layoutFile->getBuffer();
+ int index = 0;
+
+ while (!content.empty()) {
+ size_t pos = content.find_first_of("\r\n");
+ StringRef line;
+
+ if (pos == StringRef::npos) {
+ line = content;
+ content = StringRef();
+ } else {
+ line = content.substr(0, pos);
+ content = content.substr(pos);
+ while (!content.empty() && (content[0] == '\r' || content[0] == '\n'))
+ content = content.substr(1);
+ }
+
+ line = line.trim();
+ if (line.empty())
+ continue;
+
+ StringRef sectionName = line.split(' ').first;
+ ctx.config.sectionLayout[sectionName.str()] = index++;
+ }
+}
+
void LinkerDriver::parseDosStub(StringRef path) {
std::unique_ptr<MemoryBuffer> stub =
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 2c393cc94b5e3..26f6acaa3b7ad 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -102,6 +102,7 @@ def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">,
MetaVarName<"<name>=<file>">,
HelpText<"Embed the contents of <file> in the PDB as named stream <name>">;
def section : P<"section", "Specify section attributes">;
+def sectionlayout : P<"sectionlayout", "Specifies the layout strategy for output sections">;
def stack : P<"stack", "Size of the stack">;
def stub : P<"stub", "Specify DOS stub file">;
def subsystem : P<"subsystem", "Specify subsystem">;
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 21ab9d17a26f9..f777dcb4a6dac 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -219,6 +219,7 @@ class Writer {
void sortECChunks();
void appendECImportTables();
void removeUnusedSections();
+ void layoutSections();
void assignAddresses();
bool isInRange(uint16_t relType, uint64_t s, uint64_t p, int margin,
MachineTypes machine);
@@ -783,6 +784,7 @@ void Writer::run() {
appendECImportTables();
createDynamicRelocs();
removeUnusedSections();
+ layoutSections();
finalizeAddresses();
removeEmptySections();
assignOutputSectionIndices();
@@ -1413,6 +1415,34 @@ void Writer::removeUnusedSections() {
llvm::erase_if(ctx.outputSections, isUnused);
}
+void Writer::layoutSections() {
+ llvm::TimeTraceScope timeScope("Layout sections");
+ if (ctx.config.sectionLayout.empty())
+ return;
+
+ std::unordered_map<const OutputSection *, size_t> originalOrder;
+ for (size_t i = 0; i < ctx.outputSections.size(); ++i)
+ originalOrder[ctx.outputSections[i]] = i;
+
+ std::stable_sort(
+ ctx.outputSections.begin(), ctx.outputSections.end(),
+ [this, &originalOrder](const OutputSection *a, const OutputSection *b) {
+ auto itA = ctx.config.sectionLayout.find(a->name.str());
+ auto itB = ctx.config.sectionLayout.find(b->name.str());
+
+ if (itA != ctx.config.sectionLayout.end() &&
+ itB != ctx.config.sectionLayout.end())
+ return itA->second < itB->second;
+
+ if (itA == ctx.config.sectionLayout.end() &&
+ itB == ctx.config.sectionLayout.end())
+ return originalOrder[a] < originalOrder[b];
+
+ // Not found in layout file; respect the original order
+ return originalOrder[a] < originalOrder[b];
+ });
+}
+
// The Windows loader doesn't seem to like empty sections,
// so we remove them if any.
void Writer::removeEmptySections() {
diff --git a/lld/test/COFF/Inputs/sectionlayout.yaml b/lld/test/COFF/Inputs/sectionlayout.yaml
new file mode 100644
index 0000000000000..f5a2a5e333b95
--- /dev/null
+++ b/lld/test/COFF/Inputs/sectionlayout.yaml
@@ -0,0 +1,25 @@
+--- !COFF
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: []
+sections:
+ - Name: '.text1'
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 16
+ SectionData: B82A000000C3
+ - Name: '.text2'
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 16
+ SectionData: CC
+ - Name: '.text3'
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 16
+ SectionData: CC
+symbols:
+ - Name: main
+ Value: 0
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_FUNCTION
+ StorageClass: IMAGE_SYM_CLASS_EXTERNAL
+...
diff --git a/lld/test/COFF/sectionlayout.test b/lld/test/COFF/sectionlayout.test
new file mode 100644
index 0000000000000..24590ee4e26e7
--- /dev/null
+++ b/lld/test/COFF/sectionlayout.test
@@ -0,0 +1,126 @@
+RUN: yaml2obj %p/Inputs/sectionlayout.yaml -o %t.obj
+
+## Error on non-exist input layout file
+RUN: not lld-link /entry:main /sectionlayout:doesnotexist.txt %t.obj
+
+## Order in 1 -> 3 -> 2
+RUN: echo ".text1" > %t.layout.txt
+RUN: echo ".text3" >> %t.layout.txt
+RUN: echo ".text2" >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK1 %s
+
+## While /sectionlayout:abc is valid, /sectionlayout:@abc is also accepted (to align with MS link.exe)
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:@%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK1 %s
+
+CHECK1: Sections [
+CHECK1: Section {
+CHECK1: Number: 1
+CHECK1: Name: .text1
+CHECK1: }
+CHECK1: Section {
+CHECK1: Number: 2
+CHECK1: Name: .text3
+CHECK1: }
+CHECK1: Section {
+CHECK1: Number: 3
+CHECK1: Name: .text2
+CHECK1: }
+CHECK1: ]
+
+## Order in 3 -> 2 -> 1
+RUN: echo ".text3" > %t.layout.txt
+RUN: echo ".text2" >> %t.layout.txt
+RUN: echo ".text1" >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s
+
+CHECK2: Sections [
+CHECK2: Section {
+CHECK2: Number: 1
+CHECK2: Name: .text3
+CHECK2: }
+CHECK2: Section {
+CHECK2: Number: 2
+CHECK2: Name: .text2
+CHECK2: }
+CHECK2: Section {
+CHECK2: Number: 3
+CHECK2: Name: .text1
+CHECK2: }
+CHECK2: ]
+
+## Put non-exisist section in layout file has no effect; original order is respected
+RUN: echo "notexist" > %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
+
+## Empty layout file has no effect
+RUN: echo "" > %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
+
+## Empty layout file has no effect
+RUN: echo " " > %t.layout.txt
+RUN: echo " " >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK3 %s
+
+CHECK3: Sections [
+CHECK3: Section {
+CHECK3: Number: 1
+CHECK3: Name: .text1
+CHECK3: }
+CHECK3: Section {
+CHECK3: Number: 2
+CHECK3: Name: .text2
+CHECK3: }
+CHECK3: Section {
+CHECK3: Number: 3
+CHECK3: Name: .text3
+CHECK3: }
+CHECK3: ]
+
+## Order in 3 -> 1, but 2 remains unspecified
+RUN: echo ".text3" > %t.layout.txt
+RUN: echo ".text1" >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK4 %s
+
+CHECK4: Sections [
+CHECK4: Section {
+CHECK4: Number: 1
+CHECK4: Name: .text3
+CHECK4: }
+CHECK4: Section {
+CHECK4: Number: 2
+CHECK4: Name: .text1
+CHECK4: }
+CHECK4: Section {
+CHECK4: Number: 3
+CHECK4: Name: .text2
+CHECK4: }
+CHECK4: ]
+
+## Order in 3 -> 2, but 1 remains unspecified.
+## 1 should be the first, as the original order (1 -> 2 -> 3) is respected
+RUN: echo ".text3" > %t.layout.txt
+RUN: echo ".text2" >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK5 %s
+
+CHECK5: Sections [
+CHECK5: Section {
+CHECK5: Number: 1
+CHECK5: Name: .text1
+CHECK5: }
+CHECK5: Section {
+CHECK5: Number: 2
+CHECK5: Name: .text3
+CHECK5: }
+CHECK5: Section {
+CHECK5: Number: 3
+CHECK5: Name: .text2
+CHECK5: }
+CHECK5: ]
>From 8fe1010d7c7038a88db7979e4a980e33bfe788c7 Mon Sep 17 00:00:00 2001
From: kkent030315 <hrn832 at protonmail.com>
Date: Sat, 9 Aug 2025 05:46:06 +0900
Subject: [PATCH 2/3] use `llvm::stable_sort` instead of std
---
lld/COFF/Writer.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index f777dcb4a6dac..59b1322427ff3 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1424,8 +1424,8 @@ void Writer::layoutSections() {
for (size_t i = 0; i < ctx.outputSections.size(); ++i)
originalOrder[ctx.outputSections[i]] = i;
- std::stable_sort(
- ctx.outputSections.begin(), ctx.outputSections.end(),
+ llvm::stable_sort(
+ ctx.outputSections,
[this, &originalOrder](const OutputSection *a, const OutputSection *b) {
auto itA = ctx.config.sectionLayout.find(a->name.str());
auto itB = ctx.config.sectionLayout.find(b->name.str());
>From 0e9b1ef2e4a80e810d0b57481040511c85be8890 Mon Sep 17 00:00:00 2001
From: kkent030315 <hrn832 at protonmail.com>
Date: Sat, 9 Aug 2025 05:52:10 +0900
Subject: [PATCH 3/3] add check/test for multiple specification in layout file
---
lld/COFF/DriverUtils.cpp | 11 ++++++++++-
lld/test/COFF/sectionlayout.test | 11 +++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp
index a31521eccbb0b..39ce3c50aa7ac 100644
--- a/lld/COFF/DriverUtils.cpp
+++ b/lld/COFF/DriverUtils.cpp
@@ -222,6 +222,7 @@ void LinkerDriver::parseSectionLayout(StringRef path) {
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
StringRef content = layoutFile->getBuffer();
int index = 0;
+ std::set<std::string> seenSections;
while (!content.empty()) {
size_t pos = content.find_first_of("\r\n");
@@ -242,7 +243,15 @@ void LinkerDriver::parseSectionLayout(StringRef path) {
continue;
StringRef sectionName = line.split(' ').first;
- ctx.config.sectionLayout[sectionName.str()] = index++;
+ std::string sectionNameStr = sectionName.str();
+
+ if (seenSections.count(sectionNameStr)) {
+ Warn(ctx) << "duplicate section '" << sectionNameStr << "' in section layout file, ignoring";
+ continue;
+ }
+
+ seenSections.insert(sectionNameStr);
+ ctx.config.sectionLayout[sectionNameStr] = index++;
}
}
diff --git a/lld/test/COFF/sectionlayout.test b/lld/test/COFF/sectionlayout.test
index 24590ee4e26e7..e2254bbb2fe49 100644
--- a/lld/test/COFF/sectionlayout.test
+++ b/lld/test/COFF/sectionlayout.test
@@ -124,3 +124,14 @@ CHECK5: Number: 3
CHECK5: Name: .text2
CHECK5: }
CHECK5: ]
+
+## Order in 3 -> 2 -> 1, multiple specification has no effect (the first one is used)
+RUN: echo ".text3" > %t.layout.txt
+RUN: echo ".text3" >> %t.layout.txt
+RUN: echo ".text3" >> %t.layout.txt
+RUN: echo ".text2" >> %t.layout.txt
+RUN: echo ".text2" >> %t.layout.txt
+RUN: echo ".text1" >> %t.layout.txt
+RUN: echo ".text3" >> %t.layout.txt
+RUN: lld-link /out:%t.exe /entry:main /sectionlayout:%t.layout.txt %t.obj
+RUN: llvm-readobj --sections %t.exe | FileCheck -check-prefix=CHECK2 %s
More information about the llvm-commits
mailing list