To boot Mac OS X a mach bootloader has to be run. David Elliot modified the original i386 Mac OS X BIOS based bootloader to emulate the EFI entries and support multiboot, an easy but straight-forward kernel loading interface founded by grub. To boot multiboot-compliant kernels directly, this adds support for multiboot loading to the -kernel option, if a Linux kernel was not found.
Index: qemu-snapshot-2008-01-08_05/hw/pc.c =================================================================== --- qemu-snapshot-2008-01-08_05.orig/hw/pc.c +++ qemu-snapshot-2008-01-08_05/hw/pc.c @@ -493,6 +493,226 @@ static long get_file_size(FILE *f) return size; } +/* Generate an initial boot sector which sets state and jump to + a specified vector */ +static void generate_bootsect_multiboot(uint32_t mh_entry_addr, uint32_t bootinfo) +{ + uint8_t bootsect[512], *p, *pgdt; + uint32_t ip; + int i; + int hda; + + hda = drive_get_index(IF_IDE, 0, 0); + if (hda == -1) { + fprintf(stderr, "A disk image must be given for 'hda' when booting " + "a Multiboot kernel\n"); + exit(1); + /* Copy the MSDOS partition table if possible */ + } + + memset(bootsect, 0, sizeof(bootsect)); + bdrv_read(drives_table[hda].bdrv, 0, bootsect, 1); + + /* Make sure we have a partition signature */ + bootsect[510] = 0x55; + bootsect[511] = 0xaa; + + /* Actual code */ + p = bootsect; + *p++ = 0xfa; /* CLI */ + *p++ = 0xfc; /* CLD */ + + // 660f011528000000 lgdt [0x28] + *p++ = 0x66; /* 32-bit operand size */ + *p++ = 0x67; /* 32-bit addr size */ + *p++ = 0x0f; /* LGDT [0x128] */ + *p++ = 0x01; + *p++ = 0x15; + pgdt=p; /* we calculate the gdt position later */ + p+=4; + + /* get us to protected mode now */ + + *p++ = 0x66; + *p++ = 0xb8; /* MOV EAX,0x01 */ + *p++ = 0x01; + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x00; + + *p++ = 0x0f; /* MOV CR0,EAX */ + *p++ = 0x22; + *p++ = 0xc0; + + /* the JMP sets CS for us and gets us to 32-bit */ + ip = 0x00007c00 + (p - bootsect) + 8; // set i to the IP after the JMP + *p++ = 0x66; /* 32-bit operand size */ + *p++ = 0xea; /* JMP */ + *p++ = ip; /* IP */ + *p++ = ip >> 8; + *p++ = ip >> 16; + *p++ = ip >> 24; + *p++ = 0x08; + *p++ = 0x00; + + /* initialize all other segments */ + *p++ = 0xb8; /* MOV EAX,0x10 */ + *p++ = 0x10; + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x00; + for (i = 0; i < 6; i++) { + if (i == 1) /* Skip CS */ + continue; + + *p++ = 0x8e; /* MOV <seg>,EAX */ + *p++ = 0xc0 + (i << 3); + } + + /* EBX contains a pointer to the bootinfo struct */ + *p++ = 0xbb; /* MOV EBX,imm32 */ + *p++ = bootinfo; + *p++ = bootinfo >> 8; + *p++ = bootinfo >> 16; + *p++ = bootinfo >> 24; + + /* EAX has to contain the following magic */ + *p++ = 0xb8; /* MOV EAX,0x2badb002 */ + *p++ = 0x02; + *p++ = 0xb0; + *p++ = 0xad; + *p++ = 0x2b; + + /* Jump off to the kernel */ + *p++ = 0xea; /* JMP */ + *p++ = mh_entry_addr; /* IP */ + *p++ = mh_entry_addr >> 8; + *p++ = mh_entry_addr >> 16; + *p++ = mh_entry_addr >> 24; + *p++ = 0x08; + *p++ = 0x00; + + { /* GDT loading */ + uint32_t gdt_base = 0x00007c00 + (p - bootsect); // 0x00007c00 is the first IP; + uint32_t gdtr = gdt_base + 0x28; + uint8_t gdt[] = { // GDT base: 0x00000100 + // 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, + // 0x10: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, + // 0x18: code segment (base=0, limit=0x0ffff, type=16bit code exec/read/conf, DPL=0, 1b) + 0xff, 0xff, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, + // 0x20: data segment (base=0, limit=0x0ffff, type=16bit data read/write, DPL=0, 1b) + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, + // 0x28: gdtdesc + 0x27, 0x00, gdt_base, gdt_base >> 8, gdt_base >> 16, gdt_base >> 24 + }; + + memcpy(p, gdt, sizeof(gdt)); + p+=sizeof(gdt); + *pgdt++ = gdtr; + *pgdt++ = gdtr >> 8; + *pgdt++ = gdtr >> 16; + *pgdt++ = gdtr >> 24; + } + + fprintf(stderr, "qemu: multiboot loader code is %d bytes long.\n", (int)(p-bootsect)); + + bdrv_set_boot_sector(drives_table[hda].bdrv, bootsect, sizeof(bootsect)); +} + +static int load_multiboot(FILE *f, + const char *kernel_filename, + const char *initrd_filename, + const char *kernel_cmdline, + uint8_t *header) +{ + int i, is_multiboot = 0; + uint32_t flags = 0; + // XXX: multiboot header may be within the first 8192 bytes, but header + // is only the first 1024 + + + // Ok, let's see if it is a multiboot image + for(i=0; i<(256 - 12); i+=4) { // the header is 12x32bit long + if(ldl_p(header+i) == 0x1BADB002) { + uint32_t checksum = ldl_p(header+i+8); + flags = ldl_p(header+i+4); + checksum += flags; + checksum += (uint32_t)0x1BADB002; + if(!checksum) { + is_multiboot = 1; + break; + } + } + } + + if(!is_multiboot) return 0; // no multiboot + fprintf(stderr, "qemu: I believe we found a multiboot image!\n"); + + if(flags & 0x00000004) { // MULTIBOOT_HEADER_HAS_VBE + fprintf(stderr, "qemu: multiboot knows VBE. we don't.\n"); + } + if(!(flags & 0x00010000)) { // MULTIBOOT_HEADER_HAS_ADDR + // XXX: multiboot knows ELF. we don't. + fprintf(stderr, "qemu: multiboot knows ELF. we don't.\n"); + return 0; + } else { + /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_ADDR. */ + uint32_t mh_header_addr = ldl_p(header+i+12); + uint32_t mh_load_addr = ldl_p(header+i+16); + uint32_t mh_load_end_addr = ldl_p(header+i+20); + uint32_t mh_bss_end_addr = ldl_p(header+i+24); + uint32_t mh_entry_addr = ldl_p(header+i+28); + uint8_t *mb_kernel_addr = phys_ram_base + (mh_load_addr); + uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr); + uint32_t mb_kernel_size = get_file_size(f) - mb_kernel_text_offset; + uint32_t mb_bootinfo = 0x100000; + uint32_t tmp_size; + + /* Valid if mh_flags sets MULTIBOOT_HEADER_HAS_VBE. + uint32_t mh_mode_type = ldl_p(header+i+32); + uint32_t mh_width = ldl_p(header+i+36); + uint32_t mh_height = ldl_p(header+i+40); + uint32_t mh_depth = ldl_p(header+i+44); */ + + fprintf(stderr, "multiboot: mh_header_addr = %#x\n", mh_header_addr); + fprintf(stderr, "multiboot: mh_load_addr = %#x\n", mh_load_addr); + fprintf(stderr, "multiboot: mh_load_end_addr = %#x\n", mh_load_end_addr); + fprintf(stderr, "multiboot: mh_bss_end_addr = %#x\n", mh_bss_end_addr); + fprintf(stderr, "multiboot: mh_entry_addr = %#x\n", mh_entry_addr); + + fseek(f, mb_kernel_text_offset, SEEK_SET); + + fprintf(stderr, "qemu: loading multiboot kernel (%#x bytes) at %#zx\n", + mb_kernel_size, mb_kernel_addr - phys_ram_base); + + if ((tmp_size=fread(mb_kernel_addr, 1, mb_kernel_size, f)) != mb_kernel_size) { + fprintf(stderr, "qemu: read error on multiboot kernel '%s' (%#x != %#x)\n", kernel_filename, tmp_size, mb_kernel_size); + exit(1); + } + fclose(f); + + // the kernel is where we want it to be now + // XXX: add module support (hurd, xen) + + // XXX: add support for: + // - mem_lower (4), mem_upper (8) flags[0] + // - cmdline (12) flags[2] + // - mods_count (20), mods_addr(24) flags[3] + // - syms (28 - 40) flags[4] + // - mmap_length (44), mmmap_addr (48) flags[6] + stl_p(phys_ram_base + mb_bootinfo, 2); // we only support the boot_device info + stl_p(phys_ram_base + mb_bootinfo + 12, 0x8001ffff); // XXX: use the -boot switch + + generate_bootsect_multiboot(mh_entry_addr, mb_bootinfo); + } + + return 1; // yes, we are multiboot +} + static void load_linux(const char *kernel_filename, const char *initrd_filename, const char *kernel_cmdline) @@ -523,10 +743,15 @@ static void load_linux(const char *kerne #if 0 fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202)); #endif - if (ldl_p(header+0x202) == 0x53726448) - protocol = lduw_p(header+0x206); - else - protocol = 0; + if (ldl_p(header+0x202) == 0x53726448) { + protocol = lduw_p(header+0x206); + } else { + // This looks like a multiboot kernel. If it is, let's stop + // treating it like Linux. + if(load_multiboot(f,kernel_filename,initrd_filename, kernel_cmdline, header)) + return; + protocol = 0; + } if (protocol < 0x200 || !(header[0x211] & 0x01)) { /* Low kernel */