Descriptors
_There is also [descriptor coverage in BabySteps tutorial|BabyStep6]. This is intended as a reference, with exhaustive coverage of various descriptor types, their structs and options, which can be used both for analysis of existing descriptors and for synthesis of your own._
Descriptors are one of the key structures of Protected Mode. They are used to hold information about various system objects and are used as entries in the three system tables - GDT, LDT and [IDT].
All descriptors are 8 bytes in size, but different types may have different format.
One thing they all have in common is the "type_attr" field (byte at offset 5) that contains descriptor type and some common attributes. Thus, if you have a descriptor of yet unknown type, you should look at the "type_attr" byte to find out the type. (Maybe you want to analyze and display the contents of some table)
<verbatim> // a "general" structure of a descriptor whose type we don't know, // only for the sake of discussion - you will want to use a more specific structure // according to the type you're dealing with, or define a union for all types struct Descr{
uint8 bytes[8]; // --- accessor methods uint8 type_attr(){ return bytes[5]; };
}; </verbatim>
- [|type_attr]"type_attr" has the following bit fields:
- bit 7: "P" (Present) flag
- Indicates whether the descriptor is being used. Accessing an object through a descriptor which has P=0 will cause a General Protection Fault.
- bits 5..6: DPL - Descriptor Privilege Level (2 bits, 0..3)
- Used to control access to the object described by the descriptor
- bit 4: "S" - whether this is a data/code segment (S=1) or a system segment (some other object) (S=0).
- One can think of it as "(data/code) Segment" flag. This, combined with the "type" code, can be used to determine the descriptor type and structure.
- bits 0..3: type (4 bits, 0..0xF)
Here are the formulas:
type |
(type_attr & 0xF)
S |
((type_attr>>4) & 1)
DPL |
((type_attr>>5) & 3)
P |
((type_attr>>7) & 1)
!!! Code/Data Segment Descriptors
These have S=1. Bit 3 of "type" indicates whether it's (0) Data or (1) Code.
<verbatim> struct SegDescr{
uint16 limit_1; // limit, bits 0..15 uint16 base_1; // base, bits 0..15 uint8 base_2; // base, bits 16..23 uint8 type_attr; // type_attr uint8 lim_attr; //^ bits 0..3: limit, bits 16..19 //^ bits 4..7: additional data/code attributes uint8 base_3; // base, bits 24..31
}; </verbatim>
So we have (putting all chunks of each field together):
- Base - 32 bits total
- Limit - 20 bits total
- when using G=0 (granularity: bytes), limit can be from 1b to 1Mb
- when using G=1 (granularity: pages), limit can be from 4Kb to 4Gb
Type bits interpretation (general, for details specific to data or code see below):
bit 3 |
Data/Code | 0 (data)
1 (code)
bit 2 |
E/C | Expand-down (data)
Conforming (code)
bit 1 |
W/R | Writeable (data)
Readable (code)
bit 0 |
A | Accessed (code, data)
Additional attributes from lim_attr (bits 4..7):
bit 7 |
G | Granularity | 0 (limit is in bytes)
1 (limit is in pages of 4096 bytes)
bit 6 |
D/B | Default operand size/Big | 0 for 16-bit segments
1 for 32-bit segments
bit 5 |
L | 64-bit code segment | 0 normally
1 if this is a 64-bit code segment in IA-32e mode
bit 4 |
AVL | Available | For use by system software
(your OS can use this as you choose)
<verbatim>
// computing base and limit values - putting the bits together
uint32 SegDescr::base(){
return base_1 | (base_2<<16) | (base_3<<24);
};
uint32 SegDescr::limit(){
return limit_1 | ((lim_attr&0xf)<<16);
}; </verbatim>
!! Type bits for Data segments
bit 3 |
Data/Code | 0 (data)
bit 2 |
Expand-down | 0 (normal)
1 (expand-down FIXME how this works?)
bit 1 |
Writeable | 0 (read-only)
1 (read-write)
bit 0 |
Accessed | 0 (hasn't been accessed)
1 (has been accessed)
!! Type bits for Code segments
bit 3 |
Data/Code | 1 (code)
bit 2 |
Conforming | 0 (non-conforming)
1 (conforming FIXME how this works?)
bit 1 |
Readable | 0 (execute-only)
1 (executable and readable)
bit 0 |
Accessed | 0 (hasn't been accessed)
1 (has been accessed)
_to be continued_