Over the last few days I’ve been slowly attacking the source code for 386MAX, trying to build the entire product. One of the many problems I ran into turned out to be quite interesting.
There are several (16-bit) Windows components in 386MAX, and many have some sort of GUI. As is common, dialogs etc. are built from templates stored in resource (.rc) files. But… trying to process many of the resource files with the standard Windows 3.1 resource compiler fails:
The problem is dialog text labels composed of multiple strings. Something like this:
For obvious reasons, the authors wanted to automatically update the strings with the current version numbers, and used macros to build those strings. Only that doesn’t quite work in the resource compiler.
In July 1990, Microsoft released a specification for Virtual DMA Services, or VDS. This happened soon after the release of Windows 3.0, one of the first (though not the first) providers of VDS. The VDS specification was designed for writers of real-mode driver code for devices which used DMA, especially bus-master DMA.
Let’s start with some background information explaining why VDS was necessary and unavoidable.
In the days of PCs, XTs, and ATs, life was simple. In real mode, there was a given, immutable relationship between CPU addresses (16-bit segment plus 16-bit offset) and hardware visible (20-bit or 24-bit) physical addresses. Any address could be trivially converted between a segment:offset format and a physical address.
When the 386 came along, things got complicated. When paging was enabled, the CPU’s memory-management unit (MMU) inserted a layer of translation between “virtual” and physical addresses. Because paging could be (and often was) enabled in environments that ran existing real-mode code in the 386’s virtual-8086 (V86) mode, writers of V86-mode control software had to contend with a nasty problem related to DMA, caused by the fact that any existing real-mode driver software (which notably included the system’s BIOS) had no idea about paging.
A few weeks back I was reminded that the source code to the 386MAX (later Qualitas MAX) memory manager was released in 2022 on github. Back in the 1990s I used primarily EMM386 and QEMM, but I have some experience with 386MAX as well. It’s quite comparable with QEMM in terms of features and performance.
The source code itself is quite interesting. The core memory manager is of course written 100% in assembler. However, it is very well commented assembly code, written in uniform style, and it generally gives the impression of a well maintained code base (unlike, say, MS-DOS).
The github release also includes enough binaries that one can experiment with 386MAX without building a monster code base.
I was able to get enough of it installed to have a functional 386MAX setup. Then I thought that for easier experimentation, perhaps I should set up a boot floppy with 386MAX. And that’s where trouble struck.
The other day I had a pressing “need” to examine the behavior of Adaptec 154x and compatible SCSI HBAs and their DOS drivers. I found the hard way that the AHA-154xB does not work with Adaptec’s last DOS drivers from circa 1999. That includes the drivers still available for download (ASPI4DOS.SYS version 3.36), as well as the driver shipped with OEM versions of Windows 98SE (ASPI4DOS.SYS version 3.36S).
The error message is far from enlightening; effectively the driver acts as if there were no HBA at all.
Quite unsurprisingly, this problem has beennoticedbefore. It has been also observed that ASPI4DOS.SYS version 3.35 works fine, as do older versions. But why is the 3.36 driver really not working with AHA-154xB?
In my quest to understand the intricacies of x87 behavior and especially floating-point exceptions, I pulled out my trusty old Alaris Cougar board. The system board had a 100 MHz Intel OverDrive 486 DX4 plugged in and worked quite well. I could run applications, edit and compile programs, copy files to and from network, run Windows 3.1, that kind of stuff. No problems really.
But when I tried to test how floating-point exceptions behave, I had a bit of a shock: They didn’t happen at all. The machine just blazed through any and all code that was supposed to trigger FPU exceptions without a second glance. There was just nothing, no exceptions.
In an attempt to isolate the problem, I removed the DX4 OverDrive and used the onboard Blue Lightning CPU with the already installed 387 coprocessor (actually a rare Chips & Tech 387 but that doesn’t really matter). Lo and behold, math exceptions did work.
So I unplugged the 387 and put the DX4 OverDrive back. Sure enough, that did change things… but not exactly in a good way. Now any math exception hung the system instead!
Trying to obtain further data points, I plugged in a Pentium OverDrive instead of the DX4. To my great surprise, math exceptions suddenly worked properly. They did get delivered as expected and did not hang the system.
Trying to make sense out of this, I wondered if the board’s manual might perhaps have some useful information…
A few weeks ago I had the questionable pleasure of diving into the math exception handler of WIN87EM.DLL, the Windows 3.1 math emulator and FPU support library. Actually WIN87EM.DLL appears to have been first shipped with Windows 3.0, and the version in Windows 3.1 is more or less identical. The version shipped with Windows 3.11 is byte for byte identical to the one in Windows 3.1.
The main function of WIN87EM.DLL is, as the name suggests, an x87 floating-point emulator. It appears to be an outgrowth of the math emulation packages shipped with many Microsoft languages. But even on a system with x87 hardware, WIN87EM.DLL has some work to do.
Namely WIN87EM.DLL intercepts math errors (exceptions). Depending on the system type, WIN87EM.DLL hooks either NMI vector 2 (PC and PC/XT) or IRQ 13 (PC/AT and compatibles).
The math interrupt handler in WIN87EM.DLL is very, very strange. It bears all the hallmarks of code that was written, rewritten, rewritten again, hacked, tweaked, modified, and eventually beaten into submission even if the author(s) had no real idea why it finally worked.
In another useless project, I decided to find out why even trivial programs created with the Open Watcom compiler refuse to run on Windows NT 3.1. Attempting to start an executable failed with foo.exe is not a valid Windows NT application. But this time, the surgery was successful.
There turned out to be several problems, some related and some not. The biggest issue was the problem previously discussed in this post. To recap, the Open Watcom runtime calls the GetEnvironmentStringsA API, which does not exist on NT 3.1. This also causes trouble on Win32s; although newer Win32s versions do implement GetEnvironmentStringsA, it just always fails.
The solution is to use the original GetEnvironmentStrings API (which is equivalent to GetEnvironmentStringsA) . But that’s not enough.
A few weeks ago I embarked on a somewhat crazy side project: Make the Open Watcom debugger work on OS/2 1.0. This project was not entirely successful, but I learned a couple of things along the way.
The Open Watcom debugger components for OS/2 are 32-bit modules, but didn’t use to be. At least up to and including Watcom C/C++ 11.0, the OS/2 debugger components were almost entirely 16-bit. That allowed them to work on 32-bit OS/2 but also on 16-bit OS/2 version 1.x.
Needless to say, debugging 32-bit programs with a 16-bit debugger required a bit of extra work and there was an additional interface module (os2v2hlp.exe) which called the 32-bit DosDebug API on 32-bit OS/2 and interfaced with the 16-bit debugger ‘trap file’ std32.dll.
Building the 16-bit components again was not particularly difficult. And they even mostly worked on OS/2 1.2 and 1.1. But I wanted to get the code working on OS/2 1.0 as well. Because why not.
The other day I was going over various versions of the venerable DOS/16M DOS extender from Rational Systems (later Tenberry Software). The DOS/16M development kit comes with a utility called PMINFO.EXE which is meant to give the user some idea about the performance of a system running in protected mode.
I know that the utility has trouble on faster CPUs and I expected it to fail about like this:
But running the utility on an older laptop with an Intel Haswell processor, I instead got this:
Rather than cleanly exiting after catching a floating-point division by zero, the program crashed with a general protection fault. That looks like a bug, but why would it be happening? And where is the bug?
While the 16-bit real-mode DOS variant of the editor idles nicely, the 32-bit DOS extended version that’s actually shipped with the Open Watcom tools just refuses to idle. What’s notable is that it won’t idle with DOS POWER.EXE, with DOSIDLE, and it also won’t idle when run under Windows 9x or XP. And it doesn’t idle with my DR-DOS $IDLE$ driver either.
So I decided to track down what’s going on in the DR-DOS case. What I could see was that the DRIDLE.SYS driver never went into idle mode because every time it decremented the INT 16h idle counter, it was already reset to the initial (maximum) value by a timer tick.
In other words, as far as the DRIDLE.SYS was concerned, the editor wasn’t idle because it was only calling INT 16h very rarely. But I know that the editor polls INT 16h in a tight loop!
While trying to characterize the problem, I also found another oddity: The refusal to idle happens with the (default) DOS/4GW extender. But when the same vi.exe program is run using the CauseWay or DOS/32A DOS extenders (both also shipped with Open Watcom), the editor idles just fine!
Initially I thought there could be some problem specific to the protected-mode version of the editor, since the code is necessarily slightly different from the real-mode variant. But if it were the editor itself, it would behave the same with all DOS extenders, wouldn’t it?