Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Undocumented Corner


Kelly graduated with a Bachelor of Science in Electrical Engineering and Computers from the University of Waterloo in Ontario, Canada. He has spent the last ten years programming in C and assembler on Intel-based machines. Most recently he has worked on peripheral hardware design and virtual device drivers.


Introduction

by Andrew Schulman

Much of the preemptive multitasking needed for Microsoft's forthcoming Chicago operating system (Windows 4) already exists inside Windows 3.1 Enhanced Mode. The Windows 3.1 Virtual Machine Manager (VMM) that lives inside WIN386.EXE is missing features such as threads and mutexes, but it does have a fully preemptive time-slice scheduler, semaphores, lists of suspended processes, priorities, and many other features that you'd expect from a preemptive multitasking operating system. In fact, DOS386.EXE (which contains the VMM and many of the VxDs that make up the Chicago operating system) is an outgrowth of WIN386.EXE.

One reason we tend not to think of Windows 3.1 as a full-blown multitasking operating system is that, ironically, Windows doesn't extend these capabilities to Windows applications. As Kelly Zytaruk notes in this month's "Undocumented Corner," the Windows kernel is a simple, non-preemptive multitasking operating system that runs as a single task within the larger Windows Enhanced Mode preemptive multitasking operating system.

But Windows Enhanced Mode doesn't preemptively multitask Windows applications; what, then, are the "tasks" that VMM manipulates? They're Virtual Machines (VMs). All Windows applications run in a single VM, called the "System VM." Each DOS Box is a separate VM. Thus, right now, all these preemptive multitasking facilities largely benefit DOS programs. Things will be different in Chicago, where the Win32 API provides threads, thread-synchronization facilities, and preemptive multitasking.

Since each task runs in its own separate VM, Windows multitasking is largely invisible to applications. Windows doesn't provide DOS and Windows applications with an API for communicating with other tasks, apart from a handful of calls such as INT 2Fh AX=1683h (Get Current VM ID) and AX=1685h (Switch VMs and CallBack) documented in the Windows Device Driver Kit (DDK).

However, Windows 3.1 does extend a multitasking API to Virtual Device Drivers (VxDs). The DDK documents a set of scheduler services provided by VMM, such as Adjust_Exec_Priority, Begin_Critical_Section, Wait_Semaphore, Call_When_Task_Switched, Suspend_VM, Release_Time_Slice, Call_When_Idle, and so on.

VMM identifies each virtual machine with a VM handle, which is the 32-bit linear address of a Virtual Machine Control Block (VMCB).

At first glance, the structure of the VMCB appears to be documented in the VMM.INC file included with the DDK. However, Microsoft documents only the first four fields of this actually quite large structure.

This month's "Undocumented Corner" is the first of two articles in which Kelly Zytaruk lays bare the VMCB structure for Windows 3.1. This month, Kelly shows the overall structure of the VMCB and begins a detailed explanation of each VMCB field owned by VMM. Next month, he'll explain the remaining VMM fields and present a Windows VM Explorer application.

Some of the VMCB structure is apparent from even a cursory disassembly of VMM. For example, Get_Next_VM_Handle expects a VM handle in the EBX register, and returns the handle of the next VM in the EBX register. The implementation for Get_Next_VM_Handle starts with MOV EBX, DWORD PTR [EBX+68h]; not surprisingly, Kelly documents offset 68h in the VMCB as the Next pointer in VMM's linked list of VMs.

Knowing the VMCB structure raises the question of where you find a VM handle. In a VxD, it's simple: EBX in a VxD usually points to the current VM. VMM provides functions such as Get_Sys_VM_Handle and Get_Next_VM_Handle.

But how can something other than a VxD get a VM handle? In "Identify the Running DOS Application from Windows" (Windows/DOS Developer's Journal, December 1992), Paul Bonneau showed that Windows 3.1 stores a DOS Box's VM handle at offset 0FCh in the WINOLDAP data segment. This will likely change in future versions of Windows, but Figure 1 presents a VM_FROM_HWND() macro that a Windows application could use to get the VM handle for a DOS program.

A documented way for applications to get VM handles is to use my generic VxD, which gives normal DOS and Windows programs access to VMM and VxD functions, including Get_Sys_VM_handle and Get_Next_VM_Handle (see my article, "Call VxD Functions and VMM Services Using Our Generic VxD," Microsoft Systems Journal, February 1993). In Part 2 of this article, Kelly will use an improved version of the generic VxD for his VM explorer.

Once you have a VM handle, how do you get a VMCB? The VM handle is the 32-bit linear address of a VMCB. To do anything with such an address, a program needs to turn it into a protected selector:offset pointer. As noted in last month's "Undocumented Corner," the documented Windows API functions AllocSelector, SetSelectorBase, and SetSelectorLimit (or their DPMI INT 31h equivalents) can be lashed together to create a handy map_linear function that lets you turn a VM handle into a far pointer to a VMCB, then access fields in the structure; see Figure 1.

Normally, you would think of each VM as having its own address space, but various fields in the VMCB allow you to access data in other VMs. The CB_High_Linear field documented in the DDK provides a way to look at real-mode addresses in other VMs. The LDT field at offset 114h in the VMCB is also quite useful, as it provides a way to access protected-mode addresses in other VMs. In the future, I'll present the source code for a PROTDUMP utility (see Figure 2) that uses VMCB+114h to do this. While there isn't room to present the source code for PROTDUMP here, PROTDUMP.EXE (and VXD.386, the generic VxD required by certain PROTDUMP command-line options) is available electronically; see "Availability," page 3.

The VMCB structure Kelly presents here is valid only for the retail version of Windows 3.1. The LDT is at VMCB+0x11C in the debug version of Windows 3.1, and at VMCB+0x5C in the Chicago prerelease. Chicago still has a VMM, VxDs, a VMCB, and so on--in fact, in Chicago these Windows components become even more important, because they may largely replace real-mode MS-DOS--but of course all the VMCB offsets have changed. Much of the VMCB contents appear to have been moved to a Thread Control Block. For example, offset 0 in the VMCB now appears to hold the initial thread handle, as returned by Get_Initial_Thread_Handle. (Given a thread, you can get back to the VMCB with Get_VM_Handle_For_Thread.) As another example, Schedule_VM_Event now calls Get_Initial_Thread_Handle and then does a Schedule_Thread_Event.

However, the specifics of what is in which VMCB field aren't nearly as important as simply seeing what's kept in the VMCB to start with; that is, seeing what VMM maintains on a per-VM basis. The real reason to look at the VMCB isn't to start using a highly version-specific undocumented structure, but to help clarify how Windows Enhanced Mode works. Knowing about the VMCB can also improve your understanding of the DOS Protected Mode Interface (DPMI); for example, see Kelly's explanation of the CB_PM_App_CB at offset 64h.

Also consider the selector list at offset 48h in the VMCB. The Enhanced Mode DOS extender in the DOSMGR VxD has to implement certain DOS functions such as INT 21h AH=52h by returning a protected-mode selector. DOSMGR can't know when you're done using one of these selectors, so they are permanent. However, if INT 21h AH=52h is called more than once in a VM, it's important not to allocate more selectors (only 8192 are available), so such permanent selectors are allocated with a function that first consults the selector list at VMCB+0x48. Whenever a program asks this function to map a linear address to a protected-mode selector, the function first walks the selector list to see if that linear address already has a corresponding selector. If it does, the function can just return the same selector without allocating a new one.

In DPMI, this function is INT 31h AX=2 (Segment to Descriptor). The Windows kernel uses this function to create permanent selectors such as __0040 and __B800 (see page 37 of Matt Pietrek's Windows Internals, Addison-Wesley, 1993). The DPMI server in VMM implements this function by calling Map_Lin_To_VM_Addr (documented in the DDK). The DOS extender in DOSMGR implements calls such as INT 21h AH=52h using the V86MMGR Xlat_Return_Ptr service, which in turn also calls Map_Lin_ To_VM_Addr.

Figure 2 shows a sample selector list for the System VM. Figure 2(a) first uses protdump -vm to get a list of all VMs. Here, the VMCB for VM #1 (the System VM) is at 804C1000h. The offset 48h in Figure 2(b) is a pointer (32-bit linear address) to a VMM linked list; hence the protdump -ptr - list options. Each selector-list entry is an array of two dwords, so you can display eight bytes using the protdump -dword option. VM #1 selector 101Fh has a base address of F0000h. This is the Windows __F000 selector, documented in the Windows 3.1 SDK. We can examine this selector from a DOS box (a separate address space) using PROTDUMP. In Figure 2(c), the -prot option indicates that 101F:FFF0 is a protected-mode pointer, not a real-mode address; #1 indicates VM 1.

Of course F000:FF0 is just the top of the ROM BIOS, and the beginning of extended memory. We could have looked at this from any VM, using a real-mode address. But the fact that PROTDUMP examined it using a protected-mode selector in another VM means that PROTDUMP (a DOS program) could just as easily look at any Windows data structure. For example, Program Manager here happened to have a task handle of 0617h, so PROTDUMP can examine its Task Database (TDB); see Figure 2(d). PROTDUMP uses the LDT selector at VMCB+114h to examine protected-mode selectors in other VMs (see CB_LDT in Figure 4).

It is also easy for PROTDUMP to look at real-mode addresses in other VMs, using the documented CB_High_Linear field in the VMCB. The protdump -all switch examines the same address in all VMs. In Figure 2(e), PROTDUMP is showing the current PSP in each VM; "DOS" is a handy indicator for the DOS data segment, and the current PSP is at offset 330h in DOS 4 and higher.

The documentation for both DPMI INT 31h AX=2 and VMM Map_Lin_To_VM_Addr says that selectors allocated with these functions "should never be modified or freed." From Kelly's description of VMCB+48h, you can see why: The next time someone asked to map a linear address corresponding to the freed or modified selector, VMM would return the old selector, unless the selector list were also modified.

In addition to Part 2 of Kelly's article next month, future "Undocumented Corner" columns will cover topics such as the linear-executable file format used by VxDs, the W3 format used by WIN386.EXE and DOS386.EXE, the Windows instance-data manager, undocumented MFC, NetWare Lite, and 386 memory-manager IOCTL interfaces. Please send your comments and suggestions to me on CompuServe at 76320,302.

Windows Enhanced Mode is a preemptive multitasking operating system that runs one or more separate tasks. Each task believes it has sole access to the CPU and peripherals (keyboard, display, mouse, printer, and so on).

When we talk of multitasking under Windows, we instinctively think of the Windows kernel and the running of multiple Windows programs. But the Windows kernel should be seen as a simple operating system running as a single task of a larger and more complex operating system. The Windows kernel provides a means by which one or more well-behaved Windows programs can run in the same address space, sharing I/O and system resources. Task switching is non-preemptive: It doesn't take place amongst Windows programs until a program decides to call GetMessage() or a similar function (see "Inside the Windows Scheduler," by Matt Pietrek, DDJ, August 1992). Any program can effectively starve the others of CPU time.

The real excitement in Enhanced Mode is in the larger, more-complex operating system that is run by the Virtual Machine Manager (VMM). Tasks under the VMM are called Virtual Machines (VMs) because each task appears to have sole control of the machine (or CPU). It "virtually" owns the machine. Task switching is done preemptively: The VMM decides when it's time for a task switch.

The Windows Enhanced Mode operating system actually consists of both the VMM and Virtual Device Drivers (VxDs). VxDs use services provided by VMM as well as by other VxDs. These services are documented by Microsoft in the Virtual Device Adaptation Guide included with the Windows Device Driver Kit (DDK). These services are used by VxD to limit access to, control, modify, or simulate system resources that are used by Windows or DOS programs. The VxD can make its actions totally transparent to the application, or it can provide services that an application calls explicitly (that is, nontransparently).

All accesses into the operating system from a VM must first pass through the VMM. The VMM acts as a kind of distribution system for APIs and faults. The VMM then passes the request or fault on to the appropriate VM.

Each VM has its own private address space, interrupt-vector tables, and I/O ports. With few exceptions, each VM can appear to own all aspects of the computer while concurrently running with other VMs. The first VM is the System VM, which runs the Windows kernel, graphical interface, and all Windows programs. As each non-Windows program is run, a VM is created, and a DOS box is started within that VM to run the program.

It is the responsibility of the VMM to keep VMs separate and to provide preemptive task switching and scheduling services. Each VM has an associated Control Block (VMCB). VxDs can allocate and use portions of the VMCB to maintain VM-unique data areas. During system initialization, VxDs allocate areas within the VMCB by calling a documented Allocate_Device_CB_Area provided by VMM.

Figure 3 shows a sample VMCB. The VMCB size varies from machine to machine as the configuration changes. Here, the VMCB is 1B40h bytes in size; VMM owns the first 210h bytes, VPICD owns 0BCh bytes, and so on. This article examines the contents of the VMM portion at offset 0 in the VMCB, ignoring portions of the VMCB owned by other VxDs, such as DOSMGR.

Figure 4 shows the VMCB format. VMM keeps all VMs on a linked list, and the link from one VM to the next is at offset 68h in the VMCB. This link is the 32-bit linear address of the next VMCB. As another example, the selector to a VM's Local Descriptor Table (LDT) is kept at offset 114h in the VMCB.

This information is accurate only for Windows 3.1 Enhanced Mode retail version. The Windows DDK provides a debug version of WIN386.EXE (similar to the SDK's debug versions of KRNL386.EXE and other DLLs). This includes a debug version of VMM which adds several additional fields early on in the VMCB, thus throwing out later fields. For example, the Next pointer is at offset 70h in Windows 3.1 debug, and the LDT selector is at offset 11Ch. Some of the field names come from the .VC command in the debug VMM. This command is accessible from Soft-ICE/Windows and WDEB386.

Documented Fields

The Windows DDK documents only the first four fields of the VMCB; these fields are part of the block owned by VMM. The following provides some details missing from the DDK:

0x00. CB_VM_Status. Current execution status of this VM. The execution status is a bitmap with values documented in the DDK, such as VMStat_Exclusive, VMStat_Background, VMStat_PM_Exec (VM is currently running a protected-mode program), VMStat_PM_Use32 (protected-mode program is 32-bit), VMStat_Idle, and so on. For example, when a program running under Enhanced Mode calls INT 2Fh AX=1680h (documented in the MS-DOS Programmer's Reference as the "MS-DOS Idle Call"), VMM calls the Release_Time_Slice service, which turns on the VMStat_Idle bit in the current VM's CB_VM_Status field. One status bit (10000h), which indicates that Close_VM has been called for this VM, appears to be undocumented.

The status bits are, for the most part, informative only. They are set after the internal state of the VM has been altered. VMM uses them to determine the current state of the VM and to decide how to change to a different state. Altering these bits (for example, turning on the VMStat_Background bit) is unlikely to produce the desired effect.

0x04. CB_High_Linear. Address of VM's real-mode memory in entire VMM linear address space. When a VM becomes active, its Real Mode address space is mapped down to linear address 0, which is reserved for the active VM. When the VM is not active, its memory is still accessible via the CB_High_Linear address. All access to the VM's real-mode memory should be through the CB_High_Linear address. In Figure 5, for example, to read the WORD at 0040:0008 in VM #1, you must convert 0040:0008 to a linear address, then add in the VM's CB_High_Linear address.

0x08. CB_Client_Pointer. Linear pointer to Client Register Structure. When a Virtual Machine makes a call into the operating system, all registers are saved to the Client Register Structure (CRS). The registers are restored from the CRS when the operating system returns to the VM. VxDs can examine and alter the VM registers by accessing the CRS. VMM points the EBP register at the CRS, which is defined in VMM.INC in the DDK. For example, if a VxD refers to dword ptr [ebp+1Ch], it generally means Client_EAX.

0x0C. CB_VMID. Unique ID number to identify VM. Each VM has a unique ID number, starting with 1 for the System VM. Any application can call INT 2Fh AX=1683h (Get Current Virtual Machine ID) to get the ID for its VM. This is different from VM handle; VxDs refer directly to the VMCB (VM handle) when referencing VMs.

Undocumented Fields

The fields of the VMCB described in this section are undocumented.

0x10. CB_PM_Int_Table. Linear address of protected-mode Interrupt Table. This field is valid only if a protected-mode program is present in the VM (that is, [ebx+CB_VM_Status] & VMStat_PM_App) ; it is 0 if a real-mode program is executing.

While in protected mode, interrupts are processed through the Interrupt Descriptor Table (IDT), which in many cases points to VMM entry points. If the VMM decides to pass the interrupt on to the application, it reflects the interrupt through the address given in PM_Int_Table. This VMM table of 256 8-byte entries is not the same as the IDT (which only holds 60h entries, up to INT 5Fh). VMM uses this table to exert complete control over interrupt ownership. Get_PM_Int_Vector returns a value from this table. Set_PM_Int_Vector inserts a value into this table and, if the VMM permits, alters the IDT entry as well.

0x14. CB_VM_ExecTime. VM execution time--the number of milliseconds this VM has actually been active. This is not a measure of the lifetime of the VM but rather an accounting of how much CPU time the VM has had. This value is returned by the VMM Get_VM_Exec_Time and Get_Last_Updated_VM_Exec_Time.

0x18. CB_V86_PageTable. Linear address of the page table used by this VM. The first 1 Mbyte+64K (possibly up to 4 Mbytes) of each DOS box is mapped to physical memory via this page table. Each VM has its own unique page table. Thus programs in different VMs can use the same virtual addresses, but via the page tables, have different linear and physical address. When this VM becomes active, this page table is mapped to Linear address 0.

0x1C. CB_Local_Port_Trapping_BitMap. Port Trapping Enable/Disable Array. This 32-byte array is treated as a continuous string of 256 bits. There's one bit per port: If the bit is set, Local Port trapping has been enabled for this port; otherwise, it's disabled. If Global Port trapping has been enabled, the bit will be set in the system Global Port trapping array and in each VM. The bits in this array are accessed directly by calls to Enable_Local_Trapping, Disable_Local_Trapping, Enable_Global_Trapping, and Disable_Global_Trapping.

But what about port numbers higher than 256? As it turns out, each bit does not map one-to-one to a port. VMM passes the port number through a hashing function and converts the port number to a bit offset. This means you can't enable local or global I/O trapping on more than 256 different ports.

0x3C. CB_Begin_Nested_Exec_List. List handle to Nested_Exec_List. When Begin_Nested_Exec is called, the current CS:IP and status is saved and the CS:IP in the CRS is changed to an address that will cause an entry exception into the operating system. The CS:IP is saved in a list node. This is the handle to the list; End_Nested_Exec uses this list to restore the CS:IP and execution state.

0x40. CB_OS_Stack. Linear address of operating-system stack. VMM switches stacks to an internal stack for VMM calls. Each VM supplies a different private stack. This is a 32-bit protected-mode stack. While executing within the VMM, the previous ESP is saved in this field and restored from here upon exiting.

0x44. CB_Scheduler_Flags. Bitmap of scheduler flags; see Table 1.

0x48. CB_Selector_List. List handle of mapped selectors. The Map_Lin_To_VM_Addr service maps a linear address to an address in the VM address space. If the VM is in V86 mode, then the returned address is a segment:offset pair. If the VM is in protected mode, the service must allocate an LDT selector. The LDT selector is then linked onto the CB_Selector_List. If a call is made to Map_Lin_To_VM_Addr with another linear address that can be satisfied by a previously allocated selector, it will reuse the previous selector instead of allocating a new one. For this reason, it's important not to delete selectors allocated by this service. As Figure 2 shows, each selector entry includes the base address and selector; VMM uses the LSL instruction to get the selector limit (size).

0x4E, 0x50. CB_Locked_PM_Stack_LDT and CB_Locked_PM_Stack_GDT. LDT selector of locked protected-mode stack and GDT selector of locked protected-mode stack. When a protected-mode stack is allocated, both a 16-bit LDT selector and an equivalent 32-bit GDT selector are allocated. If the VM is currently running a 16-bit protected-mode program, the LDT will be used for the SS register. If the VM is currently running a 32-bit protected-mode program, the GDT selector will be used.

0x52, 0x54. CB_Locked_PM_Stack_Prev_SS and CB_Locked_PM_Stack_Prev_ESP. When a call is made to use the locked protected-mode stack, the current stack-pointer registers (SS:ESP) are saved in these fields. SS:ESP is restored from here when the application is finished with the locked PM stack.

0x58. CB_Locked_PM_Stack_hMem. Page handle to protected-mode stack. When a protected-mode stack is allocated, the page handle is saved in this field so that the stack can be locked, unlocked, and freed by calling the page allocator.

0x5C. CB_Locked_PM_Stack_Count. PM stack reference count. Each time a call is made to Use_Protected_Mode_Stack, this counter is incremented. When End_Use_Protected_Mode_Stack is called, the counter is decremented. When the counter reaches 0, the stack is switched back to the original stack as saved in the CB_Locked_PM_Stack_Prev_SS and CB_Locked_PM_Stack_Prev_ESP fields.

0x60. CB_Locked_PM_Stack_EIP. When Begin_Use_Locked_PM_Stack is called, the current application EIP is saved in this field. It is restored as the application EIP when End_Use_Locked_PM_Stack is called.

0x64. CB_PM_App_CB. Protected-mode Application Control Block. The PM App CB is the DPMI-host private-data area that a program allocates after calling INT 2Fh AX=1687h, and before switching to protected mode. VxDs can allocate space in here with the Allocate_PM_App_CB_Area call, which returns an offset into the PM App CB. The space requested by various VxDs determines the number of host-data paragraphs returned in the SI register by INT 2Fh AX=1687h.

Because this field holds a single value, rather than a pointer to a list, VMM can safely run only one DPMI client in a VM at a time. Each call to the DPMI Switch to Protected Mode entry point will overwrite this field with the address of the host private-data area allocated by the DPMI client. If DPMI clients are "nested" (that is, a DPMI client spawns a program that also calls the DPMI entry point), when the second client exits it will deallocate the first client's data area.

The first two DWORD fields of the protected-mode Application Control Block are documented as PMCB_Flags and PMCB_Parent. The VMM uses two DWORD fields in the descriptor block for DPMI: DPMI_PageList and DPMI_DOSMem_List. The offsets of the two fields are accessible only from within the VMM, because there is no way to determine where the VMM portion of the PM_App_CB starts. DPMI_PageList is a handle to a list of pages allocated for a protected-mode program by calls made to INT 31h AX=501h (Allocate Memory Block). DPMI_DOSMem_List is a handle to a list of selectors allocated for a protected-mode program by calls to INT 31h AX=101h (Allocate DOS Memory).

0x68. CB_VM_List_Link. Linear address of next VM. As VMs are created, they are linked on a list, newest VM first. The System VM is thus always the last on the list. This field is the linear address of the VMCB for the next VM on the list. The system VM, as the last in the list, has a value of 0 in this field.

Get_Next_VM_Handle uses this field to determine the next VM handle. This function gives the appearance of a circular list, as it will return the address of the first VM in the list when it finds a value of 0 in this field.

0x6C, 0x70. CB_ListNext (pointer to next VM on a VM list) and CB_ListHead (pointer to head of a VM list). Throughout its life, a VM can appear on the Ready list, the Waiting list, or a Blocked-semaphore list. This field is the linear address of the head of the list to which the VM is currently attached. The CB_ListNext field points to the next VM on the list. The list is usually ordered by execution priority, starting with the highest-priority VM.

0x74. CB_BlockedSemaphore. Handle of semaphore blocked on. If the VM has called WaitSemaphore to access code or data associated with a semaphore but the semaphore is already in use, then the VM will block on the semaphore. This field will contain the handle for the semaphore on which the VM is blocked.

0x78. CB_SuspendedList_Head. Head of VM list during Suspend. If the VM was on a list (see CB_ListHead) when a call was made to suspend the VM, the VM will be removed from the list, and the head of the VM List will be saved here. Resume_VM will use this field to place the VM back on the list when it is runable again.

0x7C. CB_Suspended_BlockedSemaphore. Semaphore handle during Suspend. If the VM was blocked on a semaphore when a call was made to suspend the VM, then the semaphore reference count will be decremented by 1, and the handle to the semaphore will be saved. Resume_VM will use this field to block the VM again on the semaphore when it is runable.

0x80. CB_Exec_Priority_Bits. Execution Priority. This is the current execution priority of this VM. It may be any number of scheduler "boost" values documented in the DDK, such as High_Pri_Device_Boost and Critical_Section_Boost. Increasing or decreasing the boost values either increases or decreases the VM's execution priority as the requirements of the VM change. The priority can be changed by calling the VMM Adjust_Exec_Priority with either a positive or a negative boost in EAX. The lists to which CB_ListHead (offset 0x70) point use these priority values to order the VM on the list.

0x88. CB_Suspend_Stack. When a task switch occurs, the state registers are saved on the stack. When a VM has been suspended, its ESP is saved in this field so that it can be restored to the same place when the task resumes. Thus the entire register set can be saved and restored.

0x8C. CB_TSS_ESP0. Task State Segment, Stack pointer 0. This specifies the linear address of the stack to use when an exception occurs that causes a Ring 0 transition and entry to the 32-bit VMM. The real-mode registers are saved onto this stack. The CRS field at offset 0x08 points to the bottom of this stack; this is where the VM registers are saved.

0x90. CB_hMem_Stack. Page handle to stacks. The VMM and VM in combination require several stacks allocated from the same page-allocate call. The handle to the page or pages that contain the stacks is held here.

0x94. CB_SuspendedVM_Count. Suspended count. Each time a VM is suspended, this value is incremented. It's decremented by a call to Resume_VM. This VM will not be permitted to resume until this value goes to 0.

0x98. CB_SuspendedVM_EventHandle. Suspend event handle. When a VM is suspended, the VM's locked stacks and other associated memory can be unlocked and the resources reused, since the VM won't be using them for a while. This isn't a time-critical operation and the operating system may not be in a stable state to perform the operation. An event is scheduled such that when the VMM becomes stable, the locked memory will be unlocked. The event handle is stored in this field.

An event is a function called when certain criteria are met and the VMM is in a stable condition; for example, when interrupts are enabled or when a critical section is unowned. Events can be scheduled globally or by VM. A global event will be called when the VMM is stable (usually just before returning to a VM). A VM event, on the other hand, will be called only when the VM is current. Events are usually scheduled by hardware interrupts or other asynchronous events, but they can be scheduled by calls to functions such as Call_VM_Event or Schedule_Global_Event.

On to Next Month

That's it for this month. In the next "Undocumented Corner," we'll examine the remaining VMCB fields and create a VM explorer. We'll also uncover a useful undocumented structure created during VM initialization.

Figure 1: Accessing a VMCB from a Windows program.

// for Windows 3.1 only!
#define VM_FROM_HWND(hWndDosBox) \
    *((LPDWORD) MK_FP(GetWindowWord(hWndDosBox, GWW_HINSTANCE), 0x0FC))
// undocumented Windows API function; see UndocWin, pp. 303-304
extern BOOL FAR PASCAL IsWinOldApTask(HANDLE hTask);
#define ISDOSBOX(hWnd) IsWinOldApTask(GetWindowTask(hWnd))
if (ISDOSBOX(hwnd)) {
    VMCB far *vm_cb = (VMCB far *) map_linear(VM_FROM_HWND(hwnd),
        sizeof(VMCB));
    WORD ldt = vm_cb->CB_LDT;
    // or *((WORD far *) ((BYTE far *) vm_cb) + 0x114)
    FreeSelector(FP_SEG(vm_cb));
    // now do something with LDT in other VM
    }

Figure 2: Using PROTDUMP to display a selector list and to examine memory in other VMs.

<b>(a)</b>
     C:\DDJ\VM>protdump -vm
     #1  VMCB=804C1000   high lin=81C00000h
     #2  VMCB=8065A000   high lin=82000000h

<b>(b)</b>
     C:\DDJ\VM>protdump -ptr -list -dword 804c1048 8
     804007E0 | 000E0000 0000104F
     804007D4 | 000D0000 00001047
     804007C8 | 000C0000 0000103F
     804007BC | 000B8000 00001037
     804007B0 | 000B0000 0000102F
     804007A4 | 000A0000 00001027
     80400798 | 000F0000 0000101F
     8040078C | 00000400 00001017
     80400770 | 00000000 0000100F
     804002D4 | 000009A0 00001007
     804002C8 | 0001E490 000000C7

<b>(c)</b>
     C:\DDJ\VM>protdump -prot #1 101f:fff0
     81CFFFF0 | EA 5B E0 00 F0 30 36 2F 30 36 2F 39 31 00 FC 00 | .[...06/06/91...
     81D00000 | 00 00 00 56 44 49 53 4B 33 2E 33 80 00 01 01 00 | ...VDISK3.3.....

<b>(d)</b>
     C:\DDJ\VM>protdump -prot #1 0617:00f0
     81C4FEF0 | 00 00 50 52 4F 47 4D 41 4E 00 54 44 00 00 00 00 | ..PROGMAN.TD....

<b>(e)</b>
     C:\DDJ\VM>protdump -all DOS:330 -word 2
     #1 81C00CD0 | 29DC
     #2 82000CD0 | 710B

Figure 3: Layout of a sample VM Control Block. Note that sizes are rounded up to a multiple of four bytes.

VxD Owner     Size     Offset into VMCB
VMM           210h     0h
VPICD          BCh     210h
VTD            17h     2CCh
VDDVGA        840h     2EAh
VKD            E3h     B24h
VFD             2h     C08h
DOSMGR         4Dh     C0Ch
.     .     .
.     .     .
.     .     .
VSERVER         4h     1A9Ch
VMPOLL         14h     1AA0h
VPFD           8Ch     1AB4h

Figure 4: The Windows 3.1 Virtual Machine Control Block (offsets are for the retail version).

typedef struct {
    DWORD CB_VM_Status;                     // 00h
    DWORD CB_High_Linear;                   // 04h
    DWORD CB_Client_Pointer;                // 08h
    DWORD CB_VMID;                         // 0Ch
    DWORD CB_PM_Int_Table;                  // 10h
    DWORD CB_VM_ExecTime;                   // 14h
    DWORD CB_V86_PageTable;                 // 18h
    DWORD CB_Local_Port_Trapping_BitMap[8];  // 1Ch
    DWORD CB_Begin_Nested_Exec_List;         // 3Ch
    DWORD CB_OS_Stack;                      // 40h
    DWORD CB_Scheduler_Flags;               // 44h
    DWORD CB_Selector_List;                 // 48h
     WORD CB_unused0;                      // 4Ch
     WORD CB_Locked_PM_Stack_LDT;           // 4Eh
     WORD CB_Locked_PM_Stack_GDT;           // 50h
     WORD CB_Locked_PM_Stack_Prev_SS;       // 52h
    DWORD CB_Locked_PM_Stack_Prev_ESP;       // 54h
    DWORD CB_Locked_PM_Stack_hMem;          // 58h
    DWORD CB_Locked_PM_Stack_Count;         // 5Ch
    DWORD CB_Locked_PM_Stack_EIP;           // 60h
    DWORD CB_PM_App_CB;                     // 64h
    DWORD CB_VM_List_Link;                  // 68h
    DWORD CB_ListNext;                      // 6Ch
    DWORD CB_ListHead;                      // 70h
    DWORD CB_BlockedSemaphore;              // 74h
    DWORD CB_SuspendedList_Head;            // 78h
    DWORD CB_Suspended_BlockedSemaphore;     // 7Ch
    DWORD CB_Exec_Priority_Bits;            // 80h
    DWORD CB_SchedulerStatus                // 84h
    DWORD CB_Suspended_Stack;               // 88h
    DWORD CB_TSS_ESP0;                      // 8Ch
    DWORD CB_hMem_Stack;                    // 90h
    DWORD CB_SuspendedVM_Count;             // 94h
    DWORD CB_SuspendedVM_EventHandle;        // 98h
     WORD CB_ForeGround_TS_Priority;         // 9Ch
     WORD CB_BackGround_TS_Priority;         // 9Eh
    DWORD CB_Weighted_Priority;             // A0h
    DWORD CB_Weighted_Time;                 // A4h
    DWORD CB_Next_Scheduled_VM;             // A8h
    DWORD CB_Last_Weighted_VMTime;           // ACh
    DWORD CB_ExtendedErrorCode;             // B0h
    DWORD CB_ExtendedErrorRefData;          // B4h
    DWORD CB_V86_PgTbl_PhysAddr;            // B8h
    DWORD CB_Int_Table_Instance;            // BCh
    DWORD CB_hMem_VMDataArea;               // C0h
    DWORD CB_Int_Table_hMem;                // C4h
    DWORD CB_DeviceV86Pages[9];             // C8h
    DWORD CB_V86PageableArray[8];           // ECh
    DWORD CB_MMGR_Flags;                   // 10Ch
    DWORD CB_MMGR_Pages;                   // 110h
     WORD CB_LDT;                         // 114h
     WORD CB_unused2;                      // 116h
    DWORD CB_hMem_LDT;                     // 118h
    DWORD CB_VM_Event_Count;               // 11Ch
    DWORD CB_VM_Event_List;                // 120h
    DWORD CB_Priority_VM_Event_List;        // 124h
    DWORD CB_CallWhenVMIntsEnabled_Count;   // 128h
    DWORD CB_CallWhenVMIntsEnabled_List;    // 12Ch
    DWORD CB_Next_Timeout_Handle;           // 130h
    DWORD CB_Prev_Timeout_Handle;           // 134h
    DWORD CB_First_Timeout;                // 138h
    DWORD CB_Expiration_Time;              // 13Ch
    DWORD CB_IDT_Base_hMem;                // 140h
     WORD CB_unused3;                      // 144h
     WORD CB_IDT_Limit;                    // 146h
    DWORD CB_IDT_Base;                     // 148h
    struct {                              // 14Ch
        DWORD Ex_EIP;
         WORD Ex_CS;
        } CB_Exception_Handlers[32];
    DWORD CB_V86_CallBack_List;             // 20Ch
    // end of VMM portion
    // start of VxD CB areas
    } VM_ControlBlock;                     // size: 210h bytes

Figure 5: Using the documented VMCB CB_High_Linear field to read real-mode memory in other VMs. The somehow_get_VM_handle() function could be implemented using VM_FROM_HWND() in Figure 1, or with the generic VxD.

DWORD LinAddr = (0x40L << 4) + 8;
DWORD VMHandle = somehow_get_VM_handle();
VMCB far *VMCB = map_linear(VMHandle, sizeof(VMCB));
LinAddr += VMCB->CB_High_Linear;
FreeSelector(FP_SEG(VMCB));
WORD far *WPtr = map_linear(LinAddr, sizeof(WORD));
WORD W = *WPtr;
FreeSelector(FP_SEG(WPtr));
// W is WORD at 40:08 in other VM

Table 1: VMM scheduler flags.

Flag      Description
0001h     The VM has either been Suspended or was just created
           and needs to be Resumed before it can continue.
0004h     A VM event has been scheduled to Call the VM when
           the VM Ints have been enabled.
0008h     The VM has completed <I>VM_Critical_Init</I>.
0010h     <I>Begin_Nested_Exec</I> has been called and interrupts from
           this VM can be serviced when it is blocked, regardless
           of whether or not VM interrupts have been enabled.
0020h     When this VM is blocked, do not switch away from it
           unless another VM has higher priority.


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.