mainly just considering PATA/IDE (QEMU's default interface of choice)
glosses over memory model
Boot process:
Execution starts in the BIOS. QEMU's default BIOS image is SeaBIOS; Bochs can also use SeaBIOS but provides its own BIOS ROM by default (https://bochs.sourceforge.io/doc/docbook/user/bochsrc.html). We will use the SeaBIOS process as a reference: [https://www.seabios.org/Execution_and_code_flow]
- POST is conducted.
- Hardware initialization is performed by
device_hardware_setupinpost.c. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/post.c#L125-L135]device_hardware_setupcallsblock_setupinblock.c(among setup functions for other hardware devices) for setting up all kinds of block devices. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/block.c#L508-L523]block_setupcallsata_setupinata.c(among setup functions for other block storage types) to setup all ATA devices.ata_setup"Scan[s the] PCI bus for ATA adapters". [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/hw/ata.c#L1018-L1037]- Control is handed over to the PCI subsystem, which calls
init_drive_atainata.c[https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/hw/ata.c#L768-L820] for each ATA controller.- These are processed in sequential order by PCI bus. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/hw/pcidevice.c#L19-L84]
init_drive_atadetects any drives attached to the controller, including slave drives which have been daisy-chained.init_drive_atadetermines the priority of the drive, for boot order purposes.- Priorities can be assigned by configuring a boot order in the
bootorderfile. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/docs/Runtime_config.md#configuring-boot-order]. The 1-based index within the file is used as the priority [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/boot.c#L245-L296]. - If a boot order has not been configured, the default boot priority is assigned based on the type of media (e.g. Floppy, HD, CD). [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/boot.c#L460-L496] On QEMU, the order is provided by the emulator (e.g. configured from the command line).
- Priorities can be assigned by configuring a boot order in the
init_drive_atacallsboot_add_hdinboot.c[https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/boot.c#L593-L598], which adds a boot entry to a global listBootList. TheBootListis sorted as follows:- Boot entries with lower priorities are listed above those with higher priorities. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/boot.c#L524-L560]
- Boot entries with the same priorities are sorted based on their media type (like with the default boot priority).
- If tied here, I believe the order is sequential by ATA controller, then order of master, slave 1, slave 2, etc.
- The bootmenu is (optionally, depending on runtime configuration) shown by
interactive_bootmenuinboot.c. [https://github.com/coreboot/seabios/blob/ea1b7a0733906b8425d948ae94fba63c32b1d425/src/boot.c#L693-L791]- When the user selects an entry, it's moved to the top of the
BootList.
- When the user selects an entry, it's moved to the top of the
- Preparation is done for the boot phase by
preparebootinpost.c. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/post.c#L160-L179]preparebootcallsbcv_prepboot, which, for each boot entry in theBootList: [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/boot.c#L817-L855]- Runs the Boot Connection Vector if applicable, though I don't think it is for ATA.
- Assigns the drive a mapping in the internal two-dimensional
IDMaparray for all external drives being used. Implementation details: [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/block.h#L95-L100]- The first dimension is floppy disks, the second dimension is hard drives, and the third dimension is CDs.
- Floppy IDs start at
0x00, HD IDs start at0x80, and CD IDs start at0xE0.
- Adds a Boot Execution Vector to
BEV. The number of HDs and floppy disks is each limited to one.- The BEV seems to only be used for Plug-and-Play devices [https://etherboot.sourceforge.net/doc50/html/devman-3.html], though an entry is added regardless, with the vector being optional.
- Hardware initialization is performed by
- The boot phase (in SeaBIOS terms) loads the bootloader into memory:
- POST fires software interrupt 0x19 once done.
- The software interrupt is handled by
entry_19inromlayout.s; it is executing in 16-bit mode. [https://github.com/coreboot/seabios/blob/ea1b7a0733906b8425d948ae94fba63c32b1d425/src/romlayout.S#L576-L579] entry_19transitions to 32-bit mode and executeshandle_19and thendo_boot, both inboot.c.do_bootattempts to boot first boot entry inBEV. [https://github.com/coreboot/seabios/blob/ea1b7a0733906b8425d948ae94fba63c32b1d425/src/boot.c#L982-L1043]- If the boot entry is a HDD,
do_bootinvokesboot_diskwith HDD0x80(the first HDD). Now, remember:- We reordered the boot entries to our liking, back in
interactive_bootmenu. Regardless of the inital priorities the devices were given, our chosen boot HDD (or other media) has been moved to the top of theBootList, and accordingly is at the top ofBEV. BEVis restricted to just one HDD, so hardcoding the device ID to the first one is sufficient.
- We reordered the boot entries to our liking, back in
boot_diskreads the first sector of the HDD by firing software interrupt 0x13, "Fixed Disk Services Entry Point". Detour time!- This is the boot sector, which is comprised of the Master Boot Record.
- As is convention, the destination buffer is located at
0x0000:0x7C00. [https://wiki.osdev.org/Boot_Sequence] [https://en.wikipedia.org/wiki/Master_boot_record#BIOS_to_MBR_interface] - The software interrupt is handled by
handle_13indisk.c. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/disk.c#L739-L770] handle_13eventually dispatches the command usingdisk_13indisk.c. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/disk.c#L620-L656]disk_13issues the command to the device.boot_diskspecifies command0x20(ah=0x2->eax=0x20), not that this is necessarily the exact command issued.- Eventually we arrive at
ata_process_opinata.c, which defers toata_readwriteinata.c. [https://github.com/coreboot/seabios/blob/b0d61ecef66eb05bd7a4eb7ada88ec5dab06dfee/src/hw/ata.c#L554-L576] ata_readwritereads the data into a buffer, either with Direct Memory Access or Programmed Input/Output.
boot_diskchecks the MBR signature, and bails if it is not the magic number.boot_diskcallscall_boot_entry, which jumps to the beginning of the MBR!
QEMUEXTRA="--fw_cfg \"etc/show-boot-menu,string=1\""
modified SeaBIOS cfg modified SeaBIOS makefile gdbinit
hexdump kernel lines up with x/120hx 0x10000 in GDB
$1 = {magic = 1179403647, elf = "\001\001\001\000\000\000\000\000\000\000\000", type = 2, machine = 3, version = 1, entry = 1048588, phoff = 52, shoff = 201436, flags = 0, ehsize = 52, phentsize = 32, phnum = 3, shentsize = 40, shnum = 17, shstrndx = 16}
~~however, if we x/10i 0x1000c, the instructions are obviously not the assembly entry we have in our source - the ELF is bad
wait no that address is in the ELF header
maybe the linker script is wrong?~~ maybe just misread? looks fine to me
okay, i hardcoded it to jump to the right place, but now it fails when jumping to a high addr
segfault simulator <3 None
=> 0x100032: jmp *%eax
0x00100032 in ?? ()
segfault simulator <3 None x/16i 0x801030d0
0x801030d0 <main>: lea 0x4(%esp),%ecx
0x801030d4 <main+4>: and $0xfffffff0,%esp
0x801030d7 <main+7>: push -0x4(%ecx)
0x801030da <main+10>: push %ebp
0x801030db <main+11>: mov %esp,%ebp
0x801030dd <main+13>: push %ebx
0x801030de <main+14>: push %ecx
0x801030df <main+15>: sub $0x8,%esp
0x801030e2 <main+18>: push $0x80400000
0x801030e7 <main+23>: push $0x801154d0
0x801030ec <main+28>: call 0x80102690 <kinit1>
0x801030f1 <main+33>: call 0x80106fb0 <kvmalloc>
0x801030f6 <main+38>: call 0x801032d0 <mpinit>
0x801030fb <main+43>: call 0x80102880 <lapicinit>
0x80103100 <main+48>: call 0x80106a70 <seginit>
0x80103105 <main+53>: call 0x80103520 <picinit>
segfault simulator <3 None ni
looking at page directories:
x/16i entry
0x10000c: mov %cr4,%eax
0x10000f: or $0x10,%eax
0x100012: mov %eax,%cr4
0x100015: mov $0x109000,%eax
0x10001a: mov %eax,%cr3
0x10001d: mov %cr0,%eax
0x100020: or $0x80010000,%eax
0x100025: mov %eax,%cr0
0x100028: mov $0x801154d0,%esp
0x10002d: mov $0x801030d0,%eax
0x100032: jmp *%eax
0x100034: xchg %ax,%ax
0x100036: xchg %ax,%ax
0x100038: xchg %ax,%ax
0x10003a: xchg %ax,%ax
0x10003c: xchg %ax,%ax
segfault simulator <3 bootmain x/16x 0x109000
0x109000: 0x00000083 0x00000000 0x00000000 0x00000000
0x109010: 0x00000000 0x00000000 0x00000000 0x00000000
0x109020: 0x00000000 0x00000000 0x00000000 0x00000000
finding the high vt page directory entry:
x/x 0x109000 + (0x80000000 >> 22) * 4
0x109800: 0x00000083
According to this pull request, xv6 fails to boot on binutils 2.36. The author is correct; the informative .note.gnu.property section causes the ELF loader to hang. [https://lists.llvm.org/pipermail/llvm-dev/2018-March/121951.html]
so, the problem occurs when linking the initcode. first, look at the compiled initcode object:
$ readelf -S initcode.o
There are 18 section headers, starting at offset 0x380:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00002c 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000268 000018 08 I 15 1 4
[ 3] .data PROGBITS 00000000 000060 000000 00 WA 0 0 1
[ 4] .bss NOBITS 00000000 000060 000000 00 WA 0 0 1
[ 5] .note.gnu.pr[...] NOTE 00000000 000060 000028 00 A 0 0 4
[ 6] .debug_line PROGBITS 00000000 000088 00004b 00 C 0 0 4
[ 7] .rel.debug_line REL 00000000 000280 000020 08 I 15 6 4
[ 8] .debug_line_str PROGBITS 00000000 0000d3 000033 01 MS 0 0 1
[ 9] .debug_info PROGBITS 00000000 000106 000024 00 0 0 1
[10] .rel.debug_info REL 00000000 0002a0 000030 08 I 15 9 4
[11] .debug_abbrev PROGBITS 00000000 00012a 000014 00 0 0 1
[12] .debug_aranges PROGBITS 00000000 000140 000020 00 0 0 8
[13] .rel.debug_a[...] REL 00000000 0002d0 000010 08 I 15 12 4
[14] .debug_str PROGBITS 00000000 000160 00003f 01 MS 0 0 1
[15] .symtab SYMTAB 00000000 0001a0 0000b0 10 16 10 4
[16] .strtab STRTAB 00000000 000250 000016 00 0 0 1
[17] .shstrtab STRTAB 00000000 0002e0 00009f 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), p (processor specific)
the Note sections exist, but they are not doing any harm. but then, look at what happens when perform linking on it (not with other objects, yet):
readelf -S initcode.out
There are 12 section headers, starting at offset 0x368:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.gnu.pr[...] NOTE 080480b4 0000e0 000028 00 A 0 0 4
[ 2] .text PROGBITS 00000000 0000b4 00002c 00 WAX 0 0 4
[ 3] .debug_aranges PROGBITS 00000000 000108 000020 00 0 0 8
[ 4] .debug_info PROGBITS 00000000 000128 000024 00 0 0 1
[ 5] .debug_abbrev PROGBITS 00000000 00014c 000014 00 0 0 1
[ 6] .debug_line PROGBITS 00000000 000160 00004c 00 0 0 1
[ 7] .debug_str PROGBITS 00000000 0001ac 00003f 01 MS 0 0 1
[ 8] .debug_line_str PROGBITS 00000000 0001eb 000033 01 MS 0 0 1
[ 9] .symtab SYMTAB 00000000 000220 000090 10 10 5 4
[10] .strtab STRTAB 00000000 0002b0 000033 00 0 0 1
[11] .shstrtab STRTAB 00000000 0002e3 000084 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), p (processor specific)
the note has been placed at a super high address