Commit f16408dfb0eaef2b900caf731cab7e0b99623dd0

Authored by Alexander Graf
Committed by Anthony Liguori
1 parent bf483392

Multiboot support v5

This patch implements support for Multiboot on x86 for -kernel.
Multiboot is a "new" approach to get rid of different bootloaders, providing
a unified interface for the kernel. It supports command line options and
kernel modules.

The two probably best known projects using multiboot are Xen and GNU Hurd.

This implementation should be mostly feature-complete. It is missing VBE
extensions, but as no system uses them currently it does not really hurt.

To use multiboot, specify the kernel as -kernel option. Modules should be given
as -initrd options, seperated by a comma (,). -append also works.

Please bear in mind that grub also does gzip decompression, which qemu does
not do yet. To run existing images, please ungzip them first.

The guest multiboot loader code is implemented as option rom using int 19.
Parts of the work are based on efforts by Rene Rebe, who originally ported
my code to int 19.

Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
... ... @@ -40,6 +40,9 @@
40 40 /* output Bochs bios info messages */
41 41 //#define DEBUG_BIOS
42 42  
  43 +/* Show multiboot debug output */
  44 +//#define DEBUG_MULTIBOOT
  45 +
43 46 #define BIOS_FILENAME "bios.bin"
44 47 #define VGABIOS_FILENAME "vgabios.bin"
45 48 #define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin"
... ... @@ -596,7 +599,218 @@ static long get_file_size(FILE *f)
596 599 return size;
597 600 }
598 601  
599   -static void load_linux(target_phys_addr_t option_rom,
  602 +#define MULTIBOOT_STRUCT_ADDR 0x9000
  603 +
  604 +#if MULTIBOOT_STRUCT_ADDR > 0xf0000
  605 +#error multiboot struct needs to fit in 16 bit real mode
  606 +#endif
  607 +
  608 +static int load_multiboot(void *fw_cfg,
  609 + FILE *f,
  610 + const char *kernel_filename,
  611 + const char *initrd_filename,
  612 + const char *kernel_cmdline,
  613 + uint8_t *header)
  614 +{
  615 + int i, t, is_multiboot = 0;
  616 + uint32_t flags = 0;
  617 + uint32_t mh_entry_addr;
  618 + uint32_t mh_load_addr;
  619 + uint32_t mb_kernel_size;
  620 + uint32_t mmap_addr = MULTIBOOT_STRUCT_ADDR;
  621 + uint32_t mb_bootinfo = MULTIBOOT_STRUCT_ADDR + 0x500;
  622 + uint32_t mb_cmdline = mb_bootinfo + 0x200;
  623 + uint32_t mb_mod_end;
  624 +
  625 + /* Ok, let's see if it is a multiboot image.
  626 + The header is 12x32bit long, so the latest entry may be 8192 - 48. */
  627 + for (i = 0; i < (8192 - 48); i += 4) {
  628 + if (ldl_p(header+i) == 0x1BADB002) {
  629 + uint32_t checksum = ldl_p(header+i+8);
  630 + flags = ldl_p(header+i+4);
  631 + checksum += flags;
  632 + checksum += (uint32_t)0x1BADB002;
  633 + if (!checksum) {
  634 + is_multiboot = 1;
  635 + break;
  636 + }
  637 + }
  638 + }
  639 +
  640 + if (!is_multiboot)
  641 + return 0; /* no multiboot */
  642 +
  643 +#ifdef DEBUG_MULTIBOOT
  644 + fprintf(stderr, "qemu: I believe we found a multiboot image!\n");
  645 +#endif
  646 +
  647 + if (flags & 0x00000004) { /* MULTIBOOT_HEADER_HAS_VBE */
  648 + fprintf(stderr, "qemu: multiboot knows VBE. we don't.\n");
  649 + }
  650 + if (!(flags & 0x00010000)) { /* MULTIBOOT_HEADER_HAS_ADDR */
  651 + uint64_t elf_entry;
  652 + int kernel_size;
  653 + fclose(f);
  654 + kernel_size = load_elf(kernel_filename, 0, &elf_entry, NULL, NULL);
  655 + if (kernel_size < 0) {
  656 + fprintf(stderr, "Error while loading elf kernel\n");
  657 + exit(1);
  658 + }
  659 + mh_load_addr = mh_entry_addr = elf_entry;
  660 + mb_kernel_size = kernel_size;
  661 +
  662 +#ifdef DEBUG_MULTIBOOT
  663 + fprintf(stderr, "qemu: loading multiboot-elf kernel (%#x bytes) with entry %#zx\n",
  664 + mb_kernel_size, (size_t)mh_entry_addr);
  665 +#endif
  666 + } else {
  667 + /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_ADDR. */
  668 + uint32_t mh_header_addr = ldl_p(header+i+12);
  669 + mh_load_addr = ldl_p(header+i+16);
  670 +#ifdef DEBUG_MULTIBOOT
  671 + uint32_t mh_load_end_addr = ldl_p(header+i+20);
  672 + uint32_t mh_bss_end_addr = ldl_p(header+i+24);
  673 +#endif
  674 + uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr);
  675 +
  676 + mh_entry_addr = ldl_p(header+i+28);
  677 + mb_kernel_size = get_file_size(f) - mb_kernel_text_offset;
  678 +
  679 + /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_VBE.
  680 + uint32_t mh_mode_type = ldl_p(header+i+32);
  681 + uint32_t mh_width = ldl_p(header+i+36);
  682 + uint32_t mh_height = ldl_p(header+i+40);
  683 + uint32_t mh_depth = ldl_p(header+i+44); */
  684 +
  685 +#ifdef DEBUG_MULTIBOOT
  686 + fprintf(stderr, "multiboot: mh_header_addr = %#x\n", mh_header_addr);
  687 + fprintf(stderr, "multiboot: mh_load_addr = %#x\n", mh_load_addr);
  688 + fprintf(stderr, "multiboot: mh_load_end_addr = %#x\n", mh_load_end_addr);
  689 + fprintf(stderr, "multiboot: mh_bss_end_addr = %#x\n", mh_bss_end_addr);
  690 +#endif
  691 +
  692 + fseek(f, mb_kernel_text_offset, SEEK_SET);
  693 +
  694 +#ifdef DEBUG_MULTIBOOT
  695 + fprintf(stderr, "qemu: loading multiboot kernel (%#x bytes) at %#x\n",
  696 + mb_kernel_size, mh_load_addr);
  697 +#endif
  698 +
  699 + if (!fread_targphys_ok(mh_load_addr, mb_kernel_size, f)) {
  700 + fprintf(stderr, "qemu: read error on multiboot kernel '%s' (%#x)\n",
  701 + kernel_filename, mb_kernel_size);
  702 + exit(1);
  703 + }
  704 + fclose(f);
  705 + }
  706 +
  707 + /* blob size is only the kernel for now */
  708 + mb_mod_end = mh_load_addr + mb_kernel_size;
  709 +
  710 + /* load modules */
  711 + stl_phys(mb_bootinfo + 20, 0x0); /* mods_count */
  712 + if (initrd_filename) {
  713 + uint32_t mb_mod_info = mb_bootinfo + 0x100;
  714 + uint32_t mb_mod_cmdline = mb_bootinfo + 0x300;
  715 + uint32_t mb_mod_start = mh_load_addr;
  716 + uint32_t mb_mod_length = mb_kernel_size;
  717 + char *next_initrd;
  718 + char *next_space;
  719 + int mb_mod_count = 0;
  720 +
  721 + do {
  722 + next_initrd = strchr(initrd_filename, ',');
  723 + if (next_initrd)
  724 + *next_initrd = '\0';
  725 + /* if a space comes after the module filename, treat everything
  726 + after that as parameters */
  727 + cpu_physical_memory_write(mb_mod_cmdline, (uint8_t*)initrd_filename,
  728 + strlen(initrd_filename) + 1);
  729 + stl_phys(mb_mod_info + 8, mb_mod_cmdline); /* string */
  730 + mb_mod_cmdline += strlen(initrd_filename) + 1;
  731 + if ((next_space = strchr(initrd_filename, ' ')))
  732 + *next_space = '\0';
  733 +#ifdef DEBUG_MULTIBOOT
  734 + printf("multiboot loading module: %s\n", initrd_filename);
  735 +#endif
  736 + f = fopen(initrd_filename, "rb");
  737 + if (f) {
  738 + mb_mod_start = (mb_mod_start + mb_mod_length + (TARGET_PAGE_SIZE - 1))
  739 + & (TARGET_PAGE_MASK);
  740 + mb_mod_length = get_file_size(f);
  741 + mb_mod_end = mb_mod_start + mb_mod_length;
  742 +
  743 + if (!fread_targphys_ok(mb_mod_start, mb_mod_length, f)) {
  744 + fprintf(stderr, "qemu: read error on multiboot module '%s' (%#x)\n",
  745 + initrd_filename, mb_mod_length);
  746 + exit(1);
  747 + }
  748 +
  749 + mb_mod_count++;
  750 + stl_phys(mb_mod_info + 0, mb_mod_start);
  751 + stl_phys(mb_mod_info + 4, mb_mod_start + mb_mod_length);
  752 +#ifdef DEBUG_MULTIBOOT
  753 + printf("mod_start: %#x\nmod_end: %#x\n", mb_mod_start,
  754 + mb_mod_start + mb_mod_length);
  755 +#endif
  756 + stl_phys(mb_mod_info + 12, 0x0); /* reserved */
  757 + }
  758 + initrd_filename = next_initrd+1;
  759 + mb_mod_info += 16;
  760 + } while (next_initrd);
  761 + stl_phys(mb_bootinfo + 20, mb_mod_count); /* mods_count */
  762 + stl_phys(mb_bootinfo + 24, mb_bootinfo + 0x100); /* mods_addr */
  763 + }
  764 +
  765 + /* Make sure we're getting kernel + modules back after reset */
  766 + option_rom_setup_reset(mh_load_addr, mb_mod_end - mh_load_addr);
  767 +
  768 + /* Commandline support */
  769 + stl_phys(mb_bootinfo + 16, mb_cmdline);
  770 + t = strlen(kernel_filename);
  771 + cpu_physical_memory_write(mb_cmdline, (uint8_t*)kernel_filename, t);
  772 + mb_cmdline += t;
  773 + stb_phys(mb_cmdline++, ' ');
  774 + t = strlen(kernel_cmdline) + 1;
  775 + cpu_physical_memory_write(mb_cmdline, (uint8_t*)kernel_cmdline, t);
  776 +
  777 + /* the kernel is where we want it to be now */
  778 +
  779 +#define MULTIBOOT_FLAGS_MEMORY (1 << 0)
  780 +#define MULTIBOOT_FLAGS_BOOT_DEVICE (1 << 1)
  781 +#define MULTIBOOT_FLAGS_CMDLINE (1 << 2)
  782 +#define MULTIBOOT_FLAGS_MODULES (1 << 3)
  783 +#define MULTIBOOT_FLAGS_MMAP (1 << 6)
  784 + stl_phys(mb_bootinfo, MULTIBOOT_FLAGS_MEMORY
  785 + | MULTIBOOT_FLAGS_BOOT_DEVICE
  786 + | MULTIBOOT_FLAGS_CMDLINE
  787 + | MULTIBOOT_FLAGS_MODULES
  788 + | MULTIBOOT_FLAGS_MMAP);
  789 + stl_phys(mb_bootinfo + 4, 640); /* mem_lower */
  790 + stl_phys(mb_bootinfo + 8, ram_size / 1024); /* mem_upper */
  791 + stl_phys(mb_bootinfo + 12, 0x8001ffff); /* XXX: use the -boot switch? */
  792 + stl_phys(mb_bootinfo + 48, mmap_addr); /* mmap_addr */
  793 +
  794 +#ifdef DEBUG_MULTIBOOT
  795 + fprintf(stderr, "multiboot: mh_entry_addr = %#x\n", mh_entry_addr);
  796 +#endif
  797 +
  798 + /* Pass variables to option rom */
  799 + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, mh_entry_addr);
  800 + fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, mb_bootinfo);
  801 + fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, mmap_addr);
  802 +
  803 + /* Make sure we're getting the config space back after reset */
  804 + option_rom_setup_reset(mb_bootinfo, 0x500);
  805 +
  806 + option_rom[nb_option_roms] = "multiboot.bin";
  807 + nb_option_roms++;
  808 +
  809 + return 1; /* yes, we are multiboot */
  810 +}
  811 +
  812 +static void load_linux(void *fw_cfg,
  813 + target_phys_addr_t option_rom,
600 814 const char *kernel_filename,
601 815 const char *initrd_filename,
602 816 const char *kernel_cmdline,
... ... @@ -608,7 +822,7 @@ static void load_linux(target_phys_addr_t option_rom,
608 822 uint16_t real_seg;
609 823 int setup_size, kernel_size, initrd_size = 0, cmdline_size;
610 824 uint32_t initrd_max;
611   - uint8_t header[1024];
  825 + uint8_t header[8192];
612 826 target_phys_addr_t real_addr, prot_addr, cmdline_addr, initrd_addr = 0;
613 827 FILE *f, *fi;
614 828  
... ... @@ -618,7 +832,8 @@ static void load_linux(target_phys_addr_t option_rom,
618 832 /* load the kernel header */
619 833 f = fopen(kernel_filename, "rb");
620 834 if (!f || !(kernel_size = get_file_size(f)) ||
621   - fread(header, 1, 1024, f) != 1024) {
  835 + fread(header, 1, MIN(ARRAY_SIZE(header), kernel_size), f) !=
  836 + MIN(ARRAY_SIZE(header), kernel_size)) {
622 837 fprintf(stderr, "qemu: could not load kernel '%s'\n",
623 838 kernel_filename);
624 839 exit(1);
... ... @@ -630,8 +845,14 @@ static void load_linux(target_phys_addr_t option_rom,
630 845 #endif
631 846 if (ldl_p(header+0x202) == 0x53726448)
632 847 protocol = lduw_p(header+0x206);
633   - else
  848 + else {
  849 + /* This looks like a multiboot kernel. If it is, let's stop
  850 + treating it like a Linux kernel. */
  851 + if (load_multiboot(fw_cfg, f, kernel_filename,
  852 + initrd_filename, kernel_cmdline, header))
  853 + return;
634 854 protocol = 0;
  855 + }
635 856  
636 857 if (protocol < 0x200 || !(header[0x211] & 0x01)) {
637 858 /* Low kernel */
... ... @@ -721,16 +942,25 @@ static void load_linux(target_phys_addr_t option_rom,
721 942 }
722 943  
723 944 /* store the finalized header and load the rest of the kernel */
724   - cpu_physical_memory_write(real_addr, header, 1024);
  945 + cpu_physical_memory_write(real_addr, header, ARRAY_SIZE(header));
725 946  
726 947 setup_size = header[0x1f1];
727 948 if (setup_size == 0)
728 949 setup_size = 4;
729 950  
730 951 setup_size = (setup_size+1)*512;
731   - kernel_size -= setup_size; /* Size of protected-mode code */
  952 + /* Size of protected-mode code */
  953 + kernel_size -= (setup_size > ARRAY_SIZE(header)) ? setup_size : ARRAY_SIZE(header);
  954 +
  955 + /* In case we have read too much already, copy that over */
  956 + if (setup_size < ARRAY_SIZE(header)) {
  957 + cpu_physical_memory_write(prot_addr, header + setup_size, ARRAY_SIZE(header) - setup_size);
  958 + prot_addr += (ARRAY_SIZE(header) - setup_size);
  959 + setup_size = ARRAY_SIZE(header);
  960 + }
732 961  
733   - if (!fread_targphys_ok(real_addr+1024, setup_size-1024, f) ||
  962 + if (!fread_targphys_ok(real_addr + ARRAY_SIZE(header),
  963 + setup_size - ARRAY_SIZE(header), f) ||
734 964 !fread_targphys_ok(prot_addr, kernel_size, f)) {
735 965 fprintf(stderr, "qemu: read error on kernel '%s'\n",
736 966 kernel_filename);
... ... @@ -978,7 +1208,7 @@ static void pc_init1(ram_addr_t ram_size,
978 1208 fw_cfg = bochs_bios_init();
979 1209  
980 1210 if (linux_boot) {
981   - load_linux(0xc0000 + oprom_area_size,
  1211 + load_linux(fw_cfg, 0xc0000 + oprom_area_size,
982 1212 kernel_filename, initrd_filename, kernel_cmdline, below_4g_mem_size);
983 1213 oprom_area_size += 2048;
984 1214 }
... ...
pc-bios/optionrom/multiboot.S 0 โ†’ 100644
  1 +/*
  2 + * Multiboot Option ROM
  3 + *
  4 + * This program is free software; you can redistribute it and/or modify
  5 + * it under the terms of the GNU General Public License as published by
  6 + * the Free Software Foundation; either version 2 of the License, or
  7 + * (at your option) any later version.
  8 + *
  9 + * This program is distributed in the hope that it will be useful,
  10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 + * GNU General Public License for more details.
  13 + *
  14 + * You should have received a copy of the GNU General Public License
  15 + * along with this program; if not, write to the Free Software
  16 + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17 + *
  18 + * Copyright Novell Inc, 2009
  19 + * Authors: Alexander Graf <agraf@suse.de>
  20 + */
  21 +
  22 +#define NO_QEMU_PROTOS
  23 +#include "../../hw/fw_cfg.h"
  24 +
  25 +#define BIOS_CFG_IOPORT_CFG 0x510
  26 +#define BIOS_CFG_IOPORT_DATA 0x511
  27 +
  28 +#define MULTIBOOT_MAGIC 0x2badb002
  29 +
  30 +/* Read a variable from the fw_cfg device.
  31 + Clobbers: %edx
  32 + Out: %eax */
  33 +.macro read_fw VAR
  34 + mov $\VAR, %ax
  35 + mov $BIOS_CFG_IOPORT_CFG, %dx
  36 + outw %ax, (%dx)
  37 + mov $BIOS_CFG_IOPORT_DATA, %dx
  38 + inb (%dx), %al
  39 + shl $8, %eax
  40 + inb (%dx), %al
  41 + shl $8, %eax
  42 + inb (%dx), %al
  43 + shl $8, %eax
  44 + inb (%dx), %al
  45 + bswap %eax
  46 +.endm
  47 +
  48 +.code16
  49 +.text
  50 + .global _start
  51 +_start:
  52 + .short 0xaa55
  53 + .byte (_end - _start) / 512
  54 + push %eax
  55 + push %ds
  56 +
  57 + /* setup ds so we can access the IVT */
  58 + xor %ax, %ax
  59 + mov %ax, %ds
  60 +
  61 + /* save old int 19 */
  62 + mov (0x19*4), %eax
  63 + mov %eax, %cs:old_int19
  64 +
  65 + /* install our int 19 handler */
  66 + movw $int19_handler, (0x19*4)
  67 + mov %cs, (0x19*4+2)
  68 +
  69 + pop %ds
  70 + pop %eax
  71 + lret
  72 +
  73 +int19_handler:
  74 + /* DS = CS */
  75 + movw %cs, %ax
  76 + movw %ax, %ds
  77 +
  78 + /* fall through */
  79 +
  80 +run_multiboot:
  81 +
  82 + cli
  83 + cld
  84 +
  85 + mov %cs, %eax
  86 + shl $0x4, %eax
  87 +
  88 + /* fix the gdt descriptor to be PC relative */
  89 + mov (gdt_desc+2), %ebx
  90 + add %eax, %ebx
  91 + mov %ebx, (gdt_desc+2)
  92 +
  93 + /* fix the prot mode indirect jump to be PC relative */
  94 + mov (prot_jump), %ebx
  95 + add %eax, %ebx
  96 + mov %ebx, (prot_jump)
  97 +
  98 + /* FS = bootinfo_struct */
  99 + read_fw FW_CFG_INITRD_ADDR
  100 + shr $4, %eax
  101 + mov %ax, %fs
  102 +
  103 + /* ES = mmap_addr */
  104 + read_fw FW_CFG_INITRD_SIZE
  105 + shr $4, %eax
  106 + mov %ax, %es
  107 +
  108 + /* Initialize multiboot mmap structs using int 0x15(e820) */
  109 + xor %ebx, %ebx
  110 + /* mmap start after first size */
  111 + movl $4, %edi
  112 +
  113 +mmap_loop:
  114 + /* entry size (mmap struct) & max buffer size (int15) */
  115 + movl $20, %ecx
  116 + /* store entry size */
  117 + movl %ecx, %es:-4(%edi)
  118 + /* e820 */
  119 + movl $0x0000e820, %eax
  120 + /* 'SMAP' magic */
  121 + movl $0x534d4150, %edx
  122 + int $0x15
  123 +
  124 +mmap_check_entry:
  125 + /* last entry? then we're done */
  126 + jb mmap_done
  127 + and %bx, %bx
  128 + jz mmap_done
  129 + /* valid entry, so let's loop on */
  130 +
  131 +mmap_store_entry:
  132 + /* %ax = entry_number * 24 */
  133 + mov $24, %ax
  134 + mul %bx
  135 + mov %ax, %di
  136 + movw %di, %fs:0x2c
  137 + /* %di = 4 + (entry_number * 24) */
  138 + add $4, %di
  139 + jmp mmap_loop
  140 +
  141 +mmap_done:
  142 +real_to_prot:
  143 + /* Load the GDT before going into protected mode */
  144 +lgdt:
  145 + data32 lgdt %cs:gdt_desc
  146 +
  147 + /* get us to protected mode now */
  148 + movl $1, %eax
  149 + movl %eax, %cr0
  150 +
  151 + /* the LJMP sets CS for us and gets us to 32-bit */
  152 +ljmp:
  153 + data32 ljmp *%cs:prot_jump
  154 +
  155 +prot_mode:
  156 +.code32
  157 +
  158 + /* initialize all other segments */
  159 + movl $0x10, %eax
  160 + movl %eax, %ss
  161 + movl %eax, %ds
  162 + movl %eax, %es
  163 + movl %eax, %fs
  164 + movl %eax, %gs
  165 +
  166 + /* Jump off to the kernel */
  167 + read_fw FW_CFG_KERNEL_ADDR
  168 + mov %eax, %ecx
  169 +
  170 + /* EBX contains a pointer to the bootinfo struct */
  171 + read_fw FW_CFG_INITRD_ADDR
  172 + movl %eax, %ebx
  173 +
  174 + /* EAX has to contain the magic */
  175 + movl $MULTIBOOT_MAGIC, %eax
  176 +ljmp2:
  177 + jmp *%ecx
  178 +
  179 +/* Variables */
  180 +.align 4, 0
  181 +old_int19: .long 0
  182 +
  183 +prot_jump: .long prot_mode
  184 + .short 8
  185 +
  186 +.align 4, 0
  187 +gdt:
  188 + /* 0x00 */
  189 +.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  190 +
  191 + /* 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) */
  192 +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00
  193 +
  194 + /* 0x10: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) */
  195 +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00
  196 +
  197 + /* 0x18: code segment (base=0, limit=0x0ffff, type=16bit code exec/read/conf, DPL=0, 1b) */
  198 +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00
  199 +
  200 + /* 0x20: data segment (base=0, limit=0x0ffff, type=16bit data read/write, DPL=0, 1b) */
  201 +.byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00
  202 +
  203 +gdt_desc:
  204 +.short (5 * 8) - 1
  205 +.long gdt
  206 +
  207 +.align 512, 0
  208 +_end:
  209 +
... ...