1 00:00:01,569 --> 00:00:07,960 we are finally ready to switch to long mode. Before we enter long mode, we should jump to protected mode first 2 00:00:07,960 --> 00:00:08,490 . 3 00:00:09,010 --> 00:00:13,480 So in the protected mode, the only thing we will do is preparing for long mode. 4 00:00:14,110 --> 00:00:16,920 As you see, we are currently running in the real mode. 5 00:00:17,240 --> 00:00:19,690 Then we prepare and enter protected mode. 6 00:00:20,350 --> 00:00:22,270 What we are going to do in this lecture 7 00:00:22,270 --> 00:00:25,960 is talking about protected mode and write code to switch to it in our project 8 00:00:26,260 --> 00:00:26,860 . 9 00:00:27,870 --> 00:00:29,580 OK, let's see how to do it. 10 00:00:30,300 --> 00:00:32,210 There are a couple of things we need to do. 11 00:00:35,340 --> 00:00:44,460 Loading GDT global descriptor table and IDT interrupt descriptor table. 12 00:00:44,460 --> 00:00:48,420 Enable protected mode by setting specific registers and jump to the start entry. 13 00:00:49,730 --> 00:00:52,130 So what is global descriptor table? 14 00:00:54,020 --> 00:01:00,680 Global descriptor table is a structure in memory created by us and is used by CPU to protect the memory. 15 00:01:00,680 --> 00:01:07,910 Each table entry here describes a block of memory or segment and it takes up 8 bytes 16 00:01:07,910 --> 00:01:08,420 , 17 00:01:08,750 --> 00:01:10,640 and the first entry should be empty. 18 00:01:11,560 --> 00:01:14,410 We can have more than 8000 entries in the GDT. 19 00:01:16,190 --> 00:01:19,210 But we only need at most 5 entries in this course. 20 00:01:20,340 --> 00:01:25,650 When the processor accesses memory, the value in segment register is actually an index within the gdt 21 00:01:25,650 --> 00:01:26,310 . 22 00:01:27,090 --> 00:01:32,880 This is different than we saw in real mode where the value in segment register gets shifted 4 bits 23 00:01:32,880 --> 00:01:38,760 and added to the offset. In protected mode, segment registers 24 00:01:38,760 --> 00:01:40,860 hold the index instead of base address. 25 00:01:41,820 --> 00:01:43,530 Let's take a look at an example. 26 00:01:45,050 --> 00:01:52,400 This instruction will copy the value in ds 1000 to register eax. Suppose ds holds value 16 27 00:01:52,490 --> 00:01:53,180 . 28 00:01:54,250 --> 00:01:59,920 Remember each table entry takes up 8 bytes. So 16 here points to the third entry. 29 00:02:01,580 --> 00:02:06,980 The segment registers also hold other information in the lower 3 bits. We will see that in a moment. 30 00:02:06,980 --> 00:02:15,170 The descriptor entry includes base address of the segment, segment limit and segment attributes. 31 00:02:15,170 --> 00:02:17,670 Suppose the third entry has base address 0. 32 00:02:18,350 --> 00:02:21,550 So the base of this segment we are about to access is 0. 33 00:02:22,040 --> 00:02:26,120 Then we add the offset to the base which produces the address 1000. 34 00:02:27,900 --> 00:02:33,750 The descriptor entry includes the segment attributes. So if we don’t have right to access the memory, 35 00:02:34,100 --> 00:02:37,550 this operation will fail and the exception is generated. 36 00:02:38,240 --> 00:02:42,360 If all the checks are done, we can access the data in that memory location. 37 00:02:42,920 --> 00:02:48,560 So when running in protected mode, setting up different segments with different privilege levels, 38 00:02:48,560 --> 00:02:53,270 we can protect the essential data from accessing by user applications. 39 00:02:54,760 --> 00:02:58,240 What we are going to see next is interrupt descriptor table. 40 00:02:59,280 --> 00:03:05,460 First off, we talk about what is an interrupt. Basically, an interrupt is a signal sent from a device to CPU 41 00:03:05,460 --> 00:03:12,660 and if CPU accept the interrupt, it will stop the current task and process the interrupt event. 42 00:03:13,470 --> 00:03:17,370 Different numbers are assigned to different interrupts. For example, 43 00:03:17,880 --> 00:03:20,760 the interrupt request number of keyboard is 1. 44 00:03:21,810 --> 00:03:25,950 So when we press a key, the interrupt request number we will get is 1. 45 00:03:26,790 --> 00:03:33,030 And also, a handler or interrupt service routine is used so that cpu will call that handler to do the specific task 46 00:03:33,030 --> 00:03:36,000 when an interrupt is fired. 47 00:03:37,170 --> 00:03:42,840 In order to find that handler, we use interrupt descriptor table which could have a total of 256 entries 48 00:03:42,840 --> 00:03:44,220 . 49 00:03:45,770 --> 00:03:51,320 As we have seen the global descriptor table, the interrupt descriptor table is also a structure in memory created by us. 50 00:03:51,320 --> 00:03:57,780 But the fields of IDT entries are different from those of GDT entries. 51 00:03:58,700 --> 00:04:00,100 OK, let's see an example. 52 00:04:01,400 --> 00:04:07,610 Suppose the interrupt request number 3 is fired, cpu will get the corresponding item in the IDT. 53 00:04:09,870 --> 00:04:16,680 In the idt entry, we have the information about which segment the interrupt service routine is at and the offset of the routine. 54 00:04:16,680 --> 00:04:23,210 In this example, the base address of the segment this handler locates at is 0 55 00:04:24,080 --> 00:04:26,120 and the offset is 5000. 56 00:04:28,290 --> 00:04:33,700 Then we get the address of the interrupt handler and the code of the handler is executed. 57 00:04:34,320 --> 00:04:39,930 This is the simplified process of interrupt handling. We will delve into details in the next section. 58 00:04:41,470 --> 00:04:44,800 Now move on to the privilege rings or privilege levels. 59 00:04:46,270 --> 00:04:53,590 As you can see, we have 4 rings from ring 0 to ring 3 in x86 architecture. 60 00:04:53,590 --> 00:04:59,050 The ring 0 is the most privileged level where we can execute all the instructions and access all the hardware 61 00:04:59,080 --> 00:04:59,650 . 62 00:05:00,900 --> 00:05:07,680 Ring 3 is the least privileged level where we can only execute a subset of instructions and accessing hardware 63 00:05:07,680 --> 00:05:09,290 is generally not allowed. 64 00:05:10,160 --> 00:05:16,970 Although cpu offers 4 levels of privileges, we only use 2 of them in this course, 65 00:05:16,970 --> 00:05:19,670 which is ring 0 in which our kernel runs 66 00:05:20,610 --> 00:05:23,550 and ring 3 where user applications execute. 67 00:05:25,120 --> 00:05:30,890 When we learn how the privilege check performs, we will use the term ring0 and ring3 all the time 68 00:05:30,890 --> 00:05:31,330 . 69 00:05:32,400 --> 00:05:35,940 So how do we know what level we are currently running at? 70 00:05:38,140 --> 00:05:44,530 The CPL current privilege level will tell us. It’s stored in the lower 2 bits of cs and ss segment registers. 71 00:05:44,530 --> 00:05:51,570 So when the lower 2 bits of cs and ss segment registers are 0, we are in ring0 72 00:05:51,790 --> 00:05:52,560 . 73 00:05:53,410 --> 00:05:56,140 If they are 3, then we are running in ring3. 74 00:05:56,650 --> 00:06:04,240 We will see the details of segment registers in a minute. . In protected mode, the cpl and rpl requested privilege level 75 00:06:04,240 --> 00:06:12,010 will compare against dpl descriptor privilege level, if the check fails, 76 00:06:12,010 --> 00:06:13,450 the cpu exception is generated. 77 00:06:14,630 --> 00:06:18,620 DPL is stored in descriptor table entries 78 00:06:18,650 --> 00:06:21,330 and rpl is stored in the selectors. Let’s see an example 79 00:06:21,410 --> 00:06:21,980 . 80 00:06:23,900 --> 00:06:32,000 As you see, the segment selector, as its name implies, holds the index of gdt or ldt local descriptor table entries. 81 00:06:32,000 --> 00:06:40,600 TI means table indicator. When TI=0 the GDT is used and when TI=1 the LDT is used. 82 00:06:41,060 --> 00:06:44,870 We always set it to 0, 83 00:06:44,960 --> 00:06:53,510 since we don’t use ldt in our system. RPL is stored in the lower 2 bits. Suppose we load the selector 84 00:06:53,510 --> 00:06:59,920 which points to the third entry of GDT in ds register, the privilege checks is performed. 85 00:07:00,830 --> 00:07:08,270 The cpl which is stored in cs and ss register, rpl specified in this selector are compared 86 00:07:08,270 --> 00:07:10,820 with DPL in the third GDT entry. 87 00:07:11,600 --> 00:07:18,650 If the cpl and rpl is numerically smaller or equal to dpl, the privilege checks pass. 88 00:07:19,520 --> 00:07:25,370 And the descriptor will be loaded in the hidden part of ds register if other checks also pass in this example 89 00:07:25,400 --> 00:07:25,940 . 90 00:07:27,720 --> 00:07:30,720 Ok now let’s see the details of descriptors. 91 00:07:34,010 --> 00:07:39,950 What is showing here is the general structure of data and code segment descriptors. 92 00:07:39,950 --> 00:07:46,670 As you see, we have base address of the segment, and the limit which specifies the size of the segment. 93 00:07:46,670 --> 00:07:50,780 The attribute includes DPL field, writeable, accessed bits etc. 94 00:07:52,860 --> 00:07:59,040 For example, if the DPL of the descriptor is 0, then we are not allowed to load this descriptor in the segment registers 95 00:07:59,040 --> 00:08:02,160 if we are running in ring3. 96 00:08:03,700 --> 00:08:09,410 One more thing worth mentioning is that we don’t go into details about the descriptor in protected mode 97 00:08:09,460 --> 00:08:13,140 because the segmentation is disabled in 64-bit mode. 98 00:08:13,150 --> 00:08:20,260 For example, the base and limits of descriptors are ignored. The memory is like 99 00:08:20,260 --> 00:08:23,740 flat memory starting from 0 and spanning all of the memory. 100 00:08:24,310 --> 00:08:29,260 And also, the segment register we use in 64-bit mode is only cs register. 101 00:08:30,250 --> 00:08:36,880 We don’t care about ds and es registers. ss register is only used in context switch. 102 00:08:38,169 --> 00:08:43,870 What I’m saying is that all you need to know in this course is the general idea of 103 00:08:43,870 --> 00:08:48,760 what the gdt idt and selectors are and how they interact with each other. 104 00:08:50,390 --> 00:08:50,850 Alright. 105 00:08:50,900 --> 00:08:54,110 we open the project and see how to enable protected mode. 106 00:08:56,380 --> 00:09:00,270 The print message is not used here, we just remove it. 107 00:09:06,210 --> 00:09:11,980 The first thing we are going to do is that we disable interrupt when we are doing mode switch 108 00:09:11,980 --> 00:09:14,670 so that the processor will not respond to the interrupt. 109 00:09:15,240 --> 00:09:19,740 After we switch to long mode, we will reenable and process the interrupts. 110 00:09:20,250 --> 00:09:22,130 The instruction we use is clear interrupt flag 111 00:09:22,140 --> 00:09:23,100 . 112 00:09:27,280 --> 00:09:34,600 Then we load the gdt and idt structure. Remember gdt and idt structure are stored in memory 113 00:09:34,600 --> 00:09:36,890 and we need to tell the processor where they are located. 114 00:09:37,720 --> 00:09:44,380 There is a register called global descriptor table register which points to the location of the GDT in memory 115 00:09:44,740 --> 00:09:48,910 and we load the register with the address of gdt and the size of gdt. 116 00:09:50,990 --> 00:09:53,920 The instruction we use is lgdt 117 00:09:57,050 --> 00:10:02,540 which takes one memory operand. So we define a variable called gdt pointer. 118 00:10:07,760 --> 00:10:13,850 Actually, we have two parts in this variable. The first two bytes are the size of gdt minus 1. 119 00:10:18,300 --> 00:10:20,340 The next four bytes are the address of gdt. 120 00:10:25,470 --> 00:10:28,580 We haven’t defined gdt yet. So let’s do it. 121 00:10:32,310 --> 00:10:36,060 The start of gdt is called gdt32 in this example. 122 00:10:39,520 --> 00:10:41,770 Remember the first entry is empty, 123 00:10:41,860 --> 00:10:44,800 so here we define the first entry with 0. 124 00:10:47,800 --> 00:10:55,390 Each entry is 8 bytes, we use directive dq to allocate 8 bytes space. Ok next we can define 125 00:10:55,390 --> 00:10:57,280 a code segment descriptor. 126 00:11:00,140 --> 00:11:03,530 So we can define the label code32. 127 00:11:07,820 --> 00:11:14,180 As we have seen it in the slide, the descriptor is divided into several parts. The first two bytes are 128 00:11:14,180 --> 00:11:16,490 the lower 16 bits of segment size. 129 00:11:16,970 --> 00:11:20,070 We want to define the code segment to the maximum size. 130 00:11:20,090 --> 00:11:23,090 So we set it to ffff. 131 00:11:28,420 --> 00:11:33,700 The next three bytes are the lower 24 bits of base address which we set to 0, 132 00:11:39,930 --> 00:11:42,490 meaning that the code segment starts from 0. 133 00:11:43,200 --> 00:11:50,130 Then we move to the fourth byte which specifies the segment attributes. The s means the segment descriptor is 134 00:11:50,130 --> 00:11:51,960 a system descriptor or not. 135 00:11:52,410 --> 00:11:56,990 Here we set it to 1 meaning that it’s a code or data segment descriptor. 136 00:11:57,480 --> 00:12:04,740 The type field is assigned to the value 10 or 1010 in binary, 137 00:12:04,740 --> 00:12:07,570 which means that the segment is non-conforming readable code segment. 138 00:12:08,290 --> 00:12:14,640 The major difference between conforming and non-conforming code segment is that 139 00:12:14,640 --> 00:12:20,490 the cpl is not changed when the control is transferred to higher privilege conforming code segment from lower one. 140 00:12:20,490 --> 00:12:26,790 Conforming code segment is not used in our system. As for non-conforming code, 141 00:12:27,180 --> 00:12:33,720 in the next section we will see how to transfer the execution from lower privilege non-conforming code segment to the higher one 142 00:12:34,110 --> 00:12:34,950 . 143 00:12:35,820 --> 00:12:38,940 The dpl indicates privilege level of the segment. 144 00:12:40,660 --> 00:12:47,510 Because we want to be running at ring0 when we jump to protected mode. We set dpl to 0 145 00:12:47,530 --> 00:12:54,880 and when we load the descriptor to cs register, the cpl will be 0 indicating that we are at ring0. 146 00:12:55,840 --> 00:13:02,710 Let’s move on. P is present bit which means we need to set it to 1 when we load the descriptor, 147 00:13:03,040 --> 00:13:05,530 otherwise the cpu exception is generated. 148 00:13:06,630 --> 00:13:08,130 So we assign the value 149 00:13:09,780 --> 00:13:10,560 9a. 150 00:13:16,630 --> 00:13:22,570 The next bytes is a combination of segment size and attributes. The lower half is the upper 4 bits of segment size. 151 00:13:22,570 --> 00:13:25,930 We set it to the maximum size. 152 00:13:26,170 --> 00:13:29,470 The available bit can be used by the system software, 153 00:13:29,470 --> 00:13:30,710 we simply ignore it. 154 00:13:31,330 --> 00:13:34,720 The db bit which is default operand size bit. 155 00:13:35,170 --> 00:13:39,550 If this bit is 1, it means that the default operand size is 32bits. 156 00:13:39,790 --> 00:13:45,400 Otherwise, the default operand size is 16bits. We set it to 1 in the protected mode. 157 00:13:46,610 --> 00:13:53,270 G granularity bit. We simply set it to 1 meaning that the size field is scaled by 4kb 158 00:13:53,810 --> 00:13:57,770 which gives us the maximum of 4gb of segment size. 159 00:13:59,150 --> 00:14:03,290 Ok, with all the bits set here, we assign cf to this byte. 160 00:14:08,890 --> 00:14:13,660 The last byte is the upper 8 bits of base address. We simply set to 0. 161 00:14:17,990 --> 00:14:24,290 Alright, this is the code segment descriptor we will use in protected mode. Since we will access data in memory, 162 00:14:24,290 --> 00:14:31,340 the data segment is also needed. The structure of code segment and data segment descriptors is very similar 163 00:14:31,340 --> 00:14:32,260 . 164 00:14:32,690 --> 00:14:34,820 So we just copy and paste here. 165 00:14:41,790 --> 00:14:43,830 Name it data32. 166 00:14:46,660 --> 00:14:50,900 The base address and size of data segment are the same as code segment. 167 00:14:50,930 --> 00:14:54,440 The only change we need to make in this case is the type field. 168 00:14:55,000 --> 00:15:01,000 We set it to 0010 in binary which means the segment is readable and writeable data segment. 169 00:15:01,020 --> 00:15:05,410 If we only set the segment to readable data segment, 170 00:15:06,400 --> 00:15:11,710 when we write data into the memory location referenced by this segment descriptor, the exception will be generated 171 00:15:11,710 --> 00:15:12,720 . 172 00:15:13,240 --> 00:15:18,840 So be sure to set the writeable bit to 1 to make the segment writable. 173 00:15:19,160 --> 00:15:22,840 OK, so we change 9a to 92. 174 00:15:24,220 --> 00:15:29,020 Ok, the code and data segment descriptors are all we need in protected mode 175 00:15:30,370 --> 00:15:37,540 and we can calculate the length of the descriptor table. We define a constant gdt32 length. 176 00:15:44,530 --> 00:15:51,780 Right now we assign the length of the table to the first two bytes and the address of gdt to the next four bytes. 177 00:15:52,900 --> 00:15:56,230 So we load gdt and the address of gdt pointer. 178 00:16:01,580 --> 00:16:09,170 Note that the default operand size of lgdt instruction is 16 bits in 16-bit mode. 179 00:16:09,170 --> 00:16:14,600 If the operand size is 16 bits, the address of gdt pointer is actually 24 bits. 180 00:16:15,260 --> 00:16:21,800 But here we simply define the address to be 32bits. and assgin the address of gdt to the lower 24bits 181 00:16:21,800 --> 00:16:22,670 . 182 00:16:24,250 --> 00:16:30,130 The next thing we are going to do is load interrupt descriptor table. Just like global descriptor table register 183 00:16:30,130 --> 00:16:31,160 , 184 00:16:31,180 --> 00:16:38,140 we also have interrupt descriptor table register and we need to load the register with the address and size of idt 185 00:16:38,140 --> 00:16:39,640 . 186 00:16:40,220 --> 00:16:43,090 The instruction we use is load idt instruction. 187 00:16:48,860 --> 00:16:54,740 Since we don’t want to deal with interrupts until we jump to long mode, we load the register with 188 00:16:54,740 --> 00:16:56,570 invalid address and size zero. 189 00:17:01,220 --> 00:17:02,760 So we defines a variable 190 00:17:04,569 --> 00:17:06,619 idt32 pointer 191 00:17:12,010 --> 00:17:13,619 and assign zero to it. 192 00:17:17,170 --> 00:17:18,310 Now we load this 193 00:17:20,300 --> 00:17:21,319 idt pointer 194 00:17:27,579 --> 00:17:35,100 Note that there is one type of interrupt called non-maskable interrupt which is not disabled by the instruction cli. 195 00:17:35,110 --> 00:17:38,480 So when non-maskable interrupt occurs, 196 00:17:38,580 --> 00:17:41,200 the processor will find the idt in memory. 197 00:17:42,240 --> 00:17:48,600 The cpu exception will be generated because the address and size of idt are invalid in this case 198 00:17:48,600 --> 00:17:52,860 and eventually our system will be reset, which is what we want. 199 00:17:53,040 --> 00:17:59,820 The reason is that non-maskable interrupts indicate that non-recoverable hardware errors such as ram error, 200 00:18:00,510 --> 00:18:02,370 there is no need to boot our system if such error occurs. 201 00:18:02,370 --> 00:18:06,480 The only thing we do is reset the computer. 202 00:18:07,950 --> 00:18:14,430 After we load gdt and idt, then we enable protected mode by setting the protected mode enable bit in cr0 register. 203 00:18:14,430 --> 00:18:22,920 The cr0 is a control register which changes or controls the behavior of the processor. 204 00:18:23,520 --> 00:18:26,510 The bit 0 is the protected mode enable bit. 205 00:18:26,640 --> 00:18:29,610 So we copy the content of cr0 to eax. 206 00:18:35,790 --> 00:18:38,940 And set the bit 0 to 1 using or instruction. 207 00:18:42,260 --> 00:18:45,620 Writing the value in eax back to cr0 208 00:18:50,230 --> 00:18:56,880 and we have enabled the protected mode. The last thing we will do is load the cs segment register 209 00:18:56,890 --> 00:19:00,910 with the new code segment descriptor we just defined in the gdt table. 210 00:19:02,980 --> 00:19:09,160 Loading code segment descriptor to cs register is different from another segment registers. 211 00:19:09,190 --> 00:19:12,620 We cannot use mov instruction to load cs register. 212 00:19:13,120 --> 00:19:15,610 Instead we use jump instruction to do it. 213 00:19:19,490 --> 00:19:25,520 The code segment descriptor in this example is the second entry which is 8 bytes away from the beginning of the gdt. 214 00:19:25,520 --> 00:19:30,320 So the selector we use is index being 8 215 00:19:31,780 --> 00:19:36,530 and TI 0 meaning that we use gdt and the rpl is 0. 216 00:19:36,910 --> 00:19:44,230 Therefore when cpu performs privilege checks, the rpl is 0 and is equal to the dpl of code segment. 217 00:19:44,410 --> 00:19:45,670 The check will pass. 218 00:19:46,180 --> 00:19:48,500 Then we also need to specify the offset, 219 00:19:49,090 --> 00:19:53,170 we want to jump to the protected mode entry which is label we will define. 220 00:19:59,290 --> 00:20:05,260 Before we define the label, we use directive bits to indicate that the following code is running at 32-bit mode 221 00:20:05,260 --> 00:20:06,270 . 222 00:20:11,770 --> 00:20:15,250 Ok now we define protected mode entry 223 00:20:17,300 --> 00:20:19,760 and initialize other segment registers 224 00:20:26,080 --> 00:20:29,500 such as ds es and ss registers. 225 00:20:30,890 --> 00:20:36,500 We load them with data segment descriptors. The data segment descriptor is the third entry. 226 00:20:36,830 --> 00:20:43,100 So we the index is 16 or 10 in hexadecimal. As for these segment registers, 227 00:20:43,100 --> 00:20:44,630 we can use move instruction to initialize them. 228 00:20:52,260 --> 00:20:56,730 Then we set stack pointer esp to 7c00. 229 00:21:02,050 --> 00:21:07,330 In order to see we actually enter the protected mode, we can print a character P to let us know. 230 00:21:07,870 --> 00:21:16,930 So we print P in the beginning of screen. Remember the base address for text mode is b8000 231 00:21:16,930 --> 00:21:18,640 and every character takes up 2 bytes of space. 232 00:21:26,830 --> 00:21:29,170 The first byte is for ascii code P 233 00:21:32,680 --> 00:21:35,740 and the second byte attribute of the character. 234 00:21:43,060 --> 00:21:48,130 The last thing we do is end the program using the infinite loop just as we did before. 235 00:21:50,720 --> 00:21:52,520 So we define a label pend 236 00:21:55,250 --> 00:21:57,110 and halt the processor 237 00:22:05,910 --> 00:22:11,490 Since we were only print character p, so the message here is not used. 238 00:22:14,710 --> 00:22:21,310 This block of code is used before we jump into protected mode, so we should place 239 00:22:22,420 --> 00:22:23,410 this block of code 240 00:22:29,800 --> 00:22:30,940 at the end of the code 241 00:22:35,740 --> 00:22:36,880 in 16-bit mode. 242 00:22:38,340 --> 00:22:41,700 Alright, we save the file and in the terminal 243 00:22:44,200 --> 00:22:45,460 we run the build script. 244 00:22:47,660 --> 00:22:49,480 OK, now let's run the bochs. 245 00:22:55,920 --> 00:22:58,860 as you see, the character p is printed. 246 00:22:59,720 --> 00:23:03,080 So we know that we are entering the protected mode. 247 00:23:07,210 --> 00:23:15,450 Here is testing on the real machine, you can see p is printed. In the video, we will switch to long mode. 248 00:23:15,850 --> 00:23:17,080 See you in the next video.