[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