Geoff Chappell - Software Analyst
This function obtains the calling address for the virtual-8086 (V86) or protected-mode (PM) API of an arbitrary Virtual Device Driver (VxD). It, along with VxDs and their APIs, dates from Windows 3.0. The subfunction number, 84h, is defined symbolically as W386_Get_Device_API in a header named INT2FAPI.INC which Microsoft distributed with the Device Driver Kit (DDK) at least as early as for Windows 3.1.
A VxD may provide either or both a V86 or PM API. The function discloses only the one that is immediately appropriate for the caller. A DOS program’s virtual-8086 execution calls the function to learn where to call the given VxD’s V86 API. A DOS program that is executing in protected mode as a client of the DOS Protected Mode Interface (DPMI) calls the function to learn of the given VxD’s PM API.
The function uses registers for both input and ouput. Far pointers for V86 callers have real-mode addressing, of course. For PM callers, far pointers for input are 16:16 or 16:32 for 16-bit and 32-bit DPMI clients, respectively, but far pointers for output are 16:16, either way.
|bx||non-zero VxD ID|
Each loaded VxD specifies its ID as the DDB_Req_Device_Number member of the Device Descriptor Block (DDB, but formally a VxD_Desc_Block structure) whose address is the VxD’s one export. If two or more loaded VxDs happen somehow to have the same ID, this function finds only the first.
Starting with version 4.0, bx can validly be zero on input to denote that the VxD is instead specified by name.
|es:(e)di||address of eight-byte VxD name|
Each loaded VxD specifies its name as the DDB_Name member of its DDB. All eight bytes must match exactly. If two or more loaded VxDs happen somehow to have the same name, this function finds only the first.
The eight-byte DDB_Name in the loaded VxD that is sought by name is typically, but not necessarily, padded with spaces. This happens because most VxDs are written in assembly language and the VxD_Desc_Block definition in Microsoft’s VMM.INC specifies eight spaces for the default initialisation of the DDB_Name. If a name shorter than eight bytes is specified when instantiating the structure, then the trailing spaces carry over from the default initialisation. The function’s comparison of the name from es:(e)di with the DDB_Name for a loaded VxD knows nothing of this padding, only that a match is of all eight bytes from each name.
This alternative input was not documented in the Windows Interrupt 2Fh Interface in VXD.DOC from the original DDK for Windows 95. A later edition has it in a new VXDS.DOC that’s an “Updated and combined VXD.DOC and WIN95OVR.DOC.” The insertions are helpfully distinguished by using Word’s Review feature, which dates them to January 1998.
|es:di||address of entry point|
The API is reached from ring 3 by a far call through this returned 16:16 pointer. Interpretation, e.g., of registers for input and output, varies with the VxD.
Returning NULL is the documented failure, but see below for the return of FFFF:FFFF, which is here treated as a coding error.
Before version 4.0, the function fails if the given VxD ID is zero. In all applicable versions, the function fails if no loaded VxD has the given ID or, in version 4.0 and higher, the given name.
A usable API has a ring 0 handler that the VxD defines through its DDB and a ring 3 calling address that is allocated when the API is first sought through this function. The flat address of the ring 0 handler is defined by the DDB_V86_API_Proc or DDB_PM_API_Proc member of the VxD’s DDB. If this is NULL, the VxD does not implement the API and the function fails. If a ring 3 calling address is not yet allocated for the API, then failure to allocate one is failure for the function. The ring 3 calling address is kept as the DDB_V86_API_CSIP or DDB_PM_API_CSIP member.
Given that a ring 3 calling address is allocated, calling it diverts to whatever the corresponding DDB member currently defines for the ring 0 handling. The implementation thus allows that a VxD can change the address of its ring 0 handler. How much this is intended is not known.
The ring 3 calling address is that of a V86 or PM callback. There are only so many of these (mere hundreds by default) in total with V86 breakpoints, and allocation has no reversal. Documentation is clear that they “should be treated as scarce resources.” The documented indication of failure by the Allocate_V86_Call_Back and Allocate_PM_Call_Back services is that they set the carry flag. Not documented is that they also return FFFFFFFFh in eax. VMM versions before 4.0 do not check for the set carry flag, but just assume that whatever these services return in eax is a ring 3 calling address. They can thus return FFFF:FFFF to the int 2Fh caller and to all subsequent callers. The VMM for Windows 95 doesn’t check for the set carry flag, either, but does recognise FFFFFFFFh in eax as failure which the int 2Fh function returns as NULL.