Toby Opferman http://www.opferman.net programming@opferman.net Protected Mode Tutorial II If you have read PM Tut 1, this is the supplementary Tutorial that tells you how to set protected mode physically. So, after you have read the basics from the first tutorial, let's talk about setting protected mode! This tutorial only tells you how to set pmode. You should get supplementary material to perform more operations and use protected mode. There is a lot *NOT* covered because it is not needed to set pmode. Descriptor Tables We got to start with Descriptor Tables. What are they? They hold information about mapping physical memory to virtual memory. Each entry in a descriptor table has a certain format. Each holds the length, the physical address and they have some attributes assocaited. The attributes can set readonly/execute permissions, as well as what DPL the memory will belong. The privledge level. Application programs should be in Ring3, lowest privledge level and the OS can limit what Ring3 can do. Ring0 is where the OS goes, it's the most privledged level. Setting the Tables Before you switch to protected mode, you should set these tables. Actually, you only need to set IDT and GDT, you can leave LDT alone when first setting protected mode. So, I am going to show you how to set up your GDT. For the IDT, we can cheat and you can set it up later and LDT we are going to ignore since it is not required to switch to pmode. First, we just want to do this. Find a location in memory for the IDT and just set it to 0. Then, we use CLI so when we switch, the interrupt table is not being used :). This simplifies things. LIDT [QWORD PTR IDT_BASE_ADDR] ; Load Interrupt Descriptor Table The IDT_BASE_ADDR is set like so: IDT_BASE_ADDR dw 256*8 ; Size of IDT dd 6000h ; Physical address in Memory First Word is the size. The next DWORD is the physical address in memory where it will be. Now, we do the same for the GDT. GDT_BASE_ADDR dw (8*8192) - 1 ; Size of GDT dd 6000h + 256*8 ; Physical address in Memory We are going to put the GDT right after the IDT in memory. LGDT [QWORD PTR GDT_BASE_ADDR] ; Load Global Descriptor Table Before you do this though, copy the GDT/IDT to their positions in memory. Next, I will show you how to create the GDT (That you will copy to the physical location in memory before setting, that way, it's set when you jump to pmode). The first descriptor is the NULL descriptor, and, of course, it's NULL! Descriptors are also 8 bytes. Also, Descriptors in the GDT start at 8h. This is because of the format of the selector: The bit layout: ssss stpp s = Selector. These start at 0 (NULL Descriptor which isn't used), then go to 1. 1, 2, 3, 4, 5, ... entries in the GDT. But, since they are mapped to higher bits, the actual values are: 8h, 10h, 18h, 20h, 28h, etc. t means table number. 0 = GDT, 1 = LDT. So, it knows what table to look in. pp means privledge level. 0 - 3. But, in a flat model, only 0 and 3 are used. You need to use a multisegmented model to have protection at the 1 and 2 levels. Which provides protection at the page and the segment, but is very complicated model. Most OSes use Flat model, which isn't using the Processor to it's full capabilities, but it's simple to implement. Window uses a flat model. This is how the GDT entries are layed out: 1st Word = Bottom word of Segment Length 2nd Word = Bottom Word of Physical address Next Byte = Low byte of high word to phyisical address. Next Byte = Attributes = Accessed bit, DPL, Present bit, System bit and others. Next Byte = Low nibble is the Upper part of the Segment Length. High nibble has Granularity bit, Available and Stack size. Last Byte = High byte of base phyiscal address. (For better descriptoins, please referr to the intel manuals or other materials) dq 0 ; NULL Descriptor ; 8h Selector dw 0FFFFh ; Segment Length dw 0h ; Low Word Base Address db 8 ; Low Byte of High Word of Base Address db PRESENT_BIT or DPL0 or SYSTEM_BIT or READ_EXE_BIT or CODE_BIT db 0Fh or GRAN_PAGE_BIT or DEFAULT_SIZE32 ; High Nibble of Segment Length db 0 ; High Byte of Base Address And we'll set up the first segment and that's it. 8h selector,we'll jump to 8h:0 when we set p mode. See below, I have made some contants in order to make setting the bits you want easier. This will point to code at the phyiscal address 8000h. This is where our 32bit pmode code should be loaded to. ; Equate Constants DPL0 EQU 00h DPL1 EQU 20h DPL2 EQU 40h DPL3 EQU 60h SYSTEM_BIT EQU 10h NO_SYSTEM_BIT EQU 0h PRESENT_BIT EQU 80h NO_PRESENT_BIT EQU 0h GRAN_PAGE_BIT EQU 80h GRAN_BYTE_BIT EQU 0h AVL_BIT EQU 10h UNAVL_BIT EQU 0 DEFAULT_SIZE32 EQU 40h DEFAULT_SIZE16 EQU 0 DATA_BIT EQU 0 CODE_BIT EQU 8 CONFORM_BIT EQU 4 NO_CONFORM_BIT EQU 0 READ_EXE_BIT EQU 2 EXE_ONLY_BIT EQU 0 ACCESSED_BIT EQU 1 CLR_ACCESSED_BIT EQU 0 Switching to Protected Mode Doing the switch is simple. MOV EAX, CR0 OR AL, 1 MOV CR0, EAX That's it. But, let me warn you. If you are setting it in the bootstrap for your own OS, this is fine. If you're doing a DOS pmode extender, this is NOT fine. There are two problems. 1) Windows and 2) Memory Managers If a memory mangaer is running, you will not be able to set pmode. To test for a DOS pmode extender, do like so: MOV EAX, CR0 TEST AL, 1 JNZ SHORT ERROR_MEMORY_MANAGER OR AL, 1 MOV CR0, EAX That will find that a DOS Memory Manager is present and not load. If the PM bit is already set, there is a DOS Memory manager loaded. In Windows 95, however, DOS is in a virtual machine. And, the PM bit will NOT be set! So how can you tell if you're under Windows 95? Like so: MOV EAX, CR0 TEST AL, 1 JNZ SHORT ERROR_MEMORY_MANAGER OR AL, 1 MOV CR0, EAX MOV EAX, CR0 TEST AL, 1 JZ SHORT ERROR_WINDOWS95 You can tell because you CANNOT SET the PMBit! So, Set it, check it. If it's not set, you're under Windows 95 and you won't be able to continue, so exit. I figured this out a few years ago when I was fooling with switching to pmode. Anyway, after you set protected mode, you need to clear the prefetch queue. Some people do this before they do the actual jump: JMP $+2 NOP NOP But I've seen code without it as well, so you do what you want. Finally, you should have set a selector in your GDT that was compiled as 32bit binary and loaded into memory at a certain location that you have a selector pointing to. Now, we hard code a far jump. This enables us to have the assembler not optimize the jump. Of course, I've seen code without this and there is debate on this, some peopel say you don't need to and others say you do. I just do it anyway. db 67h ; 32 Bit Memory Address db 66h ; 32 Bit Instruction db 0Eah ; Far Jump Opcode dd 0h ; Memory Offset in Selectory dw 8h ; Selector That should be it. Another thing of warning. When I set my selector registers before I jumped to pmode and tried to use them, they no longer had the values I put in them before the jump. So, as a precautionary measure, I would reset the selector registers with the correct descriptors, it could save you a lot of headache if you found something not working and didn't think of this! But, I would reccomend setting them before you jump as well. I have heard people's code crashing because of the Stack Selector not being set properly before the jump. But I would reset them again after the jump. Whatever works for you.