The unreal mode exploits two facts on how both Intel and AMD processors load and save the information to describe a segment.
The processor caches the descriptor information fetched during a move in a selector register in protected mode.
These information are stored in an architectural invisible part of the selector register themselves.
In real mode the selector registers are called segment registers but, other than that, they designate the same set of registers and as such they also have an invisible part. These parts are filled with fixed values, but for the base which is derived from the value just loaded.
In such view, real mode is just a special case of protected mode: where the information of a segment, suchlike the base and limit, are fetched without a GDT/LDT but still read from the segment register hidden part.
By switching in protected mode and crafting a GDT is possible to create a segment with the desired attributes, for example a base of 0 and a limit of 4GiB.
Through a successive loading of a selector register such attributes are cached, it is then possible to switch back in real mode and have a segment register through which access the whole 32 bit address space.
BITS 16
jmp 7c0h:__START__
__START__:
push cs
pop ds
push ds
pop ss
xor sp, sp
lgdt [GDT] ;Set the GDTR register
cli ;We don't have an IDT set, we can't handle interrupts
;Entering protected mode
mov eax, cr0
or ax, 01h ;Set bit PE (bit 0) of CR0
mov cr0, eax ;Apply
;We are now in Protected mode
mov bx, 08h ;Selector to use, RPL = 0, Table = 0 (GDT), Index = 1
mov fs, bx ;Load FS with descriptor 1 info
mov gs, bx ;Load GS with descriptor 1 info
;Exit protected mode
and ax, 0fffeh ;Clear bit PE (bit0) of CR0
mov cr0, eax ;Apply
sti
;Back to real mode
;Do nothing
cli
hlt
GDT:
;First entry, number 0
;Null descriptor
;Used to store a m16&32 object that tells the GDT start and size
dw 0fh ;Size in byte -1 of the GDT (2 descriptors = 16 bytes)
dd GDT + 7c00h ;Linear address of GDT start (24 bits)
dw 00h ;Pad
dd 0000ffffh ;Base[15:00] = 0, Limit[15:00] = 0ffffh
dd 00cf9200h ;Base[31:24] = 0, G = 1, B = 1, Limit[19:16] = 0fh,
;P = 1, DPL = 0, E = 0, W = 1, A = 0, Base[23:16] = 00h
TIMES 510-($-$$) db 00h
dw 0aa55h
Considerations
fs
and gs
to hold the "extended" segments: such registers are less likely to be used/saved/restored by the various 16 bit services.lgdt
instruction doesn't load a far pointer to the GDT, instead it loads a 24 bit (can be overridden to 32 bit) linear address. This is not a near address, it is the physical address (since paging must be disabled). That's the reason of GDT+7c00h
.cs
/ds
/ss
tp 7c00h and start the location counter from 0. So a byte at offset X in the file is at offset X in the segment 7c00h and at the linear address 7c00h + X.lgdt
is saved in the... GDT itself, in the null descriptor (the first descriptor).For a description of the GDT descriptors see Chapter 3.4.3 of Intel Manual Volume 3A.