Descriptors: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
No edit summary
 
m Bot: Replace deprecated source tag with syntaxhighlight
 
(13 intermediate revisions by 6 users not shown)
Line 1: Line 1:
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]].
{{Convert}}


== Common properties ==


All descriptors are 8 bytes in size. Different types may have different format.
_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)
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)


<syntaxhighlight lang="c">
<verbatim>
// a "general" structure of a descriptor whose type we don't know,
// 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
// 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
// according to the type you're dealing with, or define a union for all types
struct Descr{
struct Descr{
uint8 bytes[8];
uint8_t bytes[8];

// --- accessor methods
// --- accessor methods
uint8 type_attr(){ return bytes[5]; };
uint8_t type_attr(){ return bytes[5]; };
};
};
</syntaxhighlight>
</verbatim>


#[|type_attr]"type_attr" has the following bit fields:
"type_attr" has the following bit fields:
* bit 7: "P" (Present) flag
* 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.
:Indicates whether the descriptor is being used. Accessing an object through a descriptor which has P=0 will cause a <del>General Protection Fault</del> Segment Not Present Fault (Int 0Bh) - or, if it's trying to be used as a Stack Segment, a Stack Fault (Int 0Ch).
* bits 5..6: DPL - Descriptor Privilege Level (2 bits, 0..3)
* bits 5..6: DPL - Descriptor Privilege Level (2 bits, 0..3)
** Used to control access to the object described by the descriptor
: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).
* 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.
: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)
* bits 0..3: type (4 bits, 0..0xF)


Here are the formulas:
Here are the formulas:


{| {{wikitable}}
type |
|-
(type_attr & 0xF)
! type
S |
((type_attr>>4) & 1)
| (type_attr & 0xF)
|-
DPL |
! S
((type_attr>>5) & 3)
| ((type_attr>>4) & 1)
P |
|-
((type_attr>>7) & 1)
! DPL
| ((type_attr>>5) & 3)
|-
! P
| ((type_attr>>7) & 1)
|}


!!! Code/Data Segment Descriptors
== Code/Data Segment Descriptors ==


These have S=1. Bit 3 of "type" indicates whether it's (0) Data or (1) Code.
These are descriptors in the GDT that have S=1. Bit 3 of "type" indicates whether it's (0) Data or (1) Code. The interpretation of the other bits are given further down this page.


The structure for a code/data descriptor is as follows:
<verbatim>

<syntaxhighlight lang="c">
struct SegDescr{
struct SegDescr{
uint16 limit_1; // limit, bits 0..15
uint16_t limit_1; // limit, bits 0..15
uint16 base_1; // base, bits 0..15
uint16_t base_1; // base, bits 0..15
uint8 base_2; // base, bits 16..23
uint8_t base_2; // base, bits 16..23
uint8 type_attr; // type_attr
uint8_t type_attr; // type_attr
uint8 lim_attr;
uint8_t lim_attr;
//^ bits 0..3: limit, bits 16..19
//^ bits 0..3: limit, bits 16..19
//^ bits 4..7: additional data/code attributes
//^ bits 4..7: additional data/code attributes
uint8 base_3; // base, bits 24..31
uint8_t base_3; // base, bits 24..31
};
};
</syntaxhighlight>
</verbatim>


When concatenating all chunks, base has 32 bits and limit has 20 bits. To allow for a wide range of sizes, the G bit influences how the 20 bits of the limit field are converted to a 32-bit limit
So we have (putting all chunks of each field together):
* when using G=0 (granularity: bytes), the amount of accessible bytes can be from 1b to 1Mb (bytes 0x0 to limit. byte 0 will always be writable)
* Base - 32 bits total
* when using G=1 (granularity: pages), the amount of accessible bytes can be from 4Kb to 4Gb (= 0x1000 * limit + 0xfff)
* 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)<br>
1 (code)
bit 2 |
E/C |
Expand-down (data)<br>
Conforming (code)
bit 1 |
W/R |
Writeable (data)<br>
Readable (code)
bit 0 |
A |
Accessed (code, data)


Additional attributes from lim_attr (bits 4..7):
Additional attributes from lim_attr (bits 4..7):


{| {{wikitable}}
bit 7 |
|-
G |
! bit 7
Granularity |
! G
0 (limit is in bytes)<br>
! Granularity
1 (limit is in pages of 4096 bytes)
| 0 (limit is in bytes)<br>1 (limit is in pages of 4096 bytes)
bit 6 |
|-
D/B |
! bit 6
Default operand size/Big |
! D/B
0 for 16-bit segments<br>
! Default operand size/Big
1 for 32-bit segments
| 0 for 16-bit segments<br>1 for 32-bit segments
bit 5 |
|-
L |
64-bit code segment |
! bit 5
! L
0 normally<br>
1 if this is a 64-bit code segment in IA-32e mode
! 64-bit code segment
| 0 normally<br>1 if this is a 64-bit code segment in IA-32e mode
bit 4 |
|-
AVL |
! bit 4
Available |
! AVL
For use by system software<br>(your OS can use this as you choose)
! Available
| For use by system software<br>(your OS can use this as you choose)
|}


The following snippet calculates the base and 20-bit limit from a descriptor:


<syntaxhighlight lang="c">
<verbatim>
// computing base and limit values - putting the bits together
// computing base and limit values - putting the bits together


uint32 SegDescr::base(){
uint32_t SegDescr::base(){
return
return
base_1 |
base_1 |
Line 115: Line 105:
};
};


uint32 SegDescr::limit(){
uint32_t SegDescr::limit(){
return
return
limit_1 |
limit_1 |
((lim_attr&0xf)<<16);
((lim_attr&0xf)<<16);
};
};
</syntaxhighlight>
</verbatim>

=== Type bits for Data segments ===


{| {{wikitable}}
!! Type bits for Data segments
|-
! bit 3
! Data/Code
| 0 (data)
|-
! bit 2
! Expand-down
| 0 (normal) <br> 1 ([[Expand_Down|expand-down]])
|-
! bit 1
! Writable
| 0 (read-only) <br> 1 (read-write)
|-
! bit 0
! Accessed
| 0 (hasn't been accessed) <br> 1 (has been accessed)
|}


=== Type bits for Code segments ===
bit 3 |
Data/Code |
0 (data)
bit 2 |
Expand-down |
0 (normal)<br>
1 (expand-down FIXME how this works?)
bit 1 |
Writeable |
0 (read-only)<br>
1 (read-write)
bit 0 |
Accessed |
0 (hasn't been accessed)<br>
1 (has been accessed)


{| {{wikitable}}
!! Type bits for Code segments
|-
! bit 3
! Data/Code
| 1 (code)
|-
! bit 2
! Conforming
| 0 (non-conforming) <br> 1 (conforming)
|-
! bit 1
! Readable
| 0 (execute-only) <br> 1 (executable and readable)
|-
! bit 0
! Accessed
| 0 (hasn't been accessed) <br> 1 (has been accessed)
|}


== See Also ==
bit 3 |
* [[Protected Mode]]
Data/Code |
* [[Babystep6]]
1 (code)
bit 2 |
Conforming |
0 (non-conforming)<br>
1 (conforming FIXME how this works?)
bit 1 |
Readable |
0 (execute-only)<br>
1 (executable and readable)
bit 0 |
Accessed |
0 (hasn't been accessed)<br>
1 (has been accessed)


[[Category:X86 CPU]]
_to be continued_

Latest revision as of 05:13, 9 June 2024

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.

Common properties

All descriptors are 8 bytes in size. 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)

// 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_t bytes[8];

    // --- accessor methods
    uint8_t type_attr(){ return bytes[5]; };
};

"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 Segment Not Present Fault (Int 0Bh) - or, if it's trying to be used as a Stack Segment, a Stack Fault (Int 0Ch).
  • 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 are descriptors in the GDT that have S=1. Bit 3 of "type" indicates whether it's (0) Data or (1) Code. The interpretation of the other bits are given further down this page.

The structure for a code/data descriptor is as follows:

struct SegDescr{
    uint16_t limit_1;   // limit, bits 0..15
    uint16_t base_1;    // base, bits 0..15
    uint8_t base_2;     // base, bits 16..23
    uint8_t type_attr;  // type_attr
    uint8_t lim_attr;
      //^ bits 0..3: limit, bits 16..19
      //^ bits 4..7: additional data/code attributes
    uint8_t base_3;     // base, bits 24..31
};

When concatenating all chunks, base has 32 bits and limit has 20 bits. To allow for a wide range of sizes, the G bit influences how the 20 bits of the limit field are converted to a 32-bit limit

  • when using G=0 (granularity: bytes), the amount of accessible bytes can be from 1b to 1Mb (bytes 0x0 to limit. byte 0 will always be writable)
  • when using G=1 (granularity: pages), the amount of accessible bytes can be from 4Kb to 4Gb (= 0x1000 * limit + 0xfff)

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)

The following snippet calculates the base and 20-bit limit from a descriptor:

// computing base and limit values - putting the bits together

uint32_t SegDescr::base(){
 return
   base_1 |
   (base_2<<16) |
   (base_3<<24);
};

uint32_t SegDescr::limit(){
 return
  limit_1 |
  ((lim_attr&0xf)<<16);
};

Type bits for Data segments

bit 3 Data/Code 0 (data)
bit 2 Expand-down 0 (normal)
1 (expand-down)
bit 1 Writable 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)
bit 1 Readable 0 (execute-only)
1 (executable and readable)
bit 0 Accessed 0 (hasn't been accessed)
1 (has been accessed)

See Also