Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lld/COFF/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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> sectionOrder;

// Options for manifest files.
ManifestKind manifest = Default;
Expand Down
3 changes: 3 additions & 0 deletions lld/COFF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions lld/COFF/Driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class LinkerDriver {
void parseMerge(StringRef);
void parsePDBPageSize(StringRef);
void parseSection(StringRef);
void parseSectionLayout(StringRef);

void parseSameAddress(StringRef);

Expand Down
37 changes: 37 additions & 0 deletions lld/COFF/DriverUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,43 @@ 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).ltrim("\r\n");
}

line = line.trim();
if (line.empty())
continue;

StringRef sectionName = line.split(' ').first;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this ignores anything after a space on each line? On a quick glance through the tests, I don't see any of the tests exercising that aspect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MS link.exe accepts a line with more tokens like .text ALIGN=1. Although I do not see a real-world usecase for them (the ALIGN=# must be compatible with a binary's section alignment /ALIGN:#). I decided to ignore them so that it is compatible with MS link and also we could implement it in future if really needed.

It's good to have tests as you mentioned, I agree. I'll add tests for ignored tokens.


if (ctx.config.sectionOrder.count(sectionName.str())) {
Warn(ctx) << "duplicate section '" << sectionName.str()
<< "' in section layout file, ignoring";
continue;
}

ctx.config.sectionOrder[sectionName.str()] = index++;
}
}

void LinkerDriver::parseDosStub(StringRef path) {
std::unique_ptr<MemoryBuffer> stub =
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
Expand Down
1 change: 1 addition & 0 deletions lld/COFF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Expand Down
25 changes: 25 additions & 0 deletions lld/COFF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -783,6 +784,7 @@ void Writer::run() {
appendECImportTables();
createDynamicRelocs();
removeUnusedSections();
layoutSections();
finalizeAddresses();
removeEmptySections();
assignOutputSectionIndices();
Expand Down Expand Up @@ -1413,6 +1415,29 @@ void Writer::removeUnusedSections() {
llvm::erase_if(ctx.outputSections, isUnused);
}

void Writer::layoutSections() {
llvm::TimeTraceScope timeScope("Layout sections");
if (ctx.config.sectionOrder.empty())
return;

llvm::stable_sort(ctx.outputSections,
[this](const OutputSection *a, const OutputSection *b) {
auto itA = ctx.config.sectionOrder.find(a->name.str());
auto itB = ctx.config.sectionOrder.find(b->name.str());
bool aInOrder = itA != ctx.config.sectionOrder.end();
bool bInOrder = itB != ctx.config.sectionOrder.end();

// Put unspecified sections after all specified sections
if (aInOrder && bInOrder) {
return itA->second < itB->second;
} else if (aInOrder && !bInOrder) {
return true;
} else {
return false;
}
});
}

// The Windows loader doesn't seem to like empty sections,
// so we remove them if any.
void Writer::removeEmptySections() {
Expand Down
25 changes: 25 additions & 0 deletions lld/test/COFF/Inputs/sectionlayout.yaml
Original file line number Diff line number Diff line change
@@ -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
...
129 changes: 129 additions & 0 deletions lld/test/COFF/sectionlayout.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a curiousity, do you know why the @ syntax exists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a bit odd. The /sectionlayout: had a breaking change (syntax change) couple times. For instance, /SECTIONLAYOUT:.text,.data,ALIGN=1,.rsrc is no longer accepted in recent version of MS linker (they now only accept a file with @path/to/file). I am pretty sure it is somewhat relevant to the odd.

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

## Ensure tokens after section name is ignored (for now, to align with MS link.exe)
RUN: echo ".text1 ALIGN=1" > %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

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=CHECK2 %s

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