MSRs (Model Specific Registers):
These registers refer to any control register which can be used for debugging, performance monitoring and feature setting. However, the exact difference between a normal Control Register, and a MSR, is that a MSR may not be present in future versions of the same processor or family, hence making it specific to that type of model. A good example is the Test Registers (TR3, TR4, TR5, TR6 and TR7), which were used to test the speed and reliability of the TLB.
The modern term for the Test Registers has become MSRs, meaning that they may not be present in future designs of the CPU. There is two WinDbg commands which allow to read and write to a MSR, these commands are rdmsr and wrmsr. I believe, unfortunately, these commands can only be used for Live Kernel Mode debugging. These are also Assembly instructions, which can only be ran at Ring Level 0.
With rdmsr instruction, you read from the ecx register, and store the value into the edx:eax register pair. The same applies to the wrmsr, but instead your writing rather than reading.
The MSRs can be found in the Intel Developers Manual – Volume 3, Chapter 35
Task State Segment (TSS):
The Task State Segment is used for saving information about a task, while a task switch is performed. Each processor must have at least one TSS. It is also used for Context Switching.
The TSS stores specific information such as values stored in the registers; stack pointers (ESP); I/O Port Permissions and Levels and the previous TSS which is only needed for hardware task switching.
The TSS segment selector is stored in a register called the Task Register, which is used to index into the GDT and find the Task Gate Descriptor for the TSS. The Task Register (TR) is loaded with the LTR instruction, which can only be used at Ring Level 0 and can only be used when the processor is running in Protected Mode or Long Mode. The TR is 16 bits.
When a processor changes privilege levels, for example changing to Ring 0 through a System Call, the SS0 and ESP0 fields are looked at by the CPU from the TSS, and used to set the current stack pointer of the Kernel Stack; SS and ESP. Remember there’s a Kernel Stack for each thread. SS0 is stored at offset 0x04 within the TSS and ESP0 is stored at 0x08. In Long Mode, the SS is no longer used, and therefore the RSP0 and RSP 2 fields are used instead for setting the stack pointer.
The IOPB (I/O Privilege Level Bitmap) field stored at offset 0x64, is used for accessing a program’s I/O port permissions, a 0 is used to signify that program has access to that particular port, and a 1 is used to show that the program doesn’t have permission. This is checked when the program uses IN and OUT, or any other I/O instructions. The IOPL is checked to see if the program has access to all ports, for example if the IOPL was set to Ring 3. IOPL Levels and the IOPB are used for limiting access of I/O ports to User-Mode programs.
Task Gates may even be stored in the IDT, to enable interrupts and exceptions (Double Faults) to be able to cause a task switch, and load a “good stack”, for issues such as stack overflows.
We can use the .tss command in WinDbg to view a Task State Segment. Firstly, we need to get the address of the TSS for the processor, by using the !pcr extension which dumps the Processor Control Block for the given logical processor. Use .tss with the specified address, which should be the base address shown with the dg command. I’ve used two separate dump files, so it will appear as two different addresses.
The .tss command will only work on x86 processors, and I have a x64 processor running a x86 operating system, so unfortunately I can’t demonstrate this command properly.
- Do I need a TSS?
- Context Switching – OS Dev Wiki
- Task State Segment – OS Dev Wiki
- Task State Segment – Wikipedia
IDT (Interrupt Descriptor Table)
Generally speaking, a hardware interrupt is received along one of the interrupt controller lines of the APIC or PIC, the hardware interrupt is then translated into a IRQ and sent to the CPU, the CPU looks into the IDT and finds the appropriate interrupt gate or vector, and then services the interrupt with the ISR associated with the vector in the IDT. The interrupt will also be mapped to a certain IRQL too. The IDT also contains task gates (task switching) and trap gates (exceptions and system calls).
In this section, I thought I would speak about the IDT in more detail. The address of the IDT is stored in a register called the IDTR. The IDT is loaded with the LIDT (Load Interrupt Descriptor Table) instruction during Real Mode. There are currently 256 entries available in the IDT, and if a interrupt occurs which doesn’t match the entry in the IDT, then a General Protection Fault is raised about the missing entry.
I’ve taken a screenshot of the general code of a IDT Gate entry from OS Dev Wiki:
The meaning of the type_attr field can be found here – Interrupt Descriptor Table OSDev Wiki
When exiting from a interrupt, since they can be called with CALL, the IRET instruction is used to return to the caller. This is typically used for control transfers.
I would like to explain how Task Gates are used within the IDT, and how the process works. When a hardware task switch, happens then the Task Gate is looked at within the IDT, the Task Gate will point to a TSS Descriptor which stored in the GDT, which then executes the Task Switch and uses the TSS.
Now, let’s move onto the concept of the exceptions. Exceptions are errors, and are typically handled by a exception handler within the IDT. There are three different types of an exception: aborts, traps and faults.
Faults can be corrected and the operating system will continue normally. The faults include: divide by zero; bound range exceed; invalid opcode; device not available; invalid TSS; segment not present; stack segment fault; general protection fault; page fault; alignment check; x87 floating point exception and SIMD floating point exception.
With a fault, the CS and EIP values saved will point to the instruction which caused the problem, this is usually with a trap handler.
Traps are reported directly after the instruction which has caused the trap has executed, typical examples are: debug exceptions (various causes); breakpoints and overflows.
The CS and EIP values point to the instruction after the instruction causing the trap. With JMP instructions, or other instructions which alter the flow of the program, the instructions point to the target of the JMP instruction and not the instruction after the JMP instruction.
Aborts are serious errors, which can’t be handled or corrected by the operating system, and therefore will result in a serious crash straight away like a BSOD. Aborts include Double Faults, Triple Faults and Machine Check Exceptions. The CS and EIP typically don’t point to the culprit. Aborts are mainly caused by hardware errors and invalid values within important system data structures.