Examining the Windows 95 Layered File System
Adding functionality to block devices
Mark Russinovich and Bryce Cogswell
The authors are researchers in the computer science department at the University of Oregon. Mark can be reached at [email protected] and Bryce at [email protected].
One major difference between Windows 95 and its predecessors, Windows 3.1 and Windows for Workgroups (WFW) 3.11, is how Windows 95 implements its file systems. Windows 95 introduces a "layered" approach to file-system management, dividing translation of a high-level file access to an actual physical request into multiple, distinct parts. Unfortunately, this new organization has created a plethora of new terminologies and APIs. In addition, Windows 95 Device Driver Kit (DDK) documentation is often vague, incomplete, and misleading.
In this article, we'll briefly discuss how Windows 3.1 and WFW 3.11 implement their file systems, then present an overview of the Windows 95 file system. Our exploration of the file system focuses on the vendor supplied driver (VSD) layer. VSDs are virtual devices (VxDs) that can hook onto the path of device accesses for any block-based device such as a hard disk, CD-ROM, or floppy drive. Microsoft designed the VSD layer to let third-party vendors add functionality to the file system. An extensive API was added to the file system so that VSDs can alter device requests (or create new ones), making it possible to develop VSDs to perform functions ranging from block-device monitoring and data encryption to mirrored or RAID disk management.
To demonstrate how a VSD is built, we'll describe the design and implementation of a monitoring VSD that interfaces with a Win32 program to display information about block-device accesses. Besides serving as a basis for your own custom VSDs, the application will return useful information about your block-device performance and show how to connect a Windows GUI program with a virtual device.
Out With the Old
Windows 3.1 has the most-simplistic file system of the Windows incarnations. When a Windows or DOS application makes a request to read data from a file, for instance, the request is sent to DOS, which then passes it to the BIOS. If you're lucky, you have what's called a "Fast Disk"-compatible hard disk. (Choose virtual-memory information from the Enhanced 386 information on your control panel, and a check box will tell you if fast-disk access is possible, and if so, turned on.) In that case, instead of using the BIOS to do disk I/O, a virtual device called "WDCTRL.386" handles the request in 32-bit protected mode, bypassing the slower real-mode BIOS. Here, your file request is translated to a physical request by real-mode DOS, which has the request serviced in protected mode by WDCTRL.386.
WFW 3.11 introduced the prototype of Windows 95 block-device management. When a WFW or DOS program requests a file, the request is passed to a virtual device called "IFSMgr.386," which passes it to VFAT.386, a virtual device that implements the DOS file system in protected mode. After VFAT.386 has converted the request to a logical device request, it sends it to IOS.386, the I/O system supervisor. If the target hard disk is Fast Disk compatible, the request is serviced in WDCTRL.386; otherwise, it is sent to the BIOS. Thus, in WFW 3.11, if you have a Fast Disk-compatible disk, your file accesses are handled entirely by VxDs, bypassing real-mode DOS completely, and giving you maximum performance.
In With the New
Windows 95 takes the concept of protected-mode disk access a step further than WFW. To maximize Windows 95 performance, Microsoft made it easy for hard-disk manufacturers to make their own versions of WDCTRL.386-type drivers so that disk access can bypass the BIOS. Microsoft also wants to allow Windows 95 to seamlessly integrate any new or odd block-device hardware (a flash memory card used as a disk, for instance) into Windows' file-system management scheme. Therefore, Microsoft had to divide the WFW block-request path, which extends from the application to the hardware, into much more specialized layers. The new scheme is called the "Installable File System" (IFS).
The IFS is made up of 32 logical layers, each containing one or more virtual devices, through which block-device requests pass. Fortunately for performance, most layers are empty for typical hardware. For hard disks, a file-system request will usually only pass through about five virtual devices on the way to the hardware. Figure 1 shows how the layers are organized, while Figure 2 shows a typical request path. The smallest numbers represent higher layers of abstraction, with the topmost layer being the entry point to the file system. Higher numbers are closer to the hardware with the highest number (bottom layer) being the virtual devices that access the hardware directly.
The IO Supervisor (IOS) manages requests as they pass through the file-system hierarchy. Each device on the chain can select requests based on the logical or physical drive to which the request is directed. The devices can also view the result of a request as it passes back up the chain to the application. Furthermore, the VxDs on the chain can service requests themselves and not pass them to lower levels, or they can generate requests themselves. The VFAT virtual device handles many requests by reading or writing to a memory cache via the VCACHE virtual device.
Layers, Layers, Layers
At this point, we'll provide an overview of what occurs (or can occur) at each level of the file system (again, see Figure 1). Remember that most block devices do not require an entry at each level in the chain.
- IFS Manager (IFSMgr) manages high-level I/O requests from applications. It takes a call directed at a specific logical drive and passes it down the correct call-down chain to the appropriate tracker, FSD, and so on.
- Volume trackers work with groups of devices with identical removability rules. For example, the CD-ROM volume tracker ensures that a CD with a file system on it is in the drive before it will allow any requests to pass through to lower layers.
- File system drivers (FSDs) work with all devices of a particular type, such as hard disks or CD-ROM devices. They take incoming logical requests generated by IFSMgr and translate them into physical requests to pass to lower levels. In addition, FSDs can initiate logical error recovery for devices such as disks. VFAT.VXD is the standard FSD and is provided by Microsoft. VFAT takes a request in the form of "read 20 bytes from file c:\foo.bar at offset 300" and turns it into one or more physical sector reads from drive C. In other words, it is what gives the file system its structure.
- Type specific drivers (TSDs) work with all devices of a particular type. They take a logical request generated by an FSD and translate it into a physical sector request. They generally reside in the same layer as their corresponding FSDs, but are lower in the chain.
- SCSI-izers. SCSI devices require more-complex request packets than other devices such as the more-prevalent IDE/ESDI devices. SCSI-izers take a general physical request and create a SCSI Request Block (SRB) that contains detailed, SCSI-specific information about the request such as the Logical Unit Number (LUN) and Target (SCSI targets can have up to seven LUNs hanging off them).
- Vendor supplied drivers (VSDs). As mentioned, Microsoft created this special layer for third-party developers. The VSD layer functionality is determined by the VSD writer. Possible uses include: block-device monitors, low-level secondary disk caches (caching in flash memory, for example), data encryption, and RAID disk management.
- SCSI port drivers take incoming requests and determine which SCSI miniport driver should field them. Multiple SCSI types can be loaded onto the same system, each of which may require a custom SCSI miniport driver. The SCSI port driver is also in charge of initializing the miniport drivers.
- SCSI miniport drivers (MPDs) are the hardware drivers for SCSI devices. They manage the interrupt and I/O port-level interaction with the device to carry out requests from above. They can also perform adapter-specific error recovery.
- Port drivers (PDRs) (for non-SCSI hardware) carry out the same functions as the SCSI port and miniport drivers. They provide the 32-bit disk access that previously was the sole domain of WDCTRL.386, interacting directly with the hardware to perform I/O.
- Real mode mapper (RMM). With the introduction of plug-and-play BIOS, and by including many hardware-specific port drivers, Windows 95 can provide 32-bit access for most disk hardware. However, Windows 95 might be run on an older PC with esoteric hardware, so it must make allowances for the case where it can't provide a port driver to handle disk I/O in protected mode. A system might also use real-mode disk-driver software that provides functionality not available in the Windows 95 protected-mode counterpart. For these situations, the last entry on the chain of protected-mode VxDs is an RMM instead of a port driver. RMMs call down to a real-mode driver to perform hardware I/O and return results up the file-system chain. Microsoft provides the RMM.
- Real-mode drivers are hardware drivers required by the hardware or software configuration of a particular system. Microsoft discourages use of real-mode drivers because performance can suffer (due to the overhead of transitions from protected to real mode and slower execution in real mode), but makes allowances for them for flexibility and backward compatibility. Most PCs running Windows 95 will not have real-mode drivers.
The Devmon Application
Devmon (short for "DEVice MONitor") is a block-device monitoring application that demonstrates the design of a VSD. This application consists of a VSD virtual device that monitors and times all block-device requests passing through the VSD layer, and a Windows 95 32-bit GUI program that reads the monitored data and displays it textually in a window. Besides serving as the basis for your own VSD designs, Devmon (see Figure 3) contains a useful example of how a virtual device and a 32-bit Windows 95 program can communicate. In addition, Devmon will tell you about the characteristics of all the block devices in your system, allow you to enable and disable monitoring of requests to the various devices, and tell you how long each request takes. (Complete source code, executables, and other binaries for Devmon are available electronically; see "Availability," page 3.)
The Devmon Windows program initiates communication with the Devmon VSD through the Win32 DeviceIoControl interface. This interface provides the only means whereby a Win32 program can communicate with a virtual device. The first step in establishing communication is the CreateFile command. The filename parameter for this call must be the name of the virtual device to be opened. Virtual device names differ from regular filenames because they contain an initial two backslashes followed by a period, another backslash, and then the name of the virtual device. For example, the name for the Devmon VSD is \\.\devmon.vxd. Note that in a C string, a backslash is a special character, so to specify one backslash, you must enter two in the string; for example, \\\\.\\devmon.vxd.
After the file has been opened, the program can send commands to the virtual device by calling DeviceIoControl (see Example 1) with the handle returned by the CreateFile call. By using the buffers, the program can pass arbitrary amounts of information back and forth with the device. The dwIoControlCode parameter is a VxD-specific function code used to specify the operation to be carried out by the VxD.
Upon starting, Devmon opens communication with the Devmon VSD and requests that the VSD pass it the device control blocks (DCBs) of the physical devices configured in the system. Devmon then creates a menu that allows the user to select interesting information about the DCB and to enable and disable monitoring of the DCB's device.
Several times a second, the application performs a DeviceIoControl on the VSD, asking it to pass copies of the latest device-access requests sent through the IFS. These are printed in the main window and include a number for the request, the request type (read, write, and so on), the logical drive to which the request is directed (C:, for example), the sector at which the request is directed (if appropriate), the number of sectors associated with the request and finally, the time required to service the request.
The Devmon VSD
The VSD layer is created by the IOS, which, at boot time, looks for VxDs in the system\iosubsys directory under the Windows 95 main directory. It tries to load as a dynamic VxD any file in that subdirectory with the extension VXD. When the VSD receives the SYS_DYNAMIC_DEVICE_INIT call, it responds by registering itself with the IOS. This is accomplished by calling IOS_Register VxD service and passing in a device registration packet (DRP). The DRP structure is in Listing One and the code registering our monitoring VSD with the IOS is in Listing Two.
What makes a VSD a VSD and not a member of some other layer of the file system is the load number it returns as the load-group-number (DRP_LGN) in the DRP. This tells the IOS at which layer to put the VxD in the hierarchy. VSDs have nine levels to choose from: 8-10 and 12-17. The placement of a VSD is somewhat arbitrary, but a few guidelines can be followed:
- The further down in the layers the VSD is placed, the fewer layers lie between the VSD and the hardware. This can be important if you want to see requests that will actually go all the way down to the hardware. If you are above a VSD layer that is caching, your VSD will see requests that might be handled by the other VSD's cache; if you are below that VSD, you will see only the requests it can't handle and is passing down to the hardware.
- Layer 11 is the SCSI-izer layer, where incoming requests obtain a SCSI command block containing additional information specific to SCSI devices. This is significant if you wish to make new requests from your VSD.
Once the registration has successfully completed, the VSD begins receiving messages from the IOS through the AEP. The IOS can send about two dozen different types of messages, but most VSDs will only be interested in a handful. Usually, the most interesting are the AEP_CONFIG_DCB and AEP_BOOT_COMPLETE messages. At system boot time, the IOS performs a handshaking initialization with the file-system drivers. In this phase, the system determines which physical and logical block devices are attached. The IOS will send the VSD an AEP_CONFIG_DCB whenever it registers a new physical device and provides the DCB.
The DCB (see Listing Three) contains information about the physical device, including its bus type and unit number, the number of heads, tracks, and sectors contained on the device, and flags that specify device behavior. A VSD can ignore the AEP_CONFIG_DCB messages or, if it wants to receive requests that are associated with that particular DCB, it can send the IOS a request message, asking it to insert the VSD on the device's call-down chain. IOS request messages are data structures passed on the stack to the IOS's request procedure (taken from the ILB). The Devmon VSD monitors all block devices, so it has itself put on the call-down chain for every physical device that is configured.
The AEP_BOOT_COMPLETE message tells the VSD that the file system has finished initializing and that all devices have been registered. VSDs associated with only certain types of devices can tell the IOS to unload the VSD if none of the devices are present in the system.
After initialization, each block device in the system has a DCB associated with it. That DCB specifies call-down and call-back chains pointing to the VSDs through which file-system requests for that device will pass.
When the boot sequence is complete, the VSD begins getting requests through the call-down chains on which it inserted itself. (The routine that's called with requests was indicated by the VSD in the call-down insertion commands.) The call-down routine receives a pointer to a data structure called the I/O Packet (IOP), which contains another data structure called the I/O Request (IOR). The IOP and IOR contain all the information about the particular request including its type, pointers to buffers associated with it (such as read or write buffers), and parameters indicating the physical sector on the device that the request wants to access.
Each VSD is responsible for calling the next VSD in the chain. The IOP contains a pointer to the DCB associated with the request, and the DCB contains a pointer to the current position in the call-down chain. To call the next VSD, its address is read from the chain, the chain pointer in the DCB is moved to the next location, and the address is called. The call-down data structure is in Listing Four, and a code fragment demonstrating these steps is in Listing Five.
Many VSDs will want to view requests not only as they pass down to the hardware, but also as they return up the chain to the original caller. To do this, the VSD must insert itself on the call-back chain. This chain is managed by a list of data structures pointed to by the IOP_callback_ptr entry of the IOP. To insert itself on the chain, the VSD must set the IOP_cb_address of the call-back entry to the address of its call-back procedure. It must then move the pointer to the next call-back entry for the layer just above it in memory. These steps are in Listing Six. The Devmon VSD inserts itself on the call-back chain for all requests so that it can determine how long a request took to be serviced by the layers below it (which are essentially the device drivers and hardware).
If the VSD is servicing requests itself, it can indicate immediately to layers above it that the request was serviced or postpone this notification until later. To inform the layers above that a request has been serviced, the VSD does not call the layer below; instead it simply calls up the call-back chain. The IOP_callback_ptr is adjusted so that it points at the call-back entry for the layer above the VSD, and then the VSD calls the IOP_cb_address procedure with the IOP on the stack. If the VSD wishes to service the routine later, it simply returns a 0 in the eax register and performs the callback when it wants to indicate request completion. Listing Seven provides the callback data structure, and Listing Eight demonstrates the code for performing the callback.
The Devmon VSD uses an IOS feature called the "expansion area." This is a block of data that the IOS allocates for each IOP for use by the devices in the IOP's call-down chain. A VSD must tell the IOS that it wants some expansion area allocated for it when it inserts itself on a DCB's call-down chain at DCB-configuration time. The expansion area can be used for whatever purpose the VSD desires; in the case of Devmon, it is used to pass a time stamp from the call-down procedure to the call-back procedure. Thus, the VSD can determine the time it took to complete a request by comparing the current time in the call-back procedure with the time stored in the request's expansion area. The address of the expansion area is computed by adding the offset stored in the DCB_cd_entry's DCB_cd_expan_offset field to the IOP's address.
If a VSD has put itself on the call-back chain, it must service call-back calls by continuing to call up the chain using the same method as that described for initiating the call-back chain. The Devmon VSD's call-back procedure stores a copy of the request, along with a time stamp, in a buffer that it provides to the Devmon Windows program.
VSDs can initiate new device requests themselves. If disk mirroring were desired, for example, a VSD would let requests to the primary drive pass through as normal, but it would also initiate identical requests for writes to the secondary drive. Initiating a new request requires that the VSD call the IOS service that allocates a new IOP, fill in the IOP with the correct parameters, and then initiate the request. The VSD can send the request to all virtual devices on the target device's call-down chain, or just the devices beneath itself. If the request is a new SCSI request that cannot be constructed by copying a similar request, the VSD must make sure that the SCSI-izer layer processes the request as described earlier.
Conclusion
The Windows 95 file system provides opportunities for third-party drivers to add new functionality to block devices. Without a doubt, increasing use of Windows 95 will mean a growing desire for data encryption and protection. The Devmon VSD is a framework you can extend to take advantage of these coming needs.
Figure 1: File-system layers.
Figure 2: Typical file-system request chain.
Figure 3: Running the Devmon program.
Example 1: Format of DeviceIoControl call.
Copyright © 1995, Dr. Dobb's Journal
BOOL DeviceIoControl(
HANDLE hDevice, // handle of the device
DWORD dwIoControlCode, // control code of operation to perform
LPVOID lpvInBuffer, // address of buffer for input data
DWORD cbInBuffer, // size of input buffer
LPVOID lpvOutBuffer, // address of output buffer
DWORD cbOutBuffer, // size of output buffer
LPDWORD lpcbBytesReturned, // address of actual bytes of output
LPOVERLAPPED lpoOverlapped // address of overlapped structure
);
Listing One
typedef struct DRP {
CHAR DRP_eyecatch_str[8]; // eye catcher string
ULONG DRP_LGN; // drivers load group
PVOID DRP_aer; // pointer to async event outine
PVOID DRP_ilb; // ILB virtual address
CHAR DRP_ascii_name[16]; // Name of the device
BYTE DRP_revision; // driver revision
ULONG DRP_feature_code; // Feature Code
USHORT DRP_if_requirements; // I/F Requirements
UCHAR DRP_bus_type; // type of I/O bus if port driver
USHORT DRP_reg_result; // Registration Results
ULONG DRP_reference_data; // field passed in on initialize
UCHAR DRP_reserved1[2]; // filler for alignment
ULONG DRP_reserved2[1]; // reserved
} DRP, *PDRP;
Listing Two
BeginProc VSD_Device_Init
; call IOS to register. Before returning: IOS will call our
; Async_Event routine with the following messages:
; AEP_INITIALIZE
; AEP_CONFIG_DCB
push OFFSET32 _Drv_Reg_Pkt ;packet (DRP)
VxDCall IOS_Register ;call registration
add esp,04 ;Clean up stack
; decide our status based on the information that IOS gives us
cmp _Drv_Reg_Pkt.DRP_reg_result,DRP_REMAIN_RESIDENT
; should we stay?
je short VSD_Init_Done ; yes: return
cmp _Drv_Reg_Pkt.DRP_reg_result,DRP_MINIMIZE ; should we minimize?
je short VSD_Init_Done ; yes: we can't minimize any more than
; normal, so just return with success
stc ; error
VSD_Init_Done:
ret
Listing Three
typedef struct _DCB_COMMON {
ULONG DCB_physical_dcb; // DCB for physical device
ULONG DCB_expansion_length; // total length of IOP extension filled
// Link fields follow
PVOID DCB_ptr_cd; // pointer to calldown list
ULONG DCB_next_dcb; // pointer to next DCB
ULONG DCB_next_logical_dcb; // pointer to next logical dcb
// for physical device
BYTE DCB_drive_lttr_equiv; // drive number (A: = 0, etc.)
// set up by iosserv during logical
// device associate processing.
BYTE DCB_unit_number; // either physical drive number
// (sequential drive number or'd
// with 80h) or unit number within
// tsd. set up by iosbid for disk
// physical dcb's. set up by tsdpart
// for disk logical dcb's. set up by
// tsdaer for cdrom physical dcb's.
USHORT DCB_TSD_Flags; // Flags for TSD
// Volume Tracking fields follow
ULONG DCB_vrp_ptr; // pointer to VRP for this DCB
ULONG DCB_dmd_flags; // demand bits of the topmost layer
ULONG DCB_device_flags; // was BDD_Flags
ULONG DCB_device_flags2; // second set of general purpose flags
ULONG DCB_Partition_Start; // partition start sector
ULONG DCB_track_table_ptr; // pointer for the track table buffer
// for ioctls
ULONG DCB_bds_ptr; // DOS BDS corresp. to this DCB
// (logical DCB's only)
ULONG DCB_Reserved1; // reserved
ULONG DCB_Reserved2; // reserved
BYTE DCB_apparent_blk_shift; // log of apparent_blk_size
BYTE DCB_partition_type; // partition type
USHORT DCB_sig; // padding and signature
BYTE DCB_device_type; // Device Type
ULONG DCB_Exclusive_VM; // exclusive access handle to device
UCHAR DCB_disk_bpb_flags; // bpb flags see defines below
UCHAR DCB_cAssoc; // count of logical drives
// associated with this logical DCB
UCHAR DCB_Sstor_Host; // indicates a sstor host volume
USHORT DCB_user_drvlet; // the userdriveletter settings
USHORT DCB_Reserved3; // reserved
ULONG DCB_Reserved4; // reserved
} DCB_COMMON, *PDCB_COMMON;
typedef struct _DCB {
DCB_COMMON DCB_cmn;
ULONG DCB_max_xfer_len; // maximum transfer length
// Actual geometry data follows
ULONG DCB_actual_sector_cnt[2]; // number of sectors as seen below
// the tsd.
ULONG DCB_actual_blk_size; // actual block size of the device
// as seen below the tsd.
ULONG DCB_actual_head_cnt; // number of heads as seen below
// the tsd.
ULONG DCB_actual_cyl_cnt; // number of cylinders as seen
// below the tsd.
ULONG DCB_actual_spt; // number of sectors per track as
// seen below the tsd.
PVOID DCB_next_ddb_dcb; // link to next DCB on DDB chain
PVOID DCB_dev_node; // pointer to dev node for this device
BYTE DCB_bus_type; // Type of BUS
BYTE DCB_bus_number; // channel (cable) within adapter
UCHAR DCB_queue_freeze; // queue freeze depth counter
UCHAR DCB_max_sg_elements;// max # s/g elements
UCHAR DCB_io_pend_count; // number of requests pending for
// this DCB (Vol Track Layer use)
UCHAR DCB_lock_count; // depth counter for LOCK MEDIA
// SCSI fields follow
USHORT DCB_SCSI_VSD_FLAGS; // Flags for SRB builder
BYTE DCB_scsi_target_id; // SCSI target ID
BYTE DCB_scsi_lun; // SCSI logical unit number
BYTE DCB_scsi_hba; // adapter number relative to port drv.
BYTE DCB_max_sense_data_len; // Maximum sense Length
USHORT DCB_srb_ext_size; // miniport srb extension length
BYTE DCB_inquiry_flags[8]; // Device Inquiry Flags
BYTE DCB_vendor_id[8]; // Vendor ID string
BYTE DCB_product_id[16]; // Product ID string
BYTE DCB_rev_level[4]; // Product revision level
BYTE DCB_port_name[8];
UCHAR DCB_current_unit; // used to emulate mltpl log. devices
// with a single physical device
ULONG DCB_blocked_iop; // pointer to requests for an inactive
// volume
ULONG DCB_vol_unlock_timer; // unlock timer handle
UCHAR DCB_access_timer; // measures time between accesses
UCHAR DCB_Vol_Flags; // Flags for Volume Tracking
BYTE DCB_q_algo; // queuing algorithm index
BYTE DCB_unit_on_ctl; // relative device number on ctlr
ULONG DCB_Port_Specific; // bytes for PORT DRIVER use
ULONG DCB_spindown_timer; // timer for drive spin down
DCB_BLOCKDEV DCB_bdd;
} DCB, *PDCB;
// define the device control block (dcb) for logical disk devices
typedef struct _LOG_DCB {
DCB_COMMON DCB_cmn;
} LOG_DCB, *PLOG_DCB;
Listing Four
typedef struct _DCB_cd_entry {
PVOID DCB_cd_io_address; // addr of request routine
ULONG DCB_cd_flags; // demand bits
ULONG DCB_cd_ddb; // driver's DDB pointer
ULONG DCB_cd_next; // pointer to next cd entry
USHORT DCB_cd_expan_off; // offset of expansion area
UCHAR DCB_cd_layer_flags; // flags for layer's use
UCHAR DCB_cd_lgn; // load group number
} DCB_cd_entry, *pDCB_cd_entry;
Listing Five
mov eax, [ebx].IOP_calldown_ptr ; get call down address
mov eax, [eax].DCB_cd_next ; get next calldown entry
mov [ebx].IOP_calldown_ptr, eax ; reset calldown pointer
push ebx ; place IOP on stack
call [eax].DCB_CD_IO_Address ; call next layer
add esp, 4 ; restore stack
Listing Six
; insert our callback in the callback stack as default for all requests
mov eax, [ebx.IOP_callback_ptr] ; set Callback
mov [eax.IOP_cb_address], offset32 VSD_callback ; pointer
; use the calldown pointer as reference data in the callback entry
; so that we can find the offset in the IOP to the expansion area
mov edx, [ebx.IOP_calldown_ptr] ; get CD ptr
mov [eax.IOP_cb_ref_data], edx ; set reference
; add a callback entry to the callback stack, for the next layer
add [ebx.IOP_callback_ptr],size IOP_callBack_entry ; move down
Listing Seven
typedef struct IOP_callback_entry {
ULONG IOP_CB_address; // call back address
ULONG IOP_CB_ref_data; // pointer to callback ref data
} IOP_callback_entry;
Listing Eight
mov edi, [esi].IOP_callback_ptr
sub edi, size IOP_CallBack_Entry ; point to next available
; call-back entry
mov [esi].IOP_callback_ptr, edi ; update call-back Pointer
; IOP pointer is passed on the stack
push esi ; IOP's offset
call dword ptr [edi] ; make the call
add esp, 4 ; restore stack