I’m not sure if I covered the Windows NT 3.1 build 196 before. First the most obvious is that as of this moment it’s the earliest version of Windows NT available.
So let’s do some obligatory scratch of the surface. Like all the other 1991 pre-releases there really is just a text mode setup install script. Choosing the lesser amount of pain, I went with a MS-DOS hosted install. However, using MS-DOS 6.22 resulted in a broken dual boot system. But we live in the era of virtual machines, so it really doesn’t matter. I’m using Qemu 0.14-rc2, something that is ‘era correct’ for when the first avalanche broke on early Windows NT pre-releases. I’ve had issues with more modern versions of Qemu, and I felt that if we’re using vintage software may as well go in all the way.
The boot loader identifies itself as being 1990 vintage. Pretty sure it doesn’t mean anything, but we haven’t been blessed with the “blue screen” of ARC yet.
The login screen and desktop have a very strong Windows 3.1 beta feel to them. And that would land this where it was, Windows was such a big seller that Maritz had been trying to convince Gates & Balmer to ‘switch to the Windows horse’ in the spring/summer of 1990, culminating in Gate’s July decision to walk away from NT/OS2 and rebrand the new OS as Windows NT. Oddly enough it was Balmer who was in favour of OS/2 & IBM. (Showstopper 89-90)
At first glance the opening window isn’t all that interesting. It’s just very. Windows.
Rest assured Reversi, of course made the this Win32 cut. And it’s full of weird easter eggs, oddly enough in the OS/2 surviving bits.
type OS2LDR.DOS OS2LDR 01.00.01 by KeithMo 01/08/91
On the Discord there had been a big discussion about early NT executable formats, and the whole COFF vs ECOFF vs PE/PEI. I had tried to hunt down the specific version of GCC I used ages to to build a so called Dec Alpha GCC cross compiler, but the short version is that it didn’t work as we don’t have any assembler/linker for anything GNU targets. There had been a cygwin port and an OpenNT on Alpha, but all that is lost to the winds, minus what few scraps I had saved. I did try building some cross tools to elf hoping to just objcopy out the data and get linkable objects, but that didn’t work either.
So I though this was a perfect opportunity to take a look at this early pre-release version of NT, and although I do know that you have to convert your objects into something the ‘COFF’ linker will accept:
And I never really paid that much attention to the object files. I do know that you can link them with Link386 for OS/2, but the NT object files themselves report:
wsl file SIMPLE.OBJ SIMPLE.OBJ: Intel 80386 COFF object file, not stripped, 4 sections, symbol offset=0x102, 20 symbols, created Thu Jun 1 14:13:50 2023, 1st section name ".text"
Well, now that is interesting. And of course the COFF thing reminded me of Xenix! And sure enough ages ago I had found the source to a modified version of GNU GAS that outputs COFF. Once more again this is an indication that all 386 roads in Microsoft really did originate with Xenix. It’s too bad there never was a Windows/386 on Xenix/386. What an incredible OS that would have been! There must be some incredible stories from the tool teams that worked on Microsoft C/386 along with other projects. Oddly enough they never get anywhere near as much exposure as Office or OS.
Now this is fun, but nothing takes in these ancient COFF objects, do they? I tried to run LINK 1.0 from Win32s SDK and surprisingly it didn’t complain about the object, rather, it auto converted it in memory:
Microsoft (R) 32-Bit Executable Linker Version 1.00 Copyright (C) Microsoft Corp 1992-93. All rights reserved. SIMPLE.OBJ : warning LNK4016: unresolved external symbol "__chkstk" LINK : warning LNK4016: unresolved external symbol "_mainCRTStartup" SIMPLE.OBJ : warning LNK4016: unresolved external symbol "_printf" SIMPLE.exe : error LNK1120: 3 unresolved externals
Does this mean that if I give it some libraries it will actually link?
LINK.EXE SIMPLE.OBJ /SUBSYSTEM:CONSOLE /MACHINE:i386 -entry:mainCRTStartup -out:simple.exe libc.lib kernel32.lib Microsoft (R) 32-Bit Executable Linker Version 1.00 Copyright (C) Microsoft Corp 1992-93. All rights reserved. C:\temp\nt196\x\dec\x>simple Win32, it's happenin'!
This was. VERY unexpected.
So I had this crazy idea, what if the Xenix assembler could in fact build objects that are also compaible in this manner? I used the a.out GCC / Linux porting tools I had built so I could compile Linux on Windows NT using the vintage tools as a starting point. I guess I should also add that when people always say ‘use newer version of THING’ this is how you miss out on old stuff like this. If I had been obsessed with using modern tools and modern operating systems, I’d have missed out on this Xenix filled window.
I took the gcc driver & the cc1 compiler from 1.40 and the c pre-processor from 2.5.8 as it can understand C++ comments. First I manually compiled the ‘simple’ example to assembly:
gcc -v -nostdinc -I/xenixnt/h -S SIMPLE.c -O simple.S gcc version 1.40 cpp -nostdinc -v -I/xenixnt/h -undef -D__GNUC__ -Dunix -Di386 -D__unix__ -D__i386__ -D__OPTIMIZE__ SIMPLE.c C:/Users/jsteve/AppData/Local/Temp/cca2_048.cpp GNU CPP version 2.5.8 (80386, BSD syntax) #include "..." search starts here: #include <...> search starts here: /xenixnt/h End of search list. cc1 C:/Users/jsteve/AppData/Local/Temp/cca2_048.cpp -quiet -dumpbase SIMPLE.c -O -version -o SIMPLE.s GNU C version 1.40 (80386, BSD syntax) compiled by GNU C version 5.1.0. default target switches: -m80387 cpp -nostdinc -v -I/xenixnt/h -undef -D__GNUC__ -$ -Dunix -Di386 -D__unix__ -D__i386__ -D__OPTIMIZE__ simple.S C:/Users/jsteve/AppData/Local/Temp/cca2_048.s GNU CPP version 2.5.8 (80386, BSD syntax) #include "..." search starts here: #include <...> search starts here: /xenixnt/h End of search list.
Which gave me the following assembly:
.file "SIMPLE.c" gcc_compiled.: .text LC0: .ascii "Win32, it's happenin'!\0" .align 2 .globl _main _main: pushl %ebp movl %esp,%ebp pushl $LC0 call _printf leave ret
Now to assemble with the GAS Xenix assembler
C:\temp\nt196\files\MSTOOLS\SAMPLES\SIMPLE>ax386 SIMPLE.s -o SIMPLE.obj C:\temp\nt196\files\MSTOOLS\SAMPLES\SIMPLE>wsl file SIMPLE.obj SIMPLE.obj: 8086 relocatable (Microsoft), "SIMPLE.c", 1st record data length 10, 2nd record type 0x88, 2nd record data length 11
Not quite the same. But it does closer resemble the output from the OS/2 bound versions of the Pre-Rease compilers:
file EMPTY.OBJ EMPTY.OBJ: 8086 relocatable (Microsoft), "empty.c", 1st record data length 9, 2nd record type 0x88, 2nd record data length 7
So will it link?!
C:\temp\nt196\files\MSTOOLS\SAMPLES\SIMPLE>LINK.EXE SIMPLE.OBJ /SUBSYSTEM:CONSOLE /MACHINE:i386 -entry:mainCRTStartup -out:simple.exe libc.lib kernel32.lib Microsoft (R) 32-Bit Incremental Linker Version 2.50 Copyright (C) Microsoft Corp 1992-94. All rights reserved. SIMPLE.OBJ : warning LNK4033: converting object format from OMF to COFF C:\temp\nt196\files\MSTOOLS\SAMPLES\SIMPLE>simple Win32, it's happenin'!
Well now this is interesting! LONG before MinGW, or the GCC port to Windows NT, it turns out that in fact GCC could target Windows NT the entire time!
So the next thing to do is something not as trivial, like phoon.
I setup some quick script to pre-process, compile, assemble and then try to link, but as this one uses floating point, disaster struck:
phoon.obj : error LNK2001: unresolved external symbol "___fixdfsi" astro.obj : error LNK2001: unresolved external symbol "___fixdfsi" phoon.exe : error LNK1120: 1 unresolved externals
Now ages ago while messgin with old GCC & DooM I also had weird math calls not working. In the end I ended up extracting them from libgcc builds, so I thought I’d try the libgcc built during the GCC 2.6.3 on NT adventure.
link /NODEFAULTLIB:libc.lib /NODEFAULTLIB:OLDNAMES.LIB -out:phoon.exe astro.obj date_p.obj phoon.obj -entry:mainCRTStartup libgcc1.lib LIBC.LIB KERNEL32.LIB Microsoft (R) 32-Bit Incremental Linker Version 2.50 Copyright (C) Microsoft Corp 1992-94. All rights reserved.
And of course:
To try to make the steps make a little more sense, and to allow for some higher level of automation I made a Makefile:
CC=gcc AS=ax386 CPP=cpp CFLAGS= -O CPPFLAGS= -lang-c-c++-comments -nostdinc -I/xenixnt/h OBJ = astro.obj \ date_p.obj \ phoon.obj LIBS = libgcc1.lib LIBC.LIB KERNEL32.LIB phoon.exe: $(OBJ) link -out:phoon.exe $(OBJ) -entry:mainCRTStartup $(LIBS) %.obj: %.c $(CPP) $(CPPFLAGS) $< $*.i $(CC) $(CFLAGS) $(INCLUDE) -S -o $*.S $*.i $(AS) $*.S -o $*.obj clean: del $(OBJ) *.i *.S phoon.exe
I’m sure there is better ways to do this, but it breaks the compile up to it’s individual parts:
Run the pre-processor to allow // in the comments, C++ hadn’t been the default thing back when GCC 1.40 was a thing. Also path it to the headers, in this case I’m using the ones from NT 196. Trying to link with the 196 libraries gave me this:
C:\xenixnt\demos\phoon>link /NODEFAULTLIB:LIBC.LIB /NODEFAULTLIB:OLDNAMES.LIB -out:phoon.exe astro.obj date_p.obj phoon.obj -entry:mainCRTStartup base.lib wincrt.lib ntdll.lib \xenixnt\lib\libgcc1.lib Microsoft (R) 32-Bit Incremental Linker Version 2.50 Copyright (C) Microsoft Corp 1992-94. All rights reserved. wincrt.lib(maincrt0.obj) : warning LNK4078: multiple ".data" sections found with different attributes (40000040) astro.obj : error LNK2001: unresolved external symbol "_asin" astro.obj : error LNK2001: unresolved external symbol "_atan" astro.obj : error LNK2001: unresolved external symbol "_atan2" phoon.obj : error LNK2001: unresolved external symbol "_cos" astro.obj : error LNK2001: unresolved external symbol "_cos" astro.obj : error LNK2001: unresolved external symbol "_floor" astro.obj : error LNK2001: unresolved external symbol "_sin" phoon.obj : error LNK2001: unresolved external symbol "_sqrt" astro.obj : error LNK2001: unresolved external symbol "_sqrt" astro.obj : error LNK2001: unresolved external symbol "_tan" phoon.exe : error LNK1120: 8 unresolved externals
Which is not surprising as there is no FPU/Floating point math support in 196. I tried the December 1991 Pre-Release, but it failed for other reasons:
I did copy over BASE.DLL BASERTL.DLL CSR.DLL DBGDLL.DLL as it wanted, but despite the symbol being in the DLL it didn’t load.
So that’s why I’m using the libraries from the Win32s SDK.
Okay, so far now we have GCC 1.40 compiling to an old Xenix GAS assembler, and linking with Microsoft link from Visual C++ 1.0/2.0 era. The next step is to see if we can just link the objects under 196, and get a running EXE!
I have this tiny fibonacci example program, so with it compiled & assembled by GCC & GAS, I did the final link under 196, and YES it runs!
Possible things to do? GCC should be able to build itself, so it should be possible to build GCC and link that on 196 or December 1991, and get a native version of GCC on NT. The other possibility is to get newer versions of GCC (cc1 drop in replacements) to build for Xenix and / or OS/2. Obviously this Xenix linker is the gateway to older 386 Microsoft based products!
For those interested in such things, I’ve uploaded all of this to archive.org here: windows-nt-196-linking-and-running-gcc