diff --git a/Makefile b/Makefile
index dd3fcd1782e5..0aa97a2c3b48 100644
--- a/Makefile
+++ b/Makefile
@@ -1730,6 +1730,13 @@ else
 u-boot-keep-syms-lto :=
 endif
 
+ifeq ($(MSYS_VERSION),0)
+add_ld_script := -T u-boot.lds
+else
+add_ld_script := u-boot.lds
+$(warning msys)
+endif
+
 # Rule to link u-boot
 # May be overridden by arch/$(ARCH)/config.mk
 ifeq ($(LTO_ENABLE),y)
@@ -1738,7 +1745,7 @@ quiet_cmd_u-boot__ ?= LTO     $@
 		$(CC) -nostdlib -nostartfiles					\
 		$(LTO_FINAL_LDFLAGS) $(c_flags)					\
 		$(KBUILD_LDFLAGS:%=-Wl,%) $(LDFLAGS_u-boot:%=-Wl,%) -o $@	\
-		-T u-boot.lds $(u-boot-init)					\
+		$(add_ld_script) $(u-boot-init)					\
 		-Wl,--whole-archive						\
 			$(u-boot-main)						\
 			$(u-boot-keep-syms-lto)					\
@@ -1749,7 +1756,7 @@ quiet_cmd_u-boot__ ?= LTO     $@
 else
 quiet_cmd_u-boot__ ?= LD      $@
       cmd_u-boot__ ?= $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_u-boot) -o $@		\
-		-T u-boot.lds $(u-boot-init)					\
+		$(add_ld_script) $(u-boot-init)					\
 		--whole-archive							\
 			$(u-boot-main)						\
 		--no-whole-archive						\
diff --git a/arch/sandbox/config.mk b/arch/sandbox/config.mk
index 2d184c5f652a..e05daf57ef8e 100644
--- a/arch/sandbox/config.mk
+++ b/arch/sandbox/config.mk
@@ -71,3 +71,7 @@ EFI_CRT0 := crt0_sandbox_efi.o
 EFI_RELOC := reloc_sandbox_efi.o
 AFLAGS_crt0_sandbox_efi.o += -DHOST_ARCH="$(HOST_ARCH)"
 CFLAGS_reloc_sandbox_efi.o += -DHOST_ARCH="$(HOST_ARCH)"
+
+ifneq ($(MSYS_VERSION),0)
+LDSCRIPT = $(srctree)/arch/sandbox/cpu/u-boot-pe.lds
+endif
diff --git a/arch/sandbox/cpu/u-boot-pe.lds b/arch/sandbox/cpu/u-boot-pe.lds
new file mode 100644
index 000000000000..031e70fafd03
--- /dev/null
+++ b/arch/sandbox/cpu/u-boot-pe.lds
@@ -0,0 +1,447 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * U-Boot note: This was obtained by using the -verbose linker option. The
+ * U-Boot additions are marked below.
+ *
+ * Ideally we would add sections to the executable, as is done with the Linux
+ * build. But PE executables do not appear to work correctly if unexpected
+ * sections are present:
+ *
+ *   $ /tmp/b/sandbox/u-boot.exe
+ *   -bash: /tmp/b/sandbox/u-boot.exe: cannot execute binary file: Exec format error
+ *
+ * So we take a approach of rewriting the whole file, for now. This will likely
+ * break in the future when a toolchain change is made.
+ */
+
+/* Default linker script, for normal executables */
+/* Copyright (C) 2014-2023 Free Software Foundation, Inc.
+   Copying and distribution of this script, with or without modification,
+   are permitted in any medium without royalty provided the copyright
+   notice and this notice are preserved.  */
+
+OUTPUT_FORMAT(pei-x86-64)
+SEARCH_DIR("/usr/x86_64-pc-msys/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/lib/w32api");
+SECTIONS
+{
+  /* Make the virtual address and file offset synced if the alignment is
+     lower than the target page size. */
+  . = SIZEOF_HEADERS;
+  . = ALIGN(__section_alignment__);
+  .text  __image_base__ + ( __section_alignment__ < 0x1000 ? . : __section_alignment__ ) :
+  {
+    KEEP (*(SORT_NONE(.init)))
+    *(.text)
+    *(SORT(.text$*))
+     *(.text.*)
+     *(.gnu.linkonce.t.*)
+    *(.glue_7t)
+    *(.glue_7)
+    . = ALIGN(8);
+       /* Note: we always define __CTOR_LIST__ and ___CTOR_LIST__ here,
+          we do not PROVIDE them.  This is because the ctors.o startup
+	  code in libgcc defines them as common symbols, with the
+          expectation that they will be overridden by the definitions
+	  here.  If we PROVIDE the symbols then they will not be
+	  overridden and global constructors will not be run.
+	  See PR 22762 for more details.
+
+	  This does mean that it is not possible for a user to define
+	  their own __CTOR_LIST__ and __DTOR_LIST__ symbols; if they do,
+	  the content from those variables are included but the symbols
+	  defined here silently take precedence.  If they truly need to
+	  be redefined, a custom linker script will have to be used.
+	  (The custom script can just be a copy of this script with the
+	  PROVIDE() qualifiers added).
+	  In particular this means that ld -Ur does not work, because
+	  the proper __CTOR_LIST__ set by ld -Ur is overridden by a
+	  bogus __CTOR_LIST__ set by the final link.  See PR 46.  */
+       ___CTOR_LIST__ = .;
+       __CTOR_LIST__ = .;
+       LONG (-1); LONG (-1);
+       KEEP (*(.ctors));
+       KEEP (*(.ctor));
+       KEEP (*(SORT_BY_NAME(.ctors.*)));
+       LONG (0); LONG (0);
+       /* See comment about __CTOR_LIST__ above.  The same reasoning
+    	  applies here too.  */
+       ___DTOR_LIST__ = .;
+       __DTOR_LIST__ = .;
+       LONG (-1); LONG (-1);
+       KEEP (*(.dtors));
+       KEEP (*(.dtor));
+       KEEP (*(SORT_BY_NAME(.dtors.*)));
+       LONG (0); LONG (0);
+    KEEP (*(SORT_NONE(.fini)))
+    /* ??? Why is .gcc_exc here?  */
+     *(.gcc_exc)
+    PROVIDE (etext = .);
+     KEEP (*(.gcc_except_table))
+  }
+  /* The Cygwin32 library uses a section to avoid copying certain data
+     on fork.  This used to be named ".data".  The linker used
+     to include this between __data_start__ and __data_end__, but that
+     breaks building the cygwin32 dll.  Instead, we name the section
+     ".data_cygwin_nocopy" and explicitly include it after __data_end__. */
+  .data BLOCK(__section_alignment__) :
+  {
+    __data_start__ = . ;
+    *(.data)
+    *(.data2)
+    *(SORT(.data$*))
+    KEEP(*(.jcr))
+    __data_end__ = . ;
+    *(.data_cygwin_nocopy)
+  }
+  .rdata BLOCK(__section_alignment__) :
+  {
+    *(.rdata)
+	     *(SORT(.rdata$*))
+    . = ALIGN(4);
+    __rt_psrelocs_start = .;
+    KEEP(*(.rdata_runtime_pseudo_reloc))
+    __rt_psrelocs_end = .;
+
+	/* U-Boot additions from here on */
+	. = ALIGN(4);
+	KEEP(*(SORT(__u_boot_list*)));
+
+	*(_u_boot_sandbox_getopt_start)
+	*(_u_boot_sandbox_getopt)
+	*(_u_boot_sandbox_getopt_end)
+
+	*(___efi_runtime_start)
+	*(efi_runtime_text)
+	*(efi_runtime_data)
+	*(___efi_runtime_stop)
+
+	*(___efi_runtime_rel_start)
+	*(.relefi_runtime_text)
+	*(.relefi_runtime_data)
+	*(___efi_runtime_rel_stop)
+
+	. = ALIGN(4);
+	*(.rodata.ttf.init)
+	*(.rodata.splash.init)
+	*(.rodata.helloworld.init)
+	*(.dtb.init.rodata)
+
+	/* U-Boot additions end */
+   }
+  __rt_psrelocs_size = __rt_psrelocs_end - __rt_psrelocs_start;
+  ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
+  __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
+  ___RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size;
+  __RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size;
+  .eh_frame BLOCK(__section_alignment__) :
+  {
+    KEEP (*(.eh_frame*))
+  }
+  .pdata BLOCK(__section_alignment__) :
+  {
+    KEEP(*(.pdata*))
+  }
+  .xdata BLOCK(__section_alignment__) :
+  {
+    KEEP(*(.xdata*))
+  }
+  .bss BLOCK(__section_alignment__) :
+  {
+    __bss_start__ = . ;
+    *(.bss)
+    *(COMMON)
+    __bss_end__ = . ;
+  }
+  .edata BLOCK(__section_alignment__) :
+  {
+    *(.edata)
+  }
+  /DISCARD/ :
+  {
+    *(.debug$S)
+    *(.debug$T)
+    *(.debug$F)
+     *(.drectve)
+     *(.note.GNU-stack)
+     *(.gnu.lto_*)
+  }
+  .idata BLOCK(__section_alignment__) :
+  {
+    /* This cannot currently be handled with grouped sections.
+	See pep.em:sort_sections.  */
+    KEEP (SORT(*)(.idata$2))
+    KEEP (SORT(*)(.idata$3))
+    /* These zeroes mark the end of the import list.  */
+    LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
+    . = ALIGN(8);
+    KEEP (SORT(*)(.idata$4))
+    __IAT_start__ = .;
+    SORT(*)(.idata$5)
+    __IAT_end__ = .;
+    KEEP (SORT(*)(.idata$6))
+    KEEP (SORT(*)(.idata$7))
+  }
+  .CRT BLOCK(__section_alignment__) :
+  {
+    ___crt_xc_start__ = . ;
+    KEEP (*(SORT(.CRT$XC*)))  /* C initialization */
+    ___crt_xc_end__ = . ;
+    ___crt_xi_start__ = . ;
+    KEEP (*(SORT(.CRT$XI*)))  /* C++ initialization */
+    ___crt_xi_end__ = . ;
+    ___crt_xl_start__ = . ;
+    KEEP (*(SORT(.CRT$XL*)))  /* TLS callbacks */
+    /* ___crt_xl_end__ is defined in the TLS Directory support code */
+    ___crt_xp_start__ = . ;
+    KEEP (*(SORT(.CRT$XP*)))  /* Pre-termination */
+    ___crt_xp_end__ = . ;
+    ___crt_xt_start__ = . ;
+    KEEP (*(SORT(.CRT$XT*)))  /* Termination */
+    ___crt_xt_end__ = . ;
+  }
+  /* Windows TLS expects .tls$AAA to be at the start and .tls$ZZZ to be
+     at the end of the .tls section.  This is important because _tls_start MUST
+     be at the beginning of the section to enable SECREL32 relocations with TLS
+     data.  */
+  .tls BLOCK(__section_alignment__) :
+  {
+    ___tls_start__ = . ;
+    KEEP (*(.tls$AAA))
+    KEEP (*(.tls))
+    KEEP (*(.tls$))
+    KEEP (*(SORT(.tls$*)))
+    KEEP (*(.tls$ZZZ))
+    ___tls_end__ = . ;
+  }
+  .endjunk BLOCK(__section_alignment__) :
+  {
+    /* end is deprecated, don't use it */
+    PROVIDE (end = .);
+    PROVIDE ( _end = .);
+     __end__ = .;
+  }
+  .rsrc BLOCK(__section_alignment__) : SUBALIGN(4)
+  {
+    KEEP (*(.rsrc))
+    KEEP (*(.rsrc$*))
+  }
+  .reloc BLOCK(__section_alignment__) :
+  {
+    *(.reloc)
+  }
+  .stab BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.stab)
+  }
+  .stabstr BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.stabstr)
+  }
+  /* DWARF debug sections.
+     Symbols in the DWARF debugging sections are relative to the beginning
+     of the section.  Unlike other targets that fake this by putting the
+     section VMA at 0, the PE format will not allow it.  */
+  /* DWARF 1.1 and DWARF 2.  */
+  .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_aranges)
+  }
+  .zdebug_aranges BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_aranges)
+  }
+  .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_pubnames)
+  }
+  .zdebug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_pubnames)
+  }
+  /* DWARF 2.  */
+  .debug_info BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_info .gnu.linkonce.wi.*)
+  }
+  .zdebug_info BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_info .zdebug.gnu.linkonce.wi.*)
+  }
+  .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_abbrev)
+  }
+  .zdebug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_abbrev)
+  }
+  .debug_line BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_line)
+  }
+  .zdebug_line BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_line)
+  }
+  .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_frame*)
+  }
+  .zdebug_frame BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_frame*)
+  }
+  .debug_str BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_str)
+  }
+  .zdebug_str BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_str)
+  }
+  .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_loc)
+  }
+  .zdebug_loc BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_loc)
+  }
+  .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_macinfo)
+  }
+  .zdebug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_macinfo)
+  }
+  /* SGI/MIPS DWARF 2 extensions.  */
+  .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_weaknames)
+  }
+  .zdebug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_weaknames)
+  }
+  .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_funcnames)
+  }
+  .zdebug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_funcnames)
+  }
+  .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_typenames)
+  }
+  .zdebug_typenames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_typenames)
+  }
+  .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_varnames)
+  }
+  .zdebug_varnames BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_varnames)
+  }
+  /* DWARF 3.  */
+  .debug_pubtypes BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_pubtypes)
+  }
+  .zdebug_pubtypes BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_pubtypes)
+  }
+  .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_ranges)
+  }
+  .zdebug_ranges BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_ranges)
+  }
+  /* DWARF 4.  */
+  .debug_types BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_types .gnu.linkonce.wt.*)
+  }
+  .zdebug_types BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_types .gnu.linkonce.wt.*)
+  }
+  /* DWARF 5.  */
+  .debug_addr BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_addr)
+  }
+  .zdebug_addr BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_addr)
+  }
+  .debug_line_str BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_line_str)
+  }
+  .zdebug_line_str BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_line_str)
+  }
+  .debug_loclists BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_loclists)
+  }
+  .zdebug_loclists BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_loclists)
+  }
+  .debug_macro BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_macro)
+  }
+  .zdebug_macro BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_macro)
+  }
+  .debug_names BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_names)
+  }
+  .zdebug_names BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_names)
+  }
+  .debug_rnglists BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_rnglists)
+  }
+  .zdebug_rnglists BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_rnglists)
+  }
+  .debug_str_offsets BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_str_offsets)
+  }
+  .zdebug_str_offsets BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_str_offsets)
+  }
+  .debug_sup BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_sup)
+  }
+  /* For Go and Rust.  */
+  .debug_gdb_scripts BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.debug_gdb_scripts)
+  }
+  .zdebug_gdb_scripts BLOCK(__section_alignment__) (NOLOAD) :
+  {
+    *(.zdebug_gdb_scripts)
+  }
+}
