Skip to content

Commit c1c75f0

Browse files
committed
[LLD][COFF] Add support for custom DOS stub
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 0d9c027 commit c1c75f0

File tree

10 files changed

+83
-16
lines changed

10 files changed

+83
-16
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+
llvm::SmallVector<uint8_t> stub;
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
@@ -2418,6 +2418,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
24182418
config->noSEH = args.hasArg(OPT_noseh);
24192419
}
24202420

2421+
// Handle /stub
2422+
if (auto *arg = args.getLastArg(OPT_stub))
2423+
parseStub(arg);
2424+
24212425
// Handle /functionpadmin
24222426
for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
24232427
parseFunctionPadMin(arg);

lld/COFF/Driver.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ class LinkerDriver {
236236
void parseSection(StringRef);
237237
void parseAligncomm(StringRef);
238238

239+
// Parses a MS-DOS stub file
240+
void parseStub(llvm::opt::Arg *a);
241+
239242
// Parses a string in the form of "[:<integer>]"
240243
void parseFunctionPadMin(llvm::opt::Arg *a);
241244

lld/COFF/DriverUtils.cpp

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

249+
void LinkerDriver::parseStub(llvm::opt::Arg *a) {
250+
StringRef arg = a->getValue();
251+
std::unique_ptr<MemoryBuffer> stub = check(MemoryBuffer::getFile(arg));
252+
size_t bufferSize = stub->getBufferSize();
253+
const char *bufferStart = stub->getBufferStart();
254+
// MS link.exe compatibility:
255+
// 1. stub must be greater or equal than 64 bytes
256+
// 2. stub must be 8-byte aligned
257+
// 3. stub must be start with a valid dos signature 'MZ'
258+
if (bufferSize < 0x40 || bufferSize % 8 != 0 ||
259+
(bufferStart[0] != 'M' || bufferStart[1] != 'Z'))
260+
Err(ctx) << "/stub: invalid format for MS-DOS stub file: " << arg;
261+
ctx.config.stub.append(bufferStart, bufferStart + bufferSize);
262+
}
263+
249264
// Parses /functionpadmin option argument.
250265
void LinkerDriver::parseFunctionPadMin(llvm::opt::Arg *a) {
251266
StringRef arg = a->getNumValues() ? a->getValue() : "";

lld/COFF/Writer.cpp

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,6 @@ static_assert(sizeof(dosProgram) % 8 == 0,
7979

8080
static const int dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
8181
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);
8782

8883
static const int numberOfDataDirectory = 16;
8984

@@ -315,6 +310,10 @@ class Writer {
315310
uint64_t sizeOfImage;
316311
uint64_t sizeOfHeaders;
317312

313+
uint32_t coffHeaderOffset;
314+
uint32_t peHeaderOffset;
315+
uint32_t dataDirOffset64;
316+
318317
OutputSection *textSec;
319318
OutputSection *hexpthkSec;
320319
OutputSection *rdataSec;
@@ -1668,21 +1667,35 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
16681667
// When run under Windows, the loader looks at AddressOfNewExeHeader and uses
16691668
// the PE header instead.
16701669
Configuration *config = &ctx.config;
1670+
16711671
uint8_t *buf = buffer->getBufferStart();
16721672
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;
16821673

16831674
// Write DOS program.
1684-
memcpy(buf, dosProgram, sizeof(dosProgram));
1685-
buf += sizeof(dosProgram);
1675+
if (config->stub.size()) {
1676+
memcpy(buf, config->stub.data(), config->stub.size());
1677+
// MS link.exe accepts an invalid `e_lfanew` and updates it automatically.
1678+
// Replicate the same behaviour.
1679+
dos->AddressOfNewExeHeader = config->stub.size();
1680+
buf += config->stub.size();
1681+
} else {
1682+
buf += sizeof(dos_header);
1683+
dos->Magic[0] = 'M';
1684+
dos->Magic[1] = 'Z';
1685+
dos->UsedBytesInTheLastPage = dosStubSize % 512;
1686+
dos->FileSizeInPages = divideCeil(dosStubSize, 512);
1687+
dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
1688+
1689+
dos->AddressOfRelocationTable = sizeof(dos_header);
1690+
dos->AddressOfNewExeHeader = dosStubSize;
1691+
1692+
memcpy(buf, dosProgram, sizeof(dosProgram));
1693+
buf += sizeof(dosProgram);
1694+
}
1695+
1696+
coffHeaderOffset = buf - buffer->getBufferStart() + sizeof(PEMagic);
1697+
peHeaderOffset = coffHeaderOffset + sizeof(coff_file_header);
1698+
dataDirOffset64 = peHeaderOffset + sizeof(pe32plus_header);
16861699

16871700
// Write PE magic
16881701
memcpy(buf, PEMagic, sizeof(PEMagic));

lld/test/COFF/Inputs/stub511mz

63 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub512mz

64 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub512zz

64 Bytes
Binary file not shown.

lld/test/COFF/Inputs/stub516mz

68 Bytes
Binary file not shown.

lld/test/COFF/stub.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
2+
3+
# RUN: lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub512mz %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+
# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub512zz %t.obj 2>&1 | FileCheck -check-prefix=CHECK2 %s
25+
# CHECK2: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}
26+
27+
# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub516mz %t.obj 2>&1 | FileCheck -check-prefix=CHECK3 %s
28+
# CHECK3: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}
29+
30+
# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub511mz %t.obj 2>&1 | FileCheck -check-prefix=CHECK4 %s
31+
# CHECK4: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}

0 commit comments

Comments
 (0)