Skip to content

Commit fb974e8

Browse files
authored
[LLD][COFF] Add support for custom DOS stub (#122561)
This change implements support for the /stub flag to align with MS link.exe. This option is useful when a program needs to optimize the DOS program that executes when the PE runs on DOS, avoiding the traditional hardcoded DOS program in LLD.
1 parent e5992b6 commit fb974e8

File tree

10 files changed

+128
-23
lines changed

10 files changed

+128
-23
lines changed

lld/COFF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ struct Configuration {
115115
enum ManifestKind { Default, SideBySide, Embed, No };
116116
bool is64() const { return llvm::COFF::is64Bit(machine); }
117117

118+
std::unique_ptr<MemoryBuffer> dosStub;
118119
llvm::COFF::MachineTypes machine = IMAGE_FILE_MACHINE_UNKNOWN;
119120
bool machineInferred = false;
120121
size_t wordsize;

lld/COFF/Driver.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,6 +2298,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
22982298
config->noSEH = args.hasArg(OPT_noseh);
22992299
}
23002300

2301+
// Handle /stub
2302+
if (auto *arg = args.getLastArg(OPT_stub))
2303+
parseDosStub(arg->getValue());
2304+
23012305
// Handle /functionpadmin
23022306
for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
23032307
parseFunctionPadMin(arg);

lld/COFF/Driver.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ class LinkerDriver {
218218
void parseSection(StringRef);
219219
void parseAligncomm(StringRef);
220220

221+
// Parses a MS-DOS stub file
222+
void parseDosStub(StringRef path);
223+
221224
// Parses a string in the form of "[:<integer>]"
222225
void parseFunctionPadMin(llvm::opt::Arg *a);
223226

lld/COFF/DriverUtils.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,22 @@ void LinkerDriver::parseAligncomm(StringRef s) {
246246
std::max(ctx.config.alignComm[std::string(name)], 1 << v);
247247
}
248248

249+
void LinkerDriver::parseDosStub(StringRef path) {
250+
std::unique_ptr<MemoryBuffer> stub =
251+
CHECK(MemoryBuffer::getFile(path), "could not open " + path);
252+
size_t bufferSize = stub->getBufferSize();
253+
const char *bufferStart = stub->getBufferStart();
254+
// MS link.exe compatibility:
255+
// 1. stub must be greater than or equal to 64 bytes
256+
// 2. stub must start with a valid dos signature 'MZ'
257+
if (bufferSize < 64)
258+
Err(ctx) << "/stub: stub must be greater than or equal to 64 bytes: "
259+
<< path;
260+
if (bufferStart[0] != 'M' || bufferStart[1] != 'Z')
261+
Err(ctx) << "/stub: invalid DOS signature: " << path;
262+
ctx.config.dosStub = std::move(stub);
263+
}
264+
249265
// Parses /functionpadmin option argument.
250266
void LinkerDriver::parseFunctionPadMin(llvm::opt::Arg *a) {
251267
StringRef arg = a->getNumValues() ? a->getValue() : "";

lld/COFF/Writer.cpp

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,8 @@ static unsigned char dosProgram[] = {
7676
};
7777
static_assert(sizeof(dosProgram) % 8 == 0,
7878
"DOSProgram size must be multiple of 8");
79-
80-
static const int dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
81-
static_assert(dosStubSize % 8 == 0, "DOSStub size must be multiple of 8");
82-
static const uint32_t coffHeaderOffset = dosStubSize + sizeof(PEMagic);
83-
static const uint32_t peHeaderOffset =
84-
coffHeaderOffset + sizeof(coff_file_header);
85-
static const uint32_t dataDirOffset64 =
86-
peHeaderOffset + sizeof(pe32plus_header);
79+
static_assert((sizeof(dos_header) + sizeof(dosProgram)) % 8 == 0,
80+
"DOSStub size must be multiple of 8");
8781

8882
static const int numberOfDataDirectory = 16;
8983

@@ -214,6 +208,7 @@ class Writer {
214208
void run();
215209

216210
private:
211+
void calculateStubDependentSizes();
217212
void createSections();
218213
void createMiscChunks();
219214
void createImportTables();
@@ -315,6 +310,11 @@ class Writer {
315310
uint64_t sizeOfImage;
316311
uint64_t sizeOfHeaders;
317312

313+
uint32_t dosStubSize;
314+
uint32_t coffHeaderOffset;
315+
uint32_t peHeaderOffset;
316+
uint32_t dataDirOffset64;
317+
318318
OutputSection *textSec;
319319
OutputSection *hexpthkSec;
320320
OutputSection *rdataSec;
@@ -728,10 +728,8 @@ void Writer::writePEChecksum() {
728728
uint32_t *buf = (uint32_t *)buffer->getBufferStart();
729729
uint32_t size = (uint32_t)(buffer->getBufferSize());
730730

731-
coff_file_header *coffHeader =
732-
(coff_file_header *)((uint8_t *)buf + dosStubSize + sizeof(PEMagic));
733-
pe32_header *peHeader =
734-
(pe32_header *)((uint8_t *)coffHeader + sizeof(coff_file_header));
731+
pe32_header *peHeader = (pe32_header *)((uint8_t *)buf + coffHeaderOffset +
732+
sizeof(coff_file_header));
735733

736734
uint64_t sum = 0;
737735
uint32_t count = size;
@@ -762,6 +760,7 @@ void Writer::run() {
762760
llvm::TimeTraceScope timeScope("Write PE");
763761
ScopedTimer t1(ctx.codeLayoutTimer);
764762

763+
calculateStubDependentSizes();
765764
if (ctx.config.machine == ARM64X)
766765
ctx.dynamicRelocs = make<DynamicRelocsChunk>();
767766
createImportTables();
@@ -1035,6 +1034,17 @@ void Writer::sortSections() {
10351034
sortBySectionOrder(it.second->chunks);
10361035
}
10371036

1037+
void Writer::calculateStubDependentSizes() {
1038+
if (ctx.config.dosStub)
1039+
dosStubSize = alignTo(ctx.config.dosStub->getBufferSize(), 8);
1040+
else
1041+
dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
1042+
1043+
coffHeaderOffset = dosStubSize + sizeof(PEMagic);
1044+
peHeaderOffset = coffHeaderOffset + sizeof(coff_file_header);
1045+
dataDirOffset64 = peHeaderOffset + sizeof(pe32plus_header);
1046+
}
1047+
10381048
// Create output section objects and add them to OutputSections.
10391049
void Writer::createSections() {
10401050
llvm::TimeTraceScope timeScope("Output sections");
@@ -1668,21 +1678,37 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
16681678
// When run under Windows, the loader looks at AddressOfNewExeHeader and uses
16691679
// the PE header instead.
16701680
Configuration *config = &ctx.config;
1681+
16711682
uint8_t *buf = buffer->getBufferStart();
16721683
auto *dos = reinterpret_cast<dos_header *>(buf);
1673-
buf += sizeof(dos_header);
1674-
dos->Magic[0] = 'M';
1675-
dos->Magic[1] = 'Z';
1676-
dos->UsedBytesInTheLastPage = dosStubSize % 512;
1677-
dos->FileSizeInPages = divideCeil(dosStubSize, 512);
1678-
dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
1679-
1680-
dos->AddressOfRelocationTable = sizeof(dos_header);
1681-
dos->AddressOfNewExeHeader = dosStubSize;
16821684

16831685
// Write DOS program.
1684-
memcpy(buf, dosProgram, sizeof(dosProgram));
1685-
buf += sizeof(dosProgram);
1686+
if (config->dosStub) {
1687+
memcpy(buf, config->dosStub->getBufferStart(),
1688+
config->dosStub->getBufferSize());
1689+
// MS link.exe accepts an invalid `e_lfanew` (AddressOfNewExeHeader) and
1690+
// updates it automatically. Replicate the same behaviour.
1691+
dos->AddressOfNewExeHeader = alignTo(config->dosStub->getBufferSize(), 8);
1692+
// Unlike MS link.exe, LLD accepts non-8-byte-aligned stubs.
1693+
// In that case, we add zero paddings ourselves.
1694+
buf += alignTo(config->dosStub->getBufferSize(), 8);
1695+
} else {
1696+
buf += sizeof(dos_header);
1697+
dos->Magic[0] = 'M';
1698+
dos->Magic[1] = 'Z';
1699+
dos->UsedBytesInTheLastPage = dosStubSize % 512;
1700+
dos->FileSizeInPages = divideCeil(dosStubSize, 512);
1701+
dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
1702+
1703+
dos->AddressOfRelocationTable = sizeof(dos_header);
1704+
dos->AddressOfNewExeHeader = dosStubSize;
1705+
1706+
memcpy(buf, dosProgram, sizeof(dosProgram));
1707+
buf += sizeof(dosProgram);
1708+
}
1709+
1710+
// Make sure DOS stub is aligned to 8 bytes at this point
1711+
assert((buf - buffer->getBufferStart()) % 8 == 0);
16861712

16871713
// Write PE magic
16881714
memcpy(buf, PEMagic, sizeof(PEMagic));

lld/test/COFF/Inputs/stub63mz

63 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub64mz

64 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub64zz

64 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub68mz

68 Bytes
Binary file not shown.

lld/test/COFF/stub.test

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
2+
3+
# RUN: lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub64mz %t.obj
4+
# RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=CHECK1 %s
5+
6+
CHECK1: Magic: MZ
7+
CHECK1: UsedBytesInTheLastPage: 144
8+
CHECK1: FileSizeInPages: 3
9+
CHECK1: NumberOfRelocationItems: 0
10+
CHECK1: HeaderSizeInParagraphs: 4
11+
CHECK1: MinimumExtraParagraphs: 0
12+
CHECK1: MaximumExtraParagraphs: 65535
13+
CHECK1: InitialRelativeSS: 0
14+
CHECK1: InitialSP: 184
15+
CHECK1: Checksum: 0
16+
CHECK1: InitialIP: 0
17+
CHECK1: InitialRelativeCS: 0
18+
CHECK1: AddressOfRelocationTable: 64
19+
CHECK1: OverlayNumber: 0
20+
CHECK1: OEMid: 0
21+
CHECK1: OEMinfo: 0
22+
CHECK1: AddressOfNewExeHeader: 64
23+
24+
## Invalid DOS signature (must be `MZ`)
25+
# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub64zz %t.obj 2>&1 | FileCheck -check-prefix=CHECK2 %s
26+
27+
CHECK2: lld-link: error: /stub: invalid DOS signature: {{.*}}
28+
29+
## Unlike MS linker, we accept non-8byte-aligned stubs and we add paddings ourselves
30+
# RUN: lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub68mz %t.obj
31+
# RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=CHECK3 %s
32+
33+
CHECK3: Magic: MZ
34+
CHECK3: UsedBytesInTheLastPage: 144
35+
CHECK3: FileSizeInPages: 3
36+
CHECK3: NumberOfRelocationItems: 0
37+
CHECK3: HeaderSizeInParagraphs: 4
38+
CHECK3: MinimumExtraParagraphs: 0
39+
CHECK3: MaximumExtraParagraphs: 65535
40+
CHECK3: InitialRelativeSS: 0
41+
CHECK3: InitialSP: 184
42+
CHECK3: Checksum: 0
43+
CHECK3: InitialIP: 0
44+
CHECK3: InitialRelativeCS: 0
45+
CHECK3: AddressOfRelocationTable: 64
46+
CHECK3: OverlayNumber: 0
47+
CHECK3: OEMid: 0
48+
CHECK3: OEMinfo: 0
49+
## 68 is unaligned and rounded up to 72 by LLD
50+
CHECK3: AddressOfNewExeHeader: 72
51+
52+
## Too-small stub (must be at least 64 bytes long) && Unaligned
53+
# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub63mz %t.obj 2>&1 | FileCheck -check-prefix=CHECK4 %s
54+
55+
CHECK4: lld-link: error: /stub: stub must be greater than or equal to 64 bytes: {{.*}}

0 commit comments

Comments
 (0)