Geoff Chappell - Software Analyst
The Virtual DOS Machine Manager (VDMM) in Windows/386 exposes an Application Programming Interface (API) in the first of its virtual machines to help with the creation and management of additional virtual machines. The apparent intention is that each virtual machine runs a DOS application, which may then run more, essentially as if the virtual machine were real. Though each virtual machine has at any one time just one current DOS application being executed by a single-tasking operating system that is shared by all virtual machines, the collection of virtual machines has pre-emptive multi-tasking under the supervision of the VDMM.
As distributed, this multi-tasking DOS is constrained to run Windows in its first virtual machine. The user-level management of additional virtual machines is then in the hands of a Windows module named WINOLDAP.MOD, which is the one and only known user of the Virtual DOS Application (VDA) API. Had anyone sufficiently enterprising and well-resourced been paying attention in late 1987, they might have written a DOS program that makes these VDA calls. DOS users might then have realised that Microsoft had developed a pre-emptive multi-tasking DOS but tied its availability to the purchase of Windows.
Do not miss this obscure interface’s anti-trust implications. Microsoft had a natural monopoly over DOS but already had competitors for Graphical User Interface (GUI) environments such as Windows. Instead of building the pre-emptive multi-tasking of DOS programs into DOS as a DOS feature, Microsoft used it to help establish Windows in what was still then a competitive market. The case is clearer and simpler than any that was ever made against Microsoft for anti-competitive product tying, but seems never to have got near a court, nor even to have got the attention of any regulator.
The entry point for VDA calls is returned in es:di by int 2Fh function 1600h in Windows/386 version 2.01 but int 2Fh function 1601h in later versions. Beware of an unusual design. Both these functions require an address in es:di as input. This is the address at which execution is to resume in the virtual machine. Virtual-8086 execution of the output address from these int 2Fh functions appears to resume at the input address after what the virtual-8086 “caller” may presume is a transition to and from ring 0. A simple scheme is indeed to call the entry point by a far call and to resume from it by executing a far ret, but this is not the only possible scheme.
There’s some support for regarding this VDA interface as a distant ancestor of what each Virtual Device Driver (VxD) in later Windows versions can offer as its own API for being called from the unprivileged code in virtual machines. But the ancestry is distant. Aside from the different mechanism, there is that this early API is exposed only in what is later known as the System VM. A different API for use in arbitrary virtual machines was instead exposed through int 2Fh function 1602h.
Each VDA call takes input in ax. The high byte selects an internal component of the 32-bit operating system (which error messages in WIN386.EXE actually do refer to as a system, no matter that nobody at the time, neither Microsoft itself nor industry observers, was much given to talking of it as an operating system):
These would each in later Windows versions be either the Virtual Machine Manager (VMM) or a separate VxD. The low byte is then a function number. Interpretation of other registers depends on the component and function numbers. Undefined functions change no registers.
Some historian of the development of Windows might usefully write about these functions at length. That may end up being me. Meanwhile, I offer only a sample of a catalogue. Names are known from symbol tables in the WIN386.386 binaries.
When ah is 00h on input, the VDA interface selects the internal component that the symbol tables name as the VDD. A function number in al is assumed for input, and so is a VM ID in ebx.
Function Number | Symbolic Name |
---|---|
0000h | VDD_Ctl_ID |
0001h | VDD_Ctl_Mod |
0002h | VDD_Ctl_Clr |
0003h | VDD_Ctl_Map |
0004h | VDD_Ctl_UMap |
0005h | VDD_Ctl_CStt |
When ah is 01h on input, the VDA interface selects the internal component that the symbol tables name as the VKD. A function number in al is assumed for input.
Function Number | Symbolic Name |
---|---|
0100h | VKD_Ctl_ID |
0101h | VKD_Ctl_Int9 |
0102h | VKD_Ctl_SetFocus |
0103h | VKD_Ctl_DefSwt |
0104h | unlabelled |
A function 0105h seems to have been intended. It is allowed by the code that handles the VKD functions but the jump table has only five entries and so the outcome of calling with 05h in al is undefined.
When ah is 02h on input, the VDA interface selects the internal component that the symbol tables name as the VMD, but the handler is trivial.
When ah is 03h on input, al selects from functionality in the VDMM itself.
Function Number | Symbolic Name | Versions |
---|---|---|
0300h | Get_VDMM_Version | 2.01 to 2.11 |
0301h | Map_VM_Pages | 2.01 to 2.11 |
0302h | Unmap_VM_Pages | 2.01 to 2.11 |
0303h | Create_VM | 2.01 to 2.11 |
0304h | Destroy_VM | 2.01 to 2.11 |
0305h | Suspend_VM | 2.01 to 2.11 |
0306h | Resume_VM | 2.01 to 2.11 |
0307h | Set_VM_Event_Handler | 2.01 to 2.11 |
0308h | VMDA_Present | 2.01 to 2.11 |
0309h | VMDA_Not_Present | 2.01 to 2.11 |
030Ah | Set_Map_Region | 2.01 to 2.11 |
030Bh | Unset_Map_Region | 2.01 to 2.11 |
030Ch | Set_Window_Mode | 2.01 to 2.11 |
030Dh | Set_Display_Mode | 2.01 to 2.11 |
030Eh | Task_Count | 2.01 to 2.11 |
030Fh | Set_Physical_Mode | 2.01 to 2.11 |
0310h | Change_Background_Mode | 2.01 to 2.11 |
0311h | VM_GetInfo | 2.03 to 2.11 |
As noted, the interface is exposed only in the System VM and has an unusual design that requires a single address to which all VDA functions seem to return from ring 0. Very plausibly, the one known user is the one and only intended user. This is WINOLDAP.MOD which for Windows/386 is coded as a 16-bit Windows module making heavy use of 80386 instructions. From within Windows it may be seen mostly as supervising the representation of DOS programs each in their own window. Its use of the VDA calls, however, elevates WINOLDAP into supervising the execution of DOS programs each in their own pre-emptively multi-tasked virtual machine. Do not miss that these VDA calls could instead be made by an otherwise straightforward DOS program in the System VM to co-ordinate a pre-emptively multi-tasking DOS—independently of the Windows GUI and as long ago as 1987, even before anyone had yet seen any DOS version 4.