1 # 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 handler for keyboard events 5 # - as an example program, prints alphabets to the top-left position on screen (by writing to memory-mapped VGA memory) as they're typed 6 # 7 # If the initial load fails, it prints 'D' to the top-left of the screen and 8 # halts. 9 # 10 # To convert to a disk image, first prepare a realistically sized disk image: 11 # dd if=/dev/zero of=disk.img count=20160 # 512-byte sectors, so 10MB 12 # Now fill in sectors: 13 # ./bootstrap run apps/hex < apps/boot.hex > boot.bin 14 # dd if=boot.bin of=disk.img conv=notrunc 15 # To run: 16 # qemu-system-i386 disk.img 17 # Or: 18 # bochs -f apps/boot.bochsrc # boot.bochsrc loads disk.img 19 # 20 # Since we start out in 16-bit mode, we need instructions SubX doesn't 21 # support. 22 # This file contains just hex bytes and comments. Zero error-checking. Make 23 # liberal use of: 24 # - comments documenting expected offsets 25 # - size checks on the emitted file (currently: 512 bytes) 26 # - xxd to eyeball that offsets contain expected bytes 27 28 ## 16-bit entry point 29 30 # Upon reset, the IBM PC 31 # loads the first sector (512 bytes) 32 # from some bootable image (see the boot sector marker at the end of this file) 33 # to the address range [0x7c00, 0x7e00) 34 35 # offset 00 (address 0x7c00): 36 # disable interrupts for this initialization 37 fa # cli 38 39 # initialize segment registers 40 # this isn't always needed, but is considered safe not to assume 41 b8 00 00 # ax <- 0 42 8e d8 # ds <- ax 43 8e d0 # ss <- ax 44 8e c0 # es <- ax 45 8e e0 # fs <- ax 46 8e e8 # gs <- ax 47 48 # We don't read or write the stack before we get to 32-bit mode. No function 49 # calls, so we don't need to initialize the stack. 50 51 # 0e: 52 # load more sectors from disk 53 b4 02 # ah <- 2 # read sectors from disk 54 # dl comes conveniently initialized at boot time with the index of the device being booted 55 b5 00 # ch <- 0 # cylinder 0 56 b6 00 # dh <- 0 # track 0 57 b1 02 # cl <- 2 # second sector, 1-based 58 b0 01 # al <- 1 # number of sectors to read 59 # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment 60 bb 00 00 # bx <- 0 61 8e c3 # es <- bx 62 bb 00 7e # bx <- 0x7e00 63 cd 13 # int 13h, BIOS disk service 64 0f 82 76 00 # jump-if-carry disk-error 65 66 # 26: 67 # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line 68 # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S 69 # seta20.1: 70 e4 64 # al <- port 0x64 71 a8 02 # set zf if bit 1 (second-least significant) is not set 72 75 fa # if zf not set, goto seta20.1 (-6) 73 74 b0 d1 # al <- 0xd1 75 e6 64 # port 0x64 <- al 76 77 # 30: 78 # seta20.2: 79 e4 64 # al <- port 0x64 80 a8 02 # set zf if bit 1 (second-least significant) is not set 81 75 fa # if zf not set, goto seta20.2 (-6) 82 83 b0 df # al <- 0xdf 84 e6 64 # port 0x64 <- al 85 86 # 3a: 87 # switch to 32-bit mode 88 0f 01 16 # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16 89 80 7c # *gdt_descriptor 90 # 3f: 91 0f 20 c0 # eax <- cr0 92 66 83 c8 01 # eax <- or 0x1 93 0f 22 c0 # cr0 <- eax 94 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) 95 96 # padding 97 # 4e: 98 00 00 99 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 100 101 ## GDT: 3 records of 8 bytes each 102 103 # 60: 104 # gdt_start: 105 # gdt_null: mandatory null descriptor 106 00 00 00 00 00 00 00 00 107 # gdt_code: (offset 8 from gdt_start) 108 ff ff # limit[0:16] 109 00 00 00 # base[0:24] 110 9a # 1/present 00/privilege 1/descriptor type = 1001b 111 # 1/code 0/conforming 1/readable 0/accessed = 1010b 112 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b 113 # limit[16:20] = 1111b 114 00 # base[24:32] 115 # gdt_data: (offset 16 from gdt_start) 116 ff ff # limit[0:16] 117 00 00 00 # base[0:24] 118 92 # 1/present 00/privilege 1/descriptor type = 1001b 119 # 0/data 0/conforming 1/readable 0/accessed = 0010b 120 cf # same as gdt_code 121 00 # base[24:32] 122 # gdt_end: 123 124 # padding 125 # 78: 126 00 00 00 00 00 00 00 00 127 128 # 80: 129 # gdt_descriptor: 130 17 00 # final index of gdt = gdt_end - gdt_start - 1 131 60 7c 00 00 # start = gdt_start 132 133 # padding 134 # 85: 135 00 00 00 00 00 00 00 00 00 00 136 137 # 90: 138 # disk_error: 139 # print 'D' to top-left of screen to indicate disk error 140 # *0xb8000 <- 0x0f44 141 # bx <- 0xb800 142 bb 00 b8 143 # ds <- bx 144 8e db # 11b/mod 011b/reg/ds 011b/rm/bx 145 # al <- 'D' 146 b0 44 147 # ah <- 0x0f # white on black 148 b4 0f 149 # bx <- 0 150 bb 00 00 151 # *ds:bx <- ax 152 89 07 # 00b/mod/indirect 000b/reg/ax 111b/rm/bx 153 154 e9 fb ff # loop forever 155 156 # padding 157 # a1: 158 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 159 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 160 161 ## 32-bit code from this point (still some instructions not in SubX) 162 163 # c0: 164 # initialize_32bit_mode: 165 66 b8 10 00 # ax <- offset 16 from gdt_start 166 8e d8 # ds <- ax 167 8e d0 # ss <- ax 168 8e c0 # es <- ax 169 8e e0 # fs <- ax 170 8e e8 # gs <- ax 171 172 # load interrupt handlers 173 0f 01 1d # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32 174 00 7f 00 00 # *idt_descriptor 175 176 # enable keyboard IRQ 177 b0 fd # al <- 0xfd # enable just IRQ1 178 e6 21 # port 0x21 <- al 179 180 # initialization is done; enable interrupts 181 fb 182 e9 21 00 00 00 # jump to 0x7d00 183 184 # padding 185 # df: 186 00 187 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 188 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 189 190 ## 'application' SubX code: print one character to top-left of screen 191 192 # offset 100 (address 0x7d00): 193 # Entry: 194 # eax <- *0x7ff4 # random address in second segment containing 'H' 195 8b # copy rm32 to r32 196 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 197 # disp32 198 f4 7f 00 00 199 # *0xb8000 <- eax 200 89 # copy r32 to rm32 201 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 202 # disp32 203 00 80 0b 00 204 205 e9 fb ff ff ff # loop forever 206 207 # padding 208 # 111: 209 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 210 211 # 120: 212 # null interrupt handler: 213 cf # iret 214 215 # padding 216 # 121: 217 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 218 219 # 130: 220 # keyboard interrupt handler: 221 # prologue 222 fa # disable interrupts 223 60 # push all registers to stack 224 # acknowledge interrupt 225 b0 20 # al <- 0x20 226 e6 20 # port 0x20 <- al 227 # read keyboard status (TODO: why bit 0? Doesn't line up with https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) 228 #? e4 64 # al <- port 0x64 229 #? a8 01 # set zf if bit 0 (least significant) is not set 230 #? 74 11 # if bit 0 is not set, skip to epilogue 231 # read keycode into eax 232 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 233 e4 60 # al <- port 0x60 234 # map key '1' to ascii; if eax == 2, eax = 0x31 235 3d 02 00 00 00 # compare eax with 0x02 236 75 0b # if not equal, goto epilogue 237 b8 31 0f 00 00 # eax <- 0x0f31 238 # print eax to top-left of screen (*0xb8000) 239 89 # copy r32 to rm32 240 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 241 # disp32 242 00 80 0b 00 243 # epilogue 244 61 # pop all registers 245 fb # enable interrupts 246 cf # iret 247 248 # padding 249 # 14f 250 00 251 00 00 00 00 00 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 # not loaded on boot; loaded by load_disk 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