1.5 boot2 Stage

You might wonder, why boot2 comes after boot0, and not boot1. Actually, there is a 512-byte file called boot1 in the directory /boot as well. It is used for booting from a floppy. When booting from a floppy, boot1 plays the same role as boot0 for a harddisk: it locates boot2 and runs it.

You may have realized that a file /boot/mbr exists as well. It is a simplified version of boot0. The code in mbr does not provide a menu for the user, it just blindly boots the partition marked active.

The code implementing boot2 resides in sys/boot/i386/boot2/, and the executable itself is in /boot. The files boot0 and boot2 that are in /boot are not used by the bootstrap, but by utilities such as boot0cfg. The actual position for boot0 is in the MBR. For boot2 it is the beginning of a bootable FreeBSD slice. These locations are not under the filesystem's control, so they are invisible to commands like ls.

The main task for boot2 is to load the file /boot/loader, which is the third stage in the bootstrapping procedure. The code in boot2 cannot use any services like open() and read(), since the kernel is not yet loaded. It must scan the harddisk, knowing about the filesystem structure, find the file /boot/loader, read it into memory using a BIOS service, and then pass the execution to the loader's entry point.

Besides that, boot2 prompts for user input so the loader can be booted from different disk, unit, slice and partition.

The boot2 binary is created in special way:

sys/boot/i386/boot2/Makefile:
boot2: boot2.ldr boot2.bin ${BTX}/btx/btx
	btxld -v -E ${ORG2} -f bin -b ${BTX}/btx/btx -l boot2.ldr \
		-o boot2.ld -P 1 boot2.bin

This Makefile snippet shows that btxld(8) is used to link the binary. BTX, which stands for BooT eXtender, is a piece of code that provides a protected mode environment for the program, called the client, that it is linked with. So boot2 is a BTX client, i.e. it uses the service provided by BTX.

The btxld utility is the linker. It links two binaries together. The difference between btxld(8) and ld(1) is that ld usually links object files into a shared object or executable, while btxld links an object file with the BTX, producing the binary file suitable to be put on the beginning of the partition for the system boot.

boot0 passes the execution to BTX's entry point. BTX then switches the processor to protected mode, and prepares a simple environment before calling the client. This includes:

BTX creates a Global Descriptor Table (GDT):

sys/boot/i386/btx/btx/btx.s:
gdt:		.word 0x0,0x0,0x0,0x0		# Null entry
		.word 0xffff,0x0,0x9a00,0xcf	# SEL_SCODE
		.word 0xffff,0x0,0x9200,0xcf	# SEL_SDATA
		.word 0xffff,0x0,0x9a00,0x0	# SEL_RCODE
		.word 0xffff,0x0,0x9200,0x0	# SEL_RDATA
		.word 0xffff,MEM_USR,0xfa00,0xcf# SEL_UCODE
		.word 0xffff,MEM_USR,0xf200,0xcf# SEL_UDATA
		.word _TSSLM,MEM_TSS,0x8900,0x0 # SEL_TSS

The client's code and data start from address MEM_USR (0xa000), and a selector (SEL_UCODE) points to the client's code segment. The SEL_UCODE descriptor has Descriptor Privilege Level (DPL) 3, which is the lowest privilege level. But the INT 0x30 instruction handler resides in a segment pointed to by the SEL_SCODE (supervisor code) selector, as shown from the code that creates an IDT:

		mov $SEL_SCODE,%dh		# Segment selector
init.2:		shr %bx				# Handle this int?
		jnc init.3			# No
		mov %ax,(%di)			# Set handler offset
		mov %dh,0x2(%di)		#  and selector
		mov %dl,0x5(%di)		# Set P:DPL:type
		add $0x4,%ax			# Next handler

So, when the client calls __exec(), the code will be executed with the highest privileges. This allows the kernel to change the protected mode data structures, such as page tables, GDT, IDT, etc later, if needed.

boot2 defines an important structure, struct bootinfo. This structure is initialized by boot2 and passed to the loader, and then further to the kernel. Some nodes of this structures are set by boot2, the rest by the loader. This structure, among other information, contains the kernel filename, BIOS harddisk geometry, BIOS drive number for boot device, physical memory available, envp pointer etc. The definition for it is:

/usr/include/machine/bootinfo.h:
struct bootinfo {
	u_int32_t	bi_version;
	u_int32_t	bi_kernelname;		/* represents a char * */
	u_int32_t	bi_nfs_diskless;	/* struct nfs_diskless * */
				/* End of fields that are always present. */
#define	bi_endcommon	bi_n_bios_used
	u_int32_t	bi_n_bios_used;
	u_int32_t	bi_bios_geom[N_BIOS_GEOM];
	u_int32_t	bi_size;
	u_int8_t	bi_memsizes_valid;
	u_int8_t	bi_bios_dev;		/* bootdev BIOS unit number */
	u_int8_t	bi_pad[2];
	u_int32_t	bi_basemem;
	u_int32_t	bi_extmem;
	u_int32_t	bi_symtab;		/* struct symtab * */
	u_int32_t	bi_esymtab;		/* struct symtab * */
				/* Items below only from advanced bootloader */
	u_int32_t	bi_kernend;		/* end of kernel space */
	u_int32_t	bi_envp;		/* environment */
	u_int32_t	bi_modulep;		/* preloaded modules */
};

boot2 enters into an infinite loop waiting for user input, then calls load(). If the user does not press anything, the loop breaks by a timeout, so load() will load the default file (/boot/loader). Functions ino_t lookup(char *filename) and int xfsread(ino_t inode, void *buf, size_t nbyte) are used to read the content of a file into memory. /boot/loader is an ELF binary, but where the ELF header is prepended with a.out's struct exec structure. load() scans the loader's ELF header, loading the content of /boot/loader into memory, and passing the execution to the loader's entry:

sys/boot/i386/boot2/boot2.c:
    __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
	   MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part),
	   0, 0, 0, VTOP(&bootinfo));