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

Message ID 20260731144655.88354-2-evgeny.karpov@arm.com
State New
Headers
Series aarch64: mingw: Auto-import implementation |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Evgeny Karpov July 31, 2026, 2:46 p.m. UTC
  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 <evgeny@kmaps.co>

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
  

Comments

Martin Storsjö July 31, 2026, 8:48 p.m. UTC | #1
On Fri, 31 Jul 2026, Evgeny Karpov wrote:

> 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

I think the term "jump table" usually refers to something entirely 
different? What you have here normally is just called a thunk.

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

These two instructions can be just one single "ldr x16, [x16, 
:lo12:__imp_init_dyn_v]"

>  br	x16

This seems reasonable - but the wording makes it sound odd. This 
instruction sequence (with the add+ldr merged) is a totally normal import 
thunk. For the normal (non-auto-import) case, the import library contains 
such thunks already - and for this case, if you happen to have a b/bl 
instruction referencing symbol <foo>, when <foo> is missing but 
__imp_<foo> exists, then generating this seems reasonable.

FWIW, we don't handle this case in LLD, and haven't really encountered a 
need for this case so far. If referencing something that really is a 
function, then the import library really should expose that as a function 
type (with the import thunk in the import library).

That said, doing this seems reasonable if you happen to have cases where 
it's needed.

> "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

Thanks for the clear examples of what this does!

I think this may work - although on first look, it feels like it's adding 
a fair amount of overhead.

On the other hand - the overhead it adds is only paid for the cases where 
the autoimported data symbol actually is referenced; code that doesn't 
autoimport any data symbols won't have any extra overhead at all.


For reference - what we did in LLVM for this was to mirror what already 
was being done for x86_64: On x86_64, GCC (and also LLVM) generates 
indirection through a .refptr stub - which essentially works like a GOT 
entry. Whenever the compiler references a symbol which it can't be sure is 
in the same module, instead of doing

     adrp x0, symbol+offset
     add x0, x0, :lo12:symbol+offset

it generates

     adrp x0 .refptr.symbol
     ldr x0, [x0, :lo12:.refptr.symbol]

     .section .rdata$.refptr.symbol,"dr",discard,.refptr.symbol
     .globl .refptr.symbol
.refptr.symbol:
     .xword symbol

(For cases where the intended code was adrp+ldr to begin with, it becomes 
one extra ldr.)

The generated code is mostly efficient, although there can be one extra 
ldr per access, and you get the binary size overhead of all the extra 
.refptr elements for all the symbols that weren't autoimported.

Given the preexisting case of GCC doing this for x86_64, I would have 
expected that the least surprising way of dealing with it would be to do 
the same for aarch64.

But anyway, I see the pros and cons of your approach, as it adds zero 
overhead for the non-autoimported cases.

I can't vouch for it, but I think the design may be ok. (I haven't 
reviewed the code so I can't speak for the actual implementation.)

// Martin
  

Patch

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
 }