Think Globally, Act Locally: Inside the Windows Instance Data Manager
Klaus studies information technology at the Dresden University of Technology's Fraunhofer Institut for Microelectronic Circuits IMS-2. He is currently developing a heterogeneous multiprocessor system for parallel image processing based on the TMS320C40 processor. Contact Klaus on CompuServe at 100117,2526.
Introduction
Many DOS programmers still "don't do Windows," seeing it as irrelevant to DOS programming. But this is unrealistic, because Windows Enhanced mode affects even software loaded before Windows is loaded, including DOS memory-resident programs (TSRs), device drivers, and even DOS itself. In short, many DOS programs have no choice but to become Windows-aware.
Windows awareness is especially important for "instance data." For example, load a TSR like Chris Dunford's CED command-line editor and then start Windows Enhanced mode. Open two DOS boxes. Type a command in one DOS box, switch to the other one, and then press the up-arrow key. The command typed in the first DOS box appears in the second, as "state" leaks across! This unintentional interprocess communication might appear to some programmers as a feature, but it is more likely to strike users as a bug. A DOS programmer who blows off this problem with a proud "I don't do Windows" had better be sure that every one of his users feels the same way.
Now put the statement LOCALTSRS=CED in the [NonWindowsApp] section of SYSTEM.INI (if it's not already there), and restart Windows. This time, commands typed in one DOS box are recalled only in that DOS box; they don't leak across into the other one. As its name implies, LOCALTSRS= has somehow made CED's state "local" to each DOS box. Exactly how this works is the subject of this month's "Undocumented Corner."
Rather than use CED, you can switch to the DOSKEY utility that Microsoft includes in DOS 5 and 6. This command-line editor exhibits the same correct behavior as LOCALTSRS=CED, except that no LOCALTSRS=DOSKEY statement is necessary. Clearly, DOSKEY is doing something CED isn't.
Unlike CED, DOSKEY intercepts INT 2Fh and looks for calls to AX=1605h and AX=4B05h. If it receives a call to either of these functions, DOSKEY declares the address and size of its command-line history buffer as "instance data"--that is, as data that must be local (rather than shared) in each DOS box. Note that "instance data" in this context has nothing to do with multiple "instances" of Windows applications (though there are some analogies).
INT 2Fh AX=1605h is documented in the Windows Device Driver Kit (DDK). This is a crucial interface with which DOS programmers must be familiar. It may seem perverse that an API necessary for DOS programmers is located in the Windows DDK, but INT 2Fh AX=4B05h, documented as "Identify Instance Data" in the MS-DOS Programmer's Reference, is identical (at least as it relates to instance data). In fact, DOSKEY uses the same piece of code to handle both calls. Unfortunately, the MS-DOS Programmer's Reference indicates that INT 2Fh AX=4B05h is related to the relatively unused DOS task switcher and says nothing about the need for DOS programs to instance data for compatibility with the far more prevalent Windows Enhanced mode.
But what does INT 2Fh AX=1605h actually do? And how does it relate to other means of instancing data, such as the LOCALTSRS= statement (or its LOCAL= equivalent for DOS device drivers)? Eventually these methods of declaring instance data to Windows, plus several others, lead to the _AddInstanceItem function provided by the Windows Virtual Machine Manager (VMM). This call is documented in the Windows DDK and in the book Writing Windows Virtual Device Drivers, by David Thielen and Bryan Woodruff (Addison-Wesley, 1994).
Okay, so what does _AddInstanceItem do? What is instance data really, and how does VMM implement it? The Microsoft KnowledgeBase includes a surprisingly good explanation, "Instanced Data Management in Enhanced Mode Windows" (Q90796). However, this states that the internal "instance buffers are not accessible to VxDs or TSRs; they are local data structures to be accessed by the VMM only."
In this month's "Undocumented Corner," Klaus Müller shows how to access the internal instance-data structures, using a virtual device driver (VxD) loaded early in the Windows boot process, right after VMM. By using the documented Hook_Device_Service call to intercept the _AddInstanceItem function, Klaus's VxD builds up a picture of the instance-description buffer.
To further describe the Windows instance-data manager, Klaus uses another interesting method: examining the error-message strings that appear in the widely available debug version of WIN386.EXE. Many of these error messages refer to internal VMM functions whose names we otherwise would not know; see Figure 1.
Once the internal instance-data structures are located, the results must be interpreted. Let's say that (as in Figure 2) the virtual keyboard device (VKD) instances 28h bytes at address 415h. So what? Well, these 28h bytes include the BIOS keyboard buffer. VKD instances this buffer so that each DOS box--actually, each virtual machine (VM), including the System VM in which Windows applications run--has its own local BIOS keyboard buffer. Keys typed in a DOS box don't leak into the user's copy of Excel or Word for Windows. This has a downside, too: It's difficult (though not impossible) for Windows applications and full-screen DOS boxes to deliberately "push" keystrokes into each other.
In previous "Undocumented Corner" columns (January and February 1994), Kelly Zytaruk examined the Windows virtual machine control block (VMCB) and noted that offsets 0BCh and 0CCh in the VMCB refer to instance data. Klaus fleshes out this point.
I expected that all of Klaus's results would have to be thrown out for Microsoft's forthcoming Chicago operating system (Windows 4). However, Klaus reports that the instancing mechanism has not fundamentally changed and that his programs for locating the instance-data structures work in Chicago, too. Of course, Chicago is still in prerelease, and anything can happen between now and when it ships. Klaus does note that VMM in Chicago provides a _GetInstanceInfo call, which reports whether a given region is instanced or not, though whether this is more useful than the existing _TestGlobalV86Mem is unclear. Future articles from Klaus will show how to locate device CB areas and asynchronously access instance data without causing a page fault.
In addition to the usual places to download DDJ code (see page 3), you can get programs and source code mentioned in this article from the new Undocumented Corner area in the DDJ Forum on CompuServe (GO DDJ). If you have any comments or suggestions for future articles, please post messages to me there, as well; my CompuServe ID is 76320,302.
There are programmers who see Windows not as a graphical user interface, but as an operating system with preemptive multitasking of virtual machines (VMs). These developers have to worry about things like hardware interrupt handlers that operate in the context of a specific VM.
Asynchronous access of data in a specific VM is possible via the documented CB_High_Linear field in the largely undocumented VM control block (VMCB) structure (see DDJ, January/February 1994). The current VM is mapped in the first megabyte of the linear-address range. You can access any VM's address space (current or not) by adding CB_
High_Linear to its linear address.
So far, so good. But sometimes when you want to access data in a VM, you get a page fault. This page fault is transparent (applications don't see it), but it can lower performance and lead to serious problems inside an interrupt handler.
What's wrong here? It turns out that the page faults are an integral part of instance-data management in Windows Enhanced mode.
Instanced data is born as global data before Windows starts, but once Windows starts, it becomes local for each VM.
In general, it would be good if all data in a VM were local. In a genuine protected multitasking environment, global data is very dangerous. Consider an ugly TSR that crashes a DOS system. In a truly protected environment, the multitasking kernel nukes only the crashed VM.
Windows 3.x Enhanced mode does not support this level of safety. Since Windows is based on DOS, Microsoft compromised. All memory allocated before Windows starts (including DOS TSRs, device drivers, and DOS itself) will be mirrored in each VM via the 386 paging mechanism. Thus, this memory is global, shared by all VMs.
There are several reasons to allow the presence of global data. For one thing, Windows rests on top of DOS, and some DOS data must be global. Consider the example of the DOS system-file tables (SFTs) when SHARE
.EXE is loaded. The SFTs present before Windows started need to be visible to all VMs.
Global data also saves memory. Remember the days before 386 memory managers? A well-equipped system with network drivers, mouse drivers, disk-caching programs, and other resident software could consume 400 Kbytes of conventional memory. If you created three VMs, you quickly wasted 1 Mbyte of memory.
Wasted? What about the paging mechanism of 386-mode Windows with its ability to extend physical memory with disk memory? Unfortunately, TSR memory must often reside permanently in physical memory. Many TSRs maintain a hardware interrupt; paging out memory which contains hardware interrupt handlers would cause unpredictable results. The interrupt-handler code would be executed for each VM separately, so the best result from paging global TSRs would be a remarkable performance decrease. Consequently, memory belonging to DOS TSRs, device drivers, and DOS itself is, by default, global to all VMs, and is not pageable. If a TSR changes some data in its memory area, the change takes effect in all VMs. If the TSR crashes a VM because of a memory-related error, all VMs will be crashed, including the System VM with all the Windows apps.
Although the executable code of the TSRs can be the same for all VMs, the data must be private for each VM. Such privacy is called "instancing." While this should not be confused with instance data in Windows applications, it is analogous.
Any portion of software loaded before Windows can be instanced using one of several documented techniques, including INT 2Fh AX=1605h and the LOCALTSRS= and LOCAL= statements in SYSTEM.INI. Some obvious candidates for instancing are the interrupt-vector table and parts of the BIOS data area. The keyboard buffer has to be instanced, as do the history buffers belonging to any command-line editors loaded before Windows. (Why doesn't the history buffer for a command-line editor loaded inside a Windows DOS box have to be instanced? Answer: Because the memory is already local.)
The memory management of Windows Enhanced mode is based on the 80386 paging mechanism. The smallest unit of memory is one 4K page. The following types of memory are to be found in the VM's address range (the page types are documented in the DDK):
Windows saves the instanced parts of PG_INSTANCE pages in a special buffer. Because the paging mechanism has 4K granularity, a physical copy is required for any instance data item smaller than 4K. An instanced data item can be as small as a single byte. Many individual instance items can thus decrease performance.
The instance-data manager (IDM) manages the instance mechanism. It is part of the Windows Virtual Machine Manager (VMM) and exports the documented _AddInstanceItem service. While processing _InstanceInitComplete, the IDM allocates memory via the VMM _PageAllocate service for the following buffers:
all instanced data for the VM when the PG_INSTANCE pages are set not-present. The offset and handle of the VM instance buffer can be found at offsets 0BCh and 0C4h in the VMCB. In Chicago, the VM instance buffers (including the VM1 buffer) are part of the VMCB and are allocated via the _Allocate_Device_CB_
Area service. (In a future article, I'll describe a VxD that hooks this call to help enumerate these CB areas.)
_InstanceInitComplete is an internal VMM function. While working with the debug version of WIN386.EXE, I found some very informative error messages that include references to this internal function; see Figure 1. A great deal can be learned about the internals of the IDM simply by examining these error messages and pondering what the IDM must require for normal, error-free operation. To verify the interpretation of the error messages in Figure 1, it is useful to inspect the code of the IDM. Since the error messages are issued via Out_Debug_String with the string offset in ESI, it is easy to locate the desired code that would issue this message if something went wrong. From there, it is easy to work backwards to the code's normal operation.
To illustrate the handling of the PG_
INSTANCE pages, assume that they are currently owned by the System VM (SYSVM, or VM1). When a second VM is created, the VMM begins to schedule the VMs according to their priority. If the VMM schedules from SYSVM to VM2, the PG_INSTANCE pages in VM1 are still present, and the corresponding pages in VM2 are not. If any code in VM2 accesses the PG_INSTANCE pages (via the linear address), a page fault occurs. The IDM copies the SYSVM's instanced data to SYSVM's instance buffer and sets the faulting PG_INSTANCE page of the SYSVM as not present. Then the IDM sets the corresponding PG_INSTANCE of VM 2 present and copies the instanced data from the instance buffer of VM2 to the page.
This instancing mechanism is not complicated. But Microsoft has added an important twist for performance reasons: The physical copy of the instance data to the instance buffers occurs only if the pages are written (but not read) by a VM other than the one which owns the PG_INSTANCE pages. Because they are set not-present for the VM, a page fault occurs and the copy starts. Now we can understand why Microsoft has stated that "fragmented and large instance areas decrease the performance of the swapping mechanism."
In summary, for write access VMMwill: 1. Copy instance data from the PG_INSTANCE page physical base of the former owner to the VM's instance buffer; and 2. copy instance data from the instance buffer of the owner to the PG_INSTANCE page physical base. For read access VMM will copy instance data from the instance buffer of the new owner to the PG_INSTANCE page physical base.
For a deeper understanding of the instancing, it is useful to write a program that displays the address and size of all instance data and the name of the program which asked for the data to be instanced. LISTINST.386 (see Listing One, page 146) is a VxD I wrote that initializes shortly after the VMM, before other VxDs gain control, and hooks the VMM _AddInstanceItem service at Sys_Critical_Init time. INSTWALK is a DOS program that displays the information saved by LISTINST.386.
Two other required VxDs are VXDQUERY.386 and (to examine instance data in Chicago) LISTCALL.386. All three VxDs are loaded automatically if you run the DOS program VXDLOAD.EXE just before starting Windows. (VXDLOAD uses the versatile INT 2Fh AX=
1605h interface to tell Windows to load the VxDs.) While there isn't room to show all of the source code, the programs are available electronically (see "Availability," page 3).
For each call to _AddInstanceItem, LISTINST stores the address of the caller and the offset of the passed-in InstDataStruc. (This structure is documented in VMM.INC and INT2FAPI.INC, included with the DDK and with VxD-Lite.) LISTINST has a V86 API which allows DOS programs running in a VM to get the results of the _AddInstanceItem hook. INSTWALK uses this V86 API and prints out all instanced data.
Sample output is shown in Figure 2. To clarify what this output means, Figure 3 shows a hex dump (using the PROTDUMP utility discussed in the "Undocumented Corner," January and February 1994) of one of the instance data items declared by DOSKEY; clearly, this instance data is the DOSKEY-command history buffer. By specifying a VM number on the command line, PROTDUMP can view this buffer in each VM; see #1 and #2 in Figure 3. The ownership and purpose of the buffer is identical for all VMs, but the data (here, the command history) differs in each VM. This is precisely what instance data means.
The INSTWALK utility has a --p switch to dump out the instance page-ownership array (an internal IDM structure which lists all PG_INSTANCE pages and their current owners) and the instance-description buffer in raw form. Because this structure is not accessible, I built my own array. Remember, only one VM at a time can own a PG_INSTANCE page. So you have only to walk down the VM's page tables to determine the VM in which the PG_INSTANCE page is set present; see LISTINST.386 for details. This is similar to output from the .mi command available in debuggers such as WDEB386 and Soft-ICE/Windows when the debug WIN386.EXE is installed. INSTWALK --p also dumps out the offsets in the instance buffers, which is important if you want to access the data in the instance buffers.
LISTINST.386 gets the names of the callers to _AddInstanceItem via a service provided by VXDQUERY.386, which provides a complete map of the VMM and VxD address space. VXDQUERY can name all known VxD services by name and address, start and end of VxD objects and, particularly important for this article, all VxD Control procedures. Even debuggers such as WDEB386 don't provide as complete a map as VXDQUERY. Since VXDQUERY is a commercial program of mine, source code is not provided; however, I'm making a special version available electronically for DDJ readers; see page 3.
To use VXDQUERY, another VxD simply loads an address into the EDI register and calls VxdQuery_Address_To_
VxD_Name. The service returns the name of the VxD plus the closest known procedure that precedes the specified address. If you want to spy on the usage of a VxD call, as I did with _AddInstanceItem, write a VxD that uses the documented VMM function Hook_Device_Service to intercept the service at Sys_Critical_Init time, and collect the address of the callers plus any related parameters. You may need to initialize right after VXDQUERY. During Init_Complete or later, you can call VXDQUERY to translate your addresses into VxD names. Finally, your VxD should export a V86 or PM API to make your results public to the non-32-bit world (that is, to a program similar to INSTWALK).
Figure 2 (from LISTWALK) shows all instanced data with their linear addresses. At Sys_Critical_Init time, for example, VKD instances 28h bytes at 415h, one byte at 471h, 4 bytes at 480h, and 0Bh bytes at 496h. To make any sense of these numbers, you need to consult a reference that describes standard PC absolute-memory locations. A good source is the file MEMORY.LST included with Ralf Brown's "Interrupt List" (see IBMPRO library 5 on CompuServe); another source is Undocumented PC by Frank van Gilluwe (Addison-Wesley, 1994). In the VKD example, the 28h bytes at 415h include the keyboard buffer and head and tail pointer, 471h is the Ctrl-Break flag, 480h points to the keyboard buffer, and 496h includes keyboard-status bytes. It makes sense that VKD wants to instance these portions of the BIOS data area.
Of particular interest are the final calls to _AddInstanceItem in Figure 2. VMM made them from an internal routine called Create_Int_2F_Inst_Table; Figure 1 explains where this name comes from. This is the VMM code that processes
the Win386_Startup_Info_Struc chain passed back from INT 2Fh AX=1605h; this documented structure includes an SIS_Instance_Data_Ptr field listing items that software loaded before Windows (such as DOSKEY) wants instanced.
VXDLOAD.EXE determines the names of TSRs which supply a Win386_SIS. LISTINST.386 gets a pointer to the list of all SISs on entry in the Sys_Critical_Init procedure in EDX, because VXDLOAD fills the Win386_SIS for LISTINST.386 with a pointer to its reference data.
VMM first lets all virtual devices instance their data and then calls Create_
Int_2F_Inst_Table to convert the instance structures provided by DOS TSRs via their Win386_SIS to the VMM (IDM) instance structures. The result is a doubly linked list of instance-data structures. You can walk the chain during Init_
Complete with the InstLinkF and InstLinkB pointers. After the linked list is complete, the instance-description buffer will be initialized with the data from the linked list. Finally, the so-called "snapshot" is taken in order to save the startup-time values of the instanced data in the instance snap buffer. If a new VM is created, the IDM creates a VMx instance buffer (where x is the VM ID) and copies the contents of the instance snapshot buffer into it.
There is one problem with this technique of hooking _AddInstanceItem to build up a picture of the instance description buffer. As noted, LISTINST.386 hooks _AddInstanceItem as early as possible: at Sys_Critical_Init time, right after VMM, and before other VxDs. Unfortunately, this is too late to intercept the _AddInstanceItem calls that VMM makes to support the LOCALTSRS= statement. However, LISTINST is still able to find these instance items by following the doubly linked list of InstDataStrucs. VMM will instance the entire program, excepting its environment segment.
It would also be useful to determine the ownership of the PG_INSTANCE pages. Again, only one VM at a time can own a PG_INSTANCE page. LISTINST.386 provides the array to LISTWALK. Type LISTWALK --p to dump the instance page-ownership array and the instance-description buffer. The output is more interesting if, immediately after INSTWALK starts, you switch to another session or compile something in the background. In this case, the instance pages are owned by different VMs.
What can you do with this information? In addition to a better understanding of how instance data works and what sorts of data must be instanced, the information presented here might lead to some interesting techniques for accessing instance data without causing page faults. This will be taken up in a future article.
Copyright © 1994, Dr. Dobb's Journalby Andrew Schulman
Local vs. Global Data
Instance Data and Paging
Though PG_INSTANCE pages are not paged out to disk, PG_INSTANCE can be marked "not-present"; this is key to the instance-data mechanism. Only one VM at one time has a PG_INSTANCE page "present." The corresponding pages in the other VMs are marked not-present. If the pages were all swapped out during a task switch, the global data would become local; the pages would not be updated when another VM changed the global data. Conversely, if the pages were still present in the other VMs, writing data to the instanced part of the pages would make them global because all corresponding PG_INSTANCE pages have the same physical base.
The Instance-Data Manager
When a new VM is created, it also gets a VM instance buffer, which contains
Spying on Instance Data
Not Just Spying
Figure 1: Some instance-related error messages from the debug WIN386.EXE.
_InstanceInitComplete no instance list
Tells us there must be an instance list. This is obviously a chain of linked <I>InstDataStruc</I>s from documented in VMM.INC.
_InstanceInitComplete entries not sorted #esi > #edi
<I>_AddInstanceItem</I> sorts the entries and chains them together via the <I>InstLinkF</I> and <I>InstLinkB</I> fields of the <I>InstDataStruc</I>.
Computed Inst_VM_Buf_Size of 0 _InstanceInitComplete
<I>_InstanceInitComplete</I> must compute a <I>Inst_VM_Buf_Size</I>.
Allocation failure VM1 Inst _InstanceInitComplete
Allocation failure Inst snap _InstanceInitComplete
Allocation failure Inst Descrip _InstanceInitComplete
<I>_InstanceInitComplete</I> allocates space for the VM1 Inst buffer, the <I>Inst snap</I> and <I>Inst descrip</I> buffer.
Fail grow Instance desc buff AllocateInstanceMapStruc
IDM function <I>AllocateInstanceMapStruc</I> tests the size of the instance description buffer and, if needed, grows the size.
Swap_Instance_Page, 0 in Inst_Page_Owner for page #ecx
<I>Inst_Page_Owner</I> is the VM in which the PG_INSTANCE page is marked present.
Swap_Instance_PageFS ERROR INSTANCE PAGE > 10Fh
ERROR: Re-entered instance copy procedure
The IDM function <I>Swap_Instance_PageFS</I> copies the instance data in the instance buffer of the VM and changes the owner of the PG_INSTANCE page.
ERROR: Instance fault on suspended VM #EBX
A suspended VM cannot own a instanced page.
Instance fault on page with 0 IMT_Inst_Map_Size
The <I>Inst_Page_Owner</I> will change after a page fault on a instance page is detected. The IDM determines the new owner VM and saves the instanced data of the old owner via <I>Swap_Instance_Page</I>.
_AddInstanceItem failed InstDataStruc @#EDI Create_Int2F_Inst_Table
The internal routine that processes the <I>InstDataStruc</I> chain from INT 2Fh AX=1605h is called <I>Create_Int2F_Inst_Table</I>.
Figure 2: Sample output from INSTWALK.
C:\DDJ\INST>instwalk
V86 API from ListInst.386: Result of _AddInstanceItem Hook.
Summary of allocation calls to the Instance Data Manager.
Name of VxD InstLinAddr InstSize
VKD : Sys_Critical_Init_Proc 0x00000415 0x00000028
VKD : Sys_Critical_Init_Proc 0x00000471 0x00000001
VKD : Sys_Critical_Init_Proc 0x00000480 0x00000004
VKD : Sys_Critical_Init_Proc 0x00000496 0x0000000B
VMM: _Allocate_Global_V86_Data_Area 0x0002807C 0x00000374
V86MMGR : Unknown_Service 0x00028435 0x00000006
VTD : Device_Init_Proc 0x000284C0 0x00000008
VDD : Device_Init_Proc 0x00000449 0x0000001E
VDD : Device_Init_Proc 0x00000484 0x00000007
VDD : Device_Init_Proc 0x000004A8 0x00000004
VDD : Device_Init_Proc 0x00000410 0x00000002
VCD : Device_Init_Proc 0x00000400 0x00000008
VCD : Device_Init_Proc 0x0000047C 0x00000004
DOSMGR : Device_Init_Proc 0x00000413 0x00000002
DOSMGR :IGROUP 0x00000504 0x00000001
DOSMGR :IGROUP 0x00000000 0x00000400
DOSMGR :IGROUP 0x00001550 0x0000001A
DOSMGR :IGROUP 0x0000156A 0x00000772
VMM: _Allocate_Global_V86_Data_Area 0x000286B4 0x00000256
DOSMGR :IGROUP 0x00003CE0 0x00000004
DOSMGR :IGROUP 0x00001342 0x00000002
DOSMGR :IGROUP 0x00004830 0x000008F0
DOSMGR : Device_Init_Proc 0x00027FD0 0x00000010
DOSMGR: Instance_Device 0x00001278 0x00000004
DOSMGR: Instance_Device 0x0001EEC2 0x00000004
DOSMGR: Instance_Device 0x000283F0 0x00000004
VMM:Create_Int_2F_Inst_Tbl:DOSKEY 0x00017D30 0x00000288
VMM:Create_Int_2F_Inst_Tbl:DOSKEY 0x00018C53 0x00000200
VMM:Create_Int_2F_Inst_Tbl:MOUSE 0x00012158 0x00000889
VMM:Create_Int_2F_Inst_Tbl:MOUSE 0x00012AD7 0x0000010A
VMM:Create_Int_2F_Inst_Tbl:VLM 0x0000DD60 0x0000001C
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00000500 0x00000002
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x0000050E 0x00000014
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x0000070C 0x00000001
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00005140 0x00000002
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x000053B0 0x000004C8
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00001252 0x00000002
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00001262 0x00000004
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00001429 0x00000106
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x00001530 0x00000001
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x000021F0 0x00000022
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x000012B9 0x00000001
VMM:Create_Int_2F_Inst_Tbl:SYS DRV 0x000012BC 0x00000002
Figure 3: Examining DOSKEY's instance data.
C:\DDJ\INST>instwalk | grep DOSKEY
VMM:Create_Int_2F_Inst_Tbl:DOSKEY 0x00017D30 0x00000288
VMM:Create_Int_2F_Inst_Tbl:DOSKEY 0x00018C53 0x00000200
C:\DDJ\INST>\ddj\protdump\protdump #2 18c53 200
00018C53 | 45 00 63 3A 5C 65 70 73 5C 65 70 73 69 6C 6F 6E |E.c:\eps\epsilon
00018C63 | 2E 65 78 65 20 24 2A 00 69 6E 73 74 77 61 6C 6B |.exe $*.instwalk
00018C73 | 20 3E 20 69 6E 73 74 77 61 6C 6B 2E 6C 6F 67 00 | > instwalk.log.
00018C83 | 67 72 65 70 20 44 4F 53 4B 45 59 20 69 6E 73 74 |grep DOSKEY inst
00018C93 | 77 61 6C 6B 2E 6C 6F 67 00 5C 64 64 6A 5C 70 72 |walk.log.\ddj\pr
00018CA3 | 6F 74 64 75 6D 70 5C 70 72 6F 74 64 75 6D 70 20 |otdump\protdump
00018CB3 | 31 38 63 35 33 20 32 30 30 00 5C 64 64 6A 5C 70 |18c53 200.\ddj\p
... etc. ...
C:\DDJ\INST>\ddj\protdump\protdump #1 18c53 200
83018C53 | 45 00 63 3A 5C 65 70 73 5C 65 70 73 69 6C 6F 6E |E.c:\eps\epsilon
83018C63 | 2E 65 78 65 20 24 2A 00 63 64 20 74 61 70 63 69 |.exe $*.cd tapci
83018C73 | 73 00 74 61 70 63 69 73 00 63 64 5C 70 72 6F 63 |s.tapcis.cd\proc
83018C83 | 6F 6D 6D 00 70 63 70 6C 75 73 00 63 64 5C 74 61 |omm.pcplus.cd\ta
83018C93 | 70 63 69 73 00 74 61 70 63 69 73 00 67 72 65 70 |pcis.tapcis.grep
83018CA3 | 20 4E 46 5F 20 5C 62 6F 72 6C 61 6E 64 63 5C 69 | NF_ \borlandc\i
83018CB3 | 6E 63 6C 75 64 65 5C 74 6F 6F 6C 68 65 6C 70 2E |nclude\toolhelp.
83018CC3 | 68 00 65 78 69 74 00 63 64 5C 69 6E 73 74 00 63 |h.exit.cd\inst.c
... etc. ...
[LISTING ONE]
;;; _AddInstanceItem hook from LISTINST.386
;;; from DDK VMM.INC
InstDataStruc struc
InstLinkF dd 0 ; linked list forward ptr
InstLinkB dd 0 ; linked list back ptr
InstLinAddr dd ? ; Linear address of start of block
InstSize dd ? ; Size of block in bytes
InstType dd ? ; INDOS_Field or ALWAYS_Field -- ignored?
InstDataStruc ends
;;; from LISTINST.INC -- my InstData struct includes caller address
KM_InstData struc
AddInst_Caller dd ?
InstDataStruc { } ; from VMM.INC
KM_InstData ends
;;; from LISTINST.ASM
oldservice dd 0 ; return value from Hook_Device_Service
Inst_Struc_Ptr dd 0 ; InstLinkF from InstDataStruc
calladr dd 0 ; address of _AddInstanceItem caller
Data_Buf_Addr dd 0 ; created with _PageAllocate PG_SYS
Data_Buf_Size dd 0
Data_Buf_Handle dd 0
Inst_Data_Count dd 0 ; number of instance items seen so far
;;; from LISTINST.AM Sys_Critical_Init handler
;Instancing of the first byte in the 1st MB in order to get all calls to
;_AddInstanceItem befor ListInst_Sys_Critical_Init. The _AddInstanceItem
;service chains the InstDataStrucs together to a sorted double linked list
;via InstLinkF and instLinkB.
;If the LinkF field is -1, no other calls were made.
;If LinkF <> -1, then it represents a call to _AddInstanceItem caused by a
;system.ini - entry "LOCALTSRS= tsr_name". The VMM instances the whole TSR,
;the first 16 Byte represents the MCB of the PSP. So we can determine the name
;of the fully instanced TSR.
;;; ...
mov KM_Instance.InstLinAddr,0
mov KM_Instance.InstSize,1
mov KM_Instance.InstType,ALWAYS_FIELD
mov esi,offset32 KM_Instance
VMMcall _AddInstanceItem <esi,0>
cmp KM_Instance.InstLinkF,-1 ;any LOCALTSRS ?
je nolocal
mov esi,KM_Instance.InstLinkF ;yes, get it
loclp: mov Inst_Struc_Ptr,esi
mov calladr,'LTSR'
call addinst ;add instance item to our list
mov esi,[esi.InstDataStruc.InstLinkF] ;get next InstDataStruc
cmp esi,-1 ;no more strucs?
jne loclp
nolocal:mov eax,_AddInstanceItem
mov esi,offset32 myhook
VMMcall Hook_Device_Service
mov [oldservice],esi
;;; ...
BeginProc Hooked_AddInstanceItem
; The AddInstanceItem Hook stores the callers address
; and the instance data pointer in the Inst_Data_Buf buffer.
myhook:
push ebp
mov ebp,esp
push [ebp+0ch] ; Flags
push [ebp+8] ; Instance Structure Pointer
push [ebp+8]
pop Inst_Struc_Ptr
push [ebp+4] ; get caller's return address!
pop calladr
call [oldservice] ; call original _AddInstanceItem
add esp,8
pop ebp
cmp eax,0 ; error in _AddInstanceItem ?
je exit
call addinst ; add Instance Item to our list
exit: ret
;**************************************************************************
;addinst - adds an instance item to our list.
;INPUT: calladr - address of caller of _AddInstanceItem
; Inst_Struc_Ptr - address of InstDataStruc
;OUTPUT: hookerr = -1 - error growing Data_Buf
; hookerr = 0 - all O.K.
;**************************************************************************
addinst:push edi
push esi
push ecx
push eax
hook2: mov edi,Data_Buf_Addr
mov ecx,Inst_Data_Count
imul ecx,sizeof KM_InstData
add edi,ecx
push edi
add edi,sizeof KM_InstData
mov ecx,Data_Buf_Addr
add ecx,Data_Buf_Size
cmp edi,ecx
pop edi
jl hook1
mov ecx,Data_Buf_Size
add ecx,1000h
shr ecx,0ch
mov edx,Data_Buf_Handle
VMMcall _PageReAllocate <EDX, ECX, PAGEZEROINIT>
cmp eax,0
je hookerr
add Data_Buf_Size,1000h
mov Data_Buf_Handle,eax
mov Data_Buf_Addr,edx
jmp hook2
hook1: mov eax,calladr ;save caller's address in buffer
stosd
mov esi,Inst_Struc_Ptr
mov ecx,(sizeof InstDataStruc)/4
rep movsd ;save instance data struc
inc Inst_Data_Count
hookret:pop eax ;next offset pair
pop ecx
pop esi
pop edi
ret
hookerr:mov hook_err,-1
jmp hookret
EndProc Hooked_AddInstanceItem