1 # A minimal bootable image that: 2 # - loads more sectors past the first boot sector (using BIOS primitives) 3 # - switches to 32-bit mode (giving up access to BIOS primitives) 4 # - sets up a keyboard handler to print '1' at the top-left of screen when '1' is typed 5 # 6 # When it's ready to accept keys, it prints 'H' to the top-left of the screen. 7 # 8 # If the initial load fails, it prints 'D' to the top-left of the screen and 9 # halts. 10 # 11 # To convert to a disk image, first prepare a realistically sized disk image: 12 # dd if=/dev/zero of=disk.img count=20160 # 512-byte sectors, so 10MB 13 # Now fill in sectors: 14 # ./bootstrap run apps/hex < baremetal/boot0.hex > boot.bin 15 # dd if=boot.bin of=disk.img conv=notrunc 16 # To run: 17 # qemu-system-i386 disk.img 18 # Or: 19 # bochs -f baremetal/boot.bochsrc # boot.bochsrc loads disk.img 20 # 21 # Since we start out in 16-bit mode, we need instructions SubX doesn't 22 # support. 23 # This file contains just lowercase hex bytes and comments. Zero 24 # error-checking. Make liberal use of: 25 # - comments documenting expected offsets 26 # - size checks on the emitted file (currently: 512 bytes) 27 # - xxd to eyeball that offsets contain expected bytes 28 29 ## 16-bit entry point 30 31 # Upon reset, the IBM PC 32 # loads the first sector (512 bytes) 33 # from some bootable image (see the boot sector marker at the end of this file) 34 # to the address range [0x7c00, 0x7e00) 35 36 # offset 00 (address 0x7c00): 37 # disable interrupts for this initialization 38 fa # cli 39 40 # initialize segment registers 41 # this isn't always needed, but the recommendation is to not make assumptions 42 b8 00 00 # ax <- 0 43 8e d8 # ds <- ax 44 8e d0 # ss <- ax 45 8e c0 # es <- ax 46 8e e0 # fs <- ax 47 8e e8 # gs <- ax 48 49 # We don't read or write the stack before we get to 32-bit mode. No function 50 # calls, so we don't need to initialize the stack. 51 52 # 0e: 53 # load more sectors from disk 54 b4 02 # ah <- 2 # read sectors from disk 55 # dl comes conveniently initialized at boot time with the index of the device being booted 56 b5 00 # ch <- 0 # cylinder 0 57 b6 00 # dh <- 0 # track 0 58 b1 02 # cl <- 2 # second sector, 1-based 59 b0 01 # al <- 1 # number of sectors to read 60 # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment 61 bb 00 00 # bx <- 0 62 8e c3 # es <- bx 63 bb 00 7e # bx <- 0x7e00 64 cd 13 # int 13h, BIOS disk service 65 0f 82 76 00 # jump-if-carry disk-error 66 67 # 26: 68 # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line 69 # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S 70 # seta20.1: 71 e4 64 # al <- port 0x64 72 a8 02 # set zf if bit 1 (second-least significant) is not set 73 75 fa # if zf not set, goto seta20.1 (-6) 74 75 b0 d1 # al <- 0xd1 76 e6 64 # port 0x64 <- al 77 78 # 30: 79 # seta20.2: 80 e4 64 # al <- port 0x64 81 a8 02 # set zf if bit 1 (second-least significant) is not set 82 75 fa # if zf not set, goto seta20.2 (-6) 83 84 b0 df # al <- 0xdf 85 e6 64 # port 0x64 <- al 86 87 # 3a: 88 # switch to 32-bit mode 89 0f 01 16 # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16 90 80 7c # *gdt_descriptor 91 # 3f: 92 0f 20 c0 # eax <- cr0 93 66 83 c8 01 # eax <- or 0x1 94 0f 22 c0 # cr0 <- eax 95 ea c0 7c 08 00 # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code) 96 97 # padding 98 # 4e: 99 00 00 100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 101 102 ## GDT: 3 records of 8 bytes each 103 104 # 60: 105 # gdt_start: 106 # gdt_null: mandatory null descriptor 107 00 00 00 00 00 00 00 00 108 # gdt_code: (offset 8 from gdt_start) 109 ff ff # limit[0:16] 110 00 00 00 # base[0:24] 111 9a # 1/present 00/privilege 1/descriptor type = 1001b 112 # 1/code 0/conforming 1/readable 0/accessed = 1010b 113 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b 114 # limit[16:20] = 1111b 115 00 # base[24:32] 116 # gdt_data: (offset 16 from gdt_start) 117 ff ff # limit[0:16] 118 00 00 00 # base[0:24] 119 92 # 1/present 00/privilege 1/descriptor type = 1001b 120 # 0/data 0/conforming 1/readable 0/accessed = 0010b 121 cf # same as gdt_code 122 00 # base[24:32] 123 # gdt_end: 124 125 # padding 126 # 78: 127 00 00 00 00 00 00 00 00 128 129 # 80: 130 # gdt_descriptor: 131 17 00 # final index of gdt = gdt_end - gdt_start - 1 132 60 7c 00 00 # start = gdt_start 133 134 # padding 135 # 85: 136 00 00 00 00 00 00 00 00 00 00 137 138 # 90: 139 # disk_error: 140 # print 'D' to top-left of screen to indicate disk error 141 # *0xb8000 <- 0x0f44 142 # bx <- 0xb800 143 bb 00 b8 144 # ds <- bx 145 8e db # 11b/mod 011b/reg/ds 011b/rm/bx 146 # al <- 'D' 147 b0 44 148 # ah <- 0x0f # white on black 149 b4 0f 150 # bx <- 0 151 bb 00 00 152 # *ds:bx <- ax 153 89 07 # 00b/mod/indirect 000b/reg/ax 111b/rm/bx 154 155 e9 fb ff # loop forever 156 157 # padding 158 # a1: 159 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 161 162 ## 32-bit code from this point (still some instructions not in SubX) 163 164 # c0: 165 # initialize_32bit_mode: 166 66 b8 10 00 # ax <- offset 16 from gdt_start 167 8e d8 # ds <- ax 168 8e d0 # ss <- ax 169 8e c0 # es <- ax 170 8e e0 # fs <- ax 171 8e e8 # gs <- ax 172 173 # load interrupt handlers 174 0f 01 1d # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32 175 00 7f 00 00 # *idt_descriptor 176 177 # enable keyboard IRQ 178 b0 fd # al <- 0xfd # enable just IRQ1 179 e6 21 # port 0x21 <- al 180 181 # initialization is done; enable interrupts 182 fb 183 e9 21 00 00 00 # jump to 0x7d00 184 185 # padding 186 # df: 187 00 188 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 189 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 190 191 ## 'application' SubX code: print one character to top-left of screen 192 193 # offset 100 (address 0x7d00): 194 # Entry: 195 # eax <- *0x7ff4 # random address in second segment containing 'H' 196 8b # copy rm32 to r32 197 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 198 # disp32 199 f4 7f 00 00 200 # *0xb8000 <- eax 201 89 # copy r32 to rm32 202 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 203 # disp32 204 00 80 0b 00 205 206 e9 fb ff ff ff # loop forever 207 208 # padding 209 # 111: 210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 211 212 # 120: 213 # null interrupt handler: 214 cf # iret 215 216 # padding 217 # 121: 218 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 219 220 # 130: 221 # keyboard interrupt handler: 222 # prologue 223 fa # disable interrupts 224 60 # push all registers to stack 225 # acknowledge interrupt 226 b0 20 # al <- 0x20 227 e6 20 # port 0x20 <- al 228 # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) 229 e4 64 # al <- port 0x64 230 a8 01 # set zf if bit 0 (least significant) is not set 231 74 11 # if bit 0 is not set, skip to epilogue 232 # read keycode into eax 233 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 234 e4 60 # al <- port 0x60 235 # map key '1' to ascii; if eax == 2, eax = 0x31 236 3d 02 00 00 00 # compare eax with 0x02 237 75 0b # if not equal, goto epilogue 238 b8 31 0f 00 00 # eax <- 0x0f31 239 # print eax to top-left of screen (*0xb8000) 240 89 # copy r32 to rm32 241 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 242 # disp32 243 00 80 0b 00 244 # epilogue 245 61 # pop all registers 246 fb # enable interrupts 247 cf # iret 248 249 # padding 250 # 155 251 00 00 00 00 00 00 00 00 00 00 00 252 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 253 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 254 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 255 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 256 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 257 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 258 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 259 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 261 00 00 00 00 00 00 00 00 00 00 00 00 00 00 262 263 # final 2 bytes of boot sector 264 55 aa 265 266 ## sector 2 267 # loaded by load_disk, not automatically on boot 268 269 # offset 200 (address 0x7e00): interrupt descriptor table 270 # 32 entries * 8 bytes each = 256 bytes (0x100) 271 # idt_start: 272 273 00 00 00 00 00 00 00 00 274 00 00 00 00 00 00 00 00 275 00 00 00 00 00 00 00 00 276 00 00 00 00 00 00 00 00 277 00 00 00 00 00 00 00 00 278 00 00 00 00 00 00 00 00 279 00 00 00 00 00 00 00 00 280 00 00 00 00 00 00 00 00 281 282 # entry 8: clock 283 20 7d # target[0:16] = null interrupt handler 284 08 00 # segment selector (gdt_code) 285 00 # unused 286 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 287 00 00 # target[16:32] 288 289 # entry 9: keyboard 290 30 7d # target[0:16] = keyboard interrupt handler 291 08 00 # segment selector (gdt_code) 292 00 # unused 293 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 294 00 00 # target[16:32] 295 296 00 00 00 00 00 00 00 00 297 00 00 00 00 00 00 00 00 298 00 00 00 00 00 00 00 00 299 00 00 00 00 00 00 00 00 300 00 00 00 00 00 00 00 00 301 00 00 00 00 00 00 00 00 302 00 00 00 00 00 00 00 00 303 00 00 00 00 00 00 00 00 304 00 00 00 00 00 00 00 00 305 00 00 00 00 00 00 00 00 306 00 00 00 00 00 00 00 00 307 00 00 00 00 00 00 00 00 308 00 00 00 00 00 00 00 00 309 00 00 00 00 00 00 00 00 310 00 00 00 00 00 00 00 00 311 00 00 00 00 00 00 00 00 312 00 00 00 00 00 00 00 00 313 00 00 00 00 00 00 00 00 314 00 00 00 00 00 00 00 00 315 00 00 00 00 00 00 00 00 316 00 00 00 00 00 00 00 00 317 00 00 00 00 00 00 00 00 318 # idt_end: 319 320 # offset 300 (address 0x7f00): 321 # idt_descriptor: 322 ff 00 # idt_end - idt_start - 1 323 00 7e 00 00 # start = idt_start 324 325 # padding 326 00 00 00 00 00 00 00 00 00 00 327 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 328 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 329 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 331 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 332 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 333 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 334 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 335 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 337 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 338 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 339 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 341 00 00 00 00 48 0f 00 00 00 00 00 00 00 00 00 00 # spot the 'H' with attributes 342 # offset 400 (address 0x8000) 343 344 # vim:ft=conf