To get into pmode, change the last bit in the control register cr0 without messing up any other bits:
mov eax, cr0
inc ax
mov cr0, eax
That's it. You are now in protected mode.
The trouble is that now the segment registers (CS, DS, etc) no longer are segments (as in segment:offset addressing used in real mode, where the segment value is shifted left by 4 then the offset is added to it).
The value in the segment registers (usually called a selector) contains various information. The bottom three bits will confuse you, so just make sure for now they are 0's. The remaining 13 bits (remember that a segment register is 16 bits) are an index into a table (which you create) of "descriptors". A descriptor is a record of various information needed by the CPU to access memory in pmode.
I gave a detailed look at descriptors here:
http://www.mega-tokyo.com/forum/index.p ... readid=741
Here is what the descriptor table might look like:
gdtinfo:
dw gdt_end - gdt - 1 ;last byte in table
dd gdt ;start of table
gdt dd 0,0 ; first descriptor is null
rm_data db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
pm_code db 0xff, 0xff, 0, 0, 0, 10011010b, 11001111b, 0
pm_data db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
This is formatted to match my review of the descriptors, so it should be easy to follow.
In order for the CPU to know about the table, you have to use a special instruction to tell it the location in memory and how big it is.
lgdt [gdtinfo] ; load gdt register
Finally, you have to jump to the protected mode code. This is done by specifying the code descriptor (using the selector) and the offset in memory where the new code is located.
jmp 0x10:0x0600 ; go to setup code
This matches the table given above since 0x10 = 10000b. Now strip off the bottom three bits and you get 10b, which is 2d. Look at the table and you will see that pm_code is entry #2.
0x0600 just happens to be where I put the 32-bit protected mode code.
You can see all this in action at my web page (such as it is).