[lld] [LLD][COFF] Add support for custom DOS stub (PR #122561)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jan 10 18:39:00 PST 2025
https://github.com/kkent030315 updated https://github.com/llvm/llvm-project/pull/122561
>From 446d70ab9ccab29f7cc61734ba79db08b98e4da7 Mon Sep 17 00:00:00 2001
From: kkent030315 <hrn832 at protonmail.com>
Date: Sat, 11 Jan 2025 09:53:21 +0900
Subject: [PATCH] [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.
---
lld/COFF/Config.h | 1 +
lld/COFF/Driver.cpp | 4 +++
lld/COFF/Driver.h | 3 ++
lld/COFF/DriverUtils.cpp | 15 +++++++++
lld/COFF/Writer.cpp | 60 ++++++++++++++++++++++-----------
lld/test/COFF/Inputs/stub511mz | Bin 0 -> 63 bytes
lld/test/COFF/Inputs/stub512mz | Bin 0 -> 64 bytes
lld/test/COFF/Inputs/stub512zz | Bin 0 -> 64 bytes
lld/test/COFF/Inputs/stub516mz | Bin 0 -> 68 bytes
lld/test/COFF/stub.test | 31 +++++++++++++++++
10 files changed, 95 insertions(+), 19 deletions(-)
create mode 100644 lld/test/COFF/Inputs/stub511mz
create mode 100644 lld/test/COFF/Inputs/stub512mz
create mode 100644 lld/test/COFF/Inputs/stub512zz
create mode 100644 lld/test/COFF/Inputs/stub516mz
create mode 100644 lld/test/COFF/stub.test
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 9e6b17e87c9e70..294df1912ff5ae 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -115,6 +115,7 @@ struct Configuration {
enum ManifestKind { Default, SideBySide, Embed, No };
bool is64() const { return llvm::COFF::is64Bit(machine); }
+ llvm::SmallVector<uint8_t> stub;
llvm::COFF::MachineTypes machine = IMAGE_FILE_MACHINE_UNKNOWN;
bool machineInferred = false;
size_t wordsize;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 791382fd9bdd4a..14619e7af236bb 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2418,6 +2418,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
config->noSEH = args.hasArg(OPT_noseh);
}
+ // Handle /stub
+ if (auto *arg = args.getLastArg(OPT_stub))
+ parseStub(arg->getValue());
+
// Handle /functionpadmin
for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
parseFunctionPadMin(arg);
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 51325689042981..9afff23a7ed255 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -236,6 +236,9 @@ class LinkerDriver {
void parseSection(StringRef);
void parseAligncomm(StringRef);
+ // Parses a MS-DOS stub file
+ void parseStub(StringRef path);
+
// Parses a string in the form of "[:<integer>]"
void parseFunctionPadMin(llvm::opt::Arg *a);
diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp
index 1148be09fb10cc..fdee5dcbc26d14 100644
--- a/lld/COFF/DriverUtils.cpp
+++ b/lld/COFF/DriverUtils.cpp
@@ -246,6 +246,21 @@ void LinkerDriver::parseAligncomm(StringRef s) {
std::max(ctx.config.alignComm[std::string(name)], 1 << v);
}
+void LinkerDriver::parseStub(StringRef path) {
+ std::unique_ptr<MemoryBuffer> stub =
+ CHECK(MemoryBuffer::getFile(path), "could not open " + path);
+ size_t bufferSize = stub->getBufferSize();
+ const char *bufferStart = stub->getBufferStart();
+ // MS link.exe compatibility:
+ // 1. stub must be greater or equal than 64 bytes
+ // 2. stub must be 8-byte aligned
+ // 3. stub must be start with a valid dos signature 'MZ'
+ if (bufferSize < 0x40 || bufferSize % 8 != 0 ||
+ (bufferStart[0] != 'M' || bufferStart[1] != 'Z'))
+ Err(ctx) << "/stub: invalid format for MS-DOS stub file: " << path;
+ ctx.config.stub.append(bufferStart, bufferStart + bufferSize);
+}
+
// Parses /functionpadmin option argument.
void LinkerDriver::parseFunctionPadMin(llvm::opt::Arg *a) {
StringRef arg = a->getNumValues() ? a->getValue() : "";
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index eb82a9cc015933..29543da5419833 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -76,14 +76,8 @@ static unsigned char dosProgram[] = {
};
static_assert(sizeof(dosProgram) % 8 == 0,
"DOSProgram size must be multiple of 8");
-
-static const int dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
-static_assert(dosStubSize % 8 == 0, "DOSStub size must be multiple of 8");
-static const uint32_t coffHeaderOffset = dosStubSize + sizeof(PEMagic);
-static const uint32_t peHeaderOffset =
- coffHeaderOffset + sizeof(coff_file_header);
-static const uint32_t dataDirOffset64 =
- peHeaderOffset + sizeof(pe32plus_header);
+static_assert((sizeof(dos_header) + sizeof(dosProgram)) % 8 == 0,
+ "DOSStub size must be multiple of 8");
static const int numberOfDataDirectory = 16;
@@ -214,6 +208,7 @@ class Writer {
void run();
private:
+ void calculateStubDependentSizes();
void createSections();
void createMiscChunks();
void createImportTables();
@@ -315,6 +310,11 @@ class Writer {
uint64_t sizeOfImage;
uint64_t sizeOfHeaders;
+ uint32_t dosStubSize;
+ uint32_t coffHeaderOffset;
+ uint32_t peHeaderOffset;
+ uint32_t dataDirOffset64;
+
OutputSection *textSec;
OutputSection *hexpthkSec;
OutputSection *rdataSec;
@@ -762,6 +762,7 @@ void Writer::run() {
llvm::TimeTraceScope timeScope("Write PE");
ScopedTimer t1(ctx.codeLayoutTimer);
+ calculateStubDependentSizes();
if (ctx.config.machine == ARM64X)
ctx.dynamicRelocs = make<DynamicRelocsChunk>();
createImportTables();
@@ -1035,6 +1036,17 @@ void Writer::sortSections() {
sortBySectionOrder(it.second->chunks);
}
+void Writer::calculateStubDependentSizes() {
+ if (ctx.config.stub.size())
+ dosStubSize = ctx.config.stub.size();
+ else
+ dosStubSize = sizeof(dos_header) + sizeof(dosProgram);
+
+ coffHeaderOffset = dosStubSize + sizeof(PEMagic);
+ peHeaderOffset = coffHeaderOffset + sizeof(coff_file_header);
+ dataDirOffset64 = peHeaderOffset + sizeof(pe32plus_header);
+}
+
// Create output section objects and add them to OutputSections.
void Writer::createSections() {
llvm::TimeTraceScope timeScope("Output sections");
@@ -1668,21 +1680,31 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
// When run under Windows, the loader looks at AddressOfNewExeHeader and uses
// the PE header instead.
Configuration *config = &ctx.config;
+
uint8_t *buf = buffer->getBufferStart();
auto *dos = reinterpret_cast<dos_header *>(buf);
- buf += sizeof(dos_header);
- dos->Magic[0] = 'M';
- dos->Magic[1] = 'Z';
- dos->UsedBytesInTheLastPage = dosStubSize % 512;
- dos->FileSizeInPages = divideCeil(dosStubSize, 512);
- dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
-
- dos->AddressOfRelocationTable = sizeof(dos_header);
- dos->AddressOfNewExeHeader = dosStubSize;
// Write DOS program.
- memcpy(buf, dosProgram, sizeof(dosProgram));
- buf += sizeof(dosProgram);
+ if (config->stub.size()) {
+ memcpy(buf, config->stub.data(), config->stub.size());
+ // MS link.exe accepts an invalid `e_lfanew` and updates it automatically.
+ // Replicate the same behaviour.
+ dos->AddressOfNewExeHeader = config->stub.size();
+ buf += config->stub.size();
+ } else {
+ buf += sizeof(dos_header);
+ dos->Magic[0] = 'M';
+ dos->Magic[1] = 'Z';
+ dos->UsedBytesInTheLastPage = dosStubSize % 512;
+ dos->FileSizeInPages = divideCeil(dosStubSize, 512);
+ dos->HeaderSizeInParagraphs = sizeof(dos_header) / 16;
+
+ dos->AddressOfRelocationTable = sizeof(dos_header);
+ dos->AddressOfNewExeHeader = dosStubSize;
+
+ memcpy(buf, dosProgram, sizeof(dosProgram));
+ buf += sizeof(dosProgram);
+ }
// Write PE magic
memcpy(buf, PEMagic, sizeof(PEMagic));
diff --git a/lld/test/COFF/Inputs/stub511mz b/lld/test/COFF/Inputs/stub511mz
new file mode 100644
index 0000000000000000000000000000000000000000..2a8954d2d6917fc39b8f285a84b470d19cd775c1
GIT binary patch
literal 63
ecmeZ`n!v!!z`(!)#Q*;@Fzf)*Am9Kd2@(L@{sc7u
literal 0
HcmV?d00001
diff --git a/lld/test/COFF/Inputs/stub512mz b/lld/test/COFF/Inputs/stub512mz
new file mode 100644
index 0000000000000000000000000000000000000000..aaeb005adb54cb6bf5a89f8490884eea07f898d0
GIT binary patch
literal 64
ecmeZ`n!v!!z`(!)#Q*;@Fzf)*Am9Kd2@?R}GXymN
literal 0
HcmV?d00001
diff --git a/lld/test/COFF/Inputs/stub512zz b/lld/test/COFF/Inputs/stub512zz
new file mode 100644
index 0000000000000000000000000000000000000000..fa58df18aabe77009483019e8dee9d72fb051a50
GIT binary patch
literal 64
ecma!wn!v!!z`(!)#Q*;@Fzf)*Am9Kd2@?S1a|A*F
literal 0
HcmV?d00001
diff --git a/lld/test/COFF/Inputs/stub516mz b/lld/test/COFF/Inputs/stub516mz
new file mode 100644
index 0000000000000000000000000000000000000000..a4ee6b2291e67284e2b5bf1587aed06111af5d60
GIT binary patch
literal 68
ecma!wn!v!!z`(!)#Q*;@Fzf)*Am9Kdi6a2 at djvuN
literal 0
HcmV?d00001
diff --git a/lld/test/COFF/stub.test b/lld/test/COFF/stub.test
new file mode 100644
index 00000000000000..2d5ad351d407d2
--- /dev/null
+++ b/lld/test/COFF/stub.test
@@ -0,0 +1,31 @@
+# RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+
+# RUN: lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub512mz %t.obj
+# RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=CHECK1 %s
+
+CHECK1: Magic: MZ
+CHECK1: UsedBytesInTheLastPage: 144
+CHECK1: FileSizeInPages: 3
+CHECK1: NumberOfRelocationItems: 0
+CHECK1: HeaderSizeInParagraphs: 4
+CHECK1: MinimumExtraParagraphs: 0
+CHECK1: MaximumExtraParagraphs: 65535
+CHECK1: InitialRelativeSS: 0
+CHECK1: InitialSP: 184
+CHECK1: Checksum: 0
+CHECK1: InitialIP: 0
+CHECK1: InitialRelativeCS: 0
+CHECK1: AddressOfRelocationTable: 64
+CHECK1: OverlayNumber: 0
+CHECK1: OEMid: 0
+CHECK1: OEMinfo: 0
+CHECK1: AddressOfNewExeHeader: 64
+
+# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub512zz %t.obj 2>&1 | FileCheck -check-prefix=CHECK2 %s
+# CHECK2: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}
+
+# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub516mz %t.obj 2>&1 | FileCheck -check-prefix=CHECK3 %s
+# CHECK3: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}
+
+# RUN: not lld-link /out:%t.exe /entry:main /stub:%p/Inputs/stub511mz %t.obj 2>&1 | FileCheck -check-prefix=CHECK4 %s
+# CHECK4: lld-link: error: /stub: invalid format for MS-DOS stub file: {{.*}}
More information about the llvm-commits
mailing list