Protected Mode Guide
Part Of The TiTan OS Development
Project
Author : Amgad Magdy
Madkour
Operating System Sector : Boot
Programming Language : Assembly
Revision : 2
Last Update :
Preface :
This is almost a starting guide to start knowing about protected mode programming and a little bit about history and evolution of the protected mode starting from 286 machine. This guide is also a part of the TiTan OS project developed by our group.
Introduction
:
Some basic Terms:
->Logical Address: Contains offset address and segment value Ranging from 0000 to FFFFH
->Physical Address: Is the actual address consisting of 20 bits ranging from 0000H to FFFFH
->Segment Base Address : It is the segment value multiply 10h
(Note: To change from logical to physical address just add a zero to the end of the value of the code segment and then add to it the offset)
Protected Mode
Advantages:
80386 Registers :
In the 386 + we have now extended general purpose registers which are 32 bit instead of 16 bit long . Besides that in the 386 there was 2 additional registers added which were (fs) and (gs) which are 16 bit registers which are used to access more than one register at the same time without reloading the register like before.
Also now the flag register is 32 bit instead of 16 bit .386 also added some few registers
Protected Mode And
Protected Mode Segments :
* One important point has to be clear , in real mode when we wanted to get the physical address we would add a zero to the segment register and add the IP(instruction pointer) on the result , well in protected mode its different , here we have a segment that acts as an index to an address in a table which there contains the physical address. Also in real mode segments are 64k long and between each segment and segment there is a 16 byte long space , in protected mode segments can be as big as 4GB long and you can put them in any place in memory
15 3 2 1 0
|-------------------------------------------------|
| Index | TL | RPL |
|-------------------------------------------------|
where RPL : Request Privilege level
TL : Table Indicator (GDT=0 , LDT=1)
Index : Index into the descriptive table
The three TL’s are GDT, LDT or IDT but we don’t use IDT
GDT =0
LDT =1
(Note : Segment selectors have nothing to do with where it resides in memory !, the index is responsible for such determination)
Descriptors
:
A Descriptor is basically an entry in the descriptor table (explained below), it simply contains base address and limit of certain segments , also
It contains basic privilege level bits of the segment being described.
There are two types of descriptors, code/data descriptors and system descriptors.
The descriptor is built for every piece of code or data by the operating system or more precisely for each segment.
Descriptor Table :
which describes segments of the operating system as explained earlier.
On the other hand local descriptor table contains descriptors that describe the applications of the operating system.
The interrupt descriptor table contains descriptors of the interrupts of the system.
Of course we would note that these data structures as I like to call them are only present in protected mode.
In real mode we used normal linear flat addressing mechanism where in protected mode the descriptors in the descriptive table
Contains a logical address to memory.
Descriptor tables place in memory is determined by special registers , each for its kind , we have
1. GDTR : for global descriptor table
2. LDTR : for local descriptor table
3. IDTR : for interrupt descriptor table
To determine the place of the GDT and LDT the GDTR and LDTR Registers are used . These registers are 48 bits wide (6bytes) .
The descriptor table address is held by the descriptor table registers. For example the GDTR holds the address of the GDT .
GDTR is 48-bits long , meaning 6 bytes long , first 2 bytes contains the LIMIT which is used to identify the size of the GDT in bytes. If LIMIT is 00FFH then the table is 256 bytes ( 2 power 8 ) . The GDT can be up to 65,536 bytes long because it can hold 16 bits (2 power 16) .The other four bytes of the GDTR which is labeled generally as BASE locates the beginning of the GDT in memory. This 32 bit address can make the GDT be placed anywhere in memory.
There is also an important point about descriptors , as we know that GDT contains information about code or data of the system stored in it. Descriptors in the GDT are eight bytes long , thus if the size of the table is 256 bytes then the table contains 32 descriptor (256/8). The number of descriptors can increase if the LIMIT in the GDTR increases. The value of the BASE and LIMIT must be loaded before we change from real mode to protected mode and once we are in protected mode the location cannot change of the GDT in memory.
The GDT limit is a fixed value: number of descriptors * 8 - 1
It is also important to note the difference between the GDT and LDT at this point , at the moment concerning our protected mode setting up LDT is not important as GTD , GDT keeps information about different parts of memory .
Switching to protected mode :
First step is to set what is called a null descriptor in order to use it a size template , meaning that we use it just to get its size which that size
Would be used to define the size of the other descriptors we would use , so its just a template ..
It is defined as followed :
gdt: dw 0 ; would contain segment limit from bits 0-15 of the descriptor
dw 0 ;would contain base , which is bits 0-15
db 0 ;contains base from bits 16-23
db 0 ;contains type of segment , either code/data or system
db 0 ;flags from 16-19 (Privilege level etc. )
db 0 ;Base address from 24-31
As you can see by this we covered all the descriptor bits from 0-32 bits.
Secondly we would define the GDT code and data segments our OS would be using
For code segment it would be like this :
CODE equ $-gdt ; as if u are saying sizeof(gdt) which is the null descriptor size , which is the same as this !
gdtcode: dw 0xFFFF ; limit , here it is the maximum limit of 16 bit segment
dw 0 ;base address , gets sets
from beginning , shown latter
db 0 ;contains base from bits 16-23
db 0x9A ; code segment , which is present and readable only
db 0xCF ; flags are granular and 32-bit
db 0 ;Base address from 24-31
The data would be almost the same :
DATA equ $-gdt ; as if u are saying sizeof(gdt) which is the null descriptor size , which is the same as this !
gdtdata: dw 0xFFFF ; limit , here it is the maximum limit of 16 bit segment
dw 0 ;base address , gets sets
from beginning , shown latter
db 0 ;contains base from bits 16-23
db 0x92 ; data segment , which is present and writable
db 0xCF ; flags are granular and 32-bit
db 0 ;Base address from 24-31
The next thing is to set the interrupt table , it is as simple as the global descriptor table, but the descriptor is different :
idt: dw handle ; handler of the interrupt
dw CODE ; its default segment
db 0 ; reserved for word count
db 0x8E ; flags : 32 bit , Ring 0 , Interrupt gate
dw 0 ; entry point of the interrupt from bits 16-31
Now that we know what our descriptors would look like lets set them now ..
xor ebx,ebx
mov bx,cs
shl ebx,4
mov eax,ebx
mov [gdtcode + 2],ax
mov [gdtdata + 2],ax
shr eax,16
mov [gdtcode + 4],al
mov [gdtdata + 4],al
mov [gdtcode + 7],ah
mov [gdtdata + 7],ah
Example : assume cs is 1234 and then we made the shift which would make it 12340 , but we have in ax 2340 only (16 bit) and the 1 is in the extended part of the ebx register … so we save this part in the base address place from bits 0-15 , then we shift the register by 16 bit which is 4 HEX which makes the register look like 0001 , because now the one came from the extended back to the old part again , that makes al = 01 HEX and ah =00HEX . so we set in the global descriptor table that bits 16-23 which is 8 bits wide would contain 01 HEX which seems very logical . now we set the rest which is bits from 24-31 with ah which contains 00 HEX
Thus we have the base address of the segment stored in the descriptor as a 32 bit base address with a value of ((00012340)) ..
Next after setting the global descriptor table , its time to point to it , as we said before we would use the GDTR and IDTR in our case to refer to them .
In NASM we do not have GDTR and IDTR so we would define them as followed
gdtr: dw gdt_end – gdt -1 ; define limit
dd gdt ;define linear/physical address of GDT
idtr: dw idt_end – idt -1 ;define limit
dd idt ;define linear/physical address of IDT
And also we store the 32 bit linear/physical address of both of them too , that makes each segment equals 48 bits wide
(( dw = 16 bit and dd =32 bit ))
Now to realy set these values we do the following :
add ebx,gdt ;ebx contains the 32 bit base address which we will add on the 32 bit gdt address
mov [gdtr+2],ebx
add ebx, idt – gdt
mov [idtr + 2 ],ebx
((In our case the content of ebx is 00012340n which would be added to whatever offset of gdt is and the result would be stored in ebx as the souce register
and after that we save that in the gdtr position of the linear/physical address . Same applies to idt with the difference that we subtract idt from gdt , meaning subtract the offset of idt from gdt to obtain the the actual address of idt , then this address would be saved in the linear/physical part of the idtr.))
cli ;clear interrupt flags
Next we load the gdt and idt via there registers gdtr and idtr because they contain pointers to the :
lgdt [gdtr] ; load global descriptor table
lidt [idtr] ; load interrupt descriptor table
R: indicates what co processor is used , 1 is for an 80387 and 0 for a 80287
TS: automatically gets set when switching from one task to another
EM (Emulate): Set to 1 to indicate software emulator is used to do numerical co processing instead of the hardware
MP(Math Present): Set to 1 to indicate numerical coprocessor is present in the micro computer .One of the MP or EM must be set only.
PE (Protected Mode Enable):
This is the most important bit that we check to see if the system is in real mode or in protected mode , when starting machine this is PE=0 and to enter protected mode PE=1 , and can be back to real mode by setting it to PE=0
*Out of the 5 bits rage , is a bit called PG which enables Paging (PG=1)
mov eax,cr0 ; move the content to eax to be modified
or al,1 ; enable first bit of cr0 be performing an OR operation
mov cr0,eax ; put content back to cr0
jmp CODE:promode ; CODE is the segment we are working with and promode is the offset/label to go to
[BITS 32] ; Has to be written to indicate 32 Bit environment
promode: mov ax,DATA
mov ds,ax
mov ss,ax
mov es,ax
mov fs,ax
mov gs,ax
Conclusion :
I hope I have cleared out some of the most important points about protected mode in this documentation . I have demonstrated how to program your way intro protected mode by setting your required tables and what bits to enable in order to be in protected mode .
In the end I would gladly like to hear any comments about this document . this document was meant as a beginners tutorial about protected mode .
Reference :
Intel Programming Reference
Christopher Geise “Protected Mode”
TiTan OS Notes :
This File Depends On:
None
This File Is Used By:
Kernel Initialization Structure
Any questions and comments about what is written would be appreciated and I would receive them on my mail , amgadmadkour@hotmail.com
Copyrights 2002 to the author