Skip to content

[LLD][COFF] Add support for custom section layout #152779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

kkent030315
Copy link
Contributor

@kkent030315 kkent030315 commented Aug 8, 2025

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
echo 'int main() { return 0; }' > main.c
cl main.c /link /entry:main /sectionlayout:@mylayout.txt
llvm-readobj --sections main.exe

@llvmbot
Copy link
Member

llvmbot commented Aug 8, 2025

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-coff

Author: None (kkent030315)

Changes

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
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 --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: ]

@llvmbot
Copy link
Member

llvmbot commented Aug 8, 2025

@llvm/pr-subscribers-platform-windows

Author: None (kkent030315)

Changes

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
echo 'int main() { return 0; }' &gt; 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 --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: ]

Copy link

github-actions bot commented Aug 8, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@kkent030315 kkent030315 force-pushed the sectionlayout branch 2 times, most recently from 5f5a3fa to d2c8f20 Compare August 8, 2025 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants