Skip to content

Commit 446d70a

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 446d70a

File tree

10 files changed

+95
-19
lines changed

10 files changed

+95
-19
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->getValue());
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(StringRef path);
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(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 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: " << path;
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: 41 additions & 19 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;
@@ -762,6 +762,7 @@ void Writer::run() {
762762
llvm::TimeTraceScope timeScope("Write PE");
763763
ScopedTimer t1(ctx.codeLayoutTimer);
764764

765+
calculateStubDependentSizes();
765766
if (ctx.config.machine == ARM64X)
766767
ctx.dynamicRelocs = make<DynamicRelocsChunk>();
767768
createImportTables();
@@ -1035,6 +1036,17 @@ void Writer::sortSections() {
10351036
sortBySectionOrder(it.second->chunks);
10361037
}
10371038

1039+
void Writer::calculateStubDependentSizes() {
1040+
if (ctx.config.stub.size())
1041+
dosStubSize = ctx.config.stub.size();
1042+
else
1043+
dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
1044+
1045+
coffHeaderOffset = dosStubSize + sizeof(PEMagic);
1046+
peHeaderOffset = coffHeaderOffset + sizeof(coff_file_header);
1047+
dataDirOffset64 = peHeaderOffset + sizeof(pe32plus_header);
1048+
}
1049+
10381050
// Create output section objects and add them to OutputSections.
10391051
void Writer::createSections() {
10401052
llvm::TimeTraceScope timeScope("Output sections");
@@ -1668,21 +1680,31 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
16681680
// When run under Windows, the loader looks at AddressOfNewExeHeader and uses
16691681
// the PE header instead.
16701682
Configuration *config = &ctx.config;
1683+
16711684
uint8_t *buf = buffer->getBufferStart();
16721685
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;
16821686

16831687
// Write DOS program.
1684-
memcpy(buf, dosProgram, sizeof(dosProgram));
1685-
buf += sizeof(dosProgram);
1688+
if (config->stub.size()) {
1689+
memcpy(buf, config->stub.data(), config->stub.size());
1690+
// MS link.exe accepts an invalid `e_lfanew` and updates it automatically.
1691+
// Replicate the same behaviour.
1692+
dos->AddressOfNewExeHeader = config->stub.size();
1693+
buf += config->stub.size();
1694+
} else {
1695+
buf += sizeof(dos_header);
1696+
dos->Magic[0] = 'M';
1697+
dos->Magic[1] = 'Z';
1698+
dos->UsedBytesInTheLastPage = dosStubSize % 512;
1699+
dos->FileSizeInPages = divideCeil(dosStubSize, 512);
1700+
dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
1701+
1702+
dos->AddressOfRelocationTable = sizeof(dos_header);
1703+
dos->AddressOfNewExeHeader = dosStubSize;
1704+
1705+
memcpy(buf, dosProgram, sizeof(dosProgram));
1706+
buf += sizeof(dosProgram);
1707+
}
16861708

16871709
// Write PE magic
16881710
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)