Porting GCC to 32bit OS/2

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!

First life of GCC on OS/2

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!

GCC running on OS/2 2.00 6.123

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!

Converting a.out to OMF via emxomf

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.

Ive been putting my ports onto github (cl386-research) as it handles the rapid changes well enough. It’s a bit of a complex setup and it involves using a build system that I’ve put over on archive.org here: cl386-research-v2.

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!

So it turns out GCC could have been available on Windows NT the entire time!

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.

Flow of GCC

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?!

GCC 1.40 compiled by Microsoft 386 C Compiler. Version 1.00.075

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.

Xenix GAS 1.38 compiled by by Microsoft 386 C Compiler. Version 1.00.075

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.

Hello World from GCC 1.40 on NT!

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.

phoon

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.

GCC 1.40 on OS/2 2.00 beta 6.123

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.

I uploaded the source on github, along with some binaries.

Cross compiling to BSD on Windows (BOW) from Win32

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!

bow-win32-cross.7z

I’ve been using DOSBox as it makes shuffling files through the dfs much more easier to test stuff.

hello world

First a simple hello world

I should break down how to build this with a super involved and unnecessarily complicated Makefile!

# File: Makefile (unix version)

CC = gcc
CC1 = cc1
AS = a386
CPP = cpp

LD = ld

EXE = hi

OBJ =	 hi.obj


CFLAGS= -O -m80387
INCLUDES = -I../../include
CPPFLAGS = -v -undef -D__GNUC__ -Dunix -Di386 -D__unix__ -D__i386__ -D__386BSD__
LIBS = -L..\..\lib -lc -lgnulib -lm
CRT0 = ../../lib/crt0.o

$(EXE): $(OBJ)
	$(LD) -o $(EXE) $(CRT0) $(OBJ) $(LIBS)

%.obj: %.c
	$(CPP) $(INCLUDES) $(CPPFLAGS) $< $*.i
	$(CC1) $*.i -quiet $(CFLAGS) -version -o $*.S
	$(AS) $*.S -o $*.obj


clean:
	@rm $(EXE) $(OBJ) *.S *.i

dclean:
	@rm /f $(OBJ) *.S *.i

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.

fib

Simple Fibonacci sequence

Inform ’87 interpreter

My favorite ZIP interpreter

NS32016 emulator

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!

phoon

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.

trek

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.

Since people were asking for xMach binaries

xMach doing it’s Linux calibration

Since binaries had been requested, along with the old elf cross compiler I thought I’d try that new fangled github binary releases.

This is just taken from old artifacts from the old Building OSkit & xMach adventures.

I had made a vmdk, MachUK22-lites.vmdk.7z as well, not sure if that helps anyone.

Fun with Windows ePDK NT 3.1 build 196 & Some Xenix fun!

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
CC1=cc1
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
        $(CC1) $*.i -quiet $(CFLAGS) -version -o $*.S
        $(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!

I then built the ’87 InfoTaskForce, Infocom interpreter, and it was just a simple link, and it’s running!

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

On the trail of PCC for the 8086

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:

tar --format=v7 -cf pcc.tar .

And of course, convert the tar file to a simh tap file. Or if you are like me, just download a tap file here: PCC-Machines.tap.bz2.

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:

/lib/cpp -I/usr/src/pcc/Machines/8086/lib86/include hi.c hi.i
/usr/src/pcc/Machines/8086/c86/c86 < hi.i > hi.a86
/usr/src/pcc/Machines/8086/a86/a86 hi
/usr/src/pcc/Machines/8086/a86/ld86 -X -N -r -o hi.out /usr/src/pcc/Machines/8086/lib86/crt0.b hi.b /usr/src/pcc/Machines/8086/lib86/libc.a 
/usr/src/pcc/Machines/8086/a86/cvt86 hi.out hi.com

It kind of makes sense.

Seems like somehow a lost opportunity in of itself back in the day

Compiling Mach under Windows

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.

So yeah, download and extract Ancient Linux on Windows, grab and extract mach25-X113-noblock.zip, and then run:

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!

32016 stand alone planetfall!

InfoTaskForce’87 running on a simple NS32016 emulator

What is it?

It sure may not look like much but it was an adventure getting here.

First, what is it? Well it’s the very simple NS32016 from here, with a few minor changes. I expanded the RAM from 256kb to a whopping 8MB. Then I added simple character I/O allowing me to print messages to the screen. Next looking at the toolchain page, I used my old Linux to Windows GCC 4 cross compiler to build the appropriate Canadian cross compiler to the NS3216.

Building the tools

A while back, I had built a cross compiler from Linux to Windows using GCC 4.1 as the basis as it was the last version that didn’t have massive external dependencies. NS32016 support was dropped some time in the late 3.x or early 4.x GCC so it means we need to go old anyways. I arbitrary picked GCC 2.8.1 for this build, while using the recommended Binutils 2.27

I cheated and just downloaded my existing linux-minw32.7z cross compiler as I didn’t feel like rebuilding everything again, although it is all in the Building a MIPS Compiler for Windows on a Linux VM! article. I also used an old Linux to Linux i586 32bit compiler (back from the OSKit build!) although you can use your hosts as well.

configuring Binutils is pretty simple like this:

./configure --prefix=/cross --target=ns32k-pc532-netbsd --host=i686-mingw32 --build=i586-linux

You can try omitting the –build portion, Debian GNU Linux 10 seemed okay with Gcc 8 as the default system compiler.

configuring GCC 2.8.1 was pretty similar:

./configure --target=ns32k-pc532-netbsd --prefix=/cross --disable-libssp --build=i586-linux --host=i686-mingw32

GCC 2.8.1 doesn’t quite know what we are doing so there is some flags we need to run off in auto-config.h namely

  • #define HAVE_BCMP 1
  • #define HAVE_BCOPY 1
  • #define HAVE_BZERO 1
  • #define HAVE_INDEX 1
  • #define HAVE_KILL 1
  • #define HAVE_RINDEX 1
  • #define HAVE_SYS_RESOURCE_H 1
  • #define HAVE_SYS_TIMES_H 1

You can just comment them out, or remove those lines all together.

When it came to building GCC, I did run into issues with GCC 7/8 trying to build GCC 2.8.1. I found it much easier to either have that Linux 4.1 compiler, or if you have access to Wine or WSL you can just run the Win32 binaries for the gen phases.

./configure --prefix=/cross --target=ns32k-pc532-netbsd --host=i686-mingw32
make CC=i686-mingw32-gcc xgcc cccp cc1 cc1obj

If you can run your own Win32 exe’s on Linux it’ll run just fine using the Linux to Windows GCC 4 cross. Otherwise you will need to either patch GCC or make your own GCC 4 hosted Linux to Linux cross compiler like this:

make CC=i686-mingw32-gcc HOST_CC=i586-linux-gcc xgcc cccp cc1 cc1obj

Hopefully that worked enough, and now you have your cross compiler. Now it’s time to build libgcc1.a

cp cccp cpp.exe
cp cc1 cc1.exe
cp xgcc xgcc.exe
cp ../binutils-2.27/gas/.libs/as-new.exe as.exe
cp ../binutils-2.27/binutils/.libs/ar.exe ar
cp ../binutils-2.27/binutils/.libs/ranlib.exe ranlib
make libgcc1.a TARGET_TOOLPREFIX="./" OLDCC=./xgcc.exe

Again you really want to be able to run the resulting programs on Linux but I guess you could script it out. Naturally if you wanted to just use Linux, it’d be easier to make that cross compiler directly, although I’m not sure how much of GCC 2.8.1 I want to fight, or just get GCC 4 running on Linux and use that to port.

crt0, somewhere for C to start

As mentioned a crt0.s is missing but there was enough inspiration to come up with this:

#NO_APP
gcc_compiled.:
.text
        .align 1
.globl _start
_start:
        enter [],0
#APP
#       setting the stack 256k under 8MB
        lprd sp,0x7c0000
        jsr _main
#NO_APP
L1:
        exit []
#       setting the stack 256k under 8MB
        lprd sp,0x7c0000
        bpt
        .align 1

#does nothing
.globl ___main
___main:
        ret 0

.globl _exit
_exit:
        bpt
        ret 0

I used a bit of the C example, and added some hooks that GCC was expecting namely a __main call that is made from main before it does anything (a place to init memory perhaps?), a place to catch an explicit exit call, along with setting the stack of course.

Patching InfoTaskForce without malloc / disk access

It’s not going to win any awards, but it was really great to get it to run a simple program written in GCC. Looking for something more fun, I took the old InfoTaskForce interpreter from ’87, and dug up my modification to run on cisco routers, and cooked up this version, that adds enough of printf from Linux, a bogus malloc that just allocates from a fixed memory array (otherwise you have to actually know about your platform), and a fun trick with later binutils where you can import a binary file directly as an object!

Neat!

Since I don’t have any file I/O being able to have the game data in RAM is crucial. I tried to tweak it so you could build the same working thing on Windows (maybe others?).

So for anyone who wants to look at the standalone adventure Win32 hosted tools are here, although the emulator should be somewhat portable.

Cross compiling to AIX: or missing shr.o

I was inspired by NCommander’s MinGW to Solaris cross compiler so I thought I’d dig out the one that got me started decades ago, cross compiling to the RS/6000 from Linux some time back in 1993. For this experiment I was able to beg/borrow a copy of /usr/lib & /usr/include from AIX 3.2.5 and wanted to use that as a base. I decided to use GCC 2.7.2.2 and Binutils 2.11.2 as these were old enough t build somewhat easy enough from MinGW/MSYS 1, but I figured they also had the best luck of being able to parse the headers without needing ‘fixinc’.

I was able to build both binutils and GCC with this simple incanation

sh configure --target=ppc-ibm-aix325 --prefix=/aix3

One weird thing was that binutils completely sidestepped ld, so I had to configure that manually like this:

--target=powerpc-ibm-aix --prefix=/aix3

Also ‘eaixppc.c’ didn’t generate properly I had to rebuild binutils from Linux to get it to pick up and build that file, copy that back in to get a working cross linker. Older stuff has some issues with CR/LF from time to time, and sometimes it’s easier to deal with builds from other systems and pluck files as needed.

Surprisingly things built, and transferring the to my Qemu AIX image gave me this fun error:

exec(): 0509-036 Cannot load program /cdrom/demo/hello/hello because of the following errors:
0509-150 Dependent module libc.a(shr.o) could not be loaded.
0509-022 Cannot load module libc.a(shr.o).
0509-026 System error: A file or directory in the path name does not exist.

Surprisingly IBM has a fix!

# export LIBPATH=$LIBPATH:/usr/lib
# /cdrom/demo/hello/hello
hello world, compiled by GCC 2.7.2.2!
#

Amazing.

Of course it’s not all sunshine and rainbows, bigger programs like the ‘87 Infocom interpreter bomb like this:

C:\aix3\demo\infocom>gcc -v -o infocom file.o funcs.o infocom.o init.o input.o interp.o io.o jump.o object.o options.o page.o print.o property.o support.o variable.o term.o
gcc version 2.7.2.2
ld -T512 -H512 -btextro -bhalt:4 -bnodelcsect -o infocom /aix3/lib/crt0.o -L/aix3/lib file.o funcs.o infocom.o init.o input.o interp.o io.o jump.o object.o options.o page.o print.o property.o support.o variable.o term.o /aix3/lib/libgcc.a -lc /aix3/lib/libgcc.a
ld: section .data [0000000000000000 -> 00000000000007ff] overlaps section .text [0000000000000200 -> 0000000000009b0b]
ld: section .loader [0000000000000000 -> 00000000000014a8] overlaps section .text [0000000000000200 -> 0000000000009b0b]
gcc: Internal compiler error: program ld got fatal signal 1

Initially I thought this was a problem with the GCC Linker, but after copying the objects to Qemu, and linking from there, I found out that the GNAT gcc driver calls the linker in a different manner:

ld -bpT:0x10000000 -bpD:0x20000000 -btextro -bnodelcsect -o infocom /aix3/lib/crt0.o file.o funcs.o infocom.o init.o input.o interp.o io.o jump.o object.o options.o page.o print.o property.o support.o variable.o term.o /aix3/lib/libgcc.a /aix3/lib/libc.a /aix3/lib/libgcc.a

Reformatted for my cross, but this produces a running executable.

And finally phoon which heavily relies on floating point math:

C:\aix3\demo\phoon>ld -bpT:0x10000000 -bpD:0x20000000 -btextro -bnodelcsect -o phoon /aix3/lib/crt0.o phoon.o date_parse.o astro.o /aix3/lib/libc.a /aix3/lib/libgcc.a /aix3/lib/libm.a
/aix3/lib/libm.a(atan2.o)(.pr+0x308):atan2.c: undefined reference to __itrunc' /aix3/lib/libm.a(atan2.o)(.pr+0x33c):atan2.c: undefined reference to__itrunc'
/aix3/lib/libm.a(atan2.o)(.pr+0x3c4):atan2.c: undefined reference to `__itrunc'

I thought first I could just tack -lm onto the end. However remembering years ago, linkers ARE position dependent, and on AIX libm must come before libc.

C:\aix3\demo\phoon>make
ld -bpT:0x10000000 -bpD:0x20000000 -btextro -bnodelcsect -o phoon /aix3/lib/crt0.o phoon.o date_parse.o astro.o /aix3/lib/libm.a /aix3/lib/libgcc.a /aix3/lib/libc.a

And yep it runs!

Sadly networking is a bit goofed on 4.3.3, and Im unable to upload more than a few hundred bytes before a stall on the console so slip/ppp would be a bit useless.

Speaking of useless, if anyone is crazy enough, you can follow here: MinGW-AIX325.7z

GCC 2.5.8 failure on 32bit ARM

This is really nothing more than a placeholder for me… Unless someone else knows the answer, then it’s really ‘how not to cross compile GCC’.

First I’m using the EMX’ified version of GCC from my MinGW to EMX cross. It didn’t require that much massaging to get it to build, the usual unzip as ascii to convert text, and in no time I can build cc1.


root@pinepro:/src/emx/src/gcc-2.5.8# file cc1
cc1: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=42c0c8de7175edade7614dc92d5d13e4421e0e6f, with debug_info, not stripped

and it crashes in what has to be a 2020 most unfortunte name

Reading symbols from cc1...done.
(gdb) r
Starting program: /src/emx/src/gcc-2.5.8/cc1 

Program received signal SIGSEGV, Segmentation fault.
0x004f6b84 in rtx_cost (x=<error reading variable: Cannot access memory at address 0xff7efff0>, 
    outer_code=<error reading variable: Cannot access memory at address 0xff7effec>) at cse.c:667
667     {
(gdb) 

Yes, it really crashes in rtx_cost. Good thing there isn’t a super popular card from Nvidia that is currently being short squeezed by crypto miners right now called the RTX where everyone is looking for a good price. 😐

I had then been thinking perhaps it’s because I’m using GCC 8.3.0, maybe it’s introducing some new and exciting bug? So I cross compiled GCC 4.1.2 as follows:

./configure --target=armeb-linux --host=armeb-linux --build=armeb-linux

Keeping in mind that my knowledge of ARM is pretty much nill, especially on Linux. The compile went mostly okay, just have to remember the gnu inline macro’s as needed from back in the day (-fgnu89-inline) and while it builds, it is insisting on using collect2 which of course is screwing things up. And of course I don’t want it as my system compiler. As a hack I found system gcc 8 can link things fine as I didn’t want to spend all day messing with GCC/collect2

I copied xgcc, cc1 and cpp from 4.1.2 into a /412 directory, and rebuilt 2.5.8 with the following shell:

make CC="/412/xgcc -B/412 -g -O0  -I. \
-I./config \
-I/usr/lib/gcc/arm-linux-gnueabihf/8/include \
-I/usr/local/include \
-I/usr/lib/gcc/arm-linux-gnueabihf/8/include-fixed \
-I/usr/include/arm-linux-gnueabihf \
-I/usr/include" cc1

As you can see the cross wasn’t picking up the right include paths, so I just cheated, and dumped them from 8, and just copied them into this script. I re-ran the build and had 2 issues,

/412/xgcc -B/412 -g -O0  -I. -I./config -I/usr/lib/gcc/arm-linux-gnueabihf/8/include -I/usr/local/include -I/usr/lib/gcc/arm-linux-gnueabihf/8/include-fixed -I/usr/include/arm-linux-gnueabihf -I/usr/include -c  -DIN_GCC   -g -std=gnu89     -I. -I. -I./config local-al.c

....

/tmp/ccMguyhs.s: Assembler messages:
/tmp/ccMguyhs.s:5001: Error: selected processor does not support `fltd f1,r3' in ARM mode
/tmp/ccMguyhs.s:5025: Error: selected processor does not support `fltd f0,r3' in ARM mode
/tmp/ccMguyhs.s:5026: Error: selected processor does not support `dvfd f1,f1,f0' in ARM mode
/tmp/ccMguyhs.s:5027: Error: selected processor does not support `ldfd f0,.L489' in ARM mode
/tmp/ccMguyhs.s:5028: Error: selected processor does not support `mufd f0,f1,f0' in ARM mode

and so on. Also failing was global.c Again the same weird instruction/asm mix being triggered. Other than those two, cc1 will build, but unsurprisingly:

Reading symbols from cc1...done.
(gdb) r
Starting program: /src/emx/src/gcc-2.5.8/cc1 

Program received signal SIGSEGV, Segmentation fault.
0x004f6b84 in rtx_cost (x=<error reading variable: Cannot access memory at address 0xff7efff0>, 
    outer_code=<error reading variable: Cannot access memory at address 0xff7effec>) at cse.c:667
667     {
(gdb) 

Well, at least it’s consistent?

Or a fun way to kill a couple hours.

**EDIT I went ahead and looked in the 4.1 source for ARM stuff..

root@pinepro:/src/gcc-4.1.2# grep arm config*|grep linux
grep: config: Is a directory
configure:  arm*-*-linux-gnueabi)
configure.in:  arm*-*-linux-gnueabi)

it didn’t like the gnueabihf stuff one bit.

I tried to rebuild as linux-gnueabi

./configure --target=arm-linux-gnueabi --host=arm-linux-gnueabi --build=arm-linux-gnueabi

make LANGUAGES=c HOST_CFLAGS='-fgnu89-inline' CFLAGS='-fgnu89-inline'

And then re-built GCC 2.5.8 with the same error, but slightly further into the program:

Starting program: /src/emx/src/gcc-2.5.8/cc1

Program received signal SIGSEGV, Segmentation fault.
0x004f2a20 in rtx_cost (x=0x41, outer_code=PLUS) at cse.c:679
679       code = GET_CODE (x);
(gdb) bt
#0  0x004f2a20 in rtx_cost (x=0x41, outer_code=PLUS) at cse.c:679
#1  0x004f2e20 in rtx_cost (x=0x60c3f8, outer_code=SET) at cse.c:736
#2  0x004ac2dc in init_expmed () at expmed.c:87
#3  0x0045ae28 in compile_file (name=0x5c96ec "stdin") at toplev.c:1648
#4  0x0045f6fc in main (argc=1, argv=0xfffefd04, envp=0xfffefd0c) at toplev.c:3569
(gdb)

The positive thing is that there was no weird register errors while compiling, and it built 100% normally…? “arm-linux-gnueabihf” almost seems right, specs needs fixing to point to “/lib/ld-linux-armhf.so.3” instead of “/lib/ld-linux.so.3” along with the linker target.