diff --git a/xen/Makefile b/xen/Makefile
index 85f9b763a4..9c689c2095 100644
--- a/xen/Makefile
+++ b/xen/Makefile
@@ -266,29 +266,31 @@ endif
 .PHONY: _build
 _build: $(TARGET)$(CONFIG_XEN_INSTALL_SUFFIX)
 
+define install_xen_links
+	$(INSTALL_DATA) $(TARGET)$1 $(D)$(BOOT_DIR)/$(T)-$(XEN_FULLVERSION)$1
+	ln -f -s $(T)-$(XEN_FULLVERSION)$1 $(D)$(BOOT_DIR)/$(T)-$(XEN_VERSION).$(XEN_SUBVERSION)$1
+	ln -f -s $(T)-$(XEN_FULLVERSION)$1 $(D)$(BOOT_DIR)/$(T)-$(XEN_VERSION)$1
+	ln -f -s $(T)-$(XEN_FULLVERSION)$1 $(D)$(BOOT_DIR)/$(T)$1
+endef
+
 .PHONY: _install
 _install: D=$(DESTDIR)
 _install: T=$(notdir $(TARGET))
 _install: Z=$(CONFIG_XEN_INSTALL_SUFFIX)
 _install: $(TARGET)$(CONFIG_XEN_INSTALL_SUFFIX)
 	[ -d $(D)$(BOOT_DIR) ] || $(INSTALL_DIR) $(D)$(BOOT_DIR)
-	$(INSTALL_DATA) $(TARGET)$(Z) $(D)$(BOOT_DIR)/$(T)-$(XEN_FULLVERSION)$(Z)
-	ln -f -s $(T)-$(XEN_FULLVERSION)$(Z) $(D)$(BOOT_DIR)/$(T)-$(XEN_VERSION).$(XEN_SUBVERSION)$(Z)
-	ln -f -s $(T)-$(XEN_FULLVERSION)$(Z) $(D)$(BOOT_DIR)/$(T)-$(XEN_VERSION)$(Z)
-	ln -f -s $(T)-$(XEN_FULLVERSION)$(Z) $(D)$(BOOT_DIR)/$(T)$(Z)
+	$(call install_xen_links,$(Z))
+	$(call install_xen_links,.mb.efi)
 	[ -d "$(D)$(DEBUG_DIR)" ] || $(INSTALL_DIR) $(D)$(DEBUG_DIR)
 	$(INSTALL_DATA) $(TARGET)-syms $(D)$(DEBUG_DIR)/$(T)-syms-$(XEN_FULLVERSION)
 	$(INSTALL_DATA) $(TARGET)-syms.map $(D)$(DEBUG_DIR)/$(T)-syms-$(XEN_FULLVERSION).map
 	$(INSTALL_DATA) $(KCONFIG_CONFIG) $(D)$(BOOT_DIR)/$(T)-$(XEN_FULLVERSION).config
 	if [ -r $(TARGET).efi -a -n '$(EFI_DIR)' ]; then \
 		[ -d $(D)$(EFI_DIR) ] || $(INSTALL_DIR) $(D)$(EFI_DIR); \
-		$(INSTALL_DATA) $(TARGET).efi $(D)$(EFI_DIR)/$(T)-$(XEN_FULLVERSION).efi; \
 		if [ -e $(TARGET).efi.map ]; then \
 			$(INSTALL_DATA) $(TARGET).efi.map $(D)$(DEBUG_DIR)/$(T)-$(XEN_FULLVERSION).efi.map; \
 		fi; \
-		ln -sf $(T)-$(XEN_FULLVERSION).efi $(D)$(EFI_DIR)/$(T)-$(XEN_VERSION).$(XEN_SUBVERSION).efi; \
-		ln -sf $(T)-$(XEN_FULLVERSION).efi $(D)$(EFI_DIR)/$(T)-$(XEN_VERSION).efi; \
-		ln -sf $(T)-$(XEN_FULLVERSION).efi $(D)$(EFI_DIR)/$(T).efi; \
+		$(call install_xen_links,.efi)) \
 		if [ -n '$(EFI_MOUNTPOINT)' -a -n '$(EFI_VENDOR)' ]; then \
 			$(INSTALL_DATA) $(TARGET).efi $(D)$(EFI_MOUNTPOINT)/efi/$(EFI_VENDOR)/$(T)-$(XEN_FULLVERSION).efi; \
 		elif [ "$(D)" = "$(patsubst $(shell cd $(XEN_ROOT) && pwd)/%,%,$(D))" ]; then \
@@ -341,7 +343,7 @@ _clean: delete-unfresh-files
 	$(MAKE) -f $(BASEDIR)/tools/kconfig/Makefile.kconfig ARCH=$(ARCH) SRCARCH=$(SRCARCH) clean
 	find . \( -name "*.o" -o -name ".*.d" -o -name ".*.d2" \
 		-o -name "*.gcno" -o -name ".*.cmd" \) -exec rm -f {} \;
-	rm -f include/asm $(TARGET) $(TARGET).gz $(TARGET).efi $(TARGET).efi.map $(TARGET)-syms $(TARGET)-syms.map *~ core
+	rm -f include/asm $(TARGET) $(TARGET).gz $(TARGET)*.efi $(TARGET).efi.map $(TARGET)-syms $(TARGET)-syms.map *~ core
 	rm -f include/asm-*/asm-offsets.h
 	rm -f .banner
 
diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
index 7769bb40d7..49c61adabf 100644
--- a/xen/arch/x86/Makefile
+++ b/xen/arch/x86/Makefile
@@ -110,7 +110,7 @@ syms-warn-dup-$(CONFIG_SUPPRESS_DUPLICATE_SYMBOL_WARNINGS) :=
 syms-warn-dup-$(CONFIG_ENFORCE_UNIQUE_SYMBOLS) := --error-dup
 
 $(TARGET): TMP = $(@D)/.$(@F).elf32
-$(TARGET): $(TARGET)-syms $(efi-y) boot/mkelf32
+$(TARGET): $(TARGET).mb.efi $(TARGET)-syms $(efi-y) boot/mkelf32
 	./boot/mkelf32 $(notes_phdrs) $(TARGET)-syms $(TMP) $(XEN_IMG_OFFSET) \
 	               `$(NM) $(TARGET)-syms | sed -ne 's/^\([^ ]*\) . __2M_rwdata_end$$/0x\1/p'`
 	od -t x4 -N 8192 $(TMP)  | grep 1badb002 > /dev/null || \
@@ -119,6 +119,11 @@ $(TARGET): $(TARGET)-syms $(efi-y) boot/mkelf32
 		{ echo "No Multiboot2 header found" >&2; false; }
 	mv $(TMP) $(TARGET)
 
+$(TARGET).mb.efi: $(TARGET)-syms
+	$(OBJCOPY) -O binary -S --change-section-address \
+		".efi.pe.header-`$(NM) $(TARGET)-syms | sed -ne 's/^\([^ ]*\) . __image_base__$$/0x\1/p'`" \
+		$(TARGET)-syms $(TARGET).mb.efi
+
 ifneq ($(efi-y),)
 # Check if the compiler supports the MS ABI.
 export XEN_BUILD_EFI := $(shell $(CC) $(XEN_CFLAGS) -c efi/check.c -o efi/check.o 2>/dev/null && echo y)
diff --git a/xen/arch/x86/arch.mk b/xen/arch/x86/arch.mk
index ce0c1a0e7f..073343d8ea 100644
--- a/xen/arch/x86/arch.mk
+++ b/xen/arch/x86/arch.mk
@@ -7,6 +7,8 @@ CFLAGS += -I$(BASEDIR)/include
 CFLAGS += -I$(BASEDIR)/include/asm-x86/mach-generic
 CFLAGS += -I$(BASEDIR)/include/asm-x86/mach-default
 CFLAGS += -DXEN_IMG_OFFSET=$(XEN_IMG_OFFSET)
+CFLAGS += -DXEN_LOAD_ALIGN=XEN_IMG_OFFSET
+CFLAGS += -DXEN_FILE_ALIGN=0x20
 
 # Prevent floating-point variables from creeping into Xen.
 CFLAGS += -msoft-float
diff --git a/xen/arch/x86/boot/Makefile b/xen/arch/x86/boot/Makefile
index 9b31bfcbfb..a559a75e0b 100644
--- a/xen/arch/x86/boot/Makefile
+++ b/xen/arch/x86/boot/Makefile
@@ -1,4 +1,5 @@
 obj-bin-y += head.o
+obj-bin-y += pecoff.o
 
 DEFS_H_DEPS = defs.h $(BASEDIR)/include/xen/stdbool.h
 
diff --git a/xen/arch/x86/boot/head.S b/xen/arch/x86/boot/head.S
index 150f7f90a2..2987b4f03a 100644
--- a/xen/arch/x86/boot/head.S
+++ b/xen/arch/x86/boot/head.S
@@ -1,3 +1,4 @@
+#include <xen/compile.h>
 #include <xen/multiboot.h>
 #include <xen/multiboot2.h>
 #include <public/xen.h>
diff --git a/xen/arch/x86/boot/pecoff.S b/xen/arch/x86/boot/pecoff.S
new file mode 100644
index 0000000000..fa821f42c1
--- /dev/null
+++ b/xen/arch/x86/boot/pecoff.S
@@ -0,0 +1,123 @@
+#include <xen/compile.h>
+#include <asm/page.h>
+
+#define sym_offs(sym)     ((sym) - __XEN_VIRT_START)
+
+        .section .efi.pe.header, "a", @progbits
+
+GLOBAL(efi_pe_head)
+        /*
+         * Legacy EXE header.
+         *
+         * Most of it is copied from binutils package, version 2.30,
+         * include/coff/pe.h:struct external_PEI_filehdr and
+         * bfd/peXXigen.c:_bfd_XXi_only_swap_filehdr_out().
+         *
+         * Page is equal 512 bytes here.
+         * Paragraph is equal 16 bytes here.
+         */
+        .short  0x5a4d                               /* EXE magic number. */
+        .short  0x90                                 /* Bytes on last page of file. */
+        .short  0x3                                  /* Pages in file. */
+        .short  0                                    /* Relocations. */
+        .short  0x4                                  /* Size of header in paragraphs. */
+        .short  0                                    /* Minimum extra paragraphs needed. */
+        .short  0xffff                               /* Maximum extra paragraphs needed. */
+        .short  0                                    /* Initial (relative) SS value. */
+        .short  0xb8                                 /* Initial SP value. */
+        .short  0                                    /* Checksum. */
+        .short  0                                    /* Initial IP value. */
+        .short  0                                    /* Initial (relative) CS value. */
+        .short  0x40                                 /* File address of relocation table. */
+        .short  0                                    /* Overlay number. */
+        .fill   4, 2, 0                              /* Reserved words. */
+        .short  0                                    /* OEM identifier. */
+        .short  0                                    /* OEM information. */
+        .fill   10, 2, 0                             /* Reserved words. */
+        .long   Lpe_header - efi_pe_head             /* File address of the PE header. */
+
+Lpe_header:
+        /*
+         * PE/COFF header.
+         *
+         * The PE/COFF format is defined by Microsoft, and is available from
+         * https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
+         *
+         * Some ideas are taken from Linux kernel and Xen ARM64.
+         */
+        .ascii  "PE\0\0"                             /* PE signature. */
+        .short  0x8664                               /* Machine: IMAGE_FILE_MACHINE_AMD64 */
+        .short  1                                    /* NumberOfSections. */
+        .long   XEN_COMPILE_POSIX_TIME               /* TimeDateStamp. */
+        .long   0                                    /* PointerToSymbolTable. */
+        .long   0                                    /* NumberOfSymbols. */
+        .short  Lsection_table - Loptional_header      /* SizeOfOptionalHeader. */
+        .short  0x226                                /* Characteristics:
+                                                      *   IMAGE_FILE_EXECUTABLE_IMAGE |
+                                                      *   IMAGE_FILE_LARGE_ADDRESS_AWARE |
+                                                      *   IMAGE_FILE_DEBUG_STRIPPED |
+                                                      *   IMAGE_FILE_LINE_NUMS_STRIPPED
+                                                      */
+
+Loptional_header:
+        .short  0x20b                                /* PE format: PE32+ */
+        .byte   0                                    /* MajorLinkerVersion. */
+        .byte   0                                    /* MinorLinkerVersion. */
+        .long   __2M_rwdata_end - efi_pe_head_end    /* SizeOfCode. */
+        .long   0                                    /* SizeOfInitializedData. */
+        .long   0                                    /* SizeOfUninitializedData. */
+        .long   sym_offs(efi_mb_start)               /* AddressOfEntryPoint. */
+        .long   sym_offs(start)                      /* BaseOfCode. */
+        .quad   sym_offs(__image_base__)             /* ImageBase. */
+        .long   XEN_LOAD_ALIGN                       /* SectionAlignment. */
+        .long   XEN_FILE_ALIGN                       /* FileAlignment. */
+        .short  2                                    /* MajorOperatingSystemVersion. */
+        .short  0                                    /* MinorOperatingSystemVersion. */
+        .short  XEN_VERSION                          /* MajorImageVersion. */
+        .short  XEN_SUBVERSION                       /* MinorImageVersion. */
+        .short  2                                    /* MajorSubsystemVersion. */
+        .short  0                                    /* MinorSubsystemVersion. */
+        .long   0                                    /* Win32VersionValue. */
+        .long   __pe_SizeOfImage                     /* SizeOfImage. */
+        .long   efi_pe_head_end - efi_pe_head        /* SizeOfHeaders. */
+        .long   0                                    /* CheckSum. */
+        .short  0xa                                  /* Subsystem: EFI application. */
+        .short  0                                    /* DllCharacteristics. */
+        .quad   0                                    /* SizeOfStackReserve. */
+        .quad   0                                    /* SizeOfStackCommit. */
+        .quad   0                                    /* SizeOfHeapReserve. */
+        .quad   0                                    /* SizeOfHeapCommit. */
+        .long   0                                    /* LoaderFlags. */
+        .long   0x6                                  /* NumberOfRvaAndSizes. */
+
+        /* Data Directories. */
+        .quad   0                                    /* Export Table. */
+        .quad   0                                    /* Import Table. */
+        .quad   0                                    /* Resource Table. */
+        .quad   0                                    /* Exception Table. */
+        .quad   0                                    /* Certificate Table. */
+        .quad   0                                    /* Base Relocation Table. */
+
+Lsection_table:
+        .ascii  ".text\0\0\0"                        /* Name. */
+        .long   __2M_rwdata_end - efi_pe_head_end    /* VirtualSize. */
+        .long   sym_offs(start)                      /* VirtualAddress. */
+        .long   __pe_text_raw_end - efi_pe_head_end  /* SizeOfRawData. */
+        .long   efi_pe_head_end - efi_pe_head        /* PointerToRawData. */
+        .long   0                                    /* PointerToRelocations. */
+        .long   0                                    /* PointerToLinenumbers. */
+        .short  0                                    /* NumberOfRelocations. */
+        .short  0                                    /* NumberOfLinenumbers. */
+        .long   0xe0500020                           /* Characteristics:
+                                                      *   IMAGE_SCN_CNT_CODE |
+                                                      *   IMAGE_SCN_ALIGN_16BYTES |
+                                                      *   IMAGE_SCN_MEM_EXECUTE |
+                                                      *   IMAGE_SCN_MEM_READ |
+                                                      *   IMAGE_SCN_MEM_WRITE
+                                                      */
+
+        .align XEN_FILE_ALIGN
+GLOBAL(efi_pe_head_end)
+
+        .text
+
diff --git a/xen/arch/x86/efi/efi-boot.h b/xen/arch/x86/efi/efi-boot.h
index 2541ba1f32..f694a069c9 100644
--- a/xen/arch/x86/efi/efi-boot.h
+++ b/xen/arch/x86/efi/efi-boot.h
@@ -32,7 +32,8 @@ static void __init edd_put_string(u8 *dst, size_t n, const char *src)
 }
 #define edd_put_string(d, s) edd_put_string(d, ARRAY_SIZE(d), s)
 
-extern const intpte_t __page_tables_start[], __page_tables_end[];
+extern intpte_t __page_tables_start[], __page_tables_end[];
+
 #define in_page_tables(v) ((intpte_t *)(v) >= __page_tables_start && \
                            (intpte_t *)(v) < __page_tables_end)
 
@@ -568,6 +569,7 @@ static void __init efi_arch_video_init(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
 
 static void __init efi_arch_memory_setup(void)
 {
+    intpte_t *pte;
     unsigned int i;
     EFI_STATUS status;
 
@@ -592,6 +594,13 @@ static void __init efi_arch_memory_setup(void)
     if ( !efi_enabled(EFI_LOADER) )
         return;
 
+    if ( efi_enabled(EFI_MB_LOADER) )
+        for ( pte = __page_tables_start; pte < __page_tables_end; pte += ARRAY_SIZE(l2_directmap) )
+            /* Skip relocating the directmap because start_xen() does this for us when
+             * when it updates all superpage-aligned mappings.  */
+            if ( (pte != (intpte_t *)l2_directmap) && (get_pte_flags(*pte) & _PAGE_PRESENT) )
+                *pte += xen_phys_start;
+
     /*
      * Map Xen into the higher mappings, using 2M superpages.
      *
@@ -724,7 +733,18 @@ static bool __init efi_arch_use_config_file(EFI_SYSTEM_TABLE *SystemTable)
 
 static void __init efi_arch_flush_dcache_area(const void *vaddr, UINTN size) { }
 
-void __init efi_multiboot2(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
+void EFIAPI efi_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable);
+
+void EFIAPI __init noreturn
+efi_mb_start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
+{
+    __set_bit(EFI_MB_LOADER, &efi_flags);
+    efi_start(ImageHandle, SystemTable);
+}
+
+void __init efi_multiboot2(EFI_HANDLE ImageHandle,
+                           EFI_SYSTEM_TABLE *SystemTable,
+                           multiboot2_tag_module_t *dom0_kernel)
 {
     EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
     UINTN cols, gop_mode = ~0, rows;
diff --git a/xen/arch/x86/efi/stub.c b/xen/arch/x86/efi/stub.c
index 9984932626..9bd6355ec3 100644
--- a/xen/arch/x86/efi/stub.c
+++ b/xen/arch/x86/efi/stub.c
@@ -15,9 +15,19 @@
  * Here we are in EFI stub. EFI calls are not supported due to lack
  * of relevant functionality in compiler and/or linker.
  *
- * efi_multiboot2() is an exception. Please look below for more details.
+ * efi_mb_start() and efi_multiboot2() are the exceptions.
+ * Please look below for more details.
  */
 
+asm (
+    "    .text                         \n"
+    "    .globl efi_mb_start           \n"
+    "efi_mb_start:                     \n"
+    "    mov    %rcx,%rdi              \n"
+    "    mov    %rdx,%rsi              \n"
+    "    call   efi_multiboot2         \n"
+    );
+
 void __init noreturn efi_multiboot2(EFI_HANDLE ImageHandle,
                                     EFI_SYSTEM_TABLE *SystemTable)
 {
diff --git a/xen/arch/x86/xen.lds.S b/xen/arch/x86/xen.lds.S
index 0273f79152..a58ae1e491 100644
--- a/xen/arch/x86/xen.lds.S
+++ b/xen/arch/x86/xen.lds.S
@@ -63,7 +63,22 @@ SECTIONS
 
   start_pa = ABSOLUTE(start - __XEN_VIRT_START);
 
+#ifdef EFI
   . = __XEN_VIRT_START + XEN_IMG_OFFSET;
+#else
+  /*
+   * Multiboot2 with an EFI PE/COFF header.
+   *
+   * The PE header must be followed by .text section which
+   * starts at __XEN_VIRT_START + XEN_IMG_OFFSET address.
+   */
+  . = __XEN_VIRT_START + XEN_IMG_OFFSET - efi_pe_head_end + efi_pe_head;
+
+  DECL_SECTION(.efi.pe.header) {
+       *(.efi.pe.header)
+  } :NONE
+#endif
+
   _start = .;
   DECL_SECTION(.text) {
         _stext = .;            /* Text and read-only data */
@@ -289,6 +304,13 @@ SECTIONS
        *(.data.rel)
        *(.data.rel.*)
        CONSTRUCTORS
+       /*
+        * A la the PE/COFF spec, the PE file data section must end at the
+        * alignment boundary equal to FileAlignment in the optional header,
+        * i.e., XEN_FILE_ALIGN.
+        */
+       . = ALIGN(XEN_FILE_ALIGN);
+       __pe_text_raw_end = .;
   } :text
 
   DECL_SECTION(.bss) {
@@ -312,6 +334,8 @@ SECTIONS
   . = ALIGN(SECTION_ALIGN);
   __2M_rwdata_end = .;
 
+  __pe_SizeOfImage = ALIGN(. - __image_base__, MB(16));
+
 #ifdef EFI
   . = ALIGN(4);
   DECL_SECTION(.reloc) {
@@ -341,6 +365,7 @@ SECTIONS
        *(.discard.*)
        *(.eh_frame)
 #ifdef EFI
+       *(.efi.pe.header)
        *(.comment)
        *(.comment.*)
        *(.note.Xen)
@@ -392,5 +417,14 @@ ASSERT((trampoline_end - trampoline_start) < TRAMPOLINE_SPACE - MBI_SPACE_MIN,
 ASSERT((wakeup_stack - wakeup_stack_start) >= WAKEUP_STACK_MIN,
     "wakeup stack too small")
 
+#ifndef EFI
+ASSERT(efi_pe_head_end == _start, "PE header does not end at the beginning of .text section")
+ASSERT(_start == __XEN_VIRT_START + XEN_IMG_OFFSET, ".text section begins at wrong address")
+ASSERT(IS_ALIGNED(_start,      XEN_FILE_ALIGN), "_start misaligned")
+ASSERT(IS_ALIGNED(__bss_start, XEN_FILE_ALIGN), "__bss_start misaligned")
+ASSERT(IS_ALIGNED(__pe_SizeOfImage, XEN_LOAD_ALIGN), "__pe_SizeOfImage is not multiple of XEN_LOAD_ALIGN")
+ASSERT(XEN_LOAD_ALIGN >= XEN_FILE_ALIGN, "XEN_LOAD_ALIGN < XEN_FILE_ALIGN")
+#endif
+
 /* Plenty of boot code assumes that Xen isn't larger than 16M. */
 ASSERT(_end - _start <= MB(16), "Xen too large for early-boot assumptions")
diff --git a/xen/include/xen/efi.h b/xen/include/xen/efi.h
index 94a7e547f9..f7f92b4e3d 100644
--- a/xen/include/xen/efi.h
+++ b/xen/include/xen/efi.h
@@ -11,6 +11,7 @@ extern unsigned int efi_flags;
 #define EFI_BOOT	0	/* Were we booted from EFI? */
 #define EFI_LOADER	1	/* Were we booted directly from EFI loader? */
 #define EFI_RS		2	/* Can we use runtime services? */
+#define EFI_MB_LOADER	4	/* xen.mb.efi booted directly from EFI loader? */
 
 /* Add fields here only if they need to be referenced from non-EFI code. */
 struct efi {
