1 00:00:02,270 --> 00:00:09,680 Bios which stands for Basic Input Output System, is usually stored on a small chip on the motherboard. 2 00:00:10,430 --> 00:00:15,830 It initializes hardware and provides runtime services for operating systems. 3 00:00:17,350 --> 00:00:24,420 When we boot our computer, BIOS initializes and tests the hardware, then find the boot device, read the first sector 4 00:00:24,420 --> 00:00:30,850 which is the boot sector into the memory location 7c00 5 00:00:31,300 --> 00:00:33,120 and control is transferred to it. 6 00:00:34,020 --> 00:00:37,780 The information on the first sector is also known as master boot record, 7 00:00:37,780 --> 00:00:39,880 MBR. 8 00:00:41,150 --> 00:00:47,690 The program in this video will be write into the first sector of the disk. 9 00:00:47,690 --> 00:00:50,600 When BIOS reads it into the memory, our code will be executed. 10 00:00:51,010 --> 00:00:53,960 At this point we have control of the computer. 11 00:00:56,370 --> 00:01:01,260 BIOS also provides low-level services or functions for us to use. 12 00:01:02,640 --> 00:01:06,090 and become unavailable in protected mode and long mode. 13 00:01:07,100 --> 00:01:13,370 When booting our operating system, we will go through 3 different modes real mode, protected mode 14 00:01:13,400 --> 00:01:14,290 and long mode. 15 00:01:15,810 --> 00:01:18,720 when our boot code executes, we are in real mode. 16 00:01:19,590 --> 00:01:23,640 Then we enter the protected mode for preparing for the long mode. 17 00:01:23,820 --> 00:01:25,800 Finally, we will switch to long mode. 18 00:01:26,880 --> 00:01:31,920 Long mode consists of two sub modes, 64-bit mode and compatibility mode. 19 00:01:34,010 --> 00:01:41,260 In compatibility mode, we can run 16-bit or 32-bit programs under 64-bit operating system without recompiling those programs. 20 00:01:41,270 --> 00:01:42,950 . 21 00:01:44,600 --> 00:01:51,440 Our operating system is designed for the 64-bit kernel and 64-bit programs. So we will focus on 64-bit mode in this course. 22 00:01:51,440 --> 00:01:52,490 . 23 00:01:54,020 --> 00:01:57,720 In this caused by long mode, I always mean 64-bit mode. 24 00:01:59,210 --> 00:02:04,820 As we move along with our projects, please don’t get confused with these two terms, 25 00:02:05,120 --> 00:02:06,920 they mean the same thing in this course. 26 00:02:09,430 --> 00:02:13,760 Ok, we call all the services we need before we enter long mode. 27 00:02:14,380 --> 00:02:20,350 The services we will use in our code include print function which prints characters on the screen, 28 00:02:21,010 --> 00:02:25,120 load files from hard disk in memory, such as the kernel file. 29 00:02:25,750 --> 00:02:29,260 We also get memory information of the computer and set video mode. 30 00:02:29,260 --> 00:02:29,920 . 31 00:02:31,270 --> 00:02:35,680 When we are in long mode, we cannot use print functions provided by BIOS. 32 00:02:36,400 --> 00:02:41,050 Instead, we set a specific video mode and then we can print characters in that mode. 33 00:02:44,110 --> 00:02:51,250 It is worth mentioning that Modern computers also come with UEFI, Unified Extensible Firmware Interface 34 00:02:51,250 --> 00:02:55,060 which is expected to replace BIOS. 35 00:02:56,360 --> 00:02:58,340 This course is based on BIOS. 36 00:02:58,550 --> 00:03:03,890 So when we test our code on a real machine, make sure that legacy BIOS mode is enabled. 37 00:03:06,790 --> 00:03:09,590 What we are going to talk about next is real mode. 38 00:03:10,310 --> 00:03:17,290 we don’t cover too much about real mode. Since what we will do in real mode includes load kernel in memory, 39 00:03:17,290 --> 00:03:23,860 get information about hardware, etc. Then we will switch to long mode and the operating system will be running in 64-bit mode. 40 00:03:23,860 --> 00:03:24,850 . 41 00:03:26,410 --> 00:03:31,900 So first off, we will see memory addressing and how memory address is formed in real mode. 42 00:03:32,590 --> 00:03:38,590 There are 4 special registers called segment registers which are 16 bits registers. 43 00:03:39,800 --> 00:03:43,160 Actually, there are more than four segment registers we can use. 44 00:03:44,040 --> 00:03:47,370 But in this course, we only use these four registers. 45 00:03:48,580 --> 00:03:48,910 . 46 00:03:48,940 --> 00:03:51,810 CS stands for cde segment, 47 00:03:51,820 --> 00:03:53,260 DS Data segment. 48 00:03:53,590 --> 00:03:54,230 , 49 00:03:54,250 --> 00:03:57,460 ES Extra segment and SS stack segment. 50 00:03:58,750 --> 00:04:03,760 The format of an address is the segment register, then colon and offset. 51 00:04:04,740 --> 00:04:06,480 This is called logical address. 52 00:04:06,840 --> 00:04:08,820 So how do we get physical address? 53 00:04:09,880 --> 00:04:13,630 we shift the value in segment register left by 4 bits, 54 00:04:14,380 --> 00:04:21,160 that is multiply the value by 16 and then add the offset to the result which produces 20-bit physical address. 55 00:04:21,160 --> 00:04:21,880 . 56 00:04:23,070 --> 00:04:30,330 For example, this instruction will read the value in the address at ds 50 to ax. Suppose we have 1000 in ds register, 57 00:04:30,360 --> 00:04:32,220 , 58 00:04:33,320 --> 00:04:43,160 the memory address we get is 1000 times 16 and then plus the offset 50 which produces 10050 in hexadecimal. 59 00:04:43,160 --> 00:04:45,160 . 60 00:04:45,410 --> 00:04:53,570 the addresses in this example are hexadecimal value. We can use prefix 0x or suffix h to represent a hexadecimal number. 61 00:04:53,570 --> 00:04:53,990 . 62 00:04:55,330 --> 00:05:00,640 So this instruction will copy the data in address at 10050 to register ax. 63 00:05:00,640 --> 00:05:01,180 . 64 00:05:02,590 --> 00:05:11,070 Move on to the next example, suppose ss register holds the value 100 and sp, stack pointer register 65 00:05:11,080 --> 00:05:12,940 points to address 500. 66 00:05:14,110 --> 00:05:20,610 Stack operations such as, push and pop, use ss register. When we pop the value on the stack into register bx, 67 00:05:20,630 --> 00:05:29,740 the address of the value we pop is 100 times 16 plus 500. So the address of the memory 68 00:05:29,740 --> 00:05:33,760 we actually reference is 1500. 69 00:05:35,380 --> 00:05:40,750 CS and ES registers also follow the same rule calculating the memory address. 70 00:05:41,440 --> 00:05:45,400 some other instructions will use es register to access data, 71 00:05:45,950 --> 00:05:47,890 we will talk about that when we use them. 72 00:05:49,190 --> 00:05:52,790 and cs is used to calculate the address of instructions. 73 00:05:53,900 --> 00:05:59,250 Another thing I want to mention is that the default segment register for accessing data is ds register, 74 00:05:59,270 --> 00:05:59,870 , 75 00:06:00,680 --> 00:06:07,160 so if we don’t specify it in this example, ds is still used as default register. 76 00:06:09,180 --> 00:06:15,630 the general-purpose registers. As you can see, there are registers of different sizes. 77 00:06:15,630 --> 00:06:16,390 , 78 00:06:16,410 --> 00:06:21,450 For example, aL ah bL bh are available to use. 79 00:06:21,930 --> 00:06:28,110 We can also use 16-bit and 32-bit registers such as eax, ebx registers. 80 00:06:29,040 --> 00:06:32,790 Note that 64-bit registers are not available in real mode. 81 00:06:33,090 --> 00:06:37,220 Before we enter 64-bit mode, we don’t use them in our boot file. 82 00:06:38,230 --> 00:06:40,330 OK, now back to our boot file, 83 00:06:43,300 --> 00:06:45,530 Here is the first program which will print hello message on the screen. 84 00:06:45,550 --> 00:06:50,710 Since we will run the program without an operating system, 85 00:06:51,010 --> 00:06:52,900 we have to do everything ourselves. 86 00:06:53,470 --> 00:06:58,150 As you can see, we need a couple of blocks of code to print the simple hello message. 87 00:06:59,510 --> 00:07:02,090 Now I'm going to walk you through each line of the code. 88 00:07:02,750 --> 00:07:05,210 This file can be divided into several parts. 89 00:07:06,510 --> 00:07:13,050 The first part consists of two directives. The directive bits tells the assembler that our boot code is running on the 16-bit mode, 90 00:07:13,050 --> 00:07:20,010 because real mode which the boot code is running at is 16-bit mode. 91 00:07:20,730 --> 00:07:27,000 Directive org indicates that the code is supposed to be running at memory address 7c00. 92 00:07:27,000 --> 00:07:27,360 . 93 00:07:27,900 --> 00:07:33,810 Remember BIOS loads the boot code from the first sector into memory address 7c00 94 00:07:34,170 --> 00:07:39,000 and transfers the control to it. If we don’t specify 7c00 in this case, 95 00:07:39,010 --> 00:07:44,760 the memory reference within our code will be incorrect when the program is running. 96 00:07:45,660 --> 00:07:47,700 OK, move to the assembly code. 97 00:07:48,360 --> 00:07:52,530 We use label start to indicate that this is the start of our code. 98 00:07:53,520 --> 00:08:00,870 What this block of code does is initialize the segment registers and stack pointer. The xor instruction 99 00:08:00,900 --> 00:08:07,110 zeros ax register and then copies the value in ax which is 0, to 100 00:08:07,470 --> 00:08:09,990 ds, es and ss segment registers. 101 00:08:11,220 --> 00:08:17,760 So these segment registers are set to 0 after running the first four instructions. 102 00:08:17,760 --> 00:08:19,860 Since we set the segment registers to 0, 103 00:08:20,160 --> 00:08:25,620 we don’t concern about the value in these registers when we reference memory address later in the code. 104 00:08:26,580 --> 00:08:32,159 the offset is actually the memory address we want to access because segment registers are 0s now. 105 00:08:33,190 --> 00:08:39,250 Then we assign 7c00 to sp register. Here is the memory map after we initialize segment registers 106 00:08:39,250 --> 00:08:40,929 and stack pointer. 107 00:08:41,409 --> 00:08:46,360 The boot code starts at 7c00 and the stack is set up below 7c00. 108 00:08:47,900 --> 00:08:53,750 Because stack grows downwards, when we push a value into stack, 109 00:08:53,750 --> 00:09:01,400 the stack pointer will point to 2 bytes below 7c00 because the default operand size for push and pop instructions is 2 bytes in 16-bit mode. 110 00:09:01,400 --> 00:09:02,960 . 111 00:09:04,090 --> 00:09:09,460 As we push more data on the stack, sp register will decrement as shown in this example. 112 00:09:11,520 --> 00:09:11,950 OK. 113 00:09:11,980 --> 00:09:17,670 at this point, we can access data in memory and do stack operation such as push and pop. 114 00:09:18,780 --> 00:09:25,320 The next part, as its name implies, prints message on the screen so that we can see our system is running. 115 00:09:26,100 --> 00:09:29,100 Printing characters is done by calling BIOS service. 116 00:09:29,550 --> 00:09:31,490 The BIOS services can be accessed 117 00:09:31,490 --> 00:09:33,690 with BIOS interrupts. 118 00:09:33,840 --> 00:09:35,690 As you see, we use instruction int, 119 00:09:37,090 --> 00:09:44,320 and then the interrupt number to call the specific BIOS service. 120 00:09:44,320 --> 00:09:47,620 The print function is interrupt 10 and notice that this number is hexadecimal number. 121 00:09:48,700 --> 00:09:51,610 Before we call a BIOS function, we need to pass parameters. 122 00:09:52,210 --> 00:09:58,320 Print function has relatively large set of parameters for different purposes. Since we only want to print string 123 00:09:58,330 --> 00:10:00,040 in the boot process, 124 00:10:00,070 --> 00:10:01,890 let’s see how to pass parameters to print string. 125 00:10:01,900 --> 00:10:02,860 . 126 00:10:03,930 --> 00:10:11,730 Register ah holds the function code. Here we use 13 which means print string. 127 00:10:11,730 --> 00:10:16,920 Register AL specifies the write mode, we set it to 1, meaning that the cursor will be placed at the end of the string. 128 00:10:18,010 --> 00:10:21,420 Then we save A to bx. 129 00:10:22,060 --> 00:10:27,610 Bh which is the higher part of bx register represents page number. 130 00:10:27,610 --> 00:10:31,810 BL, the lower part of bx holds the information of character attributes. 131 00:10:33,230 --> 00:10:41,510 We also zero register dx. Dh which is higher part of dx register represents rows and dL represents columns. 132 00:10:41,510 --> 00:10:42,080 . 133 00:10:42,650 --> 00:10:49,880 Since we want to print message at the beginning of the screen, we set them to 0. Bp holds the address of the string 134 00:10:49,880 --> 00:10:51,500 we want to print. 135 00:10:52,190 --> 00:10:55,520 As you see, we defined the message hello. 136 00:10:56,890 --> 00:11:00,150 Next instruction copies the address of message to bp register. 137 00:11:01,000 --> 00:11:04,460 If we want to copy the data in the variable message to bp, 138 00:11:04,480 --> 00:11:05,500 we need to add 139 00:11:07,660 --> 00:11:10,450 square brackets to do that. 140 00:11:11,080 --> 00:11:17,320 But what we want here is just the address of the variable message, so we don't use square brackets. 141 00:11:19,250 --> 00:11:26,240 Ok. Let’s move on. CX specifies the number of characters to print. The dollar sign means the current assembly position 142 00:11:26,240 --> 00:11:31,850 which is the end of the message in this case. Minus the address of message, 143 00:11:32,510 --> 00:11:36,200 the result is the size of string or the number of characters. 144 00:11:37,320 --> 00:11:45,870 Here we define a constant message length, using directive equ, to represent the number of characters, and copy it to register cx. 145 00:11:45,870 --> 00:11:46,730 . 146 00:11:46,910 --> 00:11:47,300 Ok. 147 00:11:48,570 --> 00:11:53,880 With all the parameters initialized, we will see the message hello printed on screen 148 00:11:54,120 --> 00:11:55,890 once we execute int instruction. 149 00:11:57,170 --> 00:12:02,260 And then we reach the end of the code. Halt instruction places the processor in a halt state. 150 00:12:02,270 --> 00:12:06,740 Note that interrupts will resume execution. 151 00:12:07,130 --> 00:12:10,880 So here we write jump End right after the halt instruction. 152 00:12:11,850 --> 00:12:17,830 If an interrupt is fired, the processor will run the instruction following halt, 153 00:12:17,910 --> 00:12:19,740 which is jump instruction in this case. 154 00:12:20,220 --> 00:12:24,870 As a result, we will jump back to label end and execute halt instruction again. 155 00:12:25,590 --> 00:12:27,090 So this is an infinite loop. 156 00:12:28,190 --> 00:12:29,510 The code just ends here. 157 00:12:30,550 --> 00:12:31,270 Let's move on. 158 00:12:33,830 --> 00:12:35,660 Directive times repeats command the specific times. 159 00:12:35,760 --> 00:12:43,610 Here the expression specifies how many times db is repeated. The double dollar sign 160 00:12:43,610 --> 00:12:45,650 means the beginning of the current section. 161 00:12:46,100 --> 00:12:50,300 We only have one section in this example. So it represents the start of our code. 162 00:12:51,800 --> 00:12:57,470 The dollar sign minus double dollar sign represents the size from the start of the code to the end of the message. 163 00:12:57,470 --> 00:12:58,010 . 164 00:12:59,890 --> 00:13:06,790 Then we subtract it from 1be. The result is the size shown here. As you can see, 165 00:13:06,790 --> 00:13:12,730 the result of this expression is repeat db command so that the space from the end of the message to the offset 1be are filled with 0s. 166 00:13:12,730 --> 00:13:15,190 . 167 00:13:16,030 --> 00:13:18,580 So what is in the offset 1be? 168 00:13:19,450 --> 00:13:23,380 In offset 1be, we have what it’s called partition entries. 169 00:13:23,890 --> 00:13:27,790 There are four entries with each entry being 16 bytes in size. 170 00:13:28,480 --> 00:13:32,950 You can see we only defined the first entry and other entries are simply set to 0. 171 00:13:33,700 --> 00:13:36,370 The format of partition entry is shown here. 172 00:13:36,910 --> 00:13:39,190 The first byte is boot indicator, 173 00:13:39,460 --> 00:13:43,270 we simply set 80 meaning that this is a bootable partition. 174 00:13:44,170 --> 00:13:50,980 The next three bytes construct a starting c,h,s value. C stands for cylinder, h, head and s, sector. 175 00:13:51,400 --> 00:13:55,600 The first byte represents head value. 176 00:13:56,140 --> 00:14:00,550 The second byte is divided into two parts. Bits 0 through 5 177 00:14:00,760 --> 00:14:02,020 is used as sector value. 178 00:14:03,870 --> 00:14:06,670 Bits 6 to 7 is used as cylinder value. 179 00:14:08,930 --> 00:14:11,900 The last byte holds the lower 8 bits of cylinder value. 180 00:14:13,800 --> 00:14:20,250 The head and cylinder values start from 0, so cylinder 0 is the first cylinder. 181 00:14:20,250 --> 00:14:25,320 Whereas sector values start from 1, meaning that sector 1 is the first sector. 182 00:14:26,470 --> 00:14:27,480 OK, let's move on. 183 00:14:28,180 --> 00:14:30,610 The fourth byte is partition type, 184 00:14:31,100 --> 00:14:33,130 we set it to f0. 185 00:14:34,570 --> 00:14:41,680 Then the next three bytes construct the ending c,h,s value. Here we simply set them to ff. 186 00:14:41,680 --> 00:14:43,660 FF is the max value we can set in a byte. 187 00:14:44,770 --> 00:14:48,990 The next double word represents LBA address of starting sector. 188 00:14:49,430 --> 00:14:53,360 LBA means logical block address. In the boot process, 189 00:14:53,430 --> 00:14:57,470 we will load our file using Lba instead of c,h,s value. 190 00:14:58,890 --> 00:15:02,310 The last double word specifies how many sectors the partition has. 191 00:15:02,940 --> 00:15:05,490 The value we set is 10mb. 192 00:15:06,510 --> 00:15:10,590 If you don’t fully understand the meaning of each field of the entry, no worries. 193 00:15:10,620 --> 00:15:16,470 Because the value in the partition entry does not reflect the real partition. 194 00:15:17,130 --> 00:15:22,920 The reason we define this entry is that some BIOS will try to find the valid looking partition entries. 195 00:15:22,920 --> 00:15:25,680 . 196 00:15:25,770 --> 00:15:30,270 If it is not found, the BIOS will treat usb flash drive as, for example, floppy disk. 197 00:15:31,410 --> 00:15:39,450 We want BIOS to boot the usb flash drive as hard disk. So we add the partition entry to construct a seemingly valid entries. 198 00:15:39,450 --> 00:15:41,010 . 199 00:15:43,390 --> 00:15:50,980 The last two bytes of our boot file is signature which should be 55AA. 200 00:15:50,980 --> 00:15:53,680 the size of boot file is 512 bytes. 201 00:15:54,820 --> 00:15:59,350 So sector size is assumed to be 512 bytes in this course. 202 00:15:59,800 --> 00:16:05,740 When we write the boot file into the boot sector, it gets the same data as boot file. 203 00:16:07,900 --> 00:16:08,300 OK. 204 00:16:08,320 --> 00:16:15,760 With boot the image prepared, we use build script to build our project and write the binary file into boot image. 205 00:16:15,760 --> 00:16:16,300 . 206 00:16:16,900 --> 00:16:20,890 If you know the makefile, you can build the project using Makefile. 207 00:16:21,580 --> 00:16:24,340 In this course, we will use a simple build script. 208 00:16:26,300 --> 00:16:28,340 So let's create a new file. 209 00:16:30,430 --> 00:16:38,170 In the script, we have two commands to write, the first one assembles the boot file. 210 00:16:38,170 --> 00:16:40,810 The assembler we use in this example is nasm. 211 00:16:41,230 --> 00:16:43,120 So we write nasm 212 00:16:44,520 --> 00:16:45,180 -f 213 00:16:46,020 --> 00:16:51,780 f to specify the output file we want to produce which is binary file. 214 00:16:53,580 --> 00:16:54,720 Then dash o, 215 00:16:56,810 --> 00:17:02,870 the name of the output file. We name it boot.bin 216 00:17:04,390 --> 00:17:08,770 and the file we want to assemble, that is boot.asm. 217 00:17:10,609 --> 00:17:14,869 If the assembly file has no errors, we will see the boot.bin generated. 218 00:17:15,800 --> 00:17:23,390 What we are going to do next is we are going to write the binary file into the boot image 219 00:17:23,390 --> 00:17:24,260 to make it bootable. 220 00:17:25,589 --> 00:17:29,340 So the command we use is dd command. 221 00:17:30,630 --> 00:17:31,950 And input file, 222 00:17:33,600 --> 00:17:36,180 which is boot.bin 223 00:17:37,910 --> 00:17:39,320 and the output file, 224 00:17:41,430 --> 00:17:42,480 boot image. 225 00:17:42,600 --> 00:17:43,140 . 226 00:17:45,220 --> 00:17:46,930 and then the block size 227 00:17:49,240 --> 00:17:51,460 is set to 512 bytes. 228 00:17:52,910 --> 00:17:58,070 The count means how many blocks we want to write. Here we set it to one 229 00:17:59,290 --> 00:18:04,900 because we only write the boot.bin which is 512 bytes into the first sector. 230 00:18:04,920 --> 00:18:06,060 And then we specify 231 00:18:08,180 --> 00:18:08,780 no trunc 232 00:18:11,160 --> 00:18:17,250 which means we don’t truncate the output file. So the size of boot image remains unchanged 233 00:18:17,640 --> 00:18:19,110 after we run dd command. 234 00:18:20,830 --> 00:18:26,410 If you choose the Windows environment just like I did in this video, there is extra work we need to do. 235 00:18:26,410 --> 00:18:26,800 . 236 00:18:27,430 --> 00:18:31,810 First, we show the status bar by clicking view in the menu. 237 00:18:33,080 --> 00:18:36,230 In appearance, we Click Show Status Bar. 238 00:18:39,380 --> 00:18:47,600 At the bottom of the screen, you can see the status bar. Noticed that there is CRLF, which is 239 00:18:47,600 --> 00:18:54,980 line break in windows and we need to choose LF which works for build script in Linux, so we click it 240 00:18:56,040 --> 00:18:58,230 and choose LF. 241 00:18:59,270 --> 00:19:01,040 All right, now, we can save it 242 00:19:05,640 --> 00:19:07,170 to shell script. 243 00:19:09,870 --> 00:19:10,200 Here. 244 00:19:12,510 --> 00:19:14,310 and name it build. 245 00:19:16,480 --> 00:19:17,140 click save. 246 00:19:18,080 --> 00:19:23,870 OK, that's it, since this is our first program, we will see how to build the project in Windows 10, 247 00:19:23,870 --> 00:19:30,920 Linux and Mac OS in the next three lectures, so you can choose one of those lectures based on the 248 00:19:30,920 --> 00:19:32,690 operating system you are working on.