Public Types | |
using | size_type = uint16_t |
using | index_type = uint16_t |
using | integer_pointer = uintptr_t |
using | base_type = uint64_t |
using | limit_type = uint32_t |
using | access_rights_type = uint32_t |
using | segment_descriptor_type = uint64_t |
Public Member Functions | |
gdt_x64 () noexcept | |
gdt_x64 (size_type size) noexcept | |
~gdt_x64 () noexcept=default | |
integer_pointer | base () const |
size_type | limit () const |
void | set_base (index_type index, base_type base) |
base_type | base (index_type index) const |
void | set_limit (index_type index, limit_type limit) |
limit_type | limit (index_type index) const |
void | set_access_rights (index_type index, access_rights_type access_rights) |
access_rights_type | access_rights (index_type index) const |
Global Descriptor Table
This class provides an abstraction around the global descriptor table for 64bit.
This class does not provide a "descriptor" class as the amount of code needed to completely abstract each descriptor type would be enormous for something that is setup once, and never touched again. So it is left to the user to understand how to set the access rights for each descriptor manually, as this is the part that is descriptor specific. In general, all of the information about each descriptor can be found in the Intel Manual in Volume 3.
Generally speaking, there are 2 different types of descriptors, a code/data segment descriptor, and a TSS descriptor.
A code/data segment descriptor is a descriptor that is loaded into es, cs, ss, ds, fs or gs. Information about these types of descriptors can be found in Volume 3, section 3.4.5. A code segment is any segment that is loaded into CS. Although not called out in all of the documentation, Intel does have a stack segment which is any descriptor loaded into SS, and the access rights are different for a stack segment. Finally there are data segments which are any descriptor loaded into es, ds, fs and gs. On 64bit, es and ds are not available, so they should always point to the NULL descriptor, which is the first descriptor in the table, which has to be set to all 0s (per the spec). The only parts of cs, ss, fs, and gs that are used are the access rights. The CPU in 64bit mode assumes that the base is set to 0, and the limit is 0xFFFFF. Although the limit is only 4G (because it's only 20 bits), the CPU internally sets the limit to 2^64 for you. fs and gs are the only segments that can have a base address not set to 0, but they cannot be set using the GDT, and instead have to be set using the MSRs. Bareflank uses gs to store the offset into the state save area for the exit handler.
A TSS descriptor defines the task state segment. This is a structure that is defined 7.2 (for 32bit), and 7.7 (for 64bit). The OS might fill in this structure for syscalls, but in general, this structure is not used in 64bit, but still needs to be defined. For a hypervisor, this structure can be left blank. The base address of the TSS descriptor needs to be the address of the TSS, the limit should be sizeof(TSS), which for a hypervisor that doesn't use the IO bitmap, or any custom data is 104 bytes, and the access rights are set to a present 64bit TSS. There is one complication with the TSS descriptor which is that you cannot simply call ltr (load task register) with any TSS. The TSS cannot have the busy flags set. Since there is a TSS that the host OS has, and a TSS for the VMM, this tends to be fine, up to the point where the VMM attempts to promote the guest. When this happens, there are actually two TSS descriptors marked as busy, which should never happen, but does. The solution is to mark the host OS's TSS descriptor as not busy manually before loading it.
using gdt_x64::size_type = uint16_t |
using gdt_x64::index_type = uint16_t |
using gdt_x64::integer_pointer = uintptr_t |
using gdt_x64::base_type = uint64_t |
using gdt_x64::limit_type = uint32_t |
using gdt_x64::access_rights_type = uint32_t |
using gdt_x64::segment_descriptor_type = uint64_t |
|
inlinenoexcept |
Constructor
Creates a GDT based on the GDT currently in hardware.
|
inlinenoexcept |
|
defaultnoexcept |
Destructor
|
inline |
|
inline |
|
inline |
Set Descriptor Base Address
Sets the base address of a descriptor. If the descriptor is a TSS (determined using the system descriptor flag), the base address is a 64bit address, and this operation will attempt to touch 2 64bit descriptor fields. So, if the TSS is at the end of the GDT (like they usually are) make sure you give yourself space for 2 entries for the TSS, otherwise this code will throw an invalid_argument exception. Also, since the access rights determine the descriptor type, make sure you set them first.
index | the index of the GDT descriptor |
base | the base address. For code/data descriptor this needs to be 0, and for a TSS this is a 64bit virtual address. |
|
inline |
Get Descriptor Base Address
Gets the base address of a descriptor. If the descriptor is a TSS (determined using the system descriptor flag), the base address is a 64bit address, and this operation will attempt to touch 2 64bit descriptor fields. So, if the TSS is at the end of the GDT (like they usually are) make sure you give yourself space for 2 entries for the TSS, otherwise this code will throw an invalid_argument exception. Also, since the access rights determine the descriptor type, make sure you set them first.
index | the index of the GDT descriptor |
|
inline |
Set Descriptor Limit
Sets the descriptors limit. Note that for code/data descriptors, this needs to be 0xFFFFF as segmentation is not used in 64bit. For the TSS, the limit should be the size in bytes of the TSS, and any other data you wish to store.
index | the index of the GDT descriptor |
limit | the descriptors limit |
|
inline |
Get Descriptor Limit
Gets the descriptors limit.
index | the index of the GDT descriptor |
|
inline |
Set Descriptor Access Rights
Sets the descriptors access rights. Note that Intel defines this field a little strange. Unlike the base and limit, where the fields and merged, the access rights leaves the upper "limit" bits in the access rights, so you have to leave bits 8-11 as 0 as these are bits 16-19 of the limit field. Also, each bit in the access rights field has a different meaning based on which segment register is used. If CS is used, the descriptor is a code segment, if TR is used the descriptor is a TSS descriptor, if SS is used the segment is a stack segment, and all others are data segments. For a complete list of what each bit does (based on what segment register is loading this descriptor), please see the Intel manual.
index | the index of the GDT descriptor |
access_rights | the access rights for this descriptor |
|
inline |
Get Descriptor Access Rights
Gets the access rights for the descriptor
index | the index of the GDT descriptor |