Geoff Chappell - Software Analyst
The original code in CRTDRVR.ASM, which was written in 1992, was revised in June 1996 to fix a bug (that had gone unnoticed for an embarrassingly long time) and to accommodate some changes that Windows 95 makes to DOS’s startup. These revisions are presented together in the new source code but described here separately.
The driver library has a surprisingly serious bug that is activated when the library is used together with the scheme that Chapter Six of DOS Internals presents for resident programming. In that scheme, segments are rearranged from the DOSSEG order of code then data (essential to the standard run-time library of Microsoft C) to the preferred order of resident then non-resident. When devising the one programming scheme, I failed to allow for the rearrangement devised in the other. Put the two together and the bug corrupts one word at an essentially random address just as the driver is about to return to SYSINIT after initialising. This bug is fixed by adding two lines to the CRTDRVR.ASM source code.
The release of Windows 95 requires one change to the techniques used in CRTDRVR and makes another change convenient. The change that must be made is due to something that may reasonably be deemed a bug in the DOS 7 kernel. The other change is possible only because the DOS 7 kernel fixes something that may reasonably be deemed a bug in earlier versions of the kernel.
New code in the DOS 7 kernel (previously MSDOS.SYS but now bound into IO.SYS) makes int 21h function 31h unsafe to use during device driver initialisation. This affects any device driver produced by linking the CRTDRVR library with code for a DOS program that terminates and stays resident.
Certain VxDs in Windows 95 need to know whether any DOS device drivers and programs have used resources of interest. For instance, have any hooked int 13h or a hardware interrupt? The general scheme for supporting this from the DOS side is that some DOS component takes a snapshot of the system state each time a DOS device driver gets loaded or a DOS program gets run. When the driver or program is done, the system state is compared with the snapshot to discover any changes. Information about the driver or program may then be recorded for VxDs to study later.
When SYSINIT loads device drivers, only it can easily know when the device driver returns from its initialisation, and so SYSINIT has the job of watching for drivers that change the system state. The information that SYSINIT records about the drivers it loads is entered into structures that are documented in the RMD.H file supplied with the Windows 95 DDK and are retrievable at run-time via int 2Fh function 1690h (which is documented as W386_Return_RMD).
When SYSINIT is finished loading device drivers, the DOS kernel picks up the job of watching for resource use by DOS programs. SYSINIT finds some memory for the DOS kernel to use. Some of this memory is used and reused for the snapshots of system features that DOS will take before each program’s execution. Some of the memory is to serve as a permanent record that may be examined by VxDs. The layout allows for DOS to note the resource usage of as many as 20 TSRs that run outside of Windows. Interestingly, VxDs do not have access to this record via the int 21h function 1690h interface. The IOS VxD finds the record by knowing that the address is kept as the dword at offset 1328h in the DOS kernel’s data segment. The layout and location of this TSR record appear to be undocumented.
Of particular interest to the CRTDRVR library is the method by which DOS takes its snapshots of resources used by TSRs. Two routines have been added to the DOS kernel. One takes the snapshot before a program runs. It is called every time that int 21h function 4Bh is used to start a new process, but only provided Windows Enhanced Mode is not running and only provided SYSINIT has given DOS the memory in which to record the snapshot (that is, provided that the dword at offset 1328h is not null). The other new routine acts when a program finishes. It compares with the snapshot that was taken before the program runs, and it records what has changed. It is called whenever a program terminates via int 21h function 31h and Windows Enhanced Mode is not running.
The problem for the CRTDRVR library is that whereas the first of those routines takes its snapshot only if SYSINIT has provided the necessary memory, the second routine simply assumes that the memory is there. Specifically, it does not check that the dword at offset 1328h is not null. If int 21h function 31h is used while SYSINIT is still in the business of loading device drivers, then the DOS kernel’s pointer at offset 1328h will be null and the DOS kernel will misbehave, possibly even by trampling over the interrupt vector table.
Note the inconsistency. One routine guards against a null pointer and the other doesn’t—even though the routines are adjacent in the kernel code. The understandable difference from Microsoft’s perspective is that int 21h function 4Bh is needed while SYSINIT loads device drivers and must guard against a null pointer, but int 21h function 31h is not anticipated to be used this early and therefore need not guard against a null pointer.
The updated CRTDRVR code works around this problem by providing the DOS kernel with the necessary memory temporarily. The new code is enabled only when running under DOS version 7.0 or higher.
A first update in 1996 was either too optimistic (thinking that future versions of the DOS kernel would guard against the null pointer) or too pessimistic (thinking that there would be no future version), and tested for DOS 7.0 exactly. I thank Ivan P. de Arruda Campos for bringing to my attention a problem that had this test as its cause.
For DOS versions before Windows 95, CRTDRVR already has to work around a similar problem in the DOS kernel code, namely that when terminating a process, the DOS kernel simply assumes the existence of at least one FCB-SFT. Since the FCB-SFTs are not built until SYSINIT finishes loading device drivers, all functions for terminating DOS processes are unsafe during device driver initialisation. To get around this, the CRTDRVR code presented in DOS Internals provides the DOS kernel with a temporary FCB-SFT.
Under Windows 95, Microsoft needs to execute an ordinary program during DOS’s startup, specifically a program bound into the IO.SYS file and used for reading the system registry. This ordinary program will terminate in the ordinary way. Since Microsoft now needs to run a program during DOS’s startup and have the program terminate safely, it is no surprise to find that when the DOS kernel for Windows 95 terminates a process, it now checks whether SYSINIT has yet provided a block of FCB-SFTs. Thus, the FCB-SFT workaround need not be activated when the CRTDRVR code executes under DOS 7 (or higher).
The revisions described above mean that three files must be replaced in the DEVTOOLS\DRIVER\CRTDRVR directory of the DOS Internals disk. These files are the CRTDRVR assembly-language source, the object code and the library. The CRTDRVR.LIB file should also be copied to the LIB directory. For distribution, all three files are compressed into one: download the CRTDRVR Bug Fix and Windows 95 Update.