The recently-mentioned multitasking DOS 4 disk images came with a linker called LINK4.EXE. The ‘4’ in fact stands for ‘DOS 4’, although most people who used LINK4 never saw multitasking DOS 4 (LINK4 was shipped with 16-bit Windows SDKs). LINK4 was Microsoft’s linker for the “new executable” format (NE) shared with minor modifications by DOS 4, 16-bit Windows, and 16-bit OS/2.
Now the problem with the LINK4.EXE on the multitasking DOS 4 disks was that the linker didn’t seem to work at all. Simply running LINK4 produced no output, although pressing Enter twice terminated the program. Yet on closer examination, it became apparent that feeding object files to the linker produced sane-looking executables and map files.
The initial suspicion was naturally a corrupted file—hardly a surprise on 25+ year old disks. Yet the reality turned out to be much more interesting.
Tracing the LINK4 execution showed that it doesn’t attempt to write any output when it should print messages on the console. Disassembling the executable quickly revealed that it had been written in C. Given that the file is from late 1985, it would seem safe to assume that it had been built with Microsoft C 3.0 or 4.0. Unfortunately it hadn’t—for whatever reason, the LINK4 developers used their own custom C runtime which was similar to the one in MS C 3.0, but not the same. It was slightly simpler and did not attempt to fully implement the standard C library (granted, the ANSI C standard didn’t exist yet); for example the LINK4 runtime did not keep track of
errno while the MS C 3.0 runtime did.
At any rate, concentrating on file I/O it was easy to recognize the equivalents of
fprintf(). The LINK4 code certainly did not look corrupted at all, but the
_iob array (or its equivalent), located almost at the very end of the executable, looked a bit fishy.
It was apparent where the code used
stdin/stdout/stderr. The references translated to direct pointers within
_iob (the three standard streams are its first three elements), but the
_iob contents were clearly not going to produce anything useful, as the file flags were all wrong. For example the file flag for
stderr was zero, which caused the runtime library to not even attempt to write anything to the underlying file. At the same time, the
_iob array in the executable did not look like garbage, more like the contents had been somehow shifted.
Further analysis of the LINK4 code showed a curious discrepancy: It was clear that
stdin/stdout/stderr were offset by 8 bytes (which corresponds to the size of the
FILE structure in MS C 3.0), yet the runtime code was referring to a word at offset 8 in the
FILE structure. That can’t possibly work very well.
Finally, realization dawned. The C runtime in LINK4 had been built with 10-byte
FILE structures, and viewed through that lens, the
_iob array made perfect sense—no corruption at all. That of course brought two further questions: How did this happen, and can anything be done about it?
The best theory explaining how this may have happened is as follows: The LINK4 executable was incorrectly built, using
stdio.h header file that perhaps matched the regular Microsoft C 3.0 runtime but did not match the library. Since the function signatures were compatible, and nearly all code used pointers to
FILE structures, the size difference caused no problems except when translating
_iob offsets. This is perhaps poor library design where the size of the
FILE structure, which should be treated as opaque, is unnecessarily exposed. The resulting error might not have been visible when using LINK4 through makefiles.
Can anything be done about it? Yes, with a minor change to the
_iob array. The trick is to change the flags (byte at offset 7 in the structure) to indicate an unbuffered writable stream (
_IOWRT | _IONBF in MS C 3.0 library parlance). That hides the structure size mismatch because most of the structure fields, which are related to buffering, are no longer used. After patching up the executable, LINK4 successfully produces the following output:
Microsoft (R) 8086 Object Linker Version 4.01 Copyright (C) Microsoft Corp 1984, 1985. All rights reserved. Object Modules [.OBJ]:
The resulting executable is obviously no longer an original… but it works so much better! It remains to be seen whether it can be used to produce functioning DOS 4 executables.