[lld] [LLD][COFF] Add support for custom section layout (PR #152779)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 8 12:26:57 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld-coff

Author: None (kkent030315)

<details>
<summary>Changes</summary>

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
```

---
Full diff: https://github.com/llvm/llvm-project/pull/152779.diff


8 Files Affected:

- (modified) lld/COFF/Config.h (+2) 
- (modified) lld/COFF/Driver.cpp (+3) 
- (modified) lld/COFF/Driver.h (+1) 
- (modified) lld/COFF/DriverUtils.cpp (+32) 
- (modified) lld/COFF/Options.td (+1) 
- (modified) lld/COFF/Writer.cpp (+30) 
- (added) lld/test/COFF/Inputs/sectionlayout.yaml (+25) 
- (added) lld/test/COFF/sectionlayout.test (+126) 


``````````diff
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..7970665ea5b99 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, including alignment and ordering rules.">;
 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..ed71844ae299d 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..67273626efc4d
--- /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: ]

``````````

</details>


https://github.com/llvm/llvm-project/pull/152779


More information about the llvm-commits mailing list