While researching the history of Microsoft’s segmented-executable linker originally called LINK4.EXE, I came across an OS/2 executable that was publicly released almost a year before the first OS/2 SDK was shipped, and many months before OS/2 was even announced. In fact the executable was likely released before the name “OS/2” even existed.
The file is EXEHDR.EXE, dated June 11, 1986 (size 32,896 bytes). It was shipped in the Microsoft Windows 1.03 SDK and then again unchanged in the Windows 1.04 SDK. For reference, OS/2 was announced on April 2, 1987 and the first OS/2 SDK was shipped around May 1987.
The mid-1986 EXEHDR.EXE is a classic dual-mode executable, with a DOS-compatible “stub” followed by an OS/2 NE format module—essentially two separate executables in a single file (it is not a bound executable). The OS/2 portion imports several routines from the DOSCALLS module, such as DOSOPEN, DOSREAD, DOSALLOCSEG, DOSGETVERSION, or DOSEXIT. It is notable that this old EXEHDR.EXE imports from DOSCALLS by name, while actual OS/2 applications imported by ordinal number.
This EXEHDR.EXE from June 1986 is currently the earliest known OS/2 executable released outside Microsoft/IBM. There is a slight chance that others might have been published with earlier Windows SDKs or perhaps some other Microsoft tools. It is likely that the release of this dual-mode EXEHDR.EXE was an omission, as the OS/2 portion effectively wasted about 16KB of disk space.
The June 1986 EXEHDR utility does not run on any known version of OS/2 (either early betas from mid-1987 or later 32-bit versions). On the other hand, the utility—unsurprisingly—understands its own format and produces the following output when run against itself in a DOS environment:
Magic number: 5a4dH Bytes on last page: 128 Pages in file: 65 Relocations: 3 Paragraphs in header: 32 Extra paragraphs needed: 0 Extra paragraphs wanted: 65535 Initial stack location: 0455:0800 Word checksum: ca7fH Entry point: 0000:1206 Relocation table address: 0040H Reserved words: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 New .EXE header address: 4400H Memory needed: 32K Module: EXEHDR Description: EXEHDR.EXE Linker version: 5.00 32-bit Checksum: f7de06d1 Data: INSTANCE Segment Table: 00004440 length 16 Resource table: 00004450 length 0 Resident Names Table: 00004450 length 10 Imported Names Table: 0000445c length 157 Entry Table: 000044f9 length 10 Non-resident Names Table: 00004503 length 14 Movable entry points: 1 Segment sector size: 512 Entry point: seg 1 offset 0fd7 Stack: seg 2 offset 16d0 DGROUP: 2 no. type address file mem flags 1 CODE 00004600 02ee1 02ee1 relocs, movable, preload 2 DATA 00007600 00a80 016d0 movable, preload ord seg offset name 1 type offset target PTR 2ca2 imp DOSCALLS.DOSWRITE PTR 1cad imp DOSCALLS.DOSSETVEC PTR 1729 imp DOSCALLS.DOSEXIT PTR 0ff5 imp DOSCALLS.DOSGETVERSION PTR 2a5c imp DOSCALLS.DOSALLOCSEG PTR 2dba imp DOSCALLS.DOSREALLOCSEG PTR 2c8e imp DOSCALLS.DOSCHGFILEPTR PTR 2d4a imp DOSCALLS.DOSCLOSE PTR 2e1a imp DOSCALLS.DOSOPEN PTR 2e38 imp DOSCALLS.DOSQFILEMODE PTR 1c87 imp DOSCALLS.DOSQHANDTYPE PTR 2c73 imp DOSCALLS.DOSREAD PTR 2d96 imp DOSCALLS.DOSSETFILEMODE BASE 1b6f seg 2 offset 0000
Both the DOS and OS/2 executables included in EXEHDR.EXE had been built with Microsoft C, presumably version 4.0 with unreleased OS/2 run-time libraries.
It is currently not known whether anyone discovered this back in 1986, or if they understood the implications. The fact that Microsoft was working on “Advanced DOS” was understood at the time, although not many details were known.
Worth noting is also the fact that the LINK4.EXE segmented-executable linker (dated July 18, 1986) shipped with the Windows 1.03 SDK also clearly included some OS/2 support. The list of keywords includes for example PROTMODE and IOPL, neither of which was used with Windows 1.x/2.x or with the multitasking DOS 4.
How did you determine that this version of exehdr.exe was designed for a proto-OS/2 not Windows, MT-DOS 4, or one of the fabled but never shipped attempts at porting DOS itself to protected mode or some other product started and canceled before the OS/2 agreement was finalized? The NE structure got used a lot. Someone at MS liked the basic design of overlapping structures appended to a previous structure; a bunch of API and file formats used the system.
it is like all the parts were there in 1986 for something chicago-ish before we eventually got there in 1995. CP-DOS must have been able to actually run things, and I’m guessing via the tripple fault on the 286 there was at least some DOS compatibility. Windows 2.0 was still a year away, but in that time MS would already have pushed out Windows/386 the DOS hypervisor.
And even assuming MS could pull it all together before 1990, would there be room in MS’s world for MS-DOS+Windows and CP-DOS/Windows? I guess Windows 3.0 wouldn’t have needed to happen. But then again MS was still far too timid to sell EuroDOS 4.0 on its own, just as CP-DOS was at some runnable state, but again wasn’t for larger sale.
Now that I’ve seen actual multitasking DOS 4, it’s clear that its API is entirely different from OS/2, because it is primarily DOS INT 21h based. On the other hand, the EXEHDR executable in question (the NE part of it) does not use INT 21h at all but rather only imports from DOSCALLS (the imports are OS/2 API functions, see listing). Note that multitasking DOS 4 also had a DOSCALLS module, but exported a completely different set of functions.
On the other hand, looking at what this EXEHDR understands, there’s the “protected mode only” flag which would not apply to either DOS or Windows 1.x/2.x.
You mean Microsoft was too timid to sell multitasking DOS 4.0 in retail? Because they did try selling it to OEMs and the OEMs overwhelmingly said “no thanks”.
What you might also find interesting is that this EXEHDR can recognize executables which use 386 instructions and contain 32-bit segments with 32-bit offsets and 48-bit far pointers. Clearly Microsoft was working on the technology but the world had to wait a while. This is no doubt related to the fact that Microsoft at that time had a 32-bit XENIX compiler and was developing a 386 port of XENIX.
Thinking about it, I wonder if part of the reason why “CP-DOS” targeted the 286 is to make the project go faster. Writing a 286 protected mode OS was already not a trivial task.
Compare with WinWord 1.0 which took years.
And OS/2 was basically done by the time Intel had working 32-bit chips (actually working, not just theoretically working).
What I meant is that writing a 386 one with all the paging code would have been also been much harder.
IMO, writing code for flat 32-bit address space with paging is way simpler than for 16-bit segmented memory not only on the applications side, but also for operating system authors.
First of all, managing memory in small fixed blocks is more straightforward than juggling variable-length blocks that are often larger. It applies to allocation of blocks and paging out to disk.
Second, Intel’s segmented model is over complicated and requires managing lots of in-memory structures to function properly. Any mistake filling these structures leads to hard to debug failures.
Compilers and linkers can be simplified when they do not need to deal with near/far pointers and functions, structures spanning several segments and juggling segments in general.
In addition to that, 386 gives V86 mode and a (relatively) easy way to switch back to real mode.
So I really think that given a choice no one would bother writing an OS for 286. OS/2 developers simply had no choice.
If you ever spent any debugging a problem caused by TLB mismanagement, you wouldn’t be saying that. TLB bugs are absolute hell to debug and can hide very well. No segmentation related problem is nearly as evil.
Add to that the fact that early 386s could barely run 386 code at all, and that 32-bit code is bigger and slower, and you’re looking for a repeat of Windows NT where yes, sure, it did exist in 1993, but it took years before NT moved to the mainstream.
It is not just TLB management either. Writing 386 paging code requires a lot of decisions to be made, not to mention a lot of additional code.
” Windows NT where yes, sure, it did exist in 1993, but it took years before NT moved to the mainstream.”
Side track: Has it been thoroughly discussed why NT had to have such high hardware requirements? (This for sure was one of the biggest reason for it not becoming mainstream sooner) Thinking about that Windows 3.x were able to manage some form of protected with “only” 1M of RAM, and that for example AmigaOS ran fine with 256k in ROM and 256-512k RAM at the time. (We can’t really compare for example Macintosh OS or TOS for the Atari ST as those had no attempts at multi tasking).
32-bit C code and 16-bit assembler are quite different in size. Windows 3.x was optimized for size, NT was 32-bit from top to bottom, written entirely in C, and was significantly more complex and capable.
I know that it’s generally said that x86 32-bit code becomes about 50% larger than the equivalent 68k code. But also I wounder if the compilers were way worse at that time than they became later on?
As the x86 was rather different to more or less everything else it probably needed dedicated compilers while I would think that a compiler for for example VAX could likely to a large extent be reused for producing 68k code.
(TL;DR: VAX has three operand instructions, 68k has two operand instructions. Both have 16 32-bit registers but VAX uses up more of them to dedicated uses (iirc the program counteris one of them) than the 68k (that only uses one of them as the stack pointer) but the 68k has a split where 8 are mostly usable for addressing purposes while the other 8 are mostly useable for computation purposes)).
Especially if compilers for 32-bit x86 code were based on compilers for 16-bit x86 code then they might had contained constraints that produced inefficient code.
Or maybe Dave Cutler & co just targeted the future with NT and ignored lower end systems at the time, i.e. having larger / more elaborate data structures and whatnot that would increase memory usage?
NT both targeted future systems and didn’t target x86 specifically. The NT design is vastly more elaborate than most other systems, and it’s perhaps fair to say that it’s overengineered. There’s none of the simplicity and elegance of UNIX — in fact NT is the OS by and for the UNIX haters.
Old compilers definitely weren’t as efficient, but I can’t say how much difference that made. I don’t think the Microsoft compilers used to build NT were at all bad. The 32-bit 386 code was still definitely more compact than 32-bit RISC architectures.
Don’t forget the additional RAM overhead for the paging tables as well. CP-DOS already have the overhead of the GDT/LDT/IDT, which is probably part of why MS gave on trying to fit it into 640K.