Skip to content

Conversation

@jeremyd2019
Copy link
Contributor

@jeremyd2019 jeremyd2019 commented Apr 24, 2025

This allows IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION flags to work (documented at https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#load-configuration-layout).

Add tests showing the .didat section is created with the proper characteristics.

Closes #134546

This allows
IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION
flags to work.

Add tests showing the .didat section is created with the proper
characteristics.
@jeremyd2019 jeremyd2019 force-pushed the lld-didat-own-section branch from 12cc711 to 12e03d4 Compare April 24, 2025 05:43
@jeremyd2019
Copy link
Contributor Author

jeremyd2019 commented Apr 24, 2025

real-world test code:

#include <assert.h>
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

__attribute__((__used__))
const IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = {
  .Size = sizeof(_load_config_used),
  .GuardFlags = 0x3000, /*IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION*/
};

int global_var = 42;

int main(void) {
  unsigned short ret;
  setvbuf(stdout, NULL, _IONBF, 0);
  printf("MAIN: starting\n");
  printf("MAIN: incremented global var = %d\n", ++global_var);
  printf("MAIN: about to call htons\n");
  ret = htons(0x1234);
  printf("MAIN: htons returned %hx\n", ret);
  printf("MAIN: incremented global var = %d\n", ++global_var);
  printf("MAIN: OK\n");
  return 0;
}

build with (mingw-w64, but the code should work the same with clang-cl with different commandline options and maybe a different way to specify __attribute__((__used__)))

clang -fuse-ld=lld -o main.exe main.c -lws2_32 -Wl,-delayload,WS2_32.dll -ldloadhelper

Before this PR, main.exe crashes when the loader tries to set up TLS stuff, which is now in a read-only page apparently. After, it works properly.

@jeremyd2019 jeremyd2019 marked this pull request as ready for review April 24, 2025 17:28
@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-lld-coff

@llvm/pr-subscribers-platform-windows

Author: None (jeremyd2019)

Changes

This allows IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION flags to work.

Add tests showing the .didat section is created with the proper characteristics.

Closes #134546


Patch is 35.75 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/137100.diff

12 Files Affected:

  • (modified) lld/COFF/DLL.cpp (+1-8)
  • (modified) lld/COFF/DLL.h (+3-2)
  • (modified) lld/COFF/Driver.cpp (-1)
  • (modified) lld/COFF/Writer.cpp (+3-1)
  • (modified) lld/test/COFF/arm64-delayimport.yaml (+19-3)
  • (modified) lld/test/COFF/arm64ec-delayimport.test (+25-25)
  • (modified) lld/test/COFF/arm64x-delayimport.test (+39-39)
  • (modified) lld/test/COFF/delayimports-armnt.yaml (+21-5)
  • (modified) lld/test/COFF/delayimports.test (+23-6)
  • (modified) lld/test/COFF/delayimports32.test (+22-5)
  • (modified) lld/test/COFF/delayimporttables.yaml (+4-4)
  • (modified) lld/test/COFF/giats.s (+2-2)
diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp
index 0440507b71756..5d3813ddc6bd5 100644
--- a/lld/COFF/DLL.cpp
+++ b/lld/COFF/DLL.cpp
@@ -891,7 +891,7 @@ void IdataContents::create(COFFLinkerContext &ctx) {
   dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry), 4));
 }
 
-std::vector<Chunk *> DelayLoadContents::getChunks() {
+std::vector<Chunk *> DelayLoadContents::getRDataChunks() {
   std::vector<Chunk *> v;
   v.insert(v.end(), dirs.begin(), dirs.end());
   v.insert(v.end(), names.begin(), names.end());
@@ -900,13 +900,6 @@ std::vector<Chunk *> DelayLoadContents::getChunks() {
   return v;
 }
 
-std::vector<Chunk *> DelayLoadContents::getDataChunks() {
-  std::vector<Chunk *> v;
-  v.insert(v.end(), moduleHandles.begin(), moduleHandles.end());
-  v.insert(v.end(), addresses.begin(), addresses.end());
-  return v;
-}
-
 uint64_t DelayLoadContents::getDirSize() {
   return dirs.size() * sizeof(delay_import_directory_table_entry);
 }
diff --git a/lld/COFF/DLL.h b/lld/COFF/DLL.h
index 5105b79f15d31..83e2894371a72 100644
--- a/lld/COFF/DLL.h
+++ b/lld/COFF/DLL.h
@@ -43,8 +43,9 @@ class DelayLoadContents {
   void add(DefinedImportData *sym) { imports.push_back(sym); }
   bool empty() { return imports.empty(); }
   void create();
-  std::vector<Chunk *> getChunks();
-  std::vector<Chunk *> getDataChunks();
+  ArrayRef<Chunk *> getChunks() { return addresses; }
+  std::vector<Chunk *> getRDataChunks();
+  ArrayRef<Chunk *> getDataChunks() { return moduleHandles; }
   ArrayRef<Chunk *> getCodeChunks() { return thunks; }
   ArrayRef<Chunk *> getCodePData() { return pdata; }
   ArrayRef<Chunk *> getCodeUnwindInfo() { return unwindinfo; }
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index e3ff647209e72..ef5e7cdf69209 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2015,7 +2015,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // Add default section merging rules after user rules. User rules take
   // precedence, but we will emit a warning if there is a conflict.
   parseMerge(".idata=.rdata");
-  parseMerge(".didat=.rdata");
   parseMerge(".edata=.rdata");
   parseMerge(".xdata=.rdata");
   parseMerge(".00cfg=.rdata");
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index a5582cc8074d1..3e289d8783fa6 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1083,7 +1083,7 @@ void Writer::createSections() {
   pdataSec = createSection(".pdata", data | r);
   idataSec = createSection(".idata", data | r);
   edataSec = createSection(".edata", data | r);
-  didatSec = createSection(".didat", data | r);
+  didatSec = createSection(".didat", data | r | w);
   if (isArm64EC(ctx.config.machine))
     a64xrmSec = createSection(".a64xrm", data | r);
   rsrcSec = createSection(".rsrc", data | r);
@@ -1329,6 +1329,8 @@ void Writer::appendImportThunks() {
     delayIdata.create();
     for (Chunk *c : delayIdata.getChunks())
       didatSec->addChunk(c);
+    for (Chunk *c : delayIdata.getRDataChunks())
+      rdataSec->addChunk(c);
     for (Chunk *c : delayIdata.getDataChunks())
       dataSec->addChunk(c);
     for (Chunk *c : delayIdata.getCodeChunks())
diff --git a/lld/test/COFF/arm64-delayimport.yaml b/lld/test/COFF/arm64-delayimport.yaml
index abb9f25d5c379..28099ee76aeea 100644
--- a/lld/test/COFF/arm64-delayimport.yaml
+++ b/lld/test/COFF/arm64-delayimport.yaml
@@ -4,9 +4,10 @@
 # RUN: lld-link /entry:main /subsystem:console /out:%t.exe %t.obj %p/Inputs/library-arm64.lib /alternatename:__delayLoadHelper2=main /delayload:library.dll
 # RUN: llvm-objdump --no-print-imm-hex -d %t.exe | FileCheck %s --check-prefix DISASM
 # RUN: llvm-readobj --coff-imports %t.exe | FileCheck %s -check-prefix IMPORTS
+# RUN: llvm-readobj --section-headers %t.exe | FileCheck %s -check-prefix SECTION
 
-# DISASM:  140001014:      d0000011        adrp    x17, 0x140003000
-# DISASM:  140001018:      91002231        add     x17, x17, #8
+# DISASM:  140001014:      f0000011        adrp    x17, 0x140004000
+# DISASM:  140001018:      91000231        add     x17, x17, #0
 # DISASM:  14000101c:      14000001        b       0x140001020 <.text+0x20>
 # DISASM:  140001020:      a9b37bfd        stp     x29, x30, [sp, #-208]!
 # DISASM:  140001024:      910003fd        mov     x29, sp
@@ -41,7 +42,7 @@
 # IMPORTS:   Name: library.dll
 # IMPORTS:   Attributes: 0x1
 # IMPORTS:   ModuleHandle: 0x3000
-# IMPORTS:   ImportAddressTable: 0x3008
+# IMPORTS:   ImportAddressTable: 0x4000
 # IMPORTS:   ImportNameTable: 0x2040
 # IMPORTS:   BoundDelayImportTable: 0x0
 # IMPORTS:   UnloadDelayImportTable: 0x0
@@ -50,6 +51,21 @@
 # IMPORTS:     Address: 0x140001014
 # IMPORTS:   }
 # IMPORTS: }
+# SECTION:        Name: .didat (2E 64 69 64 61 74 00 00)
+# SECTION-NEXT:   VirtualSize: 0x10
+# SECTION-NEXT:   VirtualAddress: 0x4000
+# SECTION-NEXT:   RawDataSize: 512
+# SECTION-NEXT:   PointerToRawData: 0x{{[0-9A-F]+}}
+# SECTION-NEXT:   PointerToRelocations: 0x0
+# SECTION-NEXT:   PointerToLineNumbers: 0x0
+# SECTION-NEXT:   RelocationCount: 0
+# SECTION-NEXT:   LineNumberCount: 0
+# SECTION-NEXT:   Characteristics [ (0xC0000040)
+# SECTION-NEXT:     IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
+# SECTION-NEXT:     IMAGE_SCN_MEM_READ (0x40000000)
+# SECTION-NEXT:     IMAGE_SCN_MEM_WRITE (0x80000000)
+# SECTION-NEXT:   ]
+# SECTION-NEXT: }
 
 --- !COFF
 header:
diff --git a/lld/test/COFF/arm64ec-delayimport.test b/lld/test/COFF/arm64ec-delayimport.test
index 1e0bd899ba323..c1a34740418e4 100644
--- a/lld/test/COFF/arm64ec-delayimport.test
+++ b/lld/test/COFF/arm64ec-delayimport.test
@@ -12,9 +12,9 @@ RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj
 RUN:          helper-mangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
-TESTSEC:      0x18000a000 00600000 88700000 00200000 10100000
-TESTSEC-NEXT: 0x18000a010 08600000 90700000 10200000 30100000
-TESTSEC-NEXT: 0x18000a020 1c100000 3c100000 00300000
+TESTSEC:      0x18000b000 00600000 00900000 00200000 10100000
+TESTSEC-NEXT: 0x18000b010 08600000 08900000 10200000 30100000
+TESTSEC-NEXT: 0x18000b020 1c100000 3c100000 00300000
 
 RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s
 DISASM:      0000000180001000 <.text>:
@@ -25,16 +25,16 @@ DISASM-NEXT: 18000100c: d65f03c0     ret
 DISASM-NEXT: 180001010: b0000030     adrp    x16, 0x180006000
 DISASM-NEXT: 180001014: f9400210     ldr     x16, [x16]
 DISASM-NEXT: 180001018: d61f0200     br      x16
-DISASM-NEXT: 18000101c: d000002b     adrp    x11, 0x180007000
-DISASM-NEXT: 180001020: f940456b     ldr     x11, [x11, #0x88]
+DISASM-NEXT: 18000101c: 9000004b     adrp    x11, 0x180009000
+DISASM-NEXT: 180001020: f940016b     ldr     x11, [x11]
 DISASM-NEXT: 180001024: 9000000a     adrp    x10, 0x180001000 <.text>
 DISASM-NEXT: 180001028: 9101414a     add     x10, x10, #0x50
 DISASM-NEXT: 18000102c: 17fffff5     b       0x180001000 <.text>
 DISASM-NEXT: 180001030: b0000030     adrp    x16, 0x180006000
 DISASM-NEXT: 180001034: f9400610     ldr     x16, [x16, #0x8]
 DISASM-NEXT: 180001038: d61f0200     br      x16
-DISASM-NEXT: 18000103c: d000002b     adrp    x11, 0x180007000
-DISASM-NEXT: 180001040: f940496b     ldr     x11, [x11, #0x90]
+DISASM-NEXT: 18000103c: 9000004b     adrp    x11, 0x180009000
+DISASM-NEXT: 180001040: f940056b     ldr     x11, [x11, #0x8]
 DISASM-NEXT: 180001044: 9000000a     adrp    x10, 0x180001000 <.text>
 DISASM-NEXT: 180001048: 9101614a     add     x10, x10, #0x58
 DISASM-NEXT: 18000104c: 17ffffed     b       0x180001000 <.text>
@@ -43,13 +43,13 @@ DISASM-NEXT: 180001054: d65f03c0     ret
 DISASM-NEXT: 180001058: 52800060     mov     w0, #0x3                // =3
 DISASM-NEXT: 18000105c: d65f03c0     ret
 DISASM-NEXT:                 ...
-DISASM-NEXT: 180002000: ff 25 82 50 00 00            jmpq    *0x5082(%rip)           # 0x180007088
+DISASM-NEXT: 180002000: ff 25 fa 6f 00 00            jmpq    *0x6ffa(%rip)           # 0x180009000
 DISASM-NEXT:                 ...
 DISASM-NEXT: 18000200e: 00 00                        addb    %al, (%rax)
-DISASM-NEXT: 180002010: ff 25 7a 50 00 00            jmpq    *0x507a(%rip)           # 0x180007090
-DISASM-NEXT: 180002016: 48 8d 05 6b 50 00 00         leaq    0x506b(%rip), %rax      # 0x180007088
+DISASM-NEXT: 180002010: ff 25 f2 6f 00 00            jmpq    *0x6ff2(%rip)           # 0x180009008
+DISASM-NEXT: 180002016: 48 8d 05 e3 6f 00 00         leaq    0x6fe3(%rip), %rax      # 0x180009000
 DISASM-NEXT: 18000201d: e9 0c 00 00 00               jmp     0x18000202e <.text+0x102e>
-DISASM-NEXT: 180002022: 48 8d 05 67 50 00 00         leaq    0x5067(%rip), %rax      # 0x180007090
+DISASM-NEXT: 180002022: 48 8d 05 df 6f 00 00         leaq    0x6fdf(%rip), %rax      # 0x180009008
 DISASM-NEXT: 180002029: e9 00 00 00 00               jmp     0x18000202e <.text+0x102e>
 DISASM-NEXT: 18000202e: 51                           pushq   %rcx
 DISASM-NEXT: 18000202f: 52                           pushq   %rdx
@@ -61,7 +61,7 @@ DISASM-NEXT: 18000203d: 66 0f 7f 4c 24 10            movdqa  %xmm1, 0x10(%rsp)
 DISASM-NEXT: 180002043: 66 0f 7f 54 24 20            movdqa  %xmm2, 0x20(%rsp)
 DISASM-NEXT: 180002049: 66 0f 7f 5c 24 30            movdqa  %xmm3, 0x30(%rsp)
 DISASM-NEXT: 18000204f: 48 8b d0                     movq    %rax, %rdx
-DISASM-NEXT: 180002052: 48 8d 0d a7 21 00 00         leaq    0x21a7(%rip), %rcx      # 0x180004200
+DISASM-NEXT: 180002052: 48 8d 0d a7 1f 00 00         leaq    0x1fa7(%rip), %rcx      # 0x180004000
 DISASM-NEXT: 180002059: e8 aa ef ff ff               callq   0x180001008 <.text+0x8>
 DISASM-NEXT: 18000205e: 66 0f 6f 04 24               movdqa  (%rsp), %xmm0
 DISASM-NEXT: 180002063: 66 0f 6f 4c 24 10            movdqa  0x10(%rsp), %xmm1
@@ -77,15 +77,15 @@ DISASM-NEXT: 18000207f: ff e0                        jmpq    *%rax
 RUN: llvm-readobj --coff-load-config out.dll | FileCheck --check-prefix=LOADCFG %s
 LOADCFG:      CHPEMetadata [
 LOADCFG:       AuxiliaryDelayloadIAT: 0x6000
-LOADCFG-NEXT:  AuxiliaryDelayloadIATCopy: 0x4000
+LOADCFG-NEXT:  AuxiliaryDelayloadIATCopy: 0x4078
 
 RUN: llvm-readobj --coff-imports out.dll | FileCheck --check-prefix=IMPORTS %s
 IMPORTS:      DelayImport {
 IMPORTS-NEXT:   Name: test.dll
 IMPORTS-NEXT:   Attributes: 0x1
 IMPORTS-NEXT:   ModuleHandle: 0x7080
-IMPORTS-NEXT:   ImportAddressTable: 0x7088
-IMPORTS-NEXT:   ImportNameTable: 0x4240
+IMPORTS-NEXT:   ImportAddressTable: 0x9000
+IMPORTS-NEXT:   ImportNameTable: 0x4040
 IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:   Import {
@@ -109,17 +109,17 @@ MAP-NEXT:  0001:00000058       func2_exit_thunk           0000000180001058     t
 MAP-NEXT:  0001:00001000       func                       0000000180002000     test-arm64ec:test.dll
 MAP-NEXT:  0001:00001010       func2                      0000000180002010     test-arm64ec:test.dll
 MAP-NEXT:  0002:00000000       __imp_data                 0000000180003000     test2-arm64ec:test2.dll
-MAP-NEXT:  0000:00000000       __hybrid_auxiliary_delayload_iat_copy 0000000180004000     <linker-defined>
-MAP-NEXT:  0002:00001000       __auximpcopy_func          0000000180004000     test-arm64ec:test.dll
-MAP-NEXT:  0002:00001008       __auximpcopy_func2         0000000180004008     test-arm64ec:test.dll
+MAP-NEXT:  0000:00000000       __hybrid_auxiliary_delayload_iat_copy 0000000180004078     <linker-defined>
+MAP-NEXT:  0002:00001078       __auximpcopy_func          0000000180004078     test-arm64ec:test.dll
+MAP-NEXT:  0002:00001080       __auximpcopy_func2         0000000180004080     test-arm64ec:test.dll
 MAP:       0002:00003000       __imp_func                 0000000180006000     test-arm64ec:test.dll
 MAP-NEXT:  0002:00003008       __imp_func2                0000000180006008     test-arm64ec:test.dll
-MAP:       0003:00000088       __imp_aux_func             0000000180007088     test-arm64ec:test.dll
-MAP-NEXT:  0003:00000090       __imp_aux_func2            0000000180007090     test-arm64ec:test.dll
+MAP:       0005:00000000       __imp_aux_func             0000000180009000     test-arm64ec:test.dll
+MAP-NEXT:  0005:00000008       __imp_aux_func2            0000000180009008     test-arm64ec:test.dll
 
 RUN: llvm-readobj --hex-dump=.rdata out.dll | FileCheck --check-prefix=RDATA %s
-RDATA:      0x180004000 1c100080 01000000 3c100080 01000000
-RDATA-NEXT: 0x180004010 00000000 00000000
+RDATA:      0x180004070 {{[0-9a-f]{8} [0-9a-f]{8} }}1c100080 01000000
+RDATA-NEXT: 0x180004080 3c100080 01000000 00000000 00000000
 RDATA:      0x180006000 1c100080 01000000 3c100080 01000000
 RDATA-NEXT: 0x180006010 00000000 00000000
 
@@ -127,11 +127,11 @@ RUN: llvm-readobj --coff-basereloc out.dll | FileCheck --check-prefix=RELOC %s
 RELOC:      BaseReloc [
 RELOC-NEXT:   Entry {
 RELOC-NEXT:     Type: DIR64
-RELOC-NEXT:     Address: 0x4000
+RELOC-NEXT:     Address: 0x4078
 RELOC-NEXT:   }
 RELOC-NEXT:   Entry {
 RELOC-NEXT:     Type: DIR64
-RELOC-NEXT:     Address: 0x4008
+RELOC-NEXT:     Address: 0x4080
 RELOC-NEXT:   }
 RELOC:          Address: 0x6000
 RELOC-NEXT:   }
@@ -141,7 +141,7 @@ RELOC-NEXT:     Address: 0x6008
 RELOC-NEXT:   }
 
 RUN: llvm-readobj --hex-dump=.pdata out.dll | FileCheck --check-prefix=PDATA %s
-PDATA: 0x180008000 2e200000 81200000 18400000
+PDATA: 0x180008000 2e200000 81200000 90400000
 
 Verify that a demangled version of __delayLoadHelper2 can be used.
 
diff --git a/lld/test/COFF/arm64x-delayimport.test b/lld/test/COFF/arm64x-delayimport.test
index 56923ef748d09..e2a20ac6b653e 100644
--- a/lld/test/COFF/arm64x-delayimport.test
+++ b/lld/test/COFF/arm64x-delayimport.test
@@ -20,8 +20,8 @@ IMPORTS:      DelayImport {
 IMPORTS-NEXT:   Name: test.dll
 IMPORTS-NEXT:   Attributes: 0x1
 IMPORTS-NEXT:   ModuleHandle: 0x6080
-IMPORTS-NEXT:   ImportAddressTable: 0x6088
-IMPORTS-NEXT:   ImportNameTable: 0x4390
+IMPORTS-NEXT:   ImportAddressTable: 0x8000
+IMPORTS-NEXT:   ImportNameTable: 0x4180
 IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:   Import {
@@ -34,8 +34,8 @@ IMPORTS:        DelayImport {
 IMPORTS-NEXT:     Name: test.dll
 IMPORTS-NEXT:     Attributes: 0x1
 IMPORTS-NEXT:     ModuleHandle: 0x6080
-IMPORTS-NEXT:     ImportAddressTable: 0x6098
-IMPORTS-NEXT:     ImportNameTable: 0x43A0
+IMPORTS-NEXT:     ImportAddressTable: 0x8010
+IMPORTS-NEXT:     ImportNameTable: 0x4190
 IMPORTS-NEXT:     BoundDelayImportTable: 0x0
 IMPORTS-NEXT:     UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:     Import {
@@ -46,20 +46,20 @@ IMPORTS-NEXT:   }
 IMPORTS-NEXT: }
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
-TESTSEC: 0x180009000 10500000 98600000 00300000 10200000
+TESTSEC: 0x18000a000 10500000 10800000 00300000 10200000
 
 RUN: llvm-readobj --hex-dump=.testa out.dll | FileCheck --check-prefix=TESTSECA %s
-TESTSECA: 0x18000a000 88600000 08100000
+TESTSECA: 0x18000b000 00800000 08100000
 
 RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s
 DISASM:      0000000180001000 <.text>:
 DISASM-NEXT: 180001000: 52800060     mov     w0, #0x3                // =3
 DISASM-NEXT: 180001004: d65f03c0     ret
-DISASM-NEXT: 180001008: b0000030     adrp    x16, 0x180006000
-DISASM-NEXT: 18000100c: f9404610     ldr     x16, [x16, #0x88]
+DISASM-NEXT: 180001008: f0000030     adrp    x16, 0x180008000
+DISASM-NEXT: 18000100c: f9400210     ldr     x16, [x16]
 DISASM-NEXT: 180001010: d61f0200     br      x16
-DISASM-NEXT: 180001014: b0000031     adrp    x17, 0x180006000
-DISASM-NEXT: 180001018: 91022231     add     x17, x17, #0x88
+DISASM-NEXT: 180001014: f0000031     adrp    x17, 0x180008000
+DISASM-NEXT: 180001018: 91000231     add     x17, x17, #0x0
 DISASM-NEXT: 18000101c: 14000001     b       0x180001020 <.text+0x20>
 DISASM-NEXT: 180001020: a9b37bfd     stp     x29, x30, [sp, #-0xd0]!
 DISASM-NEXT: 180001024: 910003fd     mov     x29, sp
@@ -73,7 +73,7 @@ DISASM-NEXT: 180001040: ad0497e4     stp     q4, q5, [sp, #0x90]
 DISASM-NEXT: 180001044: ad059fe6     stp     q6, q7, [sp, #0xb0]
 DISASM-NEXT: 180001048: aa1103e1     mov     x1, x17
 DISASM-NEXT: 18000104c: f0000000     adrp    x0, 0x180004000
-DISASM-NEXT: 180001050: 910d4000     add     x0, x0, #0x350
+DISASM-NEXT: 180001050: 91050000     add     x0, x0, #0x140
 DISASM-NEXT: 180001054: 97ffffeb     bl      0x180001000 <.text>
 DISASM-NEXT: 180001058: aa0003f0     mov     x16, x0
 DISASM-NEXT: 18000105c: ad459fe6     ldp     q6, q7, [sp, #0xb0]
@@ -94,16 +94,16 @@ DISASM-NEXT: 18000200c: d65f03c0     ret
 DISASM-NEXT: 180002010: f0000010     adrp    x16, 0x180005000
 DISASM-NEXT: 180002014: f9400a10     ldr     x16, [x16, #0x10]
 DISASM-NEXT: 180002018: d61f0200     br      x16
-DISASM-NEXT: 18000201c: 9000002b     adrp    x11, 0x180006000
-DISASM-NEXT: 180002020: f9404d6b     ldr     x11, [x11, #0x98]
+DISASM-NEXT: 18000201c: d000002b     adrp    x11, 0x180008000
+DISASM-NEXT: 180002020: f940096b     ldr     x11, [x11, #0x10]
 DISASM-NEXT: 180002024: 9000000a     adrp    x10, 0x180002000 <.text+0x1000>
 DISASM-NEXT: 180002028: 9100c14a     add     x10, x10, #0x30
 DISASM-NEXT: 18000202c: 17fffff5     b       0x180002000 <.text+0x1000>
 DISASM-NEXT: 180002030: 52800080     mov     w0, #0x4                // =4
 DISASM-NEXT: 180002034: d65f03c0     ret
 DISASM-NEXT:                 ...
-DISASM-NEXT: 180003000: ff 25 92 30 00 00            jmpq    *0x3092(%rip)           # 0x180006098
-DISASM-NEXT: 180003006: 48 8d 05 8b 30 00 00         leaq    0x308b(%rip), %rax      # 0x180006098
+DISASM-NEXT: 180003000: ff 25 0a 50 00 00            jmpq    *0x500a(%rip)           # 0x180008010
+DISASM-NEXT: 180003006: 48 8d 05 03 50 00 00         leaq    0x5003(%rip), %rax      # 0x180008010
 DISASM-NEXT: 18000300d: e9 00 00 00 00               jmp     0x180003012 <.text+0x2012>
 DISASM-NEXT: 180003012: 51                           pushq   %rcx
 DISASM-NEXT: 180003013: 52                           pushq   %rdx
@@ -115,7 +115,7 @@ DISASM-NEXT: 180003021: 66 0f 7f 4c 24 10            movdqa  %xmm1, 0x10(%rsp)
 DISASM-NEXT: 180003027: 66 0f 7f 54 24 20            movdqa  %xmm2, 0x20(%rsp)
 DISASM-NEXT: 18000302d: 66 0f 7f 5c 24 30            movdqa  %xmm3, 0x30(%rsp)
 DISASM-NEXT: 180003033: 48 8b d0                     movq    %rax, %rdx
-DISASM-NEXT: 180003036: 48 8d 0d 13 13 00 00         leaq    0x1313(%rip), %rcx # 0x180004350
+DISASM-NEXT: 180003036: 48 8d 0d 03 11 00 00         leaq    0x1103(%rip), %rcx # 0x180004140
 DISASM-NEXT: 18000303d: e8 c6 ef ff ff               callq   0x180002008 <.text+0x1008>
 DISASM-NEXT: 180003042: 66 0f 6f 04 24               movdqa  (%rsp), %xmm0
 DISASM-NEXT: 180003047: 66 0f 6f 4c 24 10            movdqa  0x10(%rsp), %xmm1
@@ -130,7 +130,7 @@ DISASM-NEXT: 180003063: ff e0                        jmpq    *%rax
 
 RUN: llvm-readobj --coff-load-config out.dll | FileCheck --check-prefix=LOADCFG %s
 LOADCFG:      AuxiliaryDelayloadIAT: 0x5000
-LOADCFG-NEXT: AuxiliaryDelayloadIATCopy: 0x4140
+LOADCFG-NEXT: AuxiliaryDelayloadIATCopy: 0x41C0
 
 RUN: llvm-readobj --hex-dump=.rdata out.dll | FileCheck --check-prefix=AUXIAT %s
 AUXIAT:      0x180005000 00000000 00000000 00000000 00000000
@@ -147,8 +147,8 @@ NATIVE-IMPORTS:      DelayImport {
 NATIVE-IMPORTS-NEXT:   Name: test.dll
 NATIVE-IMPORTS-NEXT:   Attributes: 0x1
 NATIVE-IMPORTS-NEXT:   ModuleHandle: 0x5080
-NATIVE-IMPORTS-NEXT:   ImportAddressTable: 0x5088
-NATIVE-IMPORTS-NEXT:   ImportNameTable: 0x3370
+NATIVE-IMPORTS-NEXT:   ImportAddressTable: 0x6000
+NATIVE-IMPORTS-NEXT:   ImportNameTable: 0x3180
 NATIVE-IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   Import {
@@ -164,25 +164,25 @@ NATIVE-IMPORTS-NEXT:   DelayImport {
 NATIVE-IMPORTS-NEXT:     Name: test.dll
 NATIVE-IMPORTS-NEXT:     Attributes: 0x1
 NATIVE-IMPORTS-NEXT:     ModuleHandle: 0x5080
-NATIVE-IMPORTS-NEXT:     ImportAddressTable: 0x5098
-NATIVE-IMPORTS-NEXT:     ImportNameTable: 0x3380
+NATIVE-IMPORTS-NEXT:     ImportAddressTable: 0x6010
+NATIVE-IMPORTS-NEXT:     ImportNameTable: 0x3190
 NATIVE-IMPORTS-NEXT:     BoundDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:     UnloadDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   }
 NATIVE-IMPORTS-NEXT: }
 
 RUN: llvm-readobj --hex-dump=.testa out-native.dll | FileCheck --check-prefix=NATIVE-TESTSECA %s
-...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-lld

Author: None (jeremyd2019)

Changes

This allows IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION flags to work.

Add tests showing the .didat section is created with the proper characteristics.

Closes #134546


Patch is 35.75 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/137100.diff

12 Files Affected:

  • (modified) lld/COFF/DLL.cpp (+1-8)
  • (modified) lld/COFF/DLL.h (+3-2)
  • (modified) lld/COFF/Driver.cpp (-1)
  • (modified) lld/COFF/Writer.cpp (+3-1)
  • (modified) lld/test/COFF/arm64-delayimport.yaml (+19-3)
  • (modified) lld/test/COFF/arm64ec-delayimport.test (+25-25)
  • (modified) lld/test/COFF/arm64x-delayimport.test (+39-39)
  • (modified) lld/test/COFF/delayimports-armnt.yaml (+21-5)
  • (modified) lld/test/COFF/delayimports.test (+23-6)
  • (modified) lld/test/COFF/delayimports32.test (+22-5)
  • (modified) lld/test/COFF/delayimporttables.yaml (+4-4)
  • (modified) lld/test/COFF/giats.s (+2-2)
diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp
index 0440507b71756..5d3813ddc6bd5 100644
--- a/lld/COFF/DLL.cpp
+++ b/lld/COFF/DLL.cpp
@@ -891,7 +891,7 @@ void IdataContents::create(COFFLinkerContext &ctx) {
   dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry), 4));
 }
 
-std::vector<Chunk *> DelayLoadContents::getChunks() {
+std::vector<Chunk *> DelayLoadContents::getRDataChunks() {
   std::vector<Chunk *> v;
   v.insert(v.end(), dirs.begin(), dirs.end());
   v.insert(v.end(), names.begin(), names.end());
@@ -900,13 +900,6 @@ std::vector<Chunk *> DelayLoadContents::getChunks() {
   return v;
 }
 
-std::vector<Chunk *> DelayLoadContents::getDataChunks() {
-  std::vector<Chunk *> v;
-  v.insert(v.end(), moduleHandles.begin(), moduleHandles.end());
-  v.insert(v.end(), addresses.begin(), addresses.end());
-  return v;
-}
-
 uint64_t DelayLoadContents::getDirSize() {
   return dirs.size() * sizeof(delay_import_directory_table_entry);
 }
diff --git a/lld/COFF/DLL.h b/lld/COFF/DLL.h
index 5105b79f15d31..83e2894371a72 100644
--- a/lld/COFF/DLL.h
+++ b/lld/COFF/DLL.h
@@ -43,8 +43,9 @@ class DelayLoadContents {
   void add(DefinedImportData *sym) { imports.push_back(sym); }
   bool empty() { return imports.empty(); }
   void create();
-  std::vector<Chunk *> getChunks();
-  std::vector<Chunk *> getDataChunks();
+  ArrayRef<Chunk *> getChunks() { return addresses; }
+  std::vector<Chunk *> getRDataChunks();
+  ArrayRef<Chunk *> getDataChunks() { return moduleHandles; }
   ArrayRef<Chunk *> getCodeChunks() { return thunks; }
   ArrayRef<Chunk *> getCodePData() { return pdata; }
   ArrayRef<Chunk *> getCodeUnwindInfo() { return unwindinfo; }
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index e3ff647209e72..ef5e7cdf69209 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2015,7 +2015,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // Add default section merging rules after user rules. User rules take
   // precedence, but we will emit a warning if there is a conflict.
   parseMerge(".idata=.rdata");
-  parseMerge(".didat=.rdata");
   parseMerge(".edata=.rdata");
   parseMerge(".xdata=.rdata");
   parseMerge(".00cfg=.rdata");
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index a5582cc8074d1..3e289d8783fa6 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1083,7 +1083,7 @@ void Writer::createSections() {
   pdataSec = createSection(".pdata", data | r);
   idataSec = createSection(".idata", data | r);
   edataSec = createSection(".edata", data | r);
-  didatSec = createSection(".didat", data | r);
+  didatSec = createSection(".didat", data | r | w);
   if (isArm64EC(ctx.config.machine))
     a64xrmSec = createSection(".a64xrm", data | r);
   rsrcSec = createSection(".rsrc", data | r);
@@ -1329,6 +1329,8 @@ void Writer::appendImportThunks() {
     delayIdata.create();
     for (Chunk *c : delayIdata.getChunks())
       didatSec->addChunk(c);
+    for (Chunk *c : delayIdata.getRDataChunks())
+      rdataSec->addChunk(c);
     for (Chunk *c : delayIdata.getDataChunks())
       dataSec->addChunk(c);
     for (Chunk *c : delayIdata.getCodeChunks())
diff --git a/lld/test/COFF/arm64-delayimport.yaml b/lld/test/COFF/arm64-delayimport.yaml
index abb9f25d5c379..28099ee76aeea 100644
--- a/lld/test/COFF/arm64-delayimport.yaml
+++ b/lld/test/COFF/arm64-delayimport.yaml
@@ -4,9 +4,10 @@
 # RUN: lld-link /entry:main /subsystem:console /out:%t.exe %t.obj %p/Inputs/library-arm64.lib /alternatename:__delayLoadHelper2=main /delayload:library.dll
 # RUN: llvm-objdump --no-print-imm-hex -d %t.exe | FileCheck %s --check-prefix DISASM
 # RUN: llvm-readobj --coff-imports %t.exe | FileCheck %s -check-prefix IMPORTS
+# RUN: llvm-readobj --section-headers %t.exe | FileCheck %s -check-prefix SECTION
 
-# DISASM:  140001014:      d0000011        adrp    x17, 0x140003000
-# DISASM:  140001018:      91002231        add     x17, x17, #8
+# DISASM:  140001014:      f0000011        adrp    x17, 0x140004000
+# DISASM:  140001018:      91000231        add     x17, x17, #0
 # DISASM:  14000101c:      14000001        b       0x140001020 <.text+0x20>
 # DISASM:  140001020:      a9b37bfd        stp     x29, x30, [sp, #-208]!
 # DISASM:  140001024:      910003fd        mov     x29, sp
@@ -41,7 +42,7 @@
 # IMPORTS:   Name: library.dll
 # IMPORTS:   Attributes: 0x1
 # IMPORTS:   ModuleHandle: 0x3000
-# IMPORTS:   ImportAddressTable: 0x3008
+# IMPORTS:   ImportAddressTable: 0x4000
 # IMPORTS:   ImportNameTable: 0x2040
 # IMPORTS:   BoundDelayImportTable: 0x0
 # IMPORTS:   UnloadDelayImportTable: 0x0
@@ -50,6 +51,21 @@
 # IMPORTS:     Address: 0x140001014
 # IMPORTS:   }
 # IMPORTS: }
+# SECTION:        Name: .didat (2E 64 69 64 61 74 00 00)
+# SECTION-NEXT:   VirtualSize: 0x10
+# SECTION-NEXT:   VirtualAddress: 0x4000
+# SECTION-NEXT:   RawDataSize: 512
+# SECTION-NEXT:   PointerToRawData: 0x{{[0-9A-F]+}}
+# SECTION-NEXT:   PointerToRelocations: 0x0
+# SECTION-NEXT:   PointerToLineNumbers: 0x0
+# SECTION-NEXT:   RelocationCount: 0
+# SECTION-NEXT:   LineNumberCount: 0
+# SECTION-NEXT:   Characteristics [ (0xC0000040)
+# SECTION-NEXT:     IMAGE_SCN_CNT_INITIALIZED_DATA (0x40)
+# SECTION-NEXT:     IMAGE_SCN_MEM_READ (0x40000000)
+# SECTION-NEXT:     IMAGE_SCN_MEM_WRITE (0x80000000)
+# SECTION-NEXT:   ]
+# SECTION-NEXT: }
 
 --- !COFF
 header:
diff --git a/lld/test/COFF/arm64ec-delayimport.test b/lld/test/COFF/arm64ec-delayimport.test
index 1e0bd899ba323..c1a34740418e4 100644
--- a/lld/test/COFF/arm64ec-delayimport.test
+++ b/lld/test/COFF/arm64ec-delayimport.test
@@ -12,9 +12,9 @@ RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj
 RUN:          helper-mangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
-TESTSEC:      0x18000a000 00600000 88700000 00200000 10100000
-TESTSEC-NEXT: 0x18000a010 08600000 90700000 10200000 30100000
-TESTSEC-NEXT: 0x18000a020 1c100000 3c100000 00300000
+TESTSEC:      0x18000b000 00600000 00900000 00200000 10100000
+TESTSEC-NEXT: 0x18000b010 08600000 08900000 10200000 30100000
+TESTSEC-NEXT: 0x18000b020 1c100000 3c100000 00300000
 
 RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s
 DISASM:      0000000180001000 <.text>:
@@ -25,16 +25,16 @@ DISASM-NEXT: 18000100c: d65f03c0     ret
 DISASM-NEXT: 180001010: b0000030     adrp    x16, 0x180006000
 DISASM-NEXT: 180001014: f9400210     ldr     x16, [x16]
 DISASM-NEXT: 180001018: d61f0200     br      x16
-DISASM-NEXT: 18000101c: d000002b     adrp    x11, 0x180007000
-DISASM-NEXT: 180001020: f940456b     ldr     x11, [x11, #0x88]
+DISASM-NEXT: 18000101c: 9000004b     adrp    x11, 0x180009000
+DISASM-NEXT: 180001020: f940016b     ldr     x11, [x11]
 DISASM-NEXT: 180001024: 9000000a     adrp    x10, 0x180001000 <.text>
 DISASM-NEXT: 180001028: 9101414a     add     x10, x10, #0x50
 DISASM-NEXT: 18000102c: 17fffff5     b       0x180001000 <.text>
 DISASM-NEXT: 180001030: b0000030     adrp    x16, 0x180006000
 DISASM-NEXT: 180001034: f9400610     ldr     x16, [x16, #0x8]
 DISASM-NEXT: 180001038: d61f0200     br      x16
-DISASM-NEXT: 18000103c: d000002b     adrp    x11, 0x180007000
-DISASM-NEXT: 180001040: f940496b     ldr     x11, [x11, #0x90]
+DISASM-NEXT: 18000103c: 9000004b     adrp    x11, 0x180009000
+DISASM-NEXT: 180001040: f940056b     ldr     x11, [x11, #0x8]
 DISASM-NEXT: 180001044: 9000000a     adrp    x10, 0x180001000 <.text>
 DISASM-NEXT: 180001048: 9101614a     add     x10, x10, #0x58
 DISASM-NEXT: 18000104c: 17ffffed     b       0x180001000 <.text>
@@ -43,13 +43,13 @@ DISASM-NEXT: 180001054: d65f03c0     ret
 DISASM-NEXT: 180001058: 52800060     mov     w0, #0x3                // =3
 DISASM-NEXT: 18000105c: d65f03c0     ret
 DISASM-NEXT:                 ...
-DISASM-NEXT: 180002000: ff 25 82 50 00 00            jmpq    *0x5082(%rip)           # 0x180007088
+DISASM-NEXT: 180002000: ff 25 fa 6f 00 00            jmpq    *0x6ffa(%rip)           # 0x180009000
 DISASM-NEXT:                 ...
 DISASM-NEXT: 18000200e: 00 00                        addb    %al, (%rax)
-DISASM-NEXT: 180002010: ff 25 7a 50 00 00            jmpq    *0x507a(%rip)           # 0x180007090
-DISASM-NEXT: 180002016: 48 8d 05 6b 50 00 00         leaq    0x506b(%rip), %rax      # 0x180007088
+DISASM-NEXT: 180002010: ff 25 f2 6f 00 00            jmpq    *0x6ff2(%rip)           # 0x180009008
+DISASM-NEXT: 180002016: 48 8d 05 e3 6f 00 00         leaq    0x6fe3(%rip), %rax      # 0x180009000
 DISASM-NEXT: 18000201d: e9 0c 00 00 00               jmp     0x18000202e <.text+0x102e>
-DISASM-NEXT: 180002022: 48 8d 05 67 50 00 00         leaq    0x5067(%rip), %rax      # 0x180007090
+DISASM-NEXT: 180002022: 48 8d 05 df 6f 00 00         leaq    0x6fdf(%rip), %rax      # 0x180009008
 DISASM-NEXT: 180002029: e9 00 00 00 00               jmp     0x18000202e <.text+0x102e>
 DISASM-NEXT: 18000202e: 51                           pushq   %rcx
 DISASM-NEXT: 18000202f: 52                           pushq   %rdx
@@ -61,7 +61,7 @@ DISASM-NEXT: 18000203d: 66 0f 7f 4c 24 10            movdqa  %xmm1, 0x10(%rsp)
 DISASM-NEXT: 180002043: 66 0f 7f 54 24 20            movdqa  %xmm2, 0x20(%rsp)
 DISASM-NEXT: 180002049: 66 0f 7f 5c 24 30            movdqa  %xmm3, 0x30(%rsp)
 DISASM-NEXT: 18000204f: 48 8b d0                     movq    %rax, %rdx
-DISASM-NEXT: 180002052: 48 8d 0d a7 21 00 00         leaq    0x21a7(%rip), %rcx      # 0x180004200
+DISASM-NEXT: 180002052: 48 8d 0d a7 1f 00 00         leaq    0x1fa7(%rip), %rcx      # 0x180004000
 DISASM-NEXT: 180002059: e8 aa ef ff ff               callq   0x180001008 <.text+0x8>
 DISASM-NEXT: 18000205e: 66 0f 6f 04 24               movdqa  (%rsp), %xmm0
 DISASM-NEXT: 180002063: 66 0f 6f 4c 24 10            movdqa  0x10(%rsp), %xmm1
@@ -77,15 +77,15 @@ DISASM-NEXT: 18000207f: ff e0                        jmpq    *%rax
 RUN: llvm-readobj --coff-load-config out.dll | FileCheck --check-prefix=LOADCFG %s
 LOADCFG:      CHPEMetadata [
 LOADCFG:       AuxiliaryDelayloadIAT: 0x6000
-LOADCFG-NEXT:  AuxiliaryDelayloadIATCopy: 0x4000
+LOADCFG-NEXT:  AuxiliaryDelayloadIATCopy: 0x4078
 
 RUN: llvm-readobj --coff-imports out.dll | FileCheck --check-prefix=IMPORTS %s
 IMPORTS:      DelayImport {
 IMPORTS-NEXT:   Name: test.dll
 IMPORTS-NEXT:   Attributes: 0x1
 IMPORTS-NEXT:   ModuleHandle: 0x7080
-IMPORTS-NEXT:   ImportAddressTable: 0x7088
-IMPORTS-NEXT:   ImportNameTable: 0x4240
+IMPORTS-NEXT:   ImportAddressTable: 0x9000
+IMPORTS-NEXT:   ImportNameTable: 0x4040
 IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:   Import {
@@ -109,17 +109,17 @@ MAP-NEXT:  0001:00000058       func2_exit_thunk           0000000180001058     t
 MAP-NEXT:  0001:00001000       func                       0000000180002000     test-arm64ec:test.dll
 MAP-NEXT:  0001:00001010       func2                      0000000180002010     test-arm64ec:test.dll
 MAP-NEXT:  0002:00000000       __imp_data                 0000000180003000     test2-arm64ec:test2.dll
-MAP-NEXT:  0000:00000000       __hybrid_auxiliary_delayload_iat_copy 0000000180004000     <linker-defined>
-MAP-NEXT:  0002:00001000       __auximpcopy_func          0000000180004000     test-arm64ec:test.dll
-MAP-NEXT:  0002:00001008       __auximpcopy_func2         0000000180004008     test-arm64ec:test.dll
+MAP-NEXT:  0000:00000000       __hybrid_auxiliary_delayload_iat_copy 0000000180004078     <linker-defined>
+MAP-NEXT:  0002:00001078       __auximpcopy_func          0000000180004078     test-arm64ec:test.dll
+MAP-NEXT:  0002:00001080       __auximpcopy_func2         0000000180004080     test-arm64ec:test.dll
 MAP:       0002:00003000       __imp_func                 0000000180006000     test-arm64ec:test.dll
 MAP-NEXT:  0002:00003008       __imp_func2                0000000180006008     test-arm64ec:test.dll
-MAP:       0003:00000088       __imp_aux_func             0000000180007088     test-arm64ec:test.dll
-MAP-NEXT:  0003:00000090       __imp_aux_func2            0000000180007090     test-arm64ec:test.dll
+MAP:       0005:00000000       __imp_aux_func             0000000180009000     test-arm64ec:test.dll
+MAP-NEXT:  0005:00000008       __imp_aux_func2            0000000180009008     test-arm64ec:test.dll
 
 RUN: llvm-readobj --hex-dump=.rdata out.dll | FileCheck --check-prefix=RDATA %s
-RDATA:      0x180004000 1c100080 01000000 3c100080 01000000
-RDATA-NEXT: 0x180004010 00000000 00000000
+RDATA:      0x180004070 {{[0-9a-f]{8} [0-9a-f]{8} }}1c100080 01000000
+RDATA-NEXT: 0x180004080 3c100080 01000000 00000000 00000000
 RDATA:      0x180006000 1c100080 01000000 3c100080 01000000
 RDATA-NEXT: 0x180006010 00000000 00000000
 
@@ -127,11 +127,11 @@ RUN: llvm-readobj --coff-basereloc out.dll | FileCheck --check-prefix=RELOC %s
 RELOC:      BaseReloc [
 RELOC-NEXT:   Entry {
 RELOC-NEXT:     Type: DIR64
-RELOC-NEXT:     Address: 0x4000
+RELOC-NEXT:     Address: 0x4078
 RELOC-NEXT:   }
 RELOC-NEXT:   Entry {
 RELOC-NEXT:     Type: DIR64
-RELOC-NEXT:     Address: 0x4008
+RELOC-NEXT:     Address: 0x4080
 RELOC-NEXT:   }
 RELOC:          Address: 0x6000
 RELOC-NEXT:   }
@@ -141,7 +141,7 @@ RELOC-NEXT:     Address: 0x6008
 RELOC-NEXT:   }
 
 RUN: llvm-readobj --hex-dump=.pdata out.dll | FileCheck --check-prefix=PDATA %s
-PDATA: 0x180008000 2e200000 81200000 18400000
+PDATA: 0x180008000 2e200000 81200000 90400000
 
 Verify that a demangled version of __delayLoadHelper2 can be used.
 
diff --git a/lld/test/COFF/arm64x-delayimport.test b/lld/test/COFF/arm64x-delayimport.test
index 56923ef748d09..e2a20ac6b653e 100644
--- a/lld/test/COFF/arm64x-delayimport.test
+++ b/lld/test/COFF/arm64x-delayimport.test
@@ -20,8 +20,8 @@ IMPORTS:      DelayImport {
 IMPORTS-NEXT:   Name: test.dll
 IMPORTS-NEXT:   Attributes: 0x1
 IMPORTS-NEXT:   ModuleHandle: 0x6080
-IMPORTS-NEXT:   ImportAddressTable: 0x6088
-IMPORTS-NEXT:   ImportNameTable: 0x4390
+IMPORTS-NEXT:   ImportAddressTable: 0x8000
+IMPORTS-NEXT:   ImportNameTable: 0x4180
 IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:   Import {
@@ -34,8 +34,8 @@ IMPORTS:        DelayImport {
 IMPORTS-NEXT:     Name: test.dll
 IMPORTS-NEXT:     Attributes: 0x1
 IMPORTS-NEXT:     ModuleHandle: 0x6080
-IMPORTS-NEXT:     ImportAddressTable: 0x6098
-IMPORTS-NEXT:     ImportNameTable: 0x43A0
+IMPORTS-NEXT:     ImportAddressTable: 0x8010
+IMPORTS-NEXT:     ImportNameTable: 0x4190
 IMPORTS-NEXT:     BoundDelayImportTable: 0x0
 IMPORTS-NEXT:     UnloadDelayImportTable: 0x0
 IMPORTS-NEXT:     Import {
@@ -46,20 +46,20 @@ IMPORTS-NEXT:   }
 IMPORTS-NEXT: }
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
-TESTSEC: 0x180009000 10500000 98600000 00300000 10200000
+TESTSEC: 0x18000a000 10500000 10800000 00300000 10200000
 
 RUN: llvm-readobj --hex-dump=.testa out.dll | FileCheck --check-prefix=TESTSECA %s
-TESTSECA: 0x18000a000 88600000 08100000
+TESTSECA: 0x18000b000 00800000 08100000
 
 RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s
 DISASM:      0000000180001000 <.text>:
 DISASM-NEXT: 180001000: 52800060     mov     w0, #0x3                // =3
 DISASM-NEXT: 180001004: d65f03c0     ret
-DISASM-NEXT: 180001008: b0000030     adrp    x16, 0x180006000
-DISASM-NEXT: 18000100c: f9404610     ldr     x16, [x16, #0x88]
+DISASM-NEXT: 180001008: f0000030     adrp    x16, 0x180008000
+DISASM-NEXT: 18000100c: f9400210     ldr     x16, [x16]
 DISASM-NEXT: 180001010: d61f0200     br      x16
-DISASM-NEXT: 180001014: b0000031     adrp    x17, 0x180006000
-DISASM-NEXT: 180001018: 91022231     add     x17, x17, #0x88
+DISASM-NEXT: 180001014: f0000031     adrp    x17, 0x180008000
+DISASM-NEXT: 180001018: 91000231     add     x17, x17, #0x0
 DISASM-NEXT: 18000101c: 14000001     b       0x180001020 <.text+0x20>
 DISASM-NEXT: 180001020: a9b37bfd     stp     x29, x30, [sp, #-0xd0]!
 DISASM-NEXT: 180001024: 910003fd     mov     x29, sp
@@ -73,7 +73,7 @@ DISASM-NEXT: 180001040: ad0497e4     stp     q4, q5, [sp, #0x90]
 DISASM-NEXT: 180001044: ad059fe6     stp     q6, q7, [sp, #0xb0]
 DISASM-NEXT: 180001048: aa1103e1     mov     x1, x17
 DISASM-NEXT: 18000104c: f0000000     adrp    x0, 0x180004000
-DISASM-NEXT: 180001050: 910d4000     add     x0, x0, #0x350
+DISASM-NEXT: 180001050: 91050000     add     x0, x0, #0x140
 DISASM-NEXT: 180001054: 97ffffeb     bl      0x180001000 <.text>
 DISASM-NEXT: 180001058: aa0003f0     mov     x16, x0
 DISASM-NEXT: 18000105c: ad459fe6     ldp     q6, q7, [sp, #0xb0]
@@ -94,16 +94,16 @@ DISASM-NEXT: 18000200c: d65f03c0     ret
 DISASM-NEXT: 180002010: f0000010     adrp    x16, 0x180005000
 DISASM-NEXT: 180002014: f9400a10     ldr     x16, [x16, #0x10]
 DISASM-NEXT: 180002018: d61f0200     br      x16
-DISASM-NEXT: 18000201c: 9000002b     adrp    x11, 0x180006000
-DISASM-NEXT: 180002020: f9404d6b     ldr     x11, [x11, #0x98]
+DISASM-NEXT: 18000201c: d000002b     adrp    x11, 0x180008000
+DISASM-NEXT: 180002020: f940096b     ldr     x11, [x11, #0x10]
 DISASM-NEXT: 180002024: 9000000a     adrp    x10, 0x180002000 <.text+0x1000>
 DISASM-NEXT: 180002028: 9100c14a     add     x10, x10, #0x30
 DISASM-NEXT: 18000202c: 17fffff5     b       0x180002000 <.text+0x1000>
 DISASM-NEXT: 180002030: 52800080     mov     w0, #0x4                // =4
 DISASM-NEXT: 180002034: d65f03c0     ret
 DISASM-NEXT:                 ...
-DISASM-NEXT: 180003000: ff 25 92 30 00 00            jmpq    *0x3092(%rip)           # 0x180006098
-DISASM-NEXT: 180003006: 48 8d 05 8b 30 00 00         leaq    0x308b(%rip), %rax      # 0x180006098
+DISASM-NEXT: 180003000: ff 25 0a 50 00 00            jmpq    *0x500a(%rip)           # 0x180008010
+DISASM-NEXT: 180003006: 48 8d 05 03 50 00 00         leaq    0x5003(%rip), %rax      # 0x180008010
 DISASM-NEXT: 18000300d: e9 00 00 00 00               jmp     0x180003012 <.text+0x2012>
 DISASM-NEXT: 180003012: 51                           pushq   %rcx
 DISASM-NEXT: 180003013: 52                           pushq   %rdx
@@ -115,7 +115,7 @@ DISASM-NEXT: 180003021: 66 0f 7f 4c 24 10            movdqa  %xmm1, 0x10(%rsp)
 DISASM-NEXT: 180003027: 66 0f 7f 54 24 20            movdqa  %xmm2, 0x20(%rsp)
 DISASM-NEXT: 18000302d: 66 0f 7f 5c 24 30            movdqa  %xmm3, 0x30(%rsp)
 DISASM-NEXT: 180003033: 48 8b d0                     movq    %rax, %rdx
-DISASM-NEXT: 180003036: 48 8d 0d 13 13 00 00         leaq    0x1313(%rip), %rcx # 0x180004350
+DISASM-NEXT: 180003036: 48 8d 0d 03 11 00 00         leaq    0x1103(%rip), %rcx # 0x180004140
 DISASM-NEXT: 18000303d: e8 c6 ef ff ff               callq   0x180002008 <.text+0x1008>
 DISASM-NEXT: 180003042: 66 0f 6f 04 24               movdqa  (%rsp), %xmm0
 DISASM-NEXT: 180003047: 66 0f 6f 4c 24 10            movdqa  0x10(%rsp), %xmm1
@@ -130,7 +130,7 @@ DISASM-NEXT: 180003063: ff e0                        jmpq    *%rax
 
 RUN: llvm-readobj --coff-load-config out.dll | FileCheck --check-prefix=LOADCFG %s
 LOADCFG:      AuxiliaryDelayloadIAT: 0x5000
-LOADCFG-NEXT: AuxiliaryDelayloadIATCopy: 0x4140
+LOADCFG-NEXT: AuxiliaryDelayloadIATCopy: 0x41C0
 
 RUN: llvm-readobj --hex-dump=.rdata out.dll | FileCheck --check-prefix=AUXIAT %s
 AUXIAT:      0x180005000 00000000 00000000 00000000 00000000
@@ -147,8 +147,8 @@ NATIVE-IMPORTS:      DelayImport {
 NATIVE-IMPORTS-NEXT:   Name: test.dll
 NATIVE-IMPORTS-NEXT:   Attributes: 0x1
 NATIVE-IMPORTS-NEXT:   ModuleHandle: 0x5080
-NATIVE-IMPORTS-NEXT:   ImportAddressTable: 0x5088
-NATIVE-IMPORTS-NEXT:   ImportNameTable: 0x3370
+NATIVE-IMPORTS-NEXT:   ImportAddressTable: 0x6000
+NATIVE-IMPORTS-NEXT:   ImportNameTable: 0x3180
 NATIVE-IMPORTS-NEXT:   BoundDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   UnloadDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   Import {
@@ -164,25 +164,25 @@ NATIVE-IMPORTS-NEXT:   DelayImport {
 NATIVE-IMPORTS-NEXT:     Name: test.dll
 NATIVE-IMPORTS-NEXT:     Attributes: 0x1
 NATIVE-IMPORTS-NEXT:     ModuleHandle: 0x5080
-NATIVE-IMPORTS-NEXT:     ImportAddressTable: 0x5098
-NATIVE-IMPORTS-NEXT:     ImportNameTable: 0x3380
+NATIVE-IMPORTS-NEXT:     ImportAddressTable: 0x6010
+NATIVE-IMPORTS-NEXT:     ImportNameTable: 0x3190
 NATIVE-IMPORTS-NEXT:     BoundDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:     UnloadDelayImportTable: 0x0
 NATIVE-IMPORTS-NEXT:   }
 NATIVE-IMPORTS-NEXT: }
 
 RUN: llvm-readobj --hex-dump=.testa out-native.dll | FileCheck --check-prefix=NATIVE-TESTSECA %s
-...
[truncated]

@jeremyd2019
Copy link
Contributor Author

I have proposed a patch to do the same in binutils at https://inbox.sourceware.org/binutils/[email protected]/T/#u

idataSec = createSection(".idata", data | r);
edataSec = createSection(".edata", data | r);
didatSec = createSection(".didat", data | r);
didatSec = createSection(".didat", data | r | w);
Copy link
Contributor Author

@jeremyd2019 jeremyd2019 Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSVC dloadsup.h sayeth:

           // This delay load helper module does not support merging the delay
           // load section to a read only section because memory management
           // would not guarantee that there is commit available - and thus a
           // low memory failure path where the delay load failure hook could
           // not be safely invoked (the delay load section would still be
           // read only) might be encountered.
           //
           // It is a build time configuration problem to produce such a
           // binary so abort here and now so that the problem can be
           // identified & fixed.

Additionally, making this section read-only by default would make such a binary incompatible with OS versions that have ResolveDelayLoadedAPI and don't support IMAGE_GUARD_PROTECT_DELAYLOAD_IAT - they wouldn't know to make the section read-write before writing to it (I know Wine is such a situation).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should put this under some condition. Something like:

if (delay_load_protected) // Check /didatownsection or guard flags directly?
  didatSec = createSection(".didat", data | r | w);
else
  didatSec = createSection(".didat", data | r);

@cjacek
Copy link
Contributor

cjacek commented Apr 28, 2025

That doesn't seem to match what I see with the MSVC linker, at least with default options. Did you figure out how to make link.exe use a separate section?

@jeremyd2019
Copy link
Contributor Author

I don't know about MSVC. Here's another doc which specifies this behavior though: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata#import-handling see the second bullet point about "Protected delay load"

@cjacek
Copy link
Contributor

cjacek commented Jun 9, 2025

Note that lld-link aims to be compatible with MSVC. The MinGW mode differs in several respects, but even that shouldn't diverge unnecessarily when it can be avoided.

I did some experimentation with MSVC, I feel this PR mixes a few distinct concerns:

  1. Marking .didat as writable
    This matches MSVC behavior. If I try linking a custom read-only .didat section, MSVC issues a warning that the section already exists with different permission flags. I think this part is fine.

  2. Skipping merging of .didat into .rdata
    This doesn't match MSVC. MSVC merges .didat into .rdata, though there are caveats:

  • The merge happens only if there is at least one delay-loaded module. If not, no merge is performed.
  • While most of .didat is merged into .rdata, .didat$5 is instead merged into .data. LLD handles this by adding synthetic chunks directly to .data, which has the same effect at runtime, though it makes the map file look different.
  1. Moving the delay IAT into a separate section
    While MSVC appears to support this, I couldn’t find a way to make it emit such layout in practice. I don’t think we should differ from MSVC by default. It might be interesting to support this as an opt-in feature, but ideally, it should be done in a way that is compatible with MSVC.
    Also, if we go that route, we should update __guard_flags accordingly. Otherwise, we’re just increasing section count without gaining any security benefit.

That said, to allow dlltool to create long-form delay-load import libraries, we could just fix (1) and condition merging of .didat on actual -delayload usage. That is, we’d assume that if someone is using a long-form import library, they aren’t using -delayload, so the merge wouldn’t happen.

Side note: While improving GNU dlltool is helpful in the short term, it would be ideal if binutils gained native -delayload support. That would make it easier for the MinGW ecosystem to adopt proper delay-loading. The current approach is cumbersome, which is probably why it’s rarely used in practice.

@jeremyd2019
Copy link
Contributor Author

I don't understand why the docs are pretty clear about should may and recommended but MSVC doesn't actually implement what the docs say. Maybe I've read too many RFCs and I'm putting too much weight on the word should 😉

The delay load IAT should be in its own section and the image should set the IMAGE_GUARD_CF_PROTECT_DELAYLOAD_IAT GuardFlags bit.
For backwards compatibility with older pre-CFG operating systems, tools may enable the option to move the delay load IAT into its own section (canonically “.didat”), protected as read/write in the image headers, and additionally set the IMAGE_GUARD_CF_DELAYLOAD_IAT_IN_ITS_OWN_SECTION flag... The option to place the delay load IAT in its own section may not be required if you do not care about running an image on operating systems that predate CFG support, but tools should make that decision based on the minimum operating system support that an image needs.
It is recommended that protected delay load be enabled by default if CFG is enabled. Images that run on older operating system versions and that use the operating system’s native delay load support, as noted, may use the delay load IAT in its own section support for backwards compatibility.

(emphasis added)

@jeremyd2019
Copy link
Contributor Author

Note that lld-link aims to be compatible with MSVC. The MinGW mode differs in several respects, but even that shouldn't diverge unnecessarily when it can be avoided.

So is that like Wine, where it aims to be bug-for-bug compatible, or does the spec control and if MS link doesn't conform that's a bug on their side?

@mstorsjo
Copy link
Member

Note that lld-link aims to be compatible with MSVC. The MinGW mode differs in several respects, but even that shouldn't diverge unnecessarily when it can be avoided.

So is that like Wine, where it aims to be bug-for-bug compatible, or does the spec control and if MS link doesn't conform that's a bug on their side?

Somewhere inbetween I would say. I don't think it's a goal of lld-link to mirror any MS link.exe bug per se, but if it is relevant for actual user cases, it is sometimes done.

For this particular case though, I don't feel very attached to following the spec to the letter if that's not what actually is being done in MS link.exe. I guess the primary question that still stands open is how to enable IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION with MS link.exe?

@jeremyd2019
Copy link
Contributor Author

I guess the primary question that still stands open is how to enable IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION with MS link.exe?

It is recommended that protected delay load be enabled by default if CFG is enabled. Images that run on older operating system versions and that use the operating system’s native delay load support, as noted, may use the delay load IAT in its own section support for backwards compatibility.
The option to place the delay load IAT in its own section may not be required if you do not care about running an image on operating systems that predate CFG support, but tools should make that decision based on the minimum operating system support that an image needs.

To me, that means IMAGE_GUARD_PROTECT_DELAYLOAD_IAT should be enabled if using delayload when CFGuard is enabled, and IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION may be enabled if it's somehow indicated to the linker that older minimum operating system support is needed (one of the version options like subsystem version?)

@jeremyd2019
Copy link
Contributor Author

Binutils 2.45 now includes the patch for it to act like this.

@kkent030315
Copy link
Contributor

That doesn't seem to match what I see with the MSVC linker, at least with default options. Did you figure out how to make link.exe use a separate section?

@jeremyd2019 There's a hidden MS link option that produces the protected delay load DLLs: /didatownsection.

LoadConfig.GuardFlags = Instrumented, Delay-load IAT protected, Delay-load private section (0x3100)

@kkent030315
Copy link
Contributor

kkent030315 commented Sep 23, 2025

I guess the primary question that still stands open is how to enable IMAGE_GUARD_PROTECT_DELAYLOAD_IAT|IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION with MS link.exe?

It is recommended that protected delay load be enabled by default if CFG is enabled. Images that run on older operating system versions and that use the operating system’s native delay load support, as noted, may use the delay load IAT in its own section support for backwards compatibility.
The option to place the delay load IAT in its own section may not be required if you do not care about running an image on operating systems that predate CFG support, but tools should make that decision based on the minimum operating system support that an image needs.

To me, that means IMAGE_GUARD_PROTECT_DELAYLOAD_IAT should be enabled if using delayload when CFGuard is enabled, and IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION may be enabled if it's somehow indicated to the linker that older minimum operating system support is needed (one of the version options like subsystem version?)

Unless I am not misunderstanding something, I do not know a single case where IMAGE_GUARD_PROTECT_DELAYLOAD_IAT is enabled without IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION. I haven't seen any such flag (including hidden ones) to only enable the IMAGE_GUARD_PROTECT_DELAYLOAD_IAT. Given that, I can almost confidently say that those two are mutually dependent. You are correct.

@kkent030315
Copy link
Contributor

kkent030315 commented Sep 23, 2025

It is recommended that protected delay load be enabled by default if CFG is enabled.
https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata

Also, the official doc quote above is a bit misleading IMO. It is mixing up with control flow guard (CFGuard or CFG here), which is completely irrelevant in terms of CFG. The purpose of emitting .didat section in its own section is to let Windows loader change the protection safely (as per MS doc say).

@kkent030315
Copy link
Contributor

kkent030315 commented Sep 27, 2025

@mstorsjo I like this PR, and would like to see get this merged. Based on my previous comments about how MS link works I think the right direction for this would be something like:

  • Adding /didatownsection flag (off by default)
  • Under the condition (if the flag on) modify the didat section to writable and don't perform merge to RDATA
  • Adding tests to verify that verifies the all delay load datas are in didat section (so tests diff in this PR would not be required)

I am actually not in favor of doing these by default and unconditionally as that is not backward-compatible. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[LLD] [COFF] PE delayload compatibility with IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION

5 participants