Thursday, March 28, 2024 | Toby Opferman
 

Protected Mode Introduction Part 2

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.











 
 
About Toby Opferman

Professional software engineer with over 15 years...

Learn more »
Codeproject Articles

Programming related articles...

Articles »
Resume

Resume »
Contact

Email: codeproject(at)opferman(dot)com