328 lines
15 KiB
Plaintext
328 lines
15 KiB
Plaintext
|
||
|
||
A CRASH COURSE IN PROTECTED MODE
|
||
|
||
|
||
By Adam Seychell
|
||
|
||
|
||
After my release of DOS32 V1.2 a lot of people were asking for basic
|
||
help in protected mode programming. If you already know what a selector is
|
||
then there is probably no need for you to read this file.
|
||
|
||
Ok you know all about the 8086 ( or a 386 in real mode ) architecture
|
||
and what to know about this fantastic protected mode stuff. I'll start off
|
||
saying that I think real mode on the 386 is like driving a car that is stuck
|
||
in first gear. There is the potential of a lot of power but it is not being
|
||
used. It really degrades the 386 processor and was not designed to normally
|
||
operate in this mode. Even the Intel data book states "Real mode is required
|
||
primarily to set up the processor for Protected Mode operation".
|
||
|
||
|
||
|
||
|
||
SEGMENTATION OF THE INTEL 80x86
|
||
|
||
A segment is a block of memory that starts at a fixed base address and
|
||
has a set length. As you should already know that *every* memory reference
|
||
by the CPU requires both a SEGMENT value and a OFFSET value to be specified.
|
||
The OFFSET value is the location relative to the base address of the segment.
|
||
The SEGMENT value contains the information for the segment. I am going to
|
||
explain very basically how this SEGMENT value is interpreted by the 80386 to
|
||
give the parameters of segments.
|
||
|
||
|
||
In protected mode this SEGMENT value is interpreted completely different
|
||
than in real mode and/or Virtual 86 mode. The SEGMENT values are now called
|
||
"selectors". You'll see why when finished reading this file. So whenever you
|
||
load a segment register you are loading it with a selector.
|
||
|
||
|
||
|
||
The Selector is word length and contains three different fields.
|
||
|
||
Bits 0..1 Request Privilege level ( just set this to zero )
|
||
|
||
Bit 2 Table Indicator 0 = Global Descriptor Table
|
||
1 = Local Descriptor Table
|
||
|
||
Bits 3..15 The INDEX field value of the desired descriptor in the GDT
|
||
This index value points to a descriptor in the table.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
The GLOBAL DESCRIPTOR TABLE (GDT)
|
||
|
||
The Global Descriptor Table ( GDT ) is a table of DESCRIPTORS and it is
|
||
stored in memory. The address of this table is given in a special 386
|
||
register called the global descriptor table register. There always must be a
|
||
GDT when in protected mode because it is in this table where all of the
|
||
segments are defined.
|
||
Each DESCRIPTOR ( stored in the GDT ) contains the complete information
|
||
about a segment. It is a description of the segment. Each Descriptor is 64
|
||
bits long and contains many different fields. I'll explain the fields later.
|
||
The INDEX field ( stored in bits 3..15 of any segment register ) selects a
|
||
descriptor to use for the type of segment wanted. So the only segments the
|
||
programmer can use are the available descriptors in the GDT.
|
||
|
||
|
||
Example:
|
||
Suppose you what to access location 012345h in your data segment and
|
||
you were told that the descriptor for your data segment is descriptor
|
||
number 6 in the Global Descriptor Table. Assume that the Global Descriptor
|
||
Table has already been set up and built for you ( example, as in DOS32).
|
||
|
||
Solution:
|
||
We need to load a segment register (SS,DS,FS,GS,ES) with a value so
|
||
that it will select (or index ) descriptor number 6 of the GDT. Then
|
||
reference the address with a instruction that will use this loaded
|
||
segment register.
|
||
|
||
One of the segment registers (FS,DS,GS,SS,CS or ES) must be loaded with the
|
||
following three fields,
|
||
|
||
Request Privilege level ( Bits 0..1 ) = 0 (always)
|
||
Table Indicator ( bit 2 ) = 0
|
||
Index ( bits 3..15 ) = 6
|
||
|
||
|
||
mov ax,0000000110000b ;load DS with the selector value
|
||
mov ds,ax
|
||
mov byte ptr DS:[ 012345h ],0 ; Using the DS segment register
|
||
|
||
|
||
|
||
The 386 has hardware for a complete multitasking system. There are
|
||
several different types of descriptors available in the GDT for managing
|
||
multitasking. You don't need to know about all the different descriptors just
|
||
to program in protected mode. Just the info above is enough. All you need to
|
||
know to program in protected mode is what descriptors are available to you
|
||
and what are the selector values to these descriptors. The base address of
|
||
the segment may also be known. See the file DOS32.DOC for obtaining the
|
||
selector values.
|
||
|
||
There are two groups of descriptors
|
||
1) CODE/DATA descriptors which are used for any code and data segments.
|
||
|
||
2) SYSTEM descriptors are used for the multitasking system of the 386. These
|
||
type of descriptors will never need to be used for programming applications.
|
||
|
||
|
||
|
||
Format of a code and data descriptor
|
||
|
||
BITS description if the field
|
||
----------------------------------------------------------------
|
||
0..15 SEGMENT LIMIT 0...15
|
||
16..39 SEGMENT BASE 0..23
|
||
40 (A) accessed bit
|
||
41..43 (TYPE) 0 = SEE BELOW
|
||
44 (0) 0 = code/data descriptor 1 = system descriptor
|
||
45..46 (DPL) Descriptor Privilege level
|
||
47 (P) Segment Present bit
|
||
48..50 SEGMENT LIMIT 16..19
|
||
51..52 (AVL) 2 bits available for the OS
|
||
53 zero for future processors
|
||
54 Default Operation size used by code descriptors only
|
||
55 Granularly: 1 = segment limit = limit field *1000h
|
||
0 = segment limit = limit field
|
||
56..63 SEGMENT BASE 24..31
|
||
|
||
format of TYPE field
|
||
bit 2 Executable (E) 0 = Descriptor is data type
|
||
1 Expansion Direction (ED) 0 = Expand up
|
||
1 = Expand up
|
||
0 Writeable (W) W = 0 data segment is read only
|
||
W = 1 data segment is R/W
|
||
|
||
|
||
bit 2 Executable (E) 1 = Descriptor is code type
|
||
1 Conforming (C) ( I don't understand )
|
||
0 Readable (R) R = 0 Code segment execute only
|
||
R = 1 Code segment is Readable
|
||
|
||
|
||
I'd better stop here, I am confusing myself. As you can see there is
|
||
more to a segment that just it's base address and limit.
|
||
The three descriptors that are available in DOS32 all have limits of
|
||
0ffffffffh (4GB). This means that the offsets can be any value.
|
||
For example, the instruction XOR EAX,ES:[0FFFFFFFFh] is allowed.
|
||
If you happen to load an invalid selector value into one of the segment
|
||
registers then the 386 will report an General Protection exception
|
||
( interrupt 13 ). In protected mode this exception is also used for many
|
||
other illegal operations.
|
||
|
||
|
||
|
||
The LINEAR ADDRESS and PHYSICAL ADDRESS
|
||
|
||
All the address translations described above is done by the 386
|
||
segmentation unit. The segmentation unit looks up the descriptor tables,
|
||
segment selectors, offsets and then outputs a 32bit linear address. This
|
||
linear address is calculated by the segmentation unit in the following
|
||
manner.
|
||
|
||
Linear address = base address of segment + offset.
|
||
The segment base address is found in the Base address field ( bits 16..39 &
|
||
56..63 ) of a descriptor which is located in the Global Descriptor Table.
|
||
The index field ( bits 3..15) of the segment register selects the descriptor
|
||
to use.
|
||
|
||
|
||
An example of the linear address of the instruction.
|
||
|
||
MOV EAX, ES:[EDX*8+012345678h]
|
||
|
||
where EDX = 100h and ES equals a selector wich points to a descriptor with
|
||
the base field equal to 02000000h.
|
||
|
||
The linear address = 2000000h + ( 100h*8 + 012345678h ) = 014345E78h
|
||
|
||
|
||
Just to make things even more complicated the 386 has an second memory
|
||
managing unit called the Paging Unit. The linear address calculated above may
|
||
still not be the physical RAM location the 386 is addressing. This linear
|
||
address has yet to go through another stage, the paging unit. If you are
|
||
having trouble with what I've said so far then you may want to take a coffee
|
||
break before continuing because this is even worse.
|
||
|
||
The 32bit linear address is directed to the paging unit. The paging unit
|
||
divides the linear address into three sections.
|
||
|
||
bits 0..12 Offset in a page
|
||
bits 13..23 Points to the page entry in the page table
|
||
bits 24..31 Points to the directory entry in the page directory table
|
||
|
||
The page table contains 1024 double word entires. In each of these entries
|
||
is the physical address of a 4KB page. The page directory also contains 1024
|
||
double word entries. Each of these directory entries hold a physical address
|
||
of a page table. See intel Documentation for the exact format of the page
|
||
table entries and directory table entries. The physical base address of the
|
||
DIRECTORY TABLE is in control register 3 ( CR3 )
|
||
|
||
This means if every entry in the directory table is used then there would
|
||
be 1024 page tables available. Because each page table hold 1024 page
|
||
addresses then there would be a total of 1024*1024 4KB pages.
|
||
The addresses in the page tables are all physical addresses. i.e the output
|
||
of the CPU address bus pins. The paging unit can be enabled or disabled
|
||
depending on wether bit 31 of CR0 is set. If the paging in disabled then the
|
||
linear address simply equals the physical address. If paging is enabled the
|
||
the linear address is translated by the paging unit to form a physical
|
||
address. Please note that it is possible to set up the page tables and
|
||
directory such that the linear address equals the physical address. This is
|
||
what DOS32 does for the first 1MB of linear address space.
|
||
If you can not understand my hopeless attempt of describing the paging unit
|
||
then the diagram below might help.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
The 32 bit linear address from the segmentation unit
|
||
-------------------------------------------------------------------------
|
||
| BITS 0..12 | BITS 13..23 | BITS 24..31 |
|
||
| | | |
|
||
-------------------------------------------------------------------------
|
||
| | |
|
||
|------------------------ | |
|
||
| | |
|
||
| | |
|
||
| | |
|
||
A Page in memory (4KB) | | |
|
||
-------------------------- | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | | | |
|
||
| | <------- | |
|
||
| | | |
|
||
| | | |
|
||
| | | |
|
||
|---> ------------------------- | |
|
||
| | |
|
||
| | |
|
||
| | |
|
||
| PAGE TABLE Offset | |
|
||
| -------------------------- | |
|
||
| | | 4092 | |
|
||
| -------------------------- | |
|
||
| . . | |
|
||
| . . | |
|
||
| . . | |
|
||
| -------------------------- | |
|
||
| | | 12 | |
|
||
| -------------------------- | |
|
||
|---<| address of page | 8 <--------- |
|
||
-------------------------- |
|
||
| | 4 |
|
||
-------------------------- |
|
||
| | 0 |
|
||
|--> -------------------------- |
|
||
| |
|
||
| |
|
||
| |
|
||
| |
|
||
| |
|
||
| DIRECTORY TABLE Offset |
|
||
| -------------------------- |
|
||
| | | 4092 |
|
||
| -------------------------- |
|
||
| . . |
|
||
| . . |
|
||
| . . |
|
||
| -------------------------- |
|
||
|---<| address of page table | 12 <-----------------------------
|
||
--------------------------
|
||
| | 8
|
||
--------------------------
|
||
| | 4
|
||
--------------------------
|
||
| | 0
|
||
|--> --------------------------
|
||
|
|
||
|
|
||
|
|
||
| ----------------------------------------------
|
||
--------<-| CR3 |
|
||
----------------------------------------------
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
This was only meant to be a rough introduction to the protected mode
|
||
segmentation mechanism of the 80386+. I hope I did not make this sound
|
||
too complicated so that you have been put off with the whole idea of
|
||
protected mode programming. If you want to know more then I suggest you buy a
|
||
book on the 80386. The "Intel Programmers Reference guide" is the most
|
||
detailed book around. The info here is only meant to give you an idea of how
|
||
protected mode works.
|
||
Please note that DOS32 does *ALL* of the setting up needed for protected
|
||
mode. Don't worry if you couldn't understand half the stuff I was talking
|
||
about. You don't have to know about any stupid things like the descriptor
|
||
format, selector index fields, privilege levels ,paging, tables, ect , ect.
|
||
What you do need to know are the selector values for all the descriptors that
|
||
are available to your program. Then the segment registers can simply be
|
||
loaded with these known selector values. DOS32 uses only 3 descriptors ( or
|
||
segments ) as described in DOS32.DOC.
|
||
|
||
|