Fixing Broken LINK4

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 write() or 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 stdout and stderr into _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.

This entry was posted in DOS, Microsoft. Bookmark the permalink.

5 Responses to Fixing Broken LINK4

  1. wow that was involved! although I guess there is nothing like VIO in MS-DOS 4.00 .. I suppose it being DOS after all it would expect you to be calling DOS like DOS.

  2. michaln says:

    Exactly, all I/O is done through INT 21h. That said, LINK4.EXE is a standard DOS ‘MZ’ executable anyway, not tied to DOS 4 at all. The utilities that came with DOS 4 are a mix, with a few core utilities in ‘NE’ format (notably COMMAND.COM) but many or most being regular DOS executables.

  3. Jonas says:

    Hi,

    Is there a way to download this DOS4 from somewhere?

    Thanks,

  4. Jeff says:

    Great Work! It’d be great if you could provide the “diff” files or similar so that everyone can enjoy the fixed LINK4

  5. michaln says:

    Not exactly in diff format, but here goes:

    offset 0000cd24: LINK4.EXE=00, new=d8
    offset 0000cd25: LINK4.EXE=00, new=5a
    offset 0000cd2c: LINK4.EXE=00, new=d8
    offset 0000cd2d: LINK4.EXE=02, new=5c
    offset 0000cd2e: LINK4.EXE=d8, new=00
    offset 0000cd2f: LINK4.EXE=5c, new=00
    offset 0000cd30: LINK4.EXE=00, new=d8
    offset 0000cd31: LINK4.EXE=02, new=5c
    offset 0000cd32: LINK4.EXE=d8, new=06
    offset 0000cd33: LINK4.EXE=5c, new=01
    offset 0000cd34: LINK4.EXE=02, new=00
    offset 0000cd35: LINK4.EXE=01, new=00
    offset 0000cd37: LINK4.EXE=02, new=00
    offset 0000cd3a: LINK4.EXE=00, new=06
    offset 0000cd3b: LINK4.EXE=00, new=02

    Applies to LINK4.EXE dated 11/26/1985, size 52,604 bytes.

Leave a Reply

Your email address will not be published. Required fields are marked *