Building MIT PC/IP, or making apple pie

“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 – InternautHow 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.

Over on bitsavers there are 3 files:

[   ]8086_C_19850820.tar2019-03-12 14:36750K 
[   ]PC-IP_19850124.tar2019-03-12 11:534.6M 
[   ]PC-IP_19860403.tar2019-03-12 11:536.9M 
bitsavers.trailing-edge.com/bits/MIT/pc-ip/

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!

A man, his best friend and a time machine! – Microsoft Sydney

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“)

a rough explanation of how old C compilers work in stages

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.

A simple do nothing program, assembled on the VAX

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:

The same program assembled on the PC

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:

The OMAGIC header is being appended!

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\proj\8086pcc>\aoutgcc\bin\ld.exe -X -r -o hi.out crt0.b hi.b ./libc.a
C:\msvc32s\proj\8086pcc>cvt86 hi.out hi.com
C:\msvc32s\proj\8086pcc>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.

#define ARMAG   "!<arch>\n"

#define SARMAG  8

#define ARFMAG  "`\n"

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

Fibonacci sequence

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.

3com 3c501

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!

New version 4.1.1

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

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!

hardware options

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!

telnet options

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.

Site config

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.

The status CR/LF is broken!

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!

Ping my local gateway!

Instead of a garbled mess, I had something I could read!

telnetting to my test BBS

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.

The updated telnet client connecting to a test BBS

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:

https://github.com/neozeed/8086pcc

Against my better judgement, I’ve added a binary package on github.

86 DOS Version 0.11 found!

86-DOS on archive.org

As of this moment, this is the oldest version of 86-DOS surviving in the wild. The prior version was 0.34. You can download a disk image over on archive.org. Thanks to F15sim for providing the uploads!

Getting this running was a little involved as I first had to build open-simh, I just used the Windows Subsystem for Linux (WSL) to build the altairz80 emulator. With the emulator built, you’ll need the BIOS 86mon.bin from schorn.ch as 86dos.zip. In the archive you’ll find 86-DOS 1.0 in the zip file. Simply editing the file 86dos and specifying the 0.11 download (I renamed it as it’s too long and too many spaces!) and you’ll be able to run 86-DOS.

86-DOS booting up on open-simh

There isn’t much on the diskette:

  • COMMAND COM
  • RDCPM COM
  • HEX2BIN COM
  • ASM COM
  • TRANS COM
  • SYS COM
  • EDLIN COM
  • CHESS COM
  • CHESS DOC

There is a simple chess game, although I’m not much of a player..

A:chess

Choose your color (W/B): W
Ply depth (1-6): 1
E2-E4
e7 e5

There is no source code in this disk image, but there is some stuff on the 0.34 image.

Just a quick post in that middle of the night.

Looking back at MS-DOS 4.00M, or in the beginning before there was OS/2

With the pre-christmas release of the Microsoft OS/2 betas 1.00, 1.01, 1.02, 1.03 & 1.05 on archive.org, and helping Ncommander with an upcoming video, it seemed like a good place to start, not with OS/2 but rather with MS-DOS 4.0.

From the book INSIDE OS/2 ( ISBN 1-55615-117-9 )

Microsoft started work on a multitasking version of MS-DOS in January 1983.  At the time, it was internally called MS-DOS version 3.0. When a new version of the single-tasking MS-DOS was shipped under the name MS-DOS version 3.0, the multitasking version was renamed, internally, to MS-DOS version 4.0. A version of this product–a multitasking, real-mode only MS-DOS–was shipped as MS-DOS version 4.0. Because MS-DOS version 4.0 runs only in real mode, it can run on 8088 and 8086 machines as well as on 80286 machines. The limitations of the real mode environment make MS-DOS version 4.0 a specialized product. Although MS-DOS version 4.0 supports full preemptive multitasking, system memory is limited to the 640 KB available in real mode, with no swapping.2 This means that all processes have to fit into the single 640 KB memory area. Only one MS-DOS version 3.x compatible real mode application can be run; the other processes must be special MS-DOS version 4.0 processes that understand their environment and cooperate with the operating system to coexist peacefully with the single MS-DOS version 3.x real mode application.     

Because of these restrictions, MS-DOS version 4.0 was not intended for general release, but as a platform for specific OEMs to support extended PC architectures. For example, a powerful telephone management system could be built into a PC by using special MS-DOS version 4.0 background processes to control the telephone equipment. The resulting machine could then be marketed as a “compatible MS-DOS 3 PC with a built-in superphone.” Although MS-DOS version 4.0 was released as a special OEM product, the project–now called MS-DOS version 5.0–continued. The goal was to take advantage of the protected mode of the 80286 to provide full general purpose multitasking without the limitations–as seen in MS-DOS version 4.0–of a real-mode only environment. Soon, Microsoft and IBM signed a Joint Development Agreement that provided for the design and development of MS-DOS version 5.0 (now called CP/DOS). The agreement is complex, but it basically provides for joint development and then subsequent joint ownership, with both companies holding full rights to the resulting product.

As the project neared completion, the marketing staffs looked at CP/DOS, nee DOS 5, nee DOS 4, nee DOS 3, and decided that it needed…you guessed it…a name change. As a result, the remainder of this book will discuss the design and function of an operating system called OS/2.

– Inside OS/2.

Although MS-DOS 4.00M disk images have been floating around for quite some time, either a 2 360k disk set, or a single 720k disk image, I don’t think anyone (including me) really tore into it that much. It does have the ability to freeze DOS 3 programs, giving the illusion of running more than one. The session manager is pretty sparse but hitting left alt twice will pop it up giving you the ability to toggle through programs with ease.

MS-DOS 4.00M

There is a FDISK, FORMAT & SYS command making it straight forward to setup a hard disk, and copy the files over, I didn’t see any installer.

there is a PS command to show running processes. Also there is a DOSSIZE to show the memory partitioning and how much is available. Although there is a SWAPPER program I’ve been unable to get it to actually fun.

multitasking!

Another interesting thing if you run the unix ‘strings’ command against all the EXE’s you’ll find the string:

C Library - (C)Copyright Microsoft Corp 1985

Implying that not only was DOS 4.00M a ‘new’ DOS, but it was also written in C. No doubt this contributed to a larger file size than DOS 3, however it would also give that holy grail of portability, at least to new CPU modes. Also many files have the name of the source files baked in such as:

@(#)append.c    1.1 85/10/09
@(#)assign.c    6.1 85/10/23
@(#)attrib.c    6.1 85/10/24
@(#)fdisk.c     1.1 85/10/09
@(#)fddata.c    1.1 85/10/09
@(#)fdlow.c     1.1 85/10/09
@(#)fdsub.c     1.1 85/10/09
@(#)joinsbst.c  6.3 85/11/08
@(#)sysvar.c    6.2 85/11/08
@(#)cds.c       6.2 85/11/08
@(#)dpb.c       6.1 85/11/08
@(#)label.c     6.1 85/10/24
@(#)newdef.y    6.2 85/10/14
@(#)ms4bnr.c    1.1 85/10/15
@(#)mode.c      6.2 85/10/24
@(#)getkey.c    6.1 85/10/25
@(#)pifmes.c    6.1 85/10/25
@(#)advpscrn.c  6.1 85/10/25
@(#)advescrn.c  6.1 85/10/25
@(#)usrscrn.c   6.1 85/10/25
@(#)rangers.c   6.1 85/10/25

Okay so far, so good. But we’ve all seen this before, and scratched this OS about this far, because what else can you do? It’s not like there is any dev tools to do anything fun!

Well the tool hidden in plain sight is LINK4, which in retrospect is specific for MS-DOS 4.00M.

Microsoft (R) 8086 Object Linker  Version 4.01
Copyright (C) Microsoft Corp 1984, 1985.  All rights reserved.

Object Modules [.OBJ]:

There is no SDK for MS-DOS 4.00M, but they were kind enough to leave the linker in place. A quick check of the Windows 1.01 SDK shows that it also includes LINK4:

Microsoft 8086 Object Linker
Version 4.00  (C) Copyright Microsoft Corp 1984, 1985

Object Modules [.OBJ]:

It appears that if the dates and versions are to be trusted they are of the same vintage, but the Windows linker is older, and that they both output to a NE or New Executable. So to start the experiment I created a simple hello world exe from a simple:

void main(){
  printf("Hello from MSC 3\n");
}

To compile this I used Microsoft C 3.0 (more on why later), and used LINK4 to create an EXE:

C:\dos\msc3>cl /c hello.c
Microsoft C Compiler  Version 3.00
(C)Copyright Microsoft Corp 1984 1985
hello.c

C:\dos\msc3>msdos dos4m\link4 hello.OBJ

Microsoft (R) 8086 Object Linker  Version 4.01
Copyright (C) Microsoft Corp 1984, 1985.  All rights reserved.

Run File [HELLO.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
Definitions File [NUL.DEF]

Okay, everything looks fine so far. Attempting to run this under MS-DOS just results in the error:

Program too big to fit into memory

Well now that’s odd. Checking the EXE with the Linux ‘file’ command reveals:

file HELLO.EXE
HELLO.EXE: MS-DOS executable, NE (unknown OS 0) (EXE)

So obviously it’s a NE, but it is an older/unknown version to the file map database. There is no stub so I suppose that is why MS-DOS is getting confused.

Now let’s try MS-DOS 4.00M

Hello!

Well now isn’t that interesting?!

Excited with the ability to create special MS-DOS 4.00M programs, I get my favorite vintage ’87 Infocom interpreter, InfoTaskForce 87, and get it building on MSC 3.0. However instead of using the MS-DOS 4.00M linker, I thought I should try to use the Windows 1.01 linker and libraries for the exe:

C:\dos\msc3\infocom>msdos ..\win101sdk\bin\LINK4.EXE @infocom.win.lnk

Microsoft 8086 Object Linker
Version 4.00  (C) Copyright Microsoft Corp 1984, 1985

Object Modules [.OBJ]: FILE.OBJ FUNCS.OBJ INFOCOM.OBJ INIT.OBJ INPUT.OBJ +
Object Modules [.OBJ]: INTERP.OBJ IO.OBJ JUMP.OBJ OBJECT.OBJ OPTIONS.OBJ PAGE.OBJ +
Object Modules [.OBJ]: PRINT.OBJ PROPERTY.OBJ SUPPORT.OBJ VARIABLE.OBJ TERM.OBJ
Run File [FILE.EXE]: INFOCOM.EXE/ALIGN:16
List File [NUL.MAP]: INFOCOM.MAP
Libraries [.LIB]: MWLIBFP MWLIBC/NOD
Definitions File [NUL.DEF] INFOW.DEF;

And for those interested this is my .DEF file:

NAME    Infocom

DESCRIPTION 'Infocom 87 interpreter for Planetfall(83)'

DATA    MULTIPLE


HEAPSIZE    1024        ; Must be non-zero to use Local memory manager
STACKSIZE   4096        ; Must be non-zero for SS == DS
                        ; suggest 4k as minimum stacksize

SEGMENTS
    _INIT   PRELOAD MOVEABLE DISCARDABLE

One thing to save you the horror is that between MS-DOS 2 & 3 the way command line arguments changed. I forget the details but no matter what I tried I was unable to parse the CLI or the environment in this setup. I suppose if I had documentation of the product there would be some hint as to what tools or setup to use. Instead, I took the easy way and hard coded to load Planetfall.

InfoTaskForce compiled with MSC 3.0, using Windows 1.01 libc / LINK4

Unfortunately, this success would prove to be the exception to the rule. I took trek, converted it to K&R C, as Microsoft C 3.00 from 1985 is well. old, and sadly it just won’t run. Likewise, I took Hack 1.03 and although it runs on MS-DOS it will not run on MS-DOS 4.00M. I am sure there is some fundamental reason why it’s not working, and probably tied to creating a proper DEF file. I’m sure it was all written down somewhere but I don’t know. And yes I tried specifying either floating point emulation via library or inline, and it made no difference.

Looking at OS/2 1.00

Loading up the infamous $3,000 OS/2 1.00 beta, and hitting ctrl+escape you are greeted with session manager!

Session Manager for OS/2

Notice the R for real-mode. With the obvious implication that everything else is protected mode. Going one step further on the excellent site pcjs.org there is OS/2 betas SIZZLE and although there is no OS/2 development bits on there, the directory DOS3TOOL reveals that the C compiler for this era for at least MS-DOS is MSC 3.0. Also included is our friend LINK4.

I create a simple def file that contains the single word ‘PROTMODE’ which should give me my OS/2 binary.

So let’s run that through hello world:

msdos sizzle\DOS3TOOL\link4  hello.OBJ,hello,,,hello.def;

Microsoft (R) Segmented-Executable Linker  Version 5.00.21
Copyright (C) Microsoft Corp 1984, 1985, 1986.  All rights reserved.


C:\dos\msc3>

However attempting to run this just crashes amazingly.

Real mode LIBC in Protected mode:

No doubt it’s because the real-mode libc is using interrupt 21 calls, which OS/2 sure wouldn’t like. I’m pretty sure it requires an OS/2 libc that uses DOSCALLS.DLL to function, which I just don’t have any pre-release versions, nor any libc source code to really make it possible. And attempting to port one to OS/2 pre-releases just doesn’t seem so worth the time.

So for the heck of it I point the LIB variable to the OS/2 1.00 SDK’s libs and re-run the link:

C:\dos\msc3>msdos sizzle\DOS3TOOL\link4  hello.OBJ,hello.exe,hello.map,C:\86box\100\x\MSC\LIB\slibc5.lib \86box\100\x\LIB\DOSCALLS.LIB,hello.def;

Microsoft (R) Segmented-Executable Linker  Version 5.00.21
Copyright (C) Microsoft Corp 1984, 1985, 1986.  All rights reserved.

By default it’s trying to link in EM.LIB, SLIBFP.LIB, SLIBC.LIB. Trying to add them all in the command line link just hangs LINK4 maybe a response file is better suited. Anyways:

Hello from MSC 3.0 in protected mode.

It does run on OS/2 1.00, which I guess isn’t surprising as the LINK4 & libraries are from/for this version.

As an interesting note, OS/2 links against doscalls library/DLL to interface to the OS. While MS-DOS 4.00M doesn’t have a seperate DLL, rather it’s baked into IBMDOS.COM

DOSCALLS
ALLOCSEG
REALLOCSEG
FREESEG
LOCKSEG
UNLOCKSEG
GETSEGSIZE
GETDSHANDLE
CRITENTER
CRITLEAVE
FCRITENTER
FCRITLEAVE
PBLOCK
PRUN
SUBSCREEN
GETPIDS
DOSDISCARDCODE
DOSGETHANDLE
DOSHANDLEJUMP

Noticeably absent is file I/O, No doubt allowing programs to use the standard int21 interface to the kernel for file I/O. No doubt this is in its primordial state, as the OS was going to evolve a bit more until it became OS/2. Unfortunately I have no idea how to link or call into this. Without any SDK it’s impossible to say. And even then is developing for a real mode OS worth the effort?

So what have we learned? LINK4, aka the MS-DOS 4.00M Linker, probably should have been called LINKNE for the NE format. Also there is references to it having it’s own virtual memory paging system, and being able to link larger EXE’s than the traditional link command. Sadly I was unable to get any non trivial programs running. I don’t think it was a memory model thing, although the C compiler has issues with InfoTaskForce and the large memory model for some reason, but small & medium work fine. I’d like to think that DOS 4.00M could support massive EXE’s much like Windows 1.01, however despite being from the same company and using the same tools, the memory manager for DOS 4.00M & Windows is fundamentally different.

With all these exiting OS/2 betas now available I’ll have to take some more time to explore them in more detail.

But until then I thought this genesis of DOS 4.00M was worth the look.

Elijah Miller’s NEC v30 on a Pi hat

v30 on a board

While talking about home brew 8080 and 8086 systems on Discord an ebay search brought me to Elijah’s store page where this small little curiosity was up for sale. It’s literally just a NEC v30 on a Raspberry Pi hat, for a mere $15 USD! Interestingly enough the v30 can operate at 3.3v meaning no special hardware is required to interface to the GPIO bus on a Pi. This reminds me so much of the CP/M cartridge for the Commodore 64, and the price being so right I quickly ordered one and eagerly awaited to 2 weeks shipping to Asia.

While I have Pi 4’s that I run Windows 10 on to drive some displays & power point, I wanted to use the slightly faster Pi400 for this. The Pi400 has a compatible GPIO expansion port so just like a cartridge it’s a simple matter of slotting the card, powering up and building the software. While there is an included binary, it’s a 32bit one, and I’m running Manjaro on the Pi400 for a similar look/feel as the PineBook Pro. Anyways the dependences are SDL2, and an odly named ‘wiringPi’ library that allows C programs to interface to the GPIO.

You can download the emulator over on homebrew8088, specifically the Raspberry Pi Second Project. The last ‘ver 2’ download has the project configured for a v30 which is an 8086 analogue, unlike the v20 which is an 8088. When physically interfacing to the processor things like this really matter!

With the emulator built it was pretty simple to fire it up, and boot into MS-DOS:

first boot!

I have to admit I was a little startled at first as I really had no idea if this was going to work at all. I’d spoken to an engineer friend and he was saying plugging a CPU directly into the GPIO bus, and toggling connections to actually emulate the board was both crazy and that without any electrical buffers it’d most likely either fry the processor and maybe the Pi as well. I suspect this being low voltage may be sparing both, although I have no EE so I’m not going to pretend to know.

Loading up Norton SI confirms what Elijah had posted on Ebay is that it runs very slowly about 1/3rd the speed of an XT. Now I may not know anything about hardware but this seemed at least something a profiler could at least tell me what is going on, and if someone like me helicoptering in on the shoulder of giants could see something.

gcc -I/usr/include/SDL2 -pg -O2 *.cpp -o pi -lSDL2 -lwiringPi -lpthread -lstdc++

This will build a profiled version of the emulator that’ll let us know which functions are being called both the number of times, and how much time to do so. Not knowing anything but having profiled other emulators, the usual pattern is that you spend most time fetching and possibly translating memory; Both in feeding instructions and pushing/popping data from stack and pointers. Waiting is usually for initialisation and for IO.

Once you’ve run your profiled executable, it’ll dump a binary file gmon.out which you can then use gprof to format to a text file like this:

gprof pi gmon.out > report.txt

And then looking at the report you can see where the top time, along with top calls are. Some things just take a while to complete and other well they get called far too often.

Each sample counts as 0.01 seconds. % cumulative self self total
time seconds seconds calls s/call s/call name
39.91 0.71 0.71 286883 0.00 0.00 Print_Char_9x16(SDL_Render er*, int, int, unsigned char)
16.30 1.00 0.29 1 0.29 1.02 Start_System_Bus(int)
12.37 1.22 0.22 1100374 0.00 0.00 Data_Bus_Direction_8086_OUT()
7.87 1.36 0.14 5954106 0.00 0.00 CLK()

As expected Start_System_Bus takes 1 second, followed by 1,100,374 calls to set the Data_Bus_Direction_8086_OUT (no doubt the Pi needs to alternate between reading and writing to the CPU), followed by 5,954,106 ticks of the CLK function. Of course the real culprit is Print_Char_9x16 which was called 286,883 times, and is responsible for nearly 40% of the tuntime!

Obviously for a simple MS-DOS boot the screen should not be calling any print char anywhere near this many times. Clearly something is amiss. Not knowing anything I added a simple counter to block at the top of the Print_Char_9x16 function to let it only execute 1:1000 times, and I got this:

Obviously it’s not right, which means that the culprit really isn’t Print_Char_9x16 but rather what is calling it. It was a simple change to each of the Mode functions to only render a fraction of the time, and I changed it to a define to let me fire it more often. This is a simple diff, assuming WordPress doesn’t screw it up. It’s not pretty but it gets the job done.

$ diff -ruN ver2/vga.cpp ver2-j/vga.cpp 
--- ver2/vga.cpp	2020-07-29 10:36:51.000000000 +0800
+++ ver2-j/vga.cpp	2021-06-04 01:51:33.546124473 +0800
@@ -1,5 +1,9 @@
 #include "vga.h"
 
+static int do9x16 = 0;
+#define VIDU 5000
+
+
 void Print_Char_18x16(SDL_Renderer *Renderer, int x, int y, unsigned char Ascii_value)
 {
 	for (int i = 0; i < 9; i++)
@@ -23,6 +27,12 @@
 
 void Mode_0_40x25(SDL_Renderer *Renderer, char* Video_Memory, char* Cursor_Position)
 {
+do9x16++;
+if(do9x16>VIDU)
+        {do9x16=0;}
+else
+        {return;}
+
 	int index = 0; 
 	for (int j = 0; j < 25; j++)
 	{
@@ -36,6 +46,7 @@
 	Print_Char_18x16(Renderer, (Cursor_Position[0] * 18), (Cursor_Position[1] * 16), 0xDB);
 	SDL_RenderPresent(Renderer);	
 }
+
 void Print_Char_9x16(SDL_Renderer *Renderer, int x, int y, unsigned char Ascii_value)
 {
 	for (int i = 0; i < 9; i++)
@@ -57,6 +68,12 @@
 }
 void Mode_2_80x25(SDL_Renderer *Renderer, char* Video_Memory, char* Cursor_Position)
 {
+do9x16++;
+if(do9x16>VIDU)
+        {do9x16=0;}
+else
+        {return;}
+
 	int index = 0; 
 	for (int j = 0; j < 25; j++)
 	{
@@ -102,6 +119,12 @@
 
 void Graphics_Mode_320_200_Palette_0(SDL_Renderer *Renderer, char* Video_Memory)
 {
+do9x16++;
+if(do9x16>VIDU)
+        {do9x16=0;}
+else
+        {return;}
+
 	SDL_RenderClear(Renderer);
 			int index = 0; 				
 			for (int j = 0; j < 100; j++)
@@ -156,6 +179,12 @@
 }
 void Graphics_Mode_320_200_Palette_1(SDL_Renderer *Renderer, char* Video_Memory)
 {
+do9x16++;
+if(do9x16>VIDU)
+        {do9x16=0;}
+else
+        {return;}
+
 	SDL_RenderClear(Renderer);
 			int index = 0; 
 			for (int j = 0; j < 100; j++)

While it feels more responsive on the console, it’s still incredibly slow. SI was returning the same speed which means that although we aren’t hitting the screen anywhere near as often it’s still doing far too much. Is it really a GPIO bus limitation? Again I have no idea. But the next function of course is the clock.

First I tried dividing the usleep in half thinking that maybe it’s not getting called enough. And running SI revealed that I’d gone from a 0.3 to a 0.1! Obviously this is not the desired effect! So instead of a divide I multiplied it by four:

diff -ruN ver2/timer.cpp ver2-j/timer.cpp 
--- ver2/timer.cpp	2020-08-12 00:32:13.000000000 +0800
+++ ver2-j/timer.cpp	2021-06-04 02:06:25.505904407 +0800
@@ -7,7 +7,7 @@
 {
    while(Stop_Flag != true)
    {
-      usleep(54926); 
+      usleep(54926*4); 
       IRQ0();
    }
 }

Now re-running SI I get this:

Norton SI with clock multiplied by four

Now it’s scoring a 1.5! Obviously these are all ‘magic numbers’ and tied to the Pi400 and more importantly I haven’t studied the code at all, I’m not trying to disparage or anything, if anything it’s just a quick example why profiling your code can be so important! At the same time trying to run games is so incredibly slow I don’t even know if my changes had any actual impact to speed as emulation of benchmarks can be such a finickie thing.

My goto game, Battletech 3025 Crescent Hawks Inception loads to the first splash but then seems to hang. I could be impatient or there could be further issues but I’m just some impatient tourist with a C compiler…

With my changes and re-running the profiler I now see this:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
 95.41    129.23   129.23 22696621     5.69     5.69  Read_Memory_Array(unsigned long long, char*, int)
  2.90    133.15     3.92                             Start_System_Bus(int)
  0.88    134.34     1.19 64369074     0.02     0.02  CLK()
  0.30    134.74     0.40                             keyboard()
  0.16    134.96     0.22   412873     0.53     0.53  Print_Char_9x16(SDL_Render
er*, int, int, unsigned char)
  0.08    135.07     0.11 11273939     0.01     0.01  Data_Bus_Direction_8086_OUT()

Which is now what I expect with the bulk of the emulation now calling Read_Memory, with the Clock following that and of course our tamed screen renderer (although its still called far too much!) with the Data_Bus_Direction being further down the list. No doubt some double buffering and checking what changed in between calls would go a LONG way to optimise it, just as would actually studying the source code.

The one cool thing about this is that if I wanted to write a PC emulator this way gives me the confidence that the CPU is not only 100% cycle accurate, but it’s 100% bug for bug accurate since we are using a physical processor.

And again for $15 USD + Shipping I cannot recommend this enough!

Virtualization Challenge IV – QNX 1.2

(This is a guest post by Antoni Sawicki aka Tenox)

This is a Virtualization Challenge. A competition to virtualize an OS inside emulator/hypervisor. (Previously 1 / 2 / 3)

This time the object of the competition is QNX version 1.2. A demo disk is covered here. This is the set of floppy disks:

As you can see the boot disk is copy-protected. As such I have imaged these disks using both KryoFlux and SuperCard Pro. The magnetic flux stream images are available here. For verification I have converted the raw stream of the demo disk in to a sector image using HFE tool. The converted disk boots and works correctly in an emulator. The demo disk can also help with analyzing the boot process since it’s known to work.

The contest is to virtualize the OS, install it and provide a fully working hard disk image with the OS installed. Any emulator of your choice or method is acceptable as long as anyone can download and run it. The prize is $100 via PayPal and of course the fame! 🙂 The winner will be whoever comments the article first with a verifiable working solution.

A bonus $50 prize will be awarded if you can patch the boot floppy disk so that it can be installed as if the copy protection was never there.

Good luck!!!

UPDATE: The competition has ben won: QNX 1.2 Virtualized

UPDATE 2 : QNX 1.2 challenge Act II – HDD Boot

UPDATE 3: Reverse-engineering QNX 1.2 to boot from HDD

QNX 1.1 Demo Disk

(This is a guest post by Antoni Sawicki aka Tenox)

Fresh from the oven, or rather Kryoflux dump – a QNX version 1.1 Demo Disk:

QNX 1.1 Demo Disk

I managed to boot it on 86Box:

QNX 1.1 booted on 86Box Emulator

For the readers with more curiosity and time at their hands please could you try it on different emulators and comment what works and what doesn’t.

For the less curious this how the demo actually looks like once you log in as demo user:

QNX 1.1 Demo Menu

As the authors demand to make as many copies of this disk as possible here it is. Please download and spread!

I also managed to dump the rest of QNX 1.2 including boot disk, utils and even c compiler. Unfortunately the boot disk is copy protected:

I have raw stream dump made with Kryoflux as well as regular disk images. If you are interested in circumventing checking the copy protection so the system could be run in an emulator let me know in a comment. Perhaps time for another Virtualization Challenge?

Previously:

Virtualizing QNX 2

QNX Windows – First Look

QNX 2.21 Arrived Today

PCem v15 released!

The new dynamic recompiler appears to be much more faster, although if you want maximum performance, make sure to set your video card to the fastest possible performance.

I was doing my typical DooM thing, and the performance was abysmal. But I did have an 8bit VGA card selected, so what would I really expect? Interestingly enough in ‘low resolution’ mode it performed quite well, but setting it to the artificial ‘fastest PCI/VLB’ speed it was performing just great.

PCem v15 released. Changes from v14 :

  • New machines added – Zenith Data SupersPort, Bull Micral 45, Tulip AT Compact, Amstrad PPC512/640, Packard Bell PB410A, ASUS P/I-P55TVP4, ASUS P/I-P55T2P4, Epox P55-VA, FIC VA-503+
  • New graphics cards added – Image Manager 1024, Sigma Designs Color 400, Trigem Korean VGA
  • Added emulation of AMD K6 family and IDT Winchip 2
  • New CPU recompiler. This provides several optimisations, and the new design allows for greater portability and more scope for optimisation in the future
  • Experimental ARM and ARM64 host support
  • Read-only cassette emulation for IBM PC and PCjr
  • Numerous bug fixes

Thanks to dns2kv2, Greatpsycho, Greg V, John Elliott, Koutakun, leilei, Martin_Riarte, rene, Tale and Tux for contributions towards this release.

As always PCem can be downloaded here:

8086tiny BIOS patch update

This fun patch allows bigger hard disks, allowing you to run larger OS’s like QNX!

You don’t have to update the emulator, it’s just for the BIOS.  Source is here: Over on github.

8086 Tiny on Windows with ansicon to render the textmode correctly.

Seeing the QNX logo sure has some flashbacks to the Burroughs/Unisys Icon from days of old.  Although it has no relationship to the Waterloo Icon, some housing complex for students.

PCem v13 released

Lots of new features added into this release!

New systems like:

  • IBM PS/2 Model 50
  • IBM PS/2 Model 55SX
  • IBM PS/2 Model 80

New disk controllers!

  • AT Fixed Disk Adapter
  • DTC 5150X
  • Fixed Disk Adapter (Xebec)
  • IBM ESDI Fixed Disk Controller
  • Western Digital WD1007V-SE1
  • Adaptec AHA-1542C
  • BusLogic BT-545S
  • Longshine LCS-6821N
  • Rancho RT1000B
  • Trantor T130B

And plenty of new fixes!  I just installed Citrix 2.0 with the older OS/2 1.2 based drivers, and it works great!

Citrix 2.0 on PCem v13

The full announcement is here, along with downloads for Windows & Linux.

Epyx Rogue 1.48

Rouge 1.48 title screen

Rouge 1.48 title screen

A while back while looking for old Rogue source, and resources I came across this page, which includes a lot of old versions, and source code, and the file rog11src.zip. But looking at the source in this directory the file rogue.h reveals that it is actually 1.48!

#define REV 1
#define VER 48

And the source is all timestamped from late 1984, and throughout 1985.  Well isn’t that exciting!  Also on the same site is rogue-1.48.zip, a binary distribution of Rogue 1.48.  So I thought I’d give it a shot to build it.  The source mentions needing the MANX C compiler, which of course a quick google search yields an ad:

Manx Aztec C86

Manx Aztec C86

Which has all kinds of fascinating information, such as the ability to cross compile from VAX BSD, or PDP-11 BSD, the Amiga, CP/M etc but they don’t actually give any information about versions.

There is, however an Aztec C museum, that hosts several versions.   And they do have the versions, along with the years to show that the C86 compiler that they had for 1985 would be 3.4b

Version 3.4b
Compiler Aztec C 8086 3.40a 7-3-86
(C) 1982,83,84,85 by Manx Software Systems, Inc.

And conveniently, they do have a download link for the comiler here: az8634b.zip

Now, since I’m on Windows 10 x64 I can’t easily run MS-DOS based compilers from 1985 at my native CLI, without a tool, and I chose Takeda Toshiya’s MSDOS.  I was able to ‘bind’ the azmake utility which then could call the needed compiler, assembler, and linker to build an executable without too much work.  I just created a command file, ‘build.cmd’ in the src directory, to setup the paths and needed variables to quickly compile Rogue from the command line.  And a quick attempt at playing it showed that although it does compile, it is unplayable!

Killed

Killed by the Copy Protection Mafia

Well isn’t that great.  There is a copy protection scheme.  But wait, we have source so can’t we just by pass it?  Yes we can!  In the file dos.asm there is some checks for the variables hit_mul & goodchk.  So I did the logical thing, which is before it checks them I just set them to good values.

; fake copy protection
mov hit_mul_, 1
mov goodchk_, 0D0DH

And the good news is that I would no longer get killed by the Mafia, but I couldn’t progress down any levels.  So in the file oprotec.asm, I saw there is some disk check routine called protect, that I went ahead and bypassed by having it immediately jump down to the ‘good’ label. Everything compiles but it still locks up going down a level.  So finally I check rogue.h and commend the #define PROTECT statement, and now it’ll run!

I don’t know if anyone would even care, but I added the PDF manual and all the zip files that I used to source this version.  You can download it here:

rogue-148_binary+source.7z

If you don’t want to run it under MS-DOS, or something like DOSBox, you can use msdos to run it.  The title screen is garbled as it doesn’t emulate CGA, but as the rest is just text mode, it’ll run just fine.