+
+ # Advance memory pointer and disk sector.
+ add $0x20, %ax
+ inc %bx
+ loop next_sector
+
+ call puts
+ .string "\r"
+
+#### Transfer control to the kernel that we loaded. We read the start
+#### address out of the ELF header (see [ELF1]) and convert it from a
+#### 32-bit linear address into a 16:16 segment:offset address for
+#### real mode, then jump to the converted address. The 80x86 doesn't
+#### have an instruction to jump to an absolute segment:offset kept in
+#### registers, so in fact we store the address in a temporary memory
+#### location, then jump indirectly through that location. To save 4
+#### bytes in the loader, we reuse 4 bytes of the loader's code for
+#### this temporary pointer.
+
+ mov $0x2000, %ax
+ mov %ax, %es
+ mov %es:0x18, %dx
+ mov %dx, start
+ movw $0x2000, start + 2
+ ljmp *start
+
+read_failed:
+start:
+ # Disk sector read failed.
+ call puts
+1: .string "\rBad read\r"
+
+ # Notify BIOS that boot failed. See [IntrList].
+ int $0x18
+
+#### Print string subroutine. To save space in the loader, this
+#### subroutine takes its null-terminated string argument from the
+#### code stream just after the call, and then returns to the byte
+#### just after the terminating null. This subroutine preserves all
+#### general-purpose registers.
+
+puts: xchg %si, %ss:(%esp)
+ push %ax
+next_char:
+ mov %cs:(%si), %al
+ inc %si
+ test %al, %al
+ jz 1f
+ call putc
+ jmp next_char
+1: pop %ax
+ xchg %si, %ss:(%esp)
+ ret
+
+#### Character output subroutine. Prints the character in AL to the
+#### VGA display and serial port 0, using BIOS services (see
+#### [IntrList]). Preserves all general-purpose registers.
+####
+#### If called upon to output a carriage return, this subroutine
+#### automatically supplies the following line feed.
+
+putc: pusha
+
+1: sub %bh, %bh # Page 0.
+ mov $0x0e, %ah # Teletype output service.
+ int $0x10
+
+ mov $0x01, %ah # Serial port output service.
+ sub %dx, %dx # Serial port 0.
+2: int $0x14 # Destroys AH.
+ test $0x80, %ah # Output timed out?
+ jz 3f
+ movw $0x9090, 2b # Turn "int $0x14" above into NOPs.
+
+3:
+ cmp $'\r', %al
+ jne popa_ret
+ mov $'\n', %al
+ jmp 1b
+
+#### Sector read subroutine. Takes a drive number in DL (0x80 = hard
+#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and
+#### reads the specified sector into memory at ES:0000. Returns with
+#### carry set on error, clear otherwise. Preserves all
+#### general-purpose registers.
+
+read_sector:
+ pusha
+ sub %ax, %ax
+ push %ax # LBA sector number [48:63]
+ push %ax # LBA sector number [32:47]
+ push %ebx # LBA sector number [0:31]
+ push %es # Buffer segment
+ push %ax # Buffer offset (always 0)
+ push $1 # Number of sectors to read
+ push $16 # Packet size
+ mov $0x42, %ah # Extended read
+ mov %sp, %si # DS:SI -> packet
+ int $0x13 # Error code in CF
+ popa # Pop 16 bytes, preserve flags
+popa_ret:
+ popa
+ ret # Error code still in CF
+
+#### Command-line arguments and their count.
+#### This is written by the `pintos' utility and read by the kernel.
+#### The loader itself does not do anything with the command line.
+ .org LOADER_ARG_CNT - LOADER_BASE
+ .fill LOADER_ARG_CNT_LEN, 1, 0
+
+ .org LOADER_ARGS - LOADER_BASE
+ .fill LOADER_ARGS_LEN, 1, 0
+
+#### Partition table.
+ .org LOADER_PARTS - LOADER_BASE
+ .fill LOADER_PARTS_LEN, 1, 0
+
+#### Boot-sector signature for BIOS inspection.
+ .org LOADER_SIG - LOADER_BASE