The first thing I wanted was to install the Pre-Release onto a HPFS disk. I’ve uploaded this over on archive.org (Windows NT December 1991 prepped for Qemu). I took the CD-ROM image, removed all the MIPS stuff, built a boot floppy, and setup the paths so that the floppy can boot onto the secondary hard disk to a ‘full’ version of NT. This lets me format the C: drive as HPFS, and then do a selective install of Windows NT to ensure that that the software tools (compiler) are installed.
I use a specially patched vintage QEMU build, qemu-0.14.0.7z which kind of makes it ‘easier’, along with the needed disk images in dec-1991-prepped.7z
This will bring up the boot selection menu. The default option is fine, you can just hit enter.
NT will load up and you now have to login as the SYSTEM user. We need the advanced permissions to format the hard disk.
From the desktop we first format the C: drive as HPFS. I made icons for all this stuff to try to make it as easy as possible.
You’ll get asked to confirm you want to do this, and give the disk a creative name.
And with the disk formatted it’s time to start the setup process.
Who are you?
And what slick account do you want? It doesn’t matter tbh.
I’m going to do a custom install as the NIC’s aren’t supported, and even if they were it’s just NetBEUI anyways.
And select your hardware platform. NT basically only supports this config, so it doesn’t matter.
The default target drive is our C drive, which we had just formatted to HPFS.
Next, I unchecked everything only leaving the MS Tools
It’ll offer the samples & help files. I always install them as I eventually need examples of stuff to steal, and to learn that including <windows.h> won’t work right unless you manually define a -Di386 on the command line. I’m saving you this pain right now up front.
Files will copy, and on a modern machine this takes seconds.
And there we go!
And Windows NT is installed.
Yay.
I put in a ‘CAD’ feature in this Qemu hitting control+alt+d will send the familiar pattern, and after a few times NT will reboot. We are pretty much done with NT for the moment, but congrats you’ve installed the December 1991 Pre-release onto a HPFS disk for those sweet long long file names!
Going over the strategy:
I’ve already built GCC 1.40 for NT, so what is the rest of the stuff needed to build Linux? It’s a quick checklist but here goes, in no specific order:
GCC 1.40
bin86
binutils
gas 1.38
bison
unzip
zip
Luckily as part of building on Windows 10 using MinGW, I had fixed the weird file issues as MS-DOS/Windows NT/OS2 handle text/binary files, as we went through with how Github mangled MS-DOS 4.00.
The primary reason I wanted a working zip/unzip was to deal with long file names, and to auto convert text files. And this ended up being an incredible waste of time trying to get the ‘old’s code on the Info-Zip page.
I’m sure like everything else, the old versions are removed as they probably suffer from some catastrophic security issue with overflows. The issue I ran into is that the version 5 stuff uses so many features of shipping NT, to even 2000 that it was going to be a LOT of work to get this far. The quicker & easier path as always turned out to be a time machine.
Thankfully, since I had made a copy of the UTZOO archives, I was able to fish out, both version 3.1 from the archives. Also known as “Portable UnZIP 3.1”, parts 1/2/3. I also found version 4.1 as well. And people wonder why you want to save these ‘huge’ data sets. If the lawyers could have their way, they would obliterate all history.
I spent a lot of time messing with Makefiles, as linking & object conversion on old NT is a big deal, and not the kind of thing you want to do more than once. Another big pain is that large files become delete only. I don’t know what the deal with notepad is, but I could remove text, but not change or add. I solved that by wrapping a number of things by including it in another file with some #define work to go around it. Needless to say, that sucked.
One thing that constantly threw issues is that this version of Windows doesn’t handle Unix style signals. I removed all the signal catch/throw stuff, and the binaries ran fine. Why on earth does ‘strip’ need signals is beyond me, but it runs fine without them!
On Windows I just unzip the bin.zip file and leave source.0.zip intact into a directly say something like temp. Then I can use a cool feature of Qemu where it can mount a directory as a read-only FAT disk. This saves a lot of time!
Will drop to the bootloader. Hit enter to login, and you’ll be at the desktop. Hit enter again, and open a command prompt.
By default, the Numlock is messing with the arrow keys (I think it’s mapping to the old 83 key keyboard no matter what?) Hit num-lock and your arrow keys should kind of work. It’s a great time saver.
I copied the binaries & the ygcc.cmd file into the \bin directory, created a \proj directory and get ready to unzip all the source code. For some reason this version of unzip doesn’t understand the zip compression, so it’s just storing instead, much like TAR. It’s not that involved but unzip with the -d flag so it creates directories as needed.
This will let us keep long file names. HPFS is case insensitive, but it also preserves the case, so don’t worry about the names being all weird. It doesn’t matter.
One thing worth mentioning is that even though the C pre-processor does compile it just hangs when trying to run it. I’m not sure what is wrong exactly, but it’s just not worth fighting. Instead, I had the better idea, of using the Microsoft C compiler to pre-process the source. Apparently, this is how they originally built Windows NT, pre-processing on OS/2, then uploading the pre-processed files to a SUN workstation with the i860 compiler and downloading the objects to be converted & linked. Wow that must have been tedious!
I created a CMD file ‘ygcc.cmd’ to run the cl386 pre-processor, call CC1 & GAS and clean up afterwards.
Before you can build Linux, you need to create both a \tmp & \temp directory. Also the include files need to be copied to the \include directory to make the pre-processor happier.
I’ve tried to make this as simple as possible there is a ‘blind.cmd’ file which I built that’ll manually compile Linux. There is no error checking.
And saving everyone the excitement here is an animation of the build process
And there we go! All compiled!
From there it’s a matter of copying the Image file out of the VM, I used the boot floppy and 7zip’s ability to extract FAT images, and then boot up Qemu using the Image file as a ‘floppy’ as back in the day we used to rawrite these to floppy disks.
Time goes on, and things are lost, and it’d come up somewhere about actually building Linux from Windows, so I thought I’d show it off.
The one thing is that modern machines are just so fast, that it’s almost hard to believe that a 386DX 16 with 4MB of ram would struggle for seemingly hours, what an i7 can churn out in mere seconds.
Time sure flies!
It’s my usual ‘DO IT LIVE’ style, I tried to clean up the audio, but I lost the steps… One day I’ll try to script & build a PowerPoint so it’s more cohesive.
“If you want to make a pie from scratch, you must first create the universe”
–Carl Sagan
A little while ago I had touched briefly on the availability of of a PCC port to the 8086 done back in the early 1980’s that was hosted on VAX running 4.1BSD. I’d ruled it basically useless as you are restricted to 64kb .COM files, and I couldn’t get much of anything interesting running on it.
After all the fun of setting up NetManage Chameleon on Windows 3.0, over on IRC lys had mentioned I should try the MIT PC/IP stack. I never knew anything about it’s history but it became the first PC TCP/IP stack. You can read more about it from “Internaut“?
Dave Clark had gone to England for sabbatical and while he was there, had implemented TCP/IP in BCPL for the TRIPOS operating system, a predecessor of the Commodore AMIGA operating system. He brought the TCP/IP code back with him, and the Lab for Computer Science had a bunch of Xerox Alto workstations, and someone at LCS ported Dave’s TCP/IP to the Alto.
Then someone ported it to Version 6 UNIX and rewrote it in C. And that was what we took, and ported to PC DOS. At that point there were no C compilers that ran on the PC, and we were using a compiler that ran on a PDP 11/45 on Version 6 UNIX, and then on a VAX 750 running BSD v4.1. That was the AT&T; portable C compiler, and a group of people on the fourth floor of the LCS had written an 8088 code generator for it. This was before Microsoft C, and before 4.2 BSD.
Our first tasks were to bring up TFTP, TCP, and a telnet client under DOS. Several people were involved. Lou Konopelski did the initial TCP and telnet work, and Dave Brigham did similar work to what I did.
John Romkey – Internaut – How PC-IP Came to Be
What is even more notable about PCIP is that it’s the reason the whole ‘MIT License’ even exists, as word seems to have spread quickly about a TCP/IP stack for the IBM PC compatible market. Jerome Saltzer has documented it all, if you want to read about it (WARNING PDF!)
Romkey would even then go on to found FTP software in that wonderful pre public internet era, before the eternal September.
Of course, the one thing that stands out is that these files look tiny. They aren’t even compressed! PCC, or the Portable C Compiler was released from AT&T, itself written in C, to make porting the C compiler easier to further allow Unix to be further easily ported. Now that I kind of had a mission, I decided to take the 8086 PCC leap, again.
Get the time machine ready!
Thankfully I had that 4.1c BSD image still up on sourceforge, aptly named: 4.1c BSD.7z, so I could follow my old instructions to create the tap file and start working with 8086 C, going back from 1985. And in no time, I had re-built the compiler, and assembler up and running. But I wanted more, as much fun as 4.1BSD is, I wanted to run everything natively on Windows.
At this point I’d remembered that this setup is a bit odd in that the object files that the assembler produces are OMAGIC (type 0407) a.out files. Thinking back to my old project of building Ancient Linux on Windows using vintage tools, it also uses OMAGIC a.out files! It’s not that unexpected as the original GNU ld linker from 1986 has hooks for both old magic & new magic (OMAGIC/NMAGIC) files, as this would be consistent from the era. Thinking this was my out, I might have a way of migrating the build process off of the VAX.
The first pass was to try to pull in all the VAX includes into my native Visual C++ 1.0, and get it to build with Microsoft C/C++ 8.0. The next thing to do of course, is look for where it’s doing anything with binary files and make sure it’s all set to O_BINARY/”rb”/”wb” where appropriate as MS-DOS/Win32/OS2 all handle text files differently from binary data. There is also fights with mktemp along with procedure name creep, like how ’round’ wasn’t a thing in 1980 but it sure is by 1993! Before the era of the 486DX/68040/Pentium not everyone had a math co-processor (FPU) so it’d make sense that a lot of things wouldn’t have this by default.
As a quick refresher the following diagram may be specific to the GNU GCC compiler, but the older PCC compiler uses the same methodology of first pre-processing files, then compiling them, assembling the resulting compiler output, then finally linking to an executable program. ( See – “So it turns out GCC could have been available on Windows NT the entire time“)
The steps for PCC 8086 are quite similar but to spell them out:
Pre-process with GNU cpp
Compile with PCC’s c86
Assemble with PCC’s a86
Link with GNU’s ld
Extract the MS-DOS .COM file with cvt86
There isn’t much to talk about the pre-processor, so I’ll skip it, suffice to say from my cl386 research, and porting GCC to OS2/NT, it just worked.
Compiling the compiler
Surprisingly getting the compiler running wasn’t too difficult. I do remember getting this running before, so seeing it run again wasn’t too much of a surprise. The simple C program:
main(){
printf("hi from 8086 pcc\n");
}
Gives us the following generated assembly:
.data
.text
.globl _main
_main:
push bp
mov bp,sp
push si
push di
sub sp,#LF1
mov ax,#L14
push ax
call _printf
pop cx
L12:
lea sp,*-4(bp)
pop di
pop si
pop bp
ret
LF1 = 0
.data
L14:
.byte 104,105,32,102,114,111,109,32
.byte 56,48,56,54,32,112,99,99
.byte 10,0
So far, so good, right! Even for fun, I was able to build it using Microsoft C 6.0! I figured I may as well try to get as much out of that purchase as possible.
Strage binary formats in a strange world
One thing that was a constant problem for me is that the assembler generated garbage, it never gave me the a.out OMAGIC file. Opening it up in a hex editor, Hex Editor Neo, and it showed problem, right away.
The OMAGIC sequence in hex should be written down as 07 01, but when I looked at the output from my PC port the file was not only too big but it didn’t have the headder:
As you can see it’s just a bunch of zeros up front. Later on, I’d realize this was a ‘pad’ so it could be filled in later by the assembler when doing relocations. The file in question was rel.c which also should have been the hint. I don’t know if anyone saw it, but let me highlight it for you:
As you can see, where the file ends on the VAX, on the PC the OMAGIC header is being appended. I did a simple cut & paste in the editor, and the object file validated just fine. So why was this happening? In my rush to just add ‘binary’ flags to any file operations I had seen this in rel.c:
(dout = fopen(Rel_name, "a")) == NULL)
I had taken this be an ‘append’ for whatever reason it would need to do that kind of thing. Well maybe that’s what it means in 1993, but in 1981 it doesn’t append, instead it just opens it normally. Is this a bug in the assembler, or a feature of 4.1BSD? Without debugging it, I just modified it to not append, as this was the only occurrence of an explicit append in the source code I could see.
(dout = fopen(Rel_name, "wb")) == NULL)
And that did the trick, I now had a working assembler running on my PC!
But we are not out of the woods yet!
Naturally trying to build a much ‘larger’ Fibonacci program crashed the assembler. Luckily debugging it was a snap to find out that it was trying to free a static pointer. Or so I think. Today, in the future RAM is cheap, so I just commented out the offending free and now it was off to the linker.
When is advanced optimization a bad idea?
When the machine you wrote this for no longer exists. In the middle of the ld86 linker is this line:
asm("movc3 r8,(r11),(r7)");
I have no idea why it’s there.
I don’t know what it should be doing.
This makes the linker un-portable.
However, as I’d mentioned before I did have the GNU linker that I’d successfully used to build Linux kernels, so there was hope!
C:\msvc32s\proj86pcc>\aoutgcc\bin\ld.exe -X -r -o hi.out crt0.b hi.b ./libc.a
C:\msvc32s\proj86pcc>cvt86 hi.out hi.com
C:\msvc32s\proj86pcc>msdos hi.com
hello from pcc for 8086!
I had now successfully run my first program without using the VAX. Although I had not mentioned a step yet, cvt86, this utility is described as creating a MS-DOS .COM file from an a.out file. I didn’t look into how it accomplishes this, but basically, it’s another linker. And this issue would come to complicate things as I had thought that everything was working.
libc & the heart of C
Libc, is simply put the central C library for getting everything done. While crt0 will setup the C environment everything else core from opening files, writing to the screen, and reading keyboard input is done through libc. So I thought re-building libc would be easy enough. To build the library you ‘archive’ them with the ‘ar’ archiver, then index them with ‘ranlib’. And again, from my a.out adventures building Linux I had both tools, however no matter what I was doing they did not work with cvt86. I wen’t back and rebuilt some kernels to verify, and yes it does generate archives but cvt86 was not happy.
I still had the SIMH VAX running in case I needed it, so I just broke down and figured I’d just port the VAX ar/ranlib to the PC. I don’t know off hand what the problem was, and I didn’t feel like spending an eternity to try to correct it, and both of the programs are somewhat portable. But of course it wasn’t that simple as ar opens files in strange ways that work on 4.1BSD but yeild chaos on the PC.
‘ar’ has it’s own magic, a simple !<arch> and a ` followed by a new line. On any UNIX the \n is 10 in decimal 0xa in hex. But on the PC it’s carriage return and linefeed! And yes despite setting all the opens to binary, it was constantly injecting carriage returns & linefeeds all over the place! Some-how the file was being opened in text mode. Thankfully debugging even in old Visual C is great and inspecting the temporary files lead me to find this part:
f = creat(file, larbuf.lar_mode & 0777);
In a few places it uses the creat (or create call. In an interview Dennis Ritchie had mentioned that this was one of his regrets in life, not naming creat create) call, which of course never has to set a mode, as everything is binary in Unix, unlike the PC. Great.
Luckily the fix was very simple after every creat, simply set the file mode to binary.
setmode(f,O_BINARY);
Great!
Apple pie!
Now I could re-build libc from source and link it to the Fibonacci program!
Yes it’d take this long to get to the point where I can now easily edit file on a modern machine and have a Win32 native toolchain! VAX no longer required! We’ve successfully extracted everything we needed from the 1980’s!
First contact!
Now it’s time to look at what brought us on this adventure, MIT PC/IP.
The MIT PC/IP (PCIP) does require changes to the libc to work correctly. Unfortunately, they didn’t provide the full copy of the libc, but rather some ed scripts. So, the first question is do I even have the version these are based off of to start? I don’t know, so I had guessed, and I had guessed incorrectly.
Configuring PCIP is somewhat involved, first you need MS-DOS 2.00 or greater which thankfully in the future is FREE! The next thing you need is a 3com 3c501 card. This is going to be a challenge but it’s just as any good time to mention 86box, and the latest version that I’ve been using that of course supports this kind of setup!
I can’t recommend it enough, 86box is like all the inconveniences of old software & hardware without having to spend a fortune for weird combinations, fighting to have space for it. I naturally setup a 386sx with CGA, 20Mb hard disk and a 3c501 card. It’s nice being able to also be very task specific, this doesn’t have to be a DooM/Quake machine!
the first thing you need to do is add the netdev.sys device driver that is created as part of building PCIP from source. a simple:
DEVICE=\NETDEV.SYS
in your config.sys is more than enough. However, how do you configure it? Well it’s the ‘custom’ program that binary edit’s the device driver.
YES, IT EDITS THE DEVICE DRIVER.
Setting stuff up is somewhat straight forward, however it displays TCP/IP information in decimal not in hex. I haven’t even looked into the how or why.
The first page options are kind of banal, it’s back in the day when people would use finger to find people on the internet and call them up or send emails. How cute. 1985 was a different world!
In the hardware options the only thing to check is the I/O base, IRQ & DMA for the Ethernet card. I just configured the card around 0x300/5/1 as it’s great being purpose built!
There is a separate window for telnet options. Naturally high speed connections frame far too fast for something built from 1985. I found lowering the TCP windows really helped with pacing.
As I had mentioned the site configuration displays all the information in decimal. Also, I’m wasn’t sure what is going on with the netmask, but looking at the old Windows calculator revealed it was being stored in OCTAL. It’s probably why the addresses have commas instead of periods. Although I had configured DNS it didn’t work, as it uses UDP port 42. It’s clearly doing something very early 1980’s.
So close and yet so far away. The only thing to do was try to figure out which of the libc stuff was ‘newest’ in the pure state and try to figure out where to go from there.
Redo!
While I had not configured the libc correctly, I had partial success! I could actually establish a telnet session! Libc wasn’t working correctly as every line feed didn’t generate a carriage return, as you’d need for MS-DOS leaving it with broken status.
But at the same time, despite all the weird ‘challenges’ for the most part ‘it just worked’. Which is pretty cool!
It turns out the answer was the 8086_C_19850820 file. As far as I can tell there was only one thing that didn’t patch correctly but I was able to build a libc in no time.. that didn’t work. In the patch it removes ulrem/auldiv from arith.a86 Not sure why, I haven’t messed with it. But that means I had to restructure to build with the non floating point n86c compiler as that’s the way PCIP is expected to be built. After rebuilding with this compiler and this seemingly properly patched library I finally had it working!
Instead of a garbled mess, I had something I could read!
Now instead of a garbled mess, I can see it was trying to display the connected IP, and a clock.
Sadly it doesn’t work with SLiRP. I’m sure it’s either classful routing or it really doesn’t like how SLiRP handles ARP. I suspect it’s also trying to do old style classful routing as well, which means you can’t just load arbitrary subnet masks wherever you want, to try to squeeze the 4 billion IP’s out of the internet.
Final thoughts
I suspect that although there were binaries in the above tar files, going through the effort to rebuild PCIP really wasn’t all that expected for most people to carry out. Sadly, there was no shared source ‘sites’ online, and we’re lucky enough someone kept a few tarballs lying around. I really can’t blame them for sticking with then current development tools, especially for what you’d need to build a C compiler back in the early 80’s. It’s a shame the QL or the Macintosh didn’t have the RAM or the DASD capacity to become that home cross compiler of the 80’s.
Most project just require you to work on that actual project, while this has been a substantially larger undertaking from anything normal, but I guess I’ve learned a bit along the way with all those “pointless” GCC port things I’d done, well it turns out they are incredibly useful! It’s been a fun archeological expedition for me, thankfully C is still a thing, I wonder what happened to all the ADA/Perl/Pascal/”Wave of the future” stuff that is always disappearing. At least more and more people work on full system emulation so there is always that!
For anyone that curious you can find all the code over on github:
I know what you are going to think, that it was already done, and it was called EMX. Or was it GCC/2? Well sure but what if you are not running the GA (General availability) version of OS/2. For example, years ago I had managed to get Citrix Multiuser 2.0, and it’s not at the GA level. All that is available is some ancient beta version of Microsoft C 5.2 from 1989?!
A little while back I had worked on getting GCC to build and run on the FPU enabled versions of Windows NT from 1991. I had mentioned that it turns out thanks to the Xenix assembler, that GCC had been basically available the entire time Windows NT had been available, but lamented that since the OS/2 compiler is 16/32bit, the 5.2 compiler couldn’t handle compiling GCC without blowing it’s heap. 16bit issues in a 64bit world.
However after doing some research on all the early cl386 compilers I could get my hands on, including the Windows NT Pre-release ones, I’d noticed that if I built CC1.EXE (the actual compiler) first for Win32, then rebuilt those object files with the December NT Pre-release compiler, that some versions of LINK386 from the OS/2 2.1 DDK would actually link with them. And sure enough it worked!
I have to admit I was pretty amazed, I had managed to ‘cross compile’ GCC using quite the tool chain.
First the compiler from the December NT Pre-release CD-ROM is shipped as a 16bit OS/2 compiler, but I’m using Windows 11. First I use the MS-DOS player with a quick fix from crazyc to allow Phar Lap 286|Dos Extender to run, which provides a basic enough OS/2 emulation to allow the compiler to run under ‘dos’. The linker on the DDK suffers the same fate as far as it also being 16bit. However the combination of MS-DOS player & Phar Lap gets stuff working! The only weird catch is that the 386 emulator causes strange floating point related crashes, while the 286 or 486 emulators work fine.
Now targeting OS/2 or running on OS/2 isn’t all that new, but building it from a Microsoft C compiler is. And now of course you’ll ask yourself, who cares? why is it interesting?
Well, the vast majority of the GCC ports to OS/2 don’t support the OMF object file binary standard, instead they used the much outdated a.out format, and rely on tools to convert the objects if needed. Additionally, they have DLL dependencies, and other startup issues with things needing to be setup. And of course they rely on a binary standard that is ‘GA’. *HOWEVER* by using a Microsoft compiler, I have OMF object files that the OS/2 built in system linker LINK386 can understand. So in plain English I can just relink the compiler and it’ll run on a new ‘version’ of unsupported OS/2.
I made a diskette image with my objects & a linker script and in a few moments I had it running!
The substantial thing here is that the binary format for OS/2 changed twice, and each release introduced changes that broke binary compatibility, in an effort to force people onto the new tools. So there is no way that the old ‘LE’ format would ever work. And you can see it’s running! In addition I could take the same object files, and copy them to my Citrix server, and likewise it was just a matter of linking, and it too now has GCC!
One annoying thing is that the LINK386 that ships with OS/2 2.00 GA doesn’t like the output of the Xenix assembler, so I built the a.out traditional assembler, and the emxomf tool to convert the a.out to OMF, and that worked well.
I still have much to mess with, including the pre-processor & main ‘gcc’ program. I have not built anything beyond a trivial program, so there is indeed much more work to be done before I can even try anything challenging. Some programs like emxomf have portions in the debug support that require the ‘long long’ type, which obviously Microsoft compilers from 1989-1991 don’t have, so I’ll have to re-build them with GCC.
Not that I can imagine anyone wanting to try but I’ve uploaded some disks with the objects. Copy them to a hard drive, and run the ‘build.cmd’ command and it’ll link to a native freestanding executable.
I’ll explain it more with a post later, along with going over all the versions of cl386 I’ve acquired, over the years in more of a part 2: Targeting OS/2 with Visual Studio 2003!
This is going to be a bit convoluted but here goes.. GCC isn’t a monolithic compiler, instead it’s various parts are separate programs. This lets us tackle it one part at a time. And/Or bypass a lot of it until I want to tackle it.
I’m sure many people have explained this far better than I ever could but in C you write source files (obviously), the pre-processor reads those and ‘header’ files that describe interfaces to libraries, other objects, various macros and definitions (magical numbers) and the pre-processor will read those files, and do simple macro expansion and test insert/replacements to generate a single .i file at the end of it’s run.
The C compiler (cc1) now reads that single .i file and translates it into native assembly. This allows for ‘mid/high level’ aspects of C to be machine independent (portable) but now will be written into a very system dependant assembly file, the single .S file. One thing of note is that so far everything is text files. You can edit the assembly file as you would any document, or even further ‘process’ it if needed/wanted.
The assembler ax386 (GAS) will then read the single assembly file and write a a binary object file hi.OBJ. There typically isn’t all that much to be said about assemblers although fancier ones allow for really strong Macro capabilities like Microsoft MASM.
From here on, it’s all binary objects!
The linker then takes your object files, and links them together with other system objects and system libraries into an executable, in this case. Linkers can build all kinds of other things, but for now we’re just pretending its static C compilation like it’s the 1970’s.
At it’s heart GCC processes text files.
The first part in this insane experiment, is to build GCC 1.40 with Microsoft Visual C++ 1.0. Surprisingly it didn’t take an insane amount of messing with stuff, and I got an executable! But everything it compiled failed to assemble. Looking at this fragment, even if you don’t know i386 assembly you might spot the error:
main:
pushl %ebp
a b,c
pushl %esi
pushl %ebx
Yeah, it’s the “a b,c” part. Those are NOT valid i386 opcodes!
Just because it compiled didn’t mean it actually worked.
I used MinGW to build the same source, same Makefile, and I got a working executable. Annoyed I started compiling random files with Microsoft C, and finally found the file that broke it all, it turned out to be insn-output.c needing to be compiled with the “/D__STDC__” flags. A quick modification of the Makefile and now I have a working CC1!
Okay, great, it’s well known back in the early dangerous ages of the 1980’s/1990’s that everyone wasn’t running Linux, nor were binary distributions of GCC that far spread, rather I think to re-enforce the source was available it was expected that you’d use your system compiler. Systems like DJGPP/EMX take the path of binding a.out object files into something that MS-DOS can run via a dos extender, or the bind utility to allow you to run the a.out on OS/2. What I wan’t to do is verify that in fact Windows NT was a viable host for GCC back in the public pre-releases of 1991.
I’m sticking with the December build 239 version as it has working floating point. Something that GCC has intrinsic support of, and I don’t feel like trying to work out emulation.
The next step is to try to build it with the family mode-OS/2 version of the C compiler, which of course lead to the real issue of this 16bit hosted cross compiler:
cl386 /u /Od /Ic:\MSVC32S\C386\INCLUDE /I. /Iconfig /c combine.c
Microsoft (R) Microsoft 386 C Compiler. Version 1.00.075
Copyright (c) Microsoft Corp 1984-1989. All rights reserved.
combine.c
combine.c(1734) : fatal error C1002: compiler is out of heap space in Pass 2
NMAKE : fatal error U1077: 'C:\WINDOWS\system32\cmd.exe' : return code '0x2'
Stop.
Very frustrating. I tried mixing and matching from Visual C++ 1.0 & this old compiler, and while it did compile, it doesn’t run. does it mean anything?!
I should point out that this should be an expected working configuration as GCC does build on Xenix using the 32bit Microsoft C 5.1/386 compiler. Furthered again that Xenix and these 1991 versions of NT use the same 32bit OMF object format. And expanding on the Xenixnt experiment using the Xenix’ified GAS assembler with old Visual C++ includes & libraries to produce a possible retro-early port of GCC to NT, the next move is to bulid GAS on NT.
GAS gave me some weird issues with ctype.h where it runs fine with the one from Visual C++ 1.0 but the OS/2 & NT pre-release both fail. However the old Pre-release compiler cannot deal with the much newer ctype include file. So after much hammering I amputated whatever was bothering it, and it’s just enough to build & run. Great!
Going back to the phases, I used a simple hello world program:
void main() {
printf("Hello World!\n");
}
While not being a good program, it doesn’t include stdio.h, nor does it return anything. It’s terrible. But in this case it allows me to be lazy and sidestep the pre-processor cpp.exe. This way I can just directly run it through cc1 and get my assembler file hi.S
Next I pass it to ax386 (GAS) and get the resulting object file hi.OBJ
And finally link it with link.exe in this case.
And with all the drama I’ve now compiled a simple hello world program on Windows NT.
If it were 1991, I would hollow out gcc.c so it doesn’t use signals or forks to invoke the needed phases, and of course build the pre-processor. In addition, libgcc needs to be compiled to allow for floating point operations to work correctly. None of which is impossible, although I’m not sure it’s all that needed as it isn’t 1991.
With a little bit more work, I got the floating point support to compile, which relies on both a working ‘native’ compiler, and a working GCC to compile the 2nd half. I usually use phoon, or Phases of the Moon, to test floating point, and as you can see, it’s working!
I’m not sure if there was a 32bit version of Microsoft C/386 available for Microsoft OS/2 2.00 betas. Also, I don’t know if the Microsoft link386 for OS/2 can also link Xenix 386 object files? Would it have been possible to bootstrap GCC/GAS on Microsoft OS/2 2.00? I really don’t know, and as of this writing no versions of the old Microsoft OS/2 2.00 betas have surfaced.
** update from the future, turns out that I found a way to convince the cl386 compilers from the NT Pre-Releases in 1991 to re-build an existing GCC that was built for NT. The catch is the linker, LINK386 of course, as the format was constantly changing. However the object files are fine, and I was able to just copy them over on diskette and re-link the compiler. It even ran. It’s not tested at all, so it turns out the 1989 compiler wasn’t good enough, but the 1991 was.
It’s interesting to me to see that even before GCC 2.6, that vintage versions from 1991 would compile and run directly on Windows NT.
On the heels of discovering BOW, I thought I’d try to make a cross compiler. Attempts at running binaries on *BSD systems had mixed results, although I thought it was interesting that my old Linux a.out cross compiler can generate object files BOW can happily link, although anything more complicated resulted in disaster. As part of that project I had build a 386BSD 0.1 cross so I figured that’d be worth a shot.
And it worked!
Sor for the two or three people who care here we go!
I’ve broken this up into each of GCC’s phases (programs) so that I can inspect the output of each as I go. This also lets me control exactly what gets passed where. And in this case forces the use of the 80387 where/when needed. It’s also nice to see where and what gets pulled in by the C pre-processor what magical numbers are set, and of course to see how the calling conventions work in the resulting assembler file. While I had built this around the idea of cross compiling the 386BSD 0.1 kernel, it’s still fascinating to me that it can be hammered into making BOW compatible executables. Although I didn’t update the CPP flags, no doubt I probably should as the headers probably expect something more FreeBSD.
Running make yields:
C:\bow\src\hello>make
cpp -I../../include -v -undef -D__GNUC__ -Dunix -Di386 -D__unix__ -D__i386__ -D__386BSD__ hi.c hi.i
GNU CPP version 1.39
cc1 hi.i -quiet -O -m80387 -version -o hi.S
GNU C version 1.39 (80386, BSD syntax) compiled by GNU C version 7.1.0.
default target switches: -m80387
a386 hi.S -o hi.obj
ld -o hi ../../lib/crt0.o hi.obj -L..\..\lib -lc -lgnulib -lm
C:\bow\src\hello>size hi
text data bss dec hex
24576 4096 0 28672 7000
C:\bow\src\hello>wsl file hi
hi: a.out little-endian 32-bit demand paged pure executable not stripped
C:\bow\src\hello>
It should also probably be worth mentioning that the linage of BOW has to be in the dark days of the AT&T v CSRG/BSDi lawsuit as this toolchain does produce binaries that run, unlike the 1.0 phase of both NetBSD/FreeBSD where they dumped all the prior code and forked harder from the common 386BSD 0.1 that we all loved.
Simple Fibonacci sequence
My favorite ZIP interpreter
Now this one is interesting it’s a NS32016 emulator! I left the ns32016 cross in the data directory if you want to generate the data file. I was surprised it worked, but wow!
Phases of the moon may not seem all that exciting at first, but the big thing is the handling of the math coprocessor, and of course to be sure to link against the BOW libm. Otherwise it just hangs the system.
And of course the old TREK game from Unix lore.
I would imagine that a newer version of GCC or at least CC1 should be easy enough to build, and of course cross compiling gives you an out of the 16MB RAM limit that WINMEM32 imposes.
The biggest WTF I had was for Hack 1.03. I’m not sure why it didn’t want to link, but rest assured, the cross compiled objects just linked fine. I don’t know.
In other BOW news I have been in contact with the author, I don’t want to bother him too much but I’ll try to glean a lot more info from him.
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 andanOpenNT 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.
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:
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:
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!
While on discord the topic came up of why there is no good/free C compiler for MS-DOS. Oh sure there is OpenWatcom but the 2 heavy hitters of the era, Microsoft C & Borland C are not open in the slightest.
There is DeSmet C, although it’s source is full of unnamed structs meaning that building it with anything sane would require a ‘lot of work ™’ which of course is not what I’m all that about. Instead, I remembered a directory up on TUHS /Applications/Portable_CC with a zip file 8086.zip Although this is a zip file, you’ll want to unzip on something Unix-y as there is a lot of case duplicate files. That said this is a PCC port to the 8086, which includes a libc, 8087 support, and is all expected to be built on a VAX-11/780 running 4.1BSD. Now this ended up being a stumbling block because I tried a *LOT* of things thinking that they were upwards compatible with 4.1, and the answer is USE 4.1!
So to effectively get going you’ll need a SIMH VAX780 and just follow my old steps on Installing 4.1BSD. As far as the zip file, I used Linux but had to create a tar file specifying the Unix v7 format with:
With that said it’s a very strange setup as it relies on the 4.1BSD Vax environment so much that there is assembly injected into the linker.
asm("movc3 r8,(r11),(r7)");
So this will not cleanly run. Just as it depends on many system a.out specifics on building for MS-DOS. It’s not so much a MS-DOS tool chain, rather it outputs to vax a.out and uses a slightly modified vax linker. The MS-DOS magic happens in the conversion of the final a.out into a com file.
That is right it’s a VAX specific cross compiler that only build’s COM files.
I’ve managed to build some trivial stuff, and they work. Sadly my attempt at building that InfoTaskforce of ’87 failed.
I haven’t dug that much further into the linker although I have to wonder if a GNU cross linker to make a.out could make something that the conversion program would be happy with. The assembler of course doesn’t work, perhaps it’s something with packing structs?
As always, the simple stuff looks trivial but it was a fair bit involved.
Since there is no real ‘cc’ it’s a script but the vauge steps are:
This is a bit cheating, but I broke down and did some dump/exports of a building system to get the file layout. Since the MiG phase was totally done native, I didn’t bother with that, or trying to ‘fix’ the nested Makefiles, rather I just dumped the output and worked with that. I guess I could make my own Makefile but for now it’s a stupid script. I used the a.out build tools for Linux as the objects are all the same anyways.
cd mach25-X113\obj\STD+WS-afs-nfs
build-gcc258.cmd
Since there is no Makefile it won’t run in parallel. About 20 seconds or so later you’ll get a linked ‘a.out’ but it won’t run. The script xport.cmd is rigged for me and Qemu 0.10.5 to create a tar file to extract inside of mach and perform the native link.
Obviously this means you can us modern UI’s tools and everything else as you are now on the outside! If you can force your build to use my ancient tools you can even do the build. Nice!
Doing a rebuild of the kernel in Qemu the 2.5.8 -O2 build shaved a whole second off the build, so yes it actually did something.
Things to do would be cross linking, fixing the drivers that don’t build, and probably improving stuff like bigger disks, filesystems and memory… or networking!.. .but that’s all too complicated for me!