[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