2020-04-07

How to cross-compile to various EXE files using OpenWatcom C compiler on Linux

This blog post explains how to use OpenWatcom 2.0 C compiler on Linux (i386 or amd64, any distribution) to cross-compile to EXE files of 16-bit DOS, 32-bit DOS, 16-bit Windows (Windows 3.1), 32-bit Windows (Windows 95 -- Windows 10), 16-bit OS/2 (1.x) and 32-bit OS/2 (2.x). The actual program compiled is quite dumb (it doesn't matter), the focus is on installation and command-line flags.

All commands below are to be run in a Linux terminal window without the leading $.

Download and extract the compiler:

$ mkdir open-watcom-2 open-watcom-2/tmp
$ R=https://github.com/open-watcom/open-watcom-v2/releases
$ wget -O open-watcom-2/tmp/open-watcom-2.zip \
    "$R"/download/Current-build/open-watcom-2_0-c-linux-x86
$ (cd open-watcom-2/tmp && unzip open-watcom-2.zip)
$ (cd open-watcom-2/tmp && mv binl h lib286 lib386 ../)
$ (cd open-watcom-2/tmp && cp binw/dos32a.exe ../binl/)
$ (cd open-watcom-2/tmp && cp binw/dos4gw.exe ../binl/)
$ (cd open-watcom-2/binl && chmod +x owcc wcc wcc386 wlink)
$ rm -rf open-watcom-2/tmp

Create the test program source code:

$ cat >myprog.c <<'END'
#include <stdio.h>  /* Code below works without it. */
const int answer = (int)0x44434241;  /* dd 'ABCD' */
int mul(int a, int b) {
  return a * b + answer;
}
int main(int argc, char **argv) {
  (void)argv;
  return mul(argc, argc);
}
END

Set up compilation environment:

$ export WATCOM="$PWD/open-watcom-2"
$ export PATH="$WATCOM/binl:$PATH" INCLUDE="$WATOM/h"

Compile to all targets:

$ owcc    -bdos -mcmodel=l -o owprog.dosl.exe  myprog.c
$ owcc    -bdos -mcmodel=s -o owprog.doss.exe  myprog.c
$ owcc    -bdos32a         -o owprog.dos32.exe myprog.c
$ owcc    -bdos4g          -o owprog.dosx.exe  myprog.c
$ owcc    -bos2            -o owprog.os216.exe myprog.c
$ owcc    -bos2v2          -o owprog.os232.exe myprog.c
$ owcc    -bwindows        -o owprog.win16.exe myprog.c
$ owcc    -bnt             -o owprog.win32.exe myprog.c
$ owcc -c -bdos -mcmodel=l -o owprog.dosl.obj  myprog.c
$ owcc -c -bdos -mcmodel=s -o owprog.doss.obj  myprog.c
$ owcc -c -bdos32a         -o owprog.dos32.obj myprog.c
$ owcc -c -bdos4g          -o owprog.dosx.obj  myprog.c
$ owcc -c -bos2            -o owprog.os216.obj myprog.c
$ owcc -c -bos2v2          -o owprog.os232.obj myprog.c
$ owcc -c -bwindows        -o owprog.win16.obj myprog.c
$ owcc -c -bnt             -o owprog.win32.obj myprog.c

More information about OpenWatcom and running it on Linux: https://wiki.archlinux.org/index.php/Open_Watcom

Check the file type of the created executable (*.exe) files:

$ file owprog.*.exe
owprog.dos32.exe: MS-DOS executable, LE executable for MS-DOS, DOS/32A DOS extender (embedded)
owprog.dosl.exe:  MS-DOS executable, MZ for MS-DOS
owprog.doss.exe:  MS-DOS executable, MZ for MS-DOS
owprog.dosx.exe:  MS-DOS executable, LE executable
owprog.os216.exe: MS-DOS executable, NE for OS/2 1.x (EXE)
owprog.os232.exe: MS-DOS executable, LX for OS/2 (console) i80386
owprog.win16.exe: MS-DOS executable, NE for MS Windows 3.x (EXE)
owprog.win32.exe: PE32 executable (console) Intel 80386, for MS Windows

FYI the created owprog.dosx.exe file also needs the dos4gw.exe DOS Extender to run. You can copy the file from the binl directory.

Check the file type of the created object (relocatable, *.obj) files:

$ file owprog.*.obj
owprog.dos32.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.dosl.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.doss.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.dosx.obj:  8086 relocatable (Microsoft), ".../myprog.c"
owprog.os216.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.os232.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.win16.obj: 8086 relocatable (Microsoft), ".../myprog.c"
owprog.win32.obj: 8086 relocatable (Microsoft), ".../myprog.c"

More information about the OMF OBJ file format (*.obj above): https://pierrelib.pagesperso-orange.fr/exec_formats/OMF_v1.1.pdf

The *.obj files contain an Intel-specific comment with code 0x9b which depends on the target and operating system: dosl has 0lOed, doss has 0sOed, dos32 has 3fOpd, dosx has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32), os216 has 0sOed (the .obj file is the same for os216 and doss), os232 has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32), win16 has 0sOed (the .obj file differs by only 2 bytes in doss and win16), win32 has 3fOpd (the .obj file is the same for dos32, dosx, os232, win32).

Alternatives for cross-compiling C code to some of these EXE targets on Linux:

  • OpenWatcom ((16-bit and 32-bit) * (DOS, Windows and OS/2) targets, see above for details)
  • Digital Mars C compiler (16-bit DOS, 32-bit DOS, 32-bit Windows targets, plus 32-bit OS/2 object target (no linking), see blog post for details)
  • mingw-w64 (win32 and win64 targets)
  • gcc-ia16 (doss and dosl targets, i.e. 16-bit DOS EXE with various memory models)
  • djgpp-linux32 (dosx target, needs cwsdpmi.exe or pmodstub.exe (PMOED/DJ) as separate downloads)
  • You may be able to run C compilers released for Windows (e.g. the Digital Mars C compiler) using Wine.

No comments: