[lld] 1c0ca8a - [LLD][COFF] Add more variety of CET and hotpatch flags (#150761)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 6 08:56:04 PDT 2025
Author: kkent030315
Date: 2025-09-06T11:56:01-04:00
New Revision: 1c0ca8afc6574c45cfe015effbf641bc2280a558
URL: https://github.com/llvm/llvm-project/commit/1c0ca8afc6574c45cfe015effbf641bc2280a558
DIFF: https://github.com/llvm/llvm-project/commit/1c0ca8afc6574c45cfe015effbf641bc2280a558.diff
LOG: [LLD][COFF] Add more variety of CET and hotpatch flags (#150761)
Those are all MS link.exe compatible flags.
### CET (Control-flow Enforcement Technology) family
- Added LLD test that covers `/cetcompat[:no]`
- Added `/cetcompatstrict[:no]` flag in LLD/COFF
- Added `/cetipvalidationrelaxed[:no]` flag in LLD/COFF
- Added `/cetdynamicapisinproc[:no]` flag in LLD/COFF
### Misc
- Added `/hotpatchcompatible[:no]` flag in LLD/COFF
- This flag requires at least 6 bytes of function padding
(`/functionpadmin:#`) as per link.exe
Added:
lld/test/COFF/exdllcharacteristics.test
Modified:
lld/COFF/Config.h
lld/COFF/Driver.cpp
lld/COFF/Options.td
lld/COFF/Writer.cpp
lld/test/COFF/options.test
Removed:
################################################################################
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 6754e303599f4..a71476adcc493 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -312,6 +312,10 @@ struct Configuration {
bool dynamicBase = true;
bool allowBind = true;
bool cetCompat = false;
+ bool cetCompatStrict = false;
+ bool cetCompatIpValidationRelaxed = false;
+ bool cetCompatDynamicApisInProcOnly = false;
+ bool hotpatchCompat = false;
bool nxCompat = true;
bool allowIsolation = true;
bool terminalServerAware = true;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index ba208c212d67e..acba156ce341d 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2143,6 +2143,14 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
config->integrityCheck =
args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false);
config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false);
+ config->cetCompatStrict =
+ args.hasFlag(OPT_cetcompatstrict, OPT_cetcompatstrict_no, false);
+ config->cetCompatIpValidationRelaxed = args.hasFlag(
+ OPT_cetipvalidationrelaxed, OPT_cetipvalidationrelaxed_no, false);
+ config->cetCompatDynamicApisInProcOnly = args.hasFlag(
+ OPT_cetdynamicapisinproc, OPT_cetdynamicapisinproc_no, false);
+ config->hotpatchCompat =
+ args.hasFlag(OPT_hotpatchcompatible, OPT_hotpatchcompatible_no, false);
config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true);
for (auto *arg : args.filtered(OPT_swaprun))
parseSwaprun(arg->getValue());
@@ -2296,6 +2304,12 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
parseFunctionPadMin(arg);
+ // MS link.exe compatibility, at least 6 bytes of function padding is
+ // required if hotpatchable
+ if (config->hotpatchCompat && config->functionPadMin < 6)
+ Err(ctx)
+ << "/hotpatchcompatible: requires at least 6 bytes of /functionpadmin";
+
// Handle /dependentloadflag
for (auto *arg :
args.filtered(OPT_dependentloadflag, OPT_dependentloadflag_opt))
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 26f6acaa3b7ad..b5334de87b6a7 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -200,6 +200,12 @@ defm appcontainer : B<"appcontainer",
"Image can run outside an app container (default)">;
defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack",
"Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">;
+defm cetcompatstrict : B<"cetcompatstrict", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode",
+ "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode (default)">;
+defm cetdynamicapisinproc : B<"cetdynamicapisinproc", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in such a way that dynamic APIs allowed in process",
+ "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with dynamic APIs allowed in process (default)">;
+defm cetipvalidationrelaxed : B<"cetipvalidationrelaxed", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation",
+ "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation (default)">;
defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)",
"Disable ASLR (default when /fixed)">;
defm fixed : B<"fixed", "Disable base relocations",
@@ -207,6 +213,8 @@ defm fixed : B<"fixed", "Disable base relocations",
defm highentropyva : B<"highentropyva",
"Enable 64-bit ASLR (default on 64-bit)",
"Disable 64-bit ASLR">;
+defm hotpatchcompatible : B<"hotpatchcompatible", "Mark executable image as compatible with hotpatch",
+ "Don't mark executable image as compatible with hotpatch (default)">;
defm incremental : B<"incremental",
"Keep original import library if contents are unchanged",
"Overwrite import library even if contents are unchanged">;
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 930673ef6c5e3..37577e8dd93d9 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1224,7 +1224,9 @@ void Writer::createMiscChunks() {
// Create Debug Information Chunks
debugInfoSec = config->mingw ? buildidSec : rdataSec;
if (config->buildIDHash != BuildIDHash::None || config->debug ||
- config->repro || config->cetCompat) {
+ config->repro || config->cetCompat || config->cetCompatStrict ||
+ config->cetCompatIpValidationRelaxed ||
+ config->cetCompatDynamicApisInProcOnly || config->hotpatchCompat) {
debugDirectory =
make<DebugDirectoryChunk>(ctx, debugRecords, config->repro);
debugDirectory->setAlignment(4);
@@ -1245,10 +1247,26 @@ void Writer::createMiscChunks() {
});
}
- if (config->cetCompat) {
- debugRecords.emplace_back(COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
- make<ExtendedDllCharacteristicsChunk>(
- IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT));
+ uint16_t ex_characteristics_flags = 0;
+ if (config->cetCompat)
+ ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT;
+ if (config->cetCompatStrict)
+ ex_characteristics_flags |=
+ IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE;
+ if (config->cetCompatIpValidationRelaxed)
+ ex_characteristics_flags |=
+ IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE;
+ if (config->cetCompatDynamicApisInProcOnly)
+ ex_characteristics_flags |=
+ IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY;
+ if (config->hotpatchCompat)
+ ex_characteristics_flags |=
+ IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE;
+
+ if (ex_characteristics_flags) {
+ debugRecords.emplace_back(
+ COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
+ make<ExtendedDllCharacteristicsChunk>(ex_characteristics_flags));
}
// Align and add each chunk referenced by the debug data directory.
diff --git a/lld/test/COFF/exdllcharacteristics.test b/lld/test/COFF/exdllcharacteristics.test
new file mode 100644
index 0000000000000..ef5a90ca9d519
--- /dev/null
+++ b/lld/test/COFF/exdllcharacteristics.test
@@ -0,0 +1,142 @@
+// ---- /cetcompat (image is CET compatible)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetcompat %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKCETCOMPAT %s
+
+CHECKCETCOMPAT: DebugEntry {
+CHECKCETCOMPAT: Characteristics: 0x0
+CHECKCETCOMPAT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKCETCOMPAT: ExtendedCharacteristics [ (0x1)
+CHECKCETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT (0x1)
+CHECKCETCOMPAT: ]
+CHECKCETCOMPAT: RawData (
+CHECKCETCOMPAT: 0000: 01000000 |....|
+CHECKCETCOMPAT: )
+CHECKCETCOMPAT: }
+
+// ---- /cetcompat:no (image is not CET compatible)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetcompat:no %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKNOCETCOMPAT %s
+
+CHECKNOCETCOMPAT-NOT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKNOCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT (0x1)
+
+// ---- /cetcompatstrict (CET in strict mode)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKCETCOMPATSTRICT %s
+
+CHECKCETCOMPATSTRICT: DebugEntry {
+CHECKCETCOMPATSTRICT: Characteristics: 0x0
+CHECKCETCOMPATSTRICT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKCETCOMPATSTRICT: ExtendedCharacteristics [ (0x2)
+CHECKCETCOMPATSTRICT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE (0x2)
+CHECKCETCOMPATSTRICT: ]
+CHECKCETCOMPATSTRICT: RawData (
+CHECKCETCOMPATSTRICT: 0000: 02000000 |....|
+CHECKCETCOMPATSTRICT: )
+CHECKCETCOMPATSTRICT: }
+
+// ---- /cetcompatstrict:no (image is not CET strict mode)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict:no %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKNOCETSTRICT %s
+
+CHECKNOCETSTRICT-NOT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKNOCETSTRICT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE (0x2)
+
+// ---- /cetdynamicapisinproc
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKCETDYNAPI %s
+
+CHECKCETDYNAPI: DebugEntry {
+CHECKCETDYNAPI: Characteristics: 0x0
+CHECKCETDYNAPI: Type: ExtendedDLLCharacteristics (0x14)
+CHECKCETDYNAPI: ExtendedCharacteristics [ (0x8)
+CHECKCETDYNAPI: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY (0x8)
+CHECKCETDYNAPI: ]
+CHECKCETDYNAPI: RawData (
+CHECKCETDYNAPI: 0000: 08000000 |....|
+CHECKCETDYNAPI: )
+CHECKCETDYNAPI: }
+
+// ---- /cetdynamicapisinproc:no (image is not CET dynamic apis allowed in proc)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc:no %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKNOCETDYNAPI %s
+
+CHECKNOCETDYNAPI-NOT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKNOCETDYNAPI-NOT: Type: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY (0x8)
+
+// ---- /cetipvalidationrelaxed (image is not CET in context ip validation relaxed mode)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKCETIPRELAXED %s
+
+CHECKCETIPRELAXED: DebugEntry {
+CHECKCETIPRELAXED: Characteristics: 0x0
+CHECKCETIPRELAXED: Type: ExtendedDLLCharacteristics (0x14)
+CHECKCETIPRELAXED: ExtendedCharacteristics [ (0x4)
+CHECKCETIPRELAXED: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE (0x4)
+CHECKCETIPRELAXED: ]
+CHECKCETIPRELAXED: RawData (
+CHECKCETIPRELAXED: 0000: 04000000 |....|
+CHECKCETIPRELAXED: )
+CHECKCETIPRELAXED: }
+
+// ---- /cetipvalidationrelaxed:no (image is not CET in IP validation relaxed mode)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed:no %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKNOCETIPRELAXED %s
+
+CHECKNOCETIPRELAXED-NOT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKNOCETIPRELAXED-NOT: Type: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE (0x4)
+
+// ---- /hotpatchcompatible requires /functionpadmin:6
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible /functionpadmin:6 %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKHOTPATCHABLE %s
+
+CHECKHOTPATCHABLE: DebugEntry {
+CHECKHOTPATCHABLE: Characteristics: 0x0
+CHECKHOTPATCHABLE: Type: ExtendedDLLCharacteristics (0x14)
+CHECKHOTPATCHABLE: ExtendedCharacteristics [ (0x80)
+CHECKHOTPATCHABLE: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+CHECKHOTPATCHABLE: ]
+CHECKHOTPATCHABLE: RawData (
+CHECKHOTPATCHABLE: 0000: 80000000 |....|
+CHECKHOTPATCHABLE: )
+CHECKHOTPATCHABLE: }
+
+// ---- /hotpatchcompatible:no (image is not hotpatch compatible)
+RUN: yaml2obj %p/Inputs/ret42.yaml -o %t.obj
+RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible:no %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKNOHOTPATCHABLE %s
+
+CHECKNOHOTPATCHABLE-NOT: Type: ExtendedDLLCharacteristics (0x14)
+CHECKNOHOTPATCHABLE-NOT: Type: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+
+// ---- /hotpatchcompatible more than 6 bytes is accepted
+RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible /functionpadmin:10 %t.obj
+RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CHECKHOTPATCHABLE2 %s
+
+CHECKHOTPATCHABLE2: DebugEntry {
+CHECKHOTPATCHABLE2: Characteristics: 0x0
+CHECKHOTPATCHABLE2: Type: ExtendedDLLCharacteristics (0x14)
+CHECKHOTPATCHABLE2: ExtendedCharacteristics [ (0x80)
+CHECKHOTPATCHABLE2: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+CHECKHOTPATCHABLE2: ]
+CHECKHOTPATCHABLE2: RawData (
+CHECKHOTPATCHABLE2: 0000: 80000000 |....|
+CHECKHOTPATCHABLE2: )
+CHECKHOTPATCHABLE2: }
+
+// ---- /hotpatchcompatible requires at least 6 bytes function padding
+RUN: env LLD_IN_TEST=1 not lld-link /out:%t.exe /entry:main /hotpatchcompatible /functionpadmin:5 %t.obj 2>&1 | FileCheck -check-prefix=CHECKHOTPATCHNOT %s
+CHECKHOTPATCHNOT: lld-link: error: /hotpatchcompatible: requires at least 6 bytes of /functionpadmin
+
+// ---- /hotpatchcompatible requires at least 6 bytes function padding -- without /functionpadmin should raise an error
+RUN: env LLD_IN_TEST=1 not lld-link /out:%t.exe /entry:main /hotpatchcompatible %t.obj 2>&1 | FileCheck -check-prefix=CHECKHOTPATCHNOT2 %s
+CHECKHOTPATCHNOT2: lld-link: error: /hotpatchcompatible: requires at least 6 bytes of /functionpadmin
diff --git a/lld/test/COFF/options.test b/lld/test/COFF/options.test
index 0dd889042869a..c21ae9685a85c 100644
--- a/lld/test/COFF/options.test
+++ b/lld/test/COFF/options.test
@@ -50,16 +50,6 @@ NXCOMPAT: IMAGE_DLL_CHARACTERISTICS_NX_COMPAT
# RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=NONXCOMPAT %s
NONXCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_NX_COMPAT
-# RUN: lld-link /out:%t.exe /entry:main /cetcompat %t.obj
-# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPAT %s
-CETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
-
-# RUN: lld-link /out:%t.exe /entry:main %t.obj
-# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s
-# RUN: lld-link /out:%t.exe /entry:main /cetcompat:no %t.obj
-# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s
-NONCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
-
# RUN: lld-link /out:%t.exe /entry:main /swaprun:CD %t.obj
# RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=SWAPCD %s
# RUN: lld-link /out:%t.exe /entry:main /swaprun:cd,net %t.obj
More information about the llvm-commits
mailing list