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


APR94: UNDOCUMENTED CORNER

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

by Andrew Schulman

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.

Local vs. Global Data

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.)

Instance Data and Paging

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):

  • Global data. Nonpageable, system-wide data shared by all VMs. Changing the data in one VM changes the data in all VMs. By default, the allocated DOS memory at Windows startup is PG_SYS. The memory is "mapped" into each VM. Page type: PG_SYS.
  • Local data. Pageable, local data specific for each VM. The free DOS memory at Windows startup is local. A DOS program started in a DOS box cannot crash other VMs because of an error that belongs to its PG_VM memory. Page type: PG_VM.
  • Instance data. A mixture of instanced and global data. Like PG_SYS pages, they are nonpageable. The difference from global (PG_SYS) memory is that some of the data is marked as local and handled in a specific manner. Page type: PG_INSTANCE.
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.

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

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:

  • For each instance item, the instance-description buffer contains its linear address, its length, and the location of its data within the instance buffers. VxDs declare instance data via the documented _AddInstanceItem service and InstDataStruc structure. The code in VMM for _AddInstanceItem builds a linked list of these structures. At _InstanceInitComplete, VMM discards any duplicate instance-data requests (for example, if one VxD instances 2 bytes at 415h, and another instances 20h bytes at 400h) and builds the instance-description buffer as a sorted array of InstMapStrucs. During Instance_Init_Complete, the sorted list is examined. With the information from the list of InstMapStrucs, the IDM builds the instance-description buffer as an array of InstanceMapStrucs.
  • The instance-snap buffer is used to save the instant data present at Windows startup time. When Windows exits back to DOS, the instance data is restored.
  • The VM1 instance buffer initially contains a copy of the data in the instance-snap buffer.
When a new VM is created, it also gets a VM instance buffer, which contains

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.

Spying on Instance Data

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.

Not Just Spying

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.

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

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.