[PATCH v2 1/1] aarch64: mingw: Auto-import implementation

Evgeny Karpov <[email protected]> Fri, 31 Jul 2026 16:46:55 +0200
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
Auto-import is needed to resolve symbols during dynamic linking.

For example,

  adrp	x0, dyn_v + 2
  add	x0, x0, :lo12:dyn_v + 2

or

  adrp  x0, dyn_v + 8
  ldr   x0, [x0, :lo12:dyn_v + 8]

or

  b init_dyn_v
  bl init_dyn_v

init_dyn_v and dyn_v are undefined at compile time and are getting resolved
during linking with a dynamic library to the IAT (import address table).

In case of b or bl, it can be done by using a jump table

init_dyn_v:
  adrp	x16, __imp_init_dyn_v
  add	x16, x16, :lo12:__imp_init_dyn_v
  ldr	x16, [x16]
  br	x16

"adrp", "add" and "ldr" are handled differently. The codegen usually
creates a reference to a symbol without an offset:
adrp x0, <symbol>
add x0, x0, :lo12:<symbol> / ldr x0, [x0, :lo12:<symbol>]

however it might be necessary to support offsets in some cases:
  adrp  x0, <symbol> +/- offset
  add   x0, x0, :lo12:<symbol> +/- offset / ldr   x0, [x0, :lo12:<symbol> +/- offset]

It is handled by detecting relocations that have symbols that are resolved
during dynamic linking.
"adrp" is replaced by an unconditional branching to a place where a symbol address
will be loaded from the IAT and then returning back.
Relocation for the "add" and "ldr" instructions is not needed in this case.

  adrp	x0, dyn_v + 8
  add	x0, x0, :lo12:dyn_v + 8  /  ldr   x0, [x0, :lo12:dyn_v + 8]

turns into

__fu1_dyn_v:
  b     __imp_dyn_v_10_8
  add	x0, x0, 8  /  ldr   x0, [x0, 8]
  ...

__imp_dyn_v_10_8:
   adrp	x0, __imp_dyn_v
   ldr	x0, [x0, :lo12:__imp_dyn_v]
   add	x0, x0, offset_4k, lsl #12
   b	__fu1_dyn_v + 0x4

Another option that was considered is using bl / ret.
However, in this case, an edge case will not be handled properly:
  adrp x30, dyn_v

Signed-off-by: Evgeny Karpov <[email protected]>

bfd/ChangeLog:

	* coff-aarch64.c (coff_pe_aarch64_relocate_section): Add relocation
	handling for dynamic linking.

ld/ChangeLog:

	* emultempl/pep.em: Update read_addend for aarch64 relocations.
	* pe-dll.c (aarch64_make_jump_stub): Add jump stub.
	(aarch64_make_imp_offset): Add adrp handler for dynamic linking.
	(pe_create_import_fixup): Add fixups for aarch64.
	* testsuite/ld-pe/pe.exp: Add auto-import-aarch64 test.
	* testsuite/ld-pe/auto-import-aarch64.d: New test.
	* testsuite/ld-pe/auto-import-aarch64.s: New test.
	* testsuite/ld-pe/auto-import-dll-aarch64.s: New test.
---
 bfd/coff-aarch64.c                           |  56 +++---
 ld/emultempl/pep.em                          |  17 +-
 ld/pe-dll.c                                  | 171 +++++++++++++++++++
 ld/testsuite/ld-pe/auto-import-aarch64.d     | 167 ++++++++++++++++++
 ld/testsuite/ld-pe/auto-import-aarch64.s     |  61 +++++++
 ld/testsuite/ld-pe/auto-import-dll-aarch64.s |  13 ++
 ld/testsuite/ld-pe/pe.exp                    |  15 +-
 7 files changed, 473 insertions(+), 27 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/auto-import-aarch64.d
 create mode 100644 ld/testsuite/ld-pe/auto-import-aarch64.s
 create mode 100644 ld/testsuite/ld-pe/auto-import-dll-aarch64.s

diff --git a/bfd/coff-aarch64.c b/bfd/coff-aarch64.c
index d889359a6fc..cd194034839 100644
--- a/bfd/coff-aarch64.c
+++ b/bfd/coff-aarch64.c
@@ -521,6 +521,38 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 
       h = obj_coff_sym_hashes (input_bfd)[symndx];
 
+      if (h && h->root.type == bfd_link_hash_defweak)
+	switch (rel->r_type)
+	  {
+	  case IMAGE_REL_ARM64_PAGEOFFSET_12A:
+	  case IMAGE_REL_ARM64_PAGEOFFSET_12L:
+	    {
+	      rel->r_ignore = 1;
+	      continue;
+	    }
+	  case IMAGE_REL_ARM64_BRANCH26:
+	    {
+	      h = (struct coff_link_hash_entry*)
+		  bfd_link_hash_lookup (info->hash, h->root.root.string + 6,
+					0, 0, 1);
+	      break;
+	    }
+	  case IMAGE_REL_ARM64_PAGEBASE_REL21:
+	    {
+	      uint32_t opcode = 0x14000000; /* b <label>.  */
+	      bfd_putl32 (opcode, contents + rel->r_vaddr);
+	      rel->r_type = IMAGE_REL_ARM64_BRANCH26;
+
+	      char* imp_label = xasprintf("%s_%x_%lx", h->root.root.string,
+					  input_section->id,
+					  (long unsigned) rel->r_vaddr);
+	      h = (struct coff_link_hash_entry*)
+		  bfd_link_hash_lookup (info->hash, imp_label, 0, 0, 1);
+	      free(imp_label);
+	      break;
+	    }
+	  }
+
       if (h && h->root.type == bfd_link_hash_defined)
 	{
 	  sec = h->root.u.def.section;
@@ -557,6 +589,7 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	  continue;
 	}
 
+      rel->r_ignore = 1;
       switch (rel->r_type)
 	{
 	case IMAGE_REL_ARM64_ADDR32NB:
@@ -578,8 +611,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 		input_section, rel->r_vaddr - input_section->vma);
 
 	    bfd_putl32 (val, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -613,8 +644,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= val & 0x3ffffff;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -648,8 +677,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= (val & 0x7ffff) << 5;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -683,8 +710,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= (val & 0x3fff) << 5;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -720,8 +745,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= (val & 0x1ffffc) << 3;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -757,8 +780,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= (val & 0x1ffffc) << 3;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -786,8 +807,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 		input_section, rel->r_vaddr - input_section->vma);
 
 	    bfd_putl32 (val, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -831,8 +850,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= val << 10;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -854,8 +871,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 	    opcode |= val << 10;
 
 	    bfd_putl32 (opcode, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -875,8 +890,6 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 		input_section, rel->r_vaddr - input_section->vma);
 
 	    bfd_putl32 (val, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
@@ -898,10 +911,7 @@ coff_pe_aarch64_relocate_section (bfd *output_bfd,
 		s = s->next;
 	      }
 
-
 	    bfd_putl16 (idx, contents + rel->r_vaddr);
-	    rel->r_ignore = 1;
-
 	    break;
 	  }
 
diff --git a/ld/emultempl/pep.em b/ld/emultempl/pep.em
index 25ce3963b36..c74192ae32b 100644
--- a/ld/emultempl/pep.em
+++ b/ld/emultempl/pep.em
@@ -1160,10 +1160,22 @@ pep_fixup_stdcalls (void)
 static bfd_vma
 read_addend (arelent *rel, asection *s)
 {
-  char buf[8];
   bfd_vma addend = 0;
   bool ok = false;
 
+#if defined (COFF_WITH_peAArch64)
+  switch (rel->howto->bitsize)
+    {
+    case 12:
+    case 21:
+    case 26:
+      ok = true;
+      break;
+    }
+
+#else
+  char buf[8];
+
   switch (rel->howto->bitsize)
     {
     case 8:
@@ -1186,7 +1198,6 @@ read_addend (arelent *rel, asection *s)
 	    addend = bfd_get_16 (s->owner, buf);
 	}
       break;
-    case 26:
     case 32:
       ok = bfd_get_section_contents (s->owner, s, buf, rel->address, 4);
       if (ok)
@@ -1203,6 +1214,8 @@ read_addend (arelent *rel, asection *s)
 	addend = bfd_get_64 (s->owner, buf);
       break;
     }
+#endif
+
   if (!ok)
     einfo (_("%P: %H: cannot get section contents - auto-import exception\n"),
 	   s->owner, s, rel->address);
diff --git a/ld/pe-dll.c b/ld/pe-dll.c
index f43bd14896e..b0a86f070d8 100644
--- a/ld/pe-dll.c
+++ b/ld/pe-dll.c
@@ -2842,10 +2842,181 @@ pe_create_runtime_relocator_reference (bfd *parent)
   return abfd;
 }
 
+static void
+aarch64_make_jump_stub(const char* symbol_name, bfd* parent)
+{
+  static struct bfd_hash_table *stub_hash = NULL;
+  if (!stub_hash)
+    {
+      stub_hash = (struct bfd_hash_table *) xmalloc
+		  (sizeof (struct bfd_hash_table));
+      bfd_hash_table_init (stub_hash, bfd_hash_newfunc,
+			  sizeof (struct bfd_hash_entry));
+    }
+
+  if (pe_dll_extra_pe_debug)
+    printf ("validate jump stub for %s\n", symbol_name);
+
+  if (bfd_hash_lookup(stub_hash, symbol_name, false, false))
+    return;
+
+  if (pe_dll_extra_pe_debug)
+    printf ("creating jump stub for %s\n", symbol_name);
+  bfd_hash_lookup(stub_hash, symbol_name, true, true);
+
+  static unsigned tmp_stub_seq = 0;
+  char *oname = xasprintf ("jump_stub_d%06d.o", tmp_stub_seq);
+  ++tmp_stub_seq;
+
+  bfd *abfd = bfd_create (oname, parent);
+  free (oname);
+  bfd_make_writable (abfd);
+
+  bfd_set_format (abfd, bfd_object);
+  bfd_set_arch_mach (abfd, pe_details->bfd_arch, 0);
+
+  symptr = 0;
+  symtab = xmalloc (12 * sizeof (asymbol *));
+
+  asection *tx  = quick_section (abfd, ".text", SEC_CODE | SEC_HAS_CONTENTS
+				 | SEC_READONLY, 2);
+  quick_symbol (abfd, "", symbol_name, "", tx, BSF_GLOBAL, 0);
+  quick_symbol (abfd, "__imp_", symbol_name, "", bfd_und_section_ptr,
+		BSF_GLOBAL, 0);
+
+  const unsigned jmp_byte_count = sizeof (jmp_aarch64_bytes);
+  bfd_set_section_size (tx, jmp_byte_count);
+  unsigned char *td = xmalloc (jmp_byte_count);
+  tx->contents = td;
+  memcpy (td, jmp_aarch64_bytes, jmp_byte_count);
+
+  quick_reloc (abfd, 0, BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL, 2);
+  quick_reloc (abfd, 4, BFD_RELOC_AARCH64_ADD_LO12, 2);
+  save_relocs (tx);
+
+  bfd_set_symtab (abfd, symtab, symptr);
+
+  bfd_set_section_contents (abfd, tx, td, 0, jmp_byte_count);
+  bfd_make_readable (abfd);
+  add_bfd_to_link (abfd, bfd_get_filename (abfd), &link_info);
+}
+
+static void
+aarch64_make_imp_offset(const char* imp_symbol_name,
+			const char* imp_offset_name, const int offset,
+			unsigned rd, const char* caller_label, bfd* parent)
+{
+  if (pe_dll_extra_pe_debug)
+    printf("symbol: %s imp_offset_name: %s offset: %u rs: %u caller: %s\n",
+	   imp_symbol_name, imp_offset_name, offset, rd, caller_label);
+
+  static const unsigned char imp_offset_bytes[] =
+    {
+      0x00, 0x00, 0x00, 0x90, /* adrp x0, <imp_symbol_name>		*/
+      0x00, 0x00, 0x40, 0xf9, /* ldr x0, [x0, :lo12:<imp_symbol_name>]	*/
+      0x00, 0x00, 0x40, 0x91, /* add x0, x0, 0, lsl 12			*/
+      0x01, 0x00, 0x00, 0x14  /* b <caller_label> + 4			*/
+    };
+
+  static unsigned tmp_stub_seq = 0;
+  char *oname = xasprintf ("imp_offset_stub_d%06d.o", tmp_stub_seq);
+  ++tmp_stub_seq;
+
+  bfd *abfd = bfd_create (oname, parent);
+  free (oname);
+  bfd_make_writable (abfd);
+
+  bfd_set_format (abfd, bfd_object);
+  bfd_set_arch_mach (abfd, pe_details->bfd_arch, 0);
+
+  symptr = 0;
+  symtab = xmalloc (12 * sizeof (asymbol *));
+
+  asection *tx = quick_section (abfd, ".text", SEC_CODE | SEC_HAS_CONTENTS
+				| SEC_READONLY, 2);
+  quick_symbol (abfd, "", imp_offset_name, "", tx, BSF_GLOBAL, 0);
+  quick_symbol (abfd, "", imp_symbol_name, "", UNDSEC, BSF_GLOBAL, 0);
+  quick_symbol (abfd, "", caller_label, "", UNDSEC, BSF_GLOBAL, 0);
+
+  const unsigned imp_offset_byte_count = sizeof (imp_offset_bytes);
+  bfd_set_section_size (tx, imp_offset_byte_count);
+  uint32_t *td = xmalloc (imp_offset_byte_count);
+  tx->contents = (bfd_byte*) td;
+  memcpy (td, imp_offset_bytes, imp_offset_byte_count);
+
+  rd &= (1 << 5) - 1;
+  td[0] |= rd;
+  td[1] |= (rd << 5) | rd;
+
+  unsigned imm = 0;
+  if (offset >= 0)
+    imm = offset;
+  else
+  {
+    imm = -offset + 4096;
+/* Change "add x0, x0, 0, lsl 12" to "sub x0, x0, 0, lsl 12".  */
+    td[2] |= 1 << 30;
+  }
+  imm >>= 12;
+  imm &= ((1 << 12) - 1);
+  td[2] |= (rd << 5) | rd;
+  td[2] |= imm << 10;
+
+  quick_reloc (abfd, 0, BFD_RELOC_AARCH64_ADR_HI21_PCREL, 2);
+  quick_reloc (abfd, 4, BFD_RELOC_AARCH64_LDST64_LO12, 2);
+  quick_reloc (abfd, 12, BFD_RELOC_AARCH64_CALL26, 3);
+  save_relocs (tx);
+
+  bfd_set_symtab (abfd, symtab, symptr);
+
+  bfd_set_section_contents (abfd, tx, td, 0, imp_offset_byte_count);
+  bfd_make_readable (abfd);
+  add_bfd_to_link (abfd, bfd_get_filename (abfd), &link_info);
+}
+
 void
 pe_create_import_fixup (arelent *rel, asection *s, bfd_vma addend, char *name,
 			const char *symname)
 {
+ if (pe_details->pe_arch == PE_ARCH_aarch64)
+   {
+      if (rel->howto->bitsize == 12)
+	return;
+      else if (rel->howto->bitsize == 26)
+	{
+/* On AArch64, a single opcode is not sufficient for relocation
+   in dynamic linking. The linker generates a jump stub instead.  */
+	  aarch64_make_jump_stub(name, s->owner);
+	  return;
+	}
+      else if (rel->howto->bitsize == 21)
+	{
+	  uint32_t opcode;
+	  if (!bfd_get_section_contents (s->owner, s, &opcode,
+					 rel->address, 4))
+	    return;
+
+	  int32_t imm = (opcode >> 5) & ((1 << 19) - 1);
+	  imm <<= 2;
+	  imm |= (opcode >> 29) & ((1 << 2) - 1);
+	  if (imm & (1 << 20))
+	    imm |= ~((1 << 21) - 1);
+
+	  char *imp_offset_label = xasprintf("__imp_%s", name);
+	  char *imp_label = xasprintf("%s_%x_%lx", imp_offset_label,
+				      current_sec->id,
+				      (long unsigned) rel->address);
+	  const char *fixup_name = make_import_fixup_mark (rel, name);
+	  unsigned rd = opcode & ((1 << 5) - 1);
+	  aarch64_make_imp_offset(imp_offset_label, imp_label, imm, rd,
+				  fixup_name, s->owner);
+
+	  free(imp_label);
+	  free(imp_offset_label);
+	  return;
+	}
+   }
+
   const char *fixup_name = make_import_fixup_mark (rel, name);
   bfd *b;
 
diff --git a/ld/testsuite/ld-pe/auto-import-aarch64.d b/ld/testsuite/ld-pe/auto-import-aarch64.d
new file mode 100644
index 00000000000..4b70cfedd2a
--- /dev/null
+++ b/ld/testsuite/ld-pe/auto-import-aarch64.d
@@ -0,0 +1,167 @@
+
+.*:     file format pei-aarch64-little
+
+
+Disassembly of section .text:
+
+0000000140001000 <.*>:
+   140001000:	14000030 	b	1400010c0 <__imp_dyn_v_10_0>
+   140001004:	91000000 	add	x0, x0, #0x0
+   140001008:	f9400000 	ldr	x0, \[x0\]
+
+000000014000100c <__fu1_dyn_v>:
+   14000100c:	14000031 	b	1400010d0 <__imp_dyn_v_10_c>
+   140001010:	91000800 	add	x0, x0, #0x2
+   140001014:	f9400400 	ldr	x0, \[x0, #8\]
+
+0000000140001018 <__fu2_dyn_v>:
+   140001018:	14000032 	b	1400010e0 <__imp_dyn_v_10_18>
+   14000101c:	913ffc00 	add	x0, x0, #0xfff
+   140001020:	f947fc00 	ldr	x0, \[x0, #4088\]
+
+0000000140001024 <__fu3_dyn_v>:
+   140001024:	14000033 	b	1400010f0 <__imp_dyn_v_10_24>
+   140001028:	91000000 	add	x0, x0, #0x0
+   14000102c:	f9400000 	ldr	x0, \[x0\]
+
+0000000140001030 <__fu4_dyn_v>:
+   140001030:	14000034 	b	140001100 <__imp_dyn_v_10_30>
+   140001034:	912eb000 	add	x0, x0, #0xbac
+   140001038:	f945d800 	ldr	x0, \[x0, #2992\]
+
+000000014000103c <__fu5_dyn_v>:
+   14000103c:	14000035 	b	140001110 <__imp_dyn_v_10_3c>
+   140001040:	913ffc00 	add	x0, x0, #0xfff
+   140001044:	f947fc00 	ldr	x0, \[x0, #4088\]
+
+0000000140001048 <__fu6_dyn_v>:
+   140001048:	14000036 	b	140001120 <__imp_dyn_v_10_48>
+   14000104c:	913ff800 	add	x0, x0, #0xffe
+   140001050:	f947fc00 	ldr	x0, \[x0, #4088\]
+
+0000000140001054 <__fu7_dyn_v>:
+   140001054:	14000037 	b	140001130 <__imp_dyn_v_10_54>
+   140001058:	91000400 	add	x0, x0, #0x1
+   14000105c:	f9400400 	ldr	x0, \[x0, #8\]
+
+0000000140001060 <__fu8_dyn_v>:
+   140001060:	14000038 	b	140001140 <__imp_dyn_v_10_60>
+   140001064:	91000000 	add	x0, x0, #0x0
+   140001068:	f9400000 	ldr	x0, \[x0\]
+
+000000014000106c <__fu9_dyn_v>:
+   14000106c:	14000039 	b	140001150 <__imp_dyn_v_10_6c>
+   140001070:	91115000 	add	x0, x0, #0x454
+   140001074:	f9422800 	ldr	x0, \[x0, #1104\]
+
+0000000140001078 <__fu10_dyn_v>:
+   140001078:	1400003a 	b	140001160 <__imp_dyn_v_10_78>
+   14000107c:	91000000 	add	x0, x0, #0x0
+   140001080:	f9400000 	ldr	x0, \[x0\]
+
+0000000140001084 <__fu11_dyn_v>:
+   140001084:	1400003b 	b	140001170 <__imp_dyn_v_10_84>
+   140001088:	91000821 	add	x1, x1, #0x2
+   14000108c:	f9400421 	ldr	x1, \[x1, #8\]
+
+0000000140001090 <__fu12_dyn_v>:
+   140001090:	1400003c 	b	140001180 <__imp_dyn_v_10_90>
+   140001094:	91000820 	add	x0, x1, #0x2
+   140001098:	f9400420 	ldr	x0, \[x1, #8\]
+
+000000014000109c <__fu13_dyn_v>:
+   14000109c:	1400003d 	b	140001190 <__imp_dyn_v_10_9c>
+   1400010a0:	913ff821 	add	x1, x1, #0xffe
+   1400010a4:	f947fc21 	ldr	x1, \[x1, #4088\]
+   1400010a8:	14000002 	b	1400010b0 <init_dyn_v>
+   1400010ac:	94000001 	bl	1400010b0 <init_dyn_v>
+
+00000001400010b0 <init_dyn_v>:
+   1400010b0:	d0000010 	adrp	x16, 140003000 <.*>
+   1400010b4:	91012210 	add	x16, x16, #0x48
+   1400010b8:	f9400210 	ldr	x16, \[x16\]
+   1400010bc:	d61f0200 	br	x16
+
+00000001400010c0 <__imp_dyn_v_10_0>:
+   1400010c0:	d0000000 	adrp	x0, 140003000 <.*>
+   1400010c4:	f9402000 	ldr	x0, \[x0, #64\]
+   1400010c8:	91400000 	add	x0, x0, #0x0, lsl #12
+   1400010cc:	17ffffce 	b	140001004 <.*\+0x4>
+
+00000001400010d0 <__imp_dyn_v_10_c>:
+   1400010d0:	d0000000 	adrp	x0, 140003000 <.*>
+   1400010d4:	f9402000 	ldr	x0, \[x0, #64\]
+   1400010d8:	91400000 	add	x0, x0, #0x0, lsl #12
+   1400010dc:	17ffffcd 	b	140001010 <__fu1_dyn_v\+0x4>
+
+00000001400010e0 <__imp_dyn_v_10_18>:
+   1400010e0:	d0000000 	adrp	x0, 140003000 <.*>
+   1400010e4:	f9402000 	ldr	x0, \[x0, #64\]
+   1400010e8:	91400000 	add	x0, x0, #0x0, lsl #12
+   1400010ec:	17ffffcc 	b	14000101c <__fu2_dyn_v\+0x4>
+
+00000001400010f0 <__imp_dyn_v_10_24>:
+   1400010f0:	d0000000 	adrp	x0, 140003000 <.*>
+   1400010f4:	f9402000 	ldr	x0, \[x0, #64\]
+   1400010f8:	91400400 	add	x0, x0, #0x1, lsl #12
+   1400010fc:	17ffffcb 	b	140001028 <__fu3_dyn_v\+0x4>
+
+0000000140001100 <__imp_dyn_v_10_30>:
+   140001100:	d0000000 	adrp	x0, 140003000 <.*>
+   140001104:	f9402000 	ldr	x0, \[x0, #64\]
+   140001108:	91436c00 	add	x0, x0, #0xdb, lsl #12
+   14000110c:	17ffffca 	b	140001034 <__fu4_dyn_v\+0x4>
+
+0000000140001110 <__imp_dyn_v_10_3c>:
+   140001110:	d0000000 	adrp	x0, 140003000 <.*>
+   140001114:	f9402000 	ldr	x0, \[x0, #64\]
+   140001118:	9143fc00 	add	x0, x0, #0xff, lsl #12
+   14000111c:	17ffffc9 	b	140001040 <__fu5_dyn_v\+0x4>
+
+0000000140001120 <__imp_dyn_v_10_48>:
+   140001120:	d0000000 	adrp	x0, 140003000 <.*>
+   140001124:	f9402000 	ldr	x0, \[x0, #64\]
+   140001128:	d1400400 	sub	x0, x0, #0x1, lsl #12
+   14000112c:	17ffffc8 	b	14000104c <__fu6_dyn_v\+0x4>
+
+0000000140001130 <__imp_dyn_v_10_54>:
+   140001130:	d0000000 	adrp	x0, 140003000 <.*>
+   140001134:	f9402000 	ldr	x0, \[x0, #64\]
+   140001138:	d1400400 	sub	x0, x0, #0x1, lsl #12
+   14000113c:	17ffffc7 	b	140001058 <__fu7_dyn_v\+0x4>
+
+0000000140001140 <__imp_dyn_v_10_60>:
+   140001140:	d0000000 	adrp	x0, 140003000 <.*>
+   140001144:	f9402000 	ldr	x0, \[x0, #64\]
+   140001148:	d1400800 	sub	x0, x0, #0x2, lsl #12
+   14000114c:	17ffffc6 	b	140001064 <__fu8_dyn_v\+0x4>
+
+0000000140001150 <__imp_dyn_v_10_6c>:
+   140001150:	d0000000 	adrp	x0, 140003000 <.*>
+   140001154:	f9402000 	ldr	x0, \[x0, #64\]
+   140001158:	d1437000 	sub	x0, x0, #0xdc, lsl #12
+   14000115c:	17ffffc5 	b	140001070 <__fu9_dyn_v\+0x4>
+
+0000000140001160 <__imp_dyn_v_10_78>:
+   140001160:	d0000000 	adrp	x0, 140003000 <.*>
+   140001164:	f9402000 	ldr	x0, \[x0, #64\]
+   140001168:	d1440400 	sub	x0, x0, #0x101, lsl #12
+   14000116c:	17ffffc4 	b	14000107c <__fu10_dyn_v\+0x4>
+
+0000000140001170 <__imp_dyn_v_10_84>:
+   140001170:	d0000001 	adrp	x1, 140003000 <.*>
+   140001174:	f9402021 	ldr	x1, \[x1, #64\]
+   140001178:	91400021 	add	x1, x1, #0x0, lsl #12
+   14000117c:	17ffffc3 	b	140001088 <__fu11_dyn_v\+0x4>
+
+0000000140001180 <__imp_dyn_v_10_90>:
+   140001180:	d0000001 	adrp	x1, 140003000 <.*>
+   140001184:	f9402021 	ldr	x1, \[x1, #64\]
+   140001188:	91400021 	add	x1, x1, #0x0, lsl #12
+   14000118c:	17ffffc2 	b	140001094 <__fu12_dyn_v\+0x4>
+
+0000000140001190 <__imp_dyn_v_10_9c>:
+   140001190:	d0000001 	adrp	x1, 140003000 <.*>
+   140001194:	f9402021 	ldr	x1, \[x1, #64\]
+   140001198:	d1400421 	sub	x1, x1, #0x1, lsl #12
+   14000119c:	17ffffc1 	b	1400010a0 <__fu13_dyn_v\+0x4>
diff --git a/ld/testsuite/ld-pe/auto-import-aarch64.s b/ld/testsuite/ld-pe/auto-import-aarch64.s
new file mode 100644
index 00000000000..411669e9d9e
--- /dev/null
+++ b/ld/testsuite/ld-pe/auto-import-aarch64.s
@@ -0,0 +1,61 @@
+	.text
+	.global main
+main:
+	adrp	x0, dyn_v
+	add	x0, x0, :lo12:dyn_v
+	ldr	x0, [x0, :lo12:dyn_v]
+
+	adrp	x0, dyn_v + 2
+	add	x0, x0, :lo12:dyn_v + 2
+	ldr	x0, [x0, :lo12:dyn_v + 8]
+
+	adrp	x0, dyn_v + 4095
+	add	x0, x0, :lo12:dyn_v + 4095
+	ldr	x0, [x0, :lo12:dyn_v + 4088]
+
+	adrp	x0, dyn_v + 4096
+	add	x0, x0, :lo12:dyn_v + 4096
+	ldr	x0, [x0, :lo12:dyn_v + 4096]
+
+	adrp	x0, dyn_v + 900012
+	add	x0, x0, :lo12:dyn_v + 900012
+	ldr	x0, [x0, :lo12:dyn_v + 900016]
+
+	adrp	x0, dyn_v + 1048575
+	add	x0, x0, :lo12:dyn_v + 1048575
+	ldr	x0, [x0, :lo12:dyn_v + 1048568]
+
+	adrp	x0, dyn_v - 2
+	add	x0, x0, :lo12:dyn_v - 2
+	ldr	x0, [x0, :lo12:dyn_v - 8]
+
+	adrp	x0, dyn_v - 4095
+	add	x0, x0, :lo12:dyn_v - 4095
+	ldr	x0, [x0, :lo12:dyn_v - 4088]
+
+	adrp	x0, dyn_v - 4096
+	add	x0, x0, :lo12:dyn_v - 4096
+	ldr	x0, [x0, :lo12:dyn_v - 4096]
+
+	adrp	x0, dyn_v - 900012
+	add	x0, x0, :lo12:dyn_v - 900012
+	ldr	x0, [x0, :lo12:dyn_v - 900016]
+
+	adrp	x0, dyn_v - 1048576
+	add	x0, x0, :lo12:dyn_v - 1048576
+	ldr	x0, [x0, :lo12:dyn_v - 1048576]
+
+	adrp	x1, dyn_v + 2
+	add	x1, x1, :lo12:dyn_v + 2
+	ldr	x1, [x1, :lo12:dyn_v + 8]
+
+	adrp	x1, dyn_v + 2
+	add	x0, x1, :lo12:dyn_v + 2
+	ldr	x0, [x1, :lo12:dyn_v + 8]
+
+	adrp	x1, dyn_v - 2
+	add	x1, x1, :lo12:dyn_v - 2
+	ldr	x1, [x1, :lo12:dyn_v - 8]
+
+	b init_dyn_v
+	bl init_dyn_v
diff --git a/ld/testsuite/ld-pe/auto-import-dll-aarch64.s b/ld/testsuite/ld-pe/auto-import-dll-aarch64.s
new file mode 100644
index 00000000000..dc9314273fa
--- /dev/null
+++ b/ld/testsuite/ld-pe/auto-import-dll-aarch64.s
@@ -0,0 +1,13 @@
+	.data
+	.global	dyn_v
+	.align	3
+	.def	dyn_v;	.scl	2;	.type	0;	.endef
+dyn_v:
+	.space	8
+
+	.text
+	.align	2
+	.global	init_dyn_v
+	.def	init_dyn_v;	.scl	2;	.type	32;	.endef
+init_dyn_v:
+	ret
diff --git a/ld/testsuite/ld-pe/pe.exp b/ld/testsuite/ld-pe/pe.exp
index b95f61b8578..53b04bb6757 100644
--- a/ld/testsuite/ld-pe/pe.exp
+++ b/ld/testsuite/ld-pe/pe.exp
@@ -121,6 +121,17 @@ if {[istarget "aarch64-*-pe*"] || [istarget "aarch64-*-mingw*"]} {
     }
 
     run_ld_link_tests $pe_tests
+
+    set auto_import_aarch64_test "auto-import-aarch64"
+    if { [ld_assemble $as $srcdir/$subdir/auto-import-dll-aarch64.s tmpdir/auto-import-dll-aarch64.o]
+	 && [ld_assemble $as $srcdir/$subdir/auto-import-aarch64.s tmpdir/auto-import-aarch64.o]
+	 && [ld_link $ld "tmpdir/auto-import-dll.dll" "-shared tmpdir/auto-import-dll-aarch64.o --out-implib tmpdir/libauto-import-dll-aarch64.a"]
+	 && [ld_link $ld "tmpdir/auto-import-aarch64.exe" "tmpdir/auto-import-aarch64.o tmpdir/libauto-import-dll-aarch64.a"]
+	 && [regexp [file_contents "$srcdir/$subdir/auto-import-aarch64.d"] [run_host_cmd "$objdump" "-d tmpdir/auto-import-aarch64.exe"]] } {
+	pass $auto_import_aarch64_test
+    } else {
+	fail $auto_import_aarch64_test
+    }
 }
 
 run_dump_test "image_size"
@@ -185,10 +196,10 @@ if [check_shared_lib_support] {
     }
     run_dump_test "symbols-ordinals-hints-exports-ld"
     # no dlltool for these two
-    setup_xfail aarch64-*-* sh-*-*
+    setup_xfail sh-*-*
     run_dump_test "symbols-ordinals-hints-exports-dlltool"
     run_dump_test "symbols-ordinals-hints-imports-ld"
-    setup_xfail aarch64-*-* sh-*-*
+    setup_xfail sh-*-*
     run_dump_test "symbols-ordinals-hints-imports-dlltool"
     set ASFLAGS $old_ASFLAGS
 }