Protected Mode Virtual Interrupts (PVI)

on Pentium and SL-enhanced i486 Intel processors

by Maciej W. Rozycki


The following information was obtained by reverse engineering, using my own test application written some years ago for debugging protected mode programs. The tests were performed on a WB-enhanced i486 DX2 processor (model = 3, stepping = 6) that returned the following value for CPUID feature flags: CPUID(1).EDX = 0000000B. No confidential sources of knowledge were used. The following publicly available documents proved to be useful: [1] and [2].

1. Introduction

Sensitive instructions (in the protected mode) are opcodes related to I/O operations. They can be dangerous to the system, but there may exist a necessity of their execution at the privilege level lower than zero. To ensure protection and accessibility at the same time, a special two-bit field called IOPL was introduced into the EFLAGS register (IOPL stands for Input/Output Privilege Level). The sensitive instructions can be executed only if CPL <= IOPL (where CPL or Current Privilege Level is PL of the CS selector). In this way, disabling of access to these instructions can be achieved. Additionally, to prevent user-level code from changing the IOPL field, POPF or IRET do not modify it when executed at CPL > 0.

Here is the list of sensitive instructions:

Trying to execute any of these instructions when CPL > IOPL will cause a General Protection Fault (#GP), that is exception 13 (for I/O instructions, the I/O permission bitmap may prevent the fault from occuring for selected I/O addresses). However, an implicit attempt to modify the IF bit, using POPF or IRET instructions will not cause any error -- simply the new value of the IF bit will be discarded.

2. Virtual Interrupts (traditional approach)

Of all sensitive instructions, only CLI and STI are discussed here as only these instructions are related to the mechanism of interrupts.

It may happen, that some application programs would like to serve hardware interrupts, originated from non-standard devices that were not known at the time the operating system (OS) was created. Additionally, such a program might not want to receive these interrupts during some special operations. In this situation the OS may detect executing of the CLI or STI instruction and set appropriate internal variables that would decide whether to let the application serve the interrupt or not. It has to be noted, that there may exist many concurrent tasks in the system that may be cyclically switched using, for example, a timer interrupt. Moreover, it may be necessary to serve some other peripherals, such as keyboard. In such case, disabling of interrupts may lead to a system crash. However, it is possible to use the mechanism of so called virtual interrupts, in which disabling of interrupts performed by one of tasks, causes only excluding the task from serving these interrupts.

Another example of using CLI and STI instructions is defining of application level programs' critical sections. Executing of the CLI instruction would guarantee uninterrupted execution of the following section, until the STI instruction is met. Also in this situation, it is necessary to preserve system's vitality -- a malicious application could execute the CLI/JMP $ sequence causing the system halt. To avoid this, the OS may treat the CLI instruction only as a request to ensure a mutual exclusion, which could be achieved, for example, using a temporary blockade of the user-level task switching.

Meeting of the above conditions may be realized in a #GP exception serving routine. Unfortunately, generating of a #GP exception on every CLI and STI occurence is very time-consuming. The routine has to be called through a gate, then it has to found out the exception's reason, then it has to perform appropriate actions and, finally, it has to return to the interrupted program. A typical action performed by this procedure in case of CLI and STI instructions, is to modify a flag defined somewhere in the task state area to inform of the actual state of task's local interrupt subsystem.

3. Pentium Processor's Virtual Interrupts Improvement

Just for improving processor's performance, in Pentium and SL-enhanced i486s, Intel introduced new mechanisms and some bits that are related to them. These bits are: the PVI bit of CR4 register (Fig.1) and VIF and VIP bits of EFLAGS register (Fig.2). The effect of these bits is as follows:

 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|                                                 |M|-|P|D|T|P|V|
|                 R E S E R V E D                 |C|-|S|E|S|V|M|
|                                                 |E|-|E| |D|I|E|
                                                     *       ^

^ -- these bits are referenced
* -- these bits are reserved

Fig.1. The CR4 Register (as defined for Pentium)

 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|                   |I|V|V|A|V|R| |N|I O|O|D|I|T|S|Z| |A| |P| |C|
|  R E S E R V E D  |D|I|I|C|M|F|0|T|P L|F|F|F|F|F|F|0|F|0|F|1|F|
|                   | |P|F| | | | | |   | | | | | | | | | | | | |
                     ^ ^ ^       *    ^      ^       *   *   *

^ -- these bits are referenced
* -- these bits are reserved

Fig.2. The EFLAGS Register (as defined for Pentium)

This mechanism significantly simplifies user-level interrupt serving routines. Before giving control the OS just checks the VIF flag and depending on its value takes an appropriate further action. In particular, using this technique, it is possible to execute some system level programs or procedures (designed for execution at CPL = 0) at the application level (at CPL = 3).

Additionally, the VIP flag makes it easier to monitor enabling of virtual interrupts. This bit may be used by the OS for marking, that having VIF = 0 (e.g. when serving a virtual interrupt), another virtual interrupt was signaled. In this case the OS stores the interrupt reason and sets the VIP flag, which means a virtual interrupt is currently pending. At the moment of enabling of virtual interrupts and having CPL = 3 and IOPL < 3, a #GP exception is generated. A handler that serves this situation may now reset the VIP flag and give control to the right procedure that will serve the previously pending interrupt. Certainly, incoming interrupts may be queued. In this situation resetting of VIP would occur only after emptying the queue.

Here is the summary of the flags that are used by the mechanism of virtual interrupts and which are subject to protection:

Certainly, all flags are modified at the time of a task switch. Note that, unlike the IF flag, VIF is not modified at the time of transition through interrupt or trap gates.

In the case of an (E)FLAGS related protection violation when executing IRET(D) or POPF(D), no exception is generated and the protected bit remains unchanged. On the other hand, a #GP exception is generated when trying to execute a program having both VIP and VIF set, provided that PVI = 1 and CPL = 3 (IOPL may have any value). That happens, for instance, after executing the IRETD instruction that changes the CPL from 0 to 3 and writes ones to VIP and VIF. The instruction finishes successfully and then, before the next opcode, a #GP exception is generated. Testing of the EFLAGS image on the stack or in the Task State Segment (depending on the #GP exception gate type) returns both VIP and VIF bits set.

4. Detection of Mechanism of Virtual Interrupts Presence

The mechanism of protected mode virtual interrupts is implemented along with virtual mode extensions and is identified by the VME bit returned by the CPUID instruction. Processors that do not implement the CPUID instruction, do not support virtual interrupts. To get the value of the VME bit the following sequence has to be performed:

 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|                                           |A|C|M|-|M|T|P|D|V|F|
|              R E S E R V E D              |P|X|C|-|S|S|S|E|M|P|
|                                           |I|8|E|-|R|C|E| |E|U|
                                                   *         ^

^ these bits are referenced
* these bits are reserved

Fig.3. The Feature Flags (as defined for Pentium)

5. Source Code Example

The following example proved to be useful for reverse engineering the PVI feature of the Pentium processor. The source code can be assembled in several ways leading to different executables (see notes at the beginning of the program).

View the source code of PVI.ASM:

Download the source code with a sample executable:

6. Literature

  1. Pentium Family User's Manual, Volume 3: Architecture and Programming Manual, Intel Corporation, 1994, order number 241430-003
  2. AP-485, Intel Processor Identification with the CPUID Instruction, Intel Corporation, 1994, order number 241618-003

Back to Books and Articles home page