Geoff Chappell - Software Analyst
This function is defined in the abstract as belonging to the DOS Protected Mode Interface (DPMI), if only in some versions of the specification. It is there described as Get Vendor-Specific API Entry Point.
This note is concerned with the function only in the concrete sense of its implementation by Microsoft to expose its DPMI servers’ MS-DOS Extensions. Implementations are known in both the DOS extenders that Microsoft distributed with Windows:
If only for now, this note is mostly specific to the VMM’s implementation.
The function uses registers for both input and output. In the intended circumstances, int 2Fh function 168Ah is called by a DOS program that is already executing in protected mode. Far pointers are 16:16 or 16:32, depending on whether the caller has got to protected mode as a 16-bit or 32-bit DPMI client, respectively.
ax | 168Ah |
ds:(e)si | address of null-terminated string “MS-DOS” |
The subfunction number, 8Ah, is defined symbolically as W386_DPMI_Extension in a header named INT2FAPI.INC which Microsoft distributed with the Device Driver Kit at least as early as for Windows 3.1.
al | 00h |
ah | 00h, DOSX only |
es:(e)di | address of API Entry Point for MS-DOS Extensions |
In the intended use, the API entry point is to be called in protected mode by a far call. This entry point also uses registers for both input and output. One register on input acts as a function number. See this page’s next section for the two implemented functions.
The returned address is allocated during the VMM’s initialisation before even any VxD executes in protected mode. This one address is returned for all DPMI clients for all remaining VMM execution. This behaviour is presumably an implementation detail. The interface itself would seem to allow different addresses for different DPMI clients (but the one address for all calls by any one DPMI client).
If the VMM does not implement the function (as with Windows 3.0) or if the input string is not exactly as expected, the function is returned with no registers changed.
Possibly just by not explicitly excluding the case, the VMM has function 168Ah succeed even when called by virtual-8086 code. The returned address, being an offset from a protected-mode selector, is not immediately useful. DOSX, by contrast, implements the function only in protected mode.
DOSX, however, has what may be a coding error for what it returns on success: where the VMM clears only al, DOSX clears the whole of ax.
The important use of int 2Fh function 168Ah is by KRNL386.EXE in Windows 3.1 and higher. This starts as an MS-DOS application with real-mode addressing but it soon switches to protected mode as a 16-bit DPMI client. It then requires that the MS-DOS Extensions be present, which is tested by seeing that int 2Fh function 168Ah changes al from 8Ah (not by requiring a change to 00h specifically). Failure causes an immediate exit with the error message
KERNEL: Inadequate DPMI Server
The only use that KRNL386 makes of the returned API entry point is to call function 0100h. See below.
Function 0000h is described by Microsoft’s DPMI Specification as Get MS-DOS Extension Version.
ax | 0000h |
flags | carry flag clear; other flags corrupt, DOSX only |
ax | 0100h, apparently meaning MS-DOS Extensions version 1.0 |
Presumably, this version enquiry can fail, with a set carry flag, but this is here dismissed as merely theoretical.
Microsoft’s DPMI Specification presents function 0100h as Get Selector to Base of LDT.
ax | 0100h |
flags | carry flag clear; other flags corrupt, DOSX only |
ax | selector for caller’s LDT |
Though the interface itself allows that different DPMI clients can be given different selectors, the implementation allocates one selector during the VMM’s initialisation and returns this to all DPMI clients in all virtual machines for the remainder of the VMM’s execution.
What the selector’s numerical value is fixed to can vary for different executions of the VMM, presumably to discourage programmers from thinking to hard-code any one observed value. The particular implementation constrains the selector to the range 0087h to 00FFh inclusive. The variability is obtained from the timer tick that the BIOS maintains at the real-mode address 0040:006C.
The interface’s usefulness surely lies in what the returned selector is expected to address, but the interface looks like it does not itself specify the Table Indicator (TI) or Requested Privilege Level (RPL) bits in the selector’s numerical value or perhaps even the Descriptor Privilege Level (DPL) or Type bits in the selected descriptor. It perhaps just happens that the implementation returns an LDT selector that requests ring 3 access and that the selected descriptor grants ring 3 access to the corresponding segment as read/write data. The segment is whatever the VMM uses as the DPMI client’s LDT. The implementation allows that this LDT can move. The VMM before Windows 95 allows that the LDT can grow. The VMM in Windows 95 avoids this by reserving the maximum 16 pages that an LDT can ever require.
The VMM for Windows 3.0 exposes this LDT self-selector through int 2Fh function 1688h.
The same selector that these functions return to protected-mode DPMI clients is also given to VxD callers of the VMM service _Allocate_LDT_Selector. The allocated selector, which is the service’s immediate reason for existence, is returned in ax (if the service is successful) but the selector to the LDT is returned in the low word of edx and the high word has the LDT’s current capacity as a count of selectors. Documentation notes that “Although this service returns a selector to the LDT, virtual devices should not attempt to edit the LDT directly.”
flags | carry flag set |
The VMM in Windows versions before Windows 95 fails this function unless the call is made in the System VM specifically.
This constraint’s removal for Windows 95 apparently dates from very early pre-release builds. Removal will have been needed for what the README.TXT file, dated 29th July 1993, in the 58s build of Chicago refers to as a “Win 3.1 MS-DOS VM” with “instructions in the release notes”. This is the same feature that Raymond Chen, from Microsoft, described in 2018 as For a brief period, Windows 95 could run Windows 3.1 in a virtual machine. The constraint was not restored at the end of Raymond’s “brief period” and therefore does not get in the way of having Windows 3.1 in a Windows 95 virtual machine even for Windows 95 as formally released.
The known user of this function, KRNL386.EXE in Windows 3.1 and higher, tests both that the function formally succeeds and that what’s returned in ax is valid as a selector that allows write access, i.e., that the verw instruction sets the zero flag. Curiously, failure on either count is not immediately fatal. At some time in the development of the Windows GUI for protected-mode execution, there must have been some intention that having this selector was merely an optimisation. Without it, KRNL386 can call one or another int 31h function to operate on the LDT. The reality, however, is that at least by the time of Windows 3.1, much of the code in KRNL386, including such seemingly simple exported functions as SetSelectorBase, merely assumes the LDT’s self-selector was obtained. A KRNL386.EXE from any Windows 3.1x will not run for long under the VMM from any Windows 3.1x except in the System VM or if the VMM’s implementation is adjusted not to fail for other VMs.