Secure Oldies III: Compiling for Windows 9x Using OpenWatcom

Secure Oldies III: Compiling for Windows 9x Using OpenWatcom

Making new code running on old platform.

We're able to compile our executable an run it on Windows NT 5 based operating systems (Windows 2000 and Up) as well as Unix (in this case macOS and Linux). That's cool and dandy. But can we push our app to be even older operating system. I'm talking about Windows 95.

Windows 95 Flag

Running it on Windows 95

For that to happen we'd need an emulator. I choose 86box as this is the most usable x86 emulator. I created 2 GB of hard drive and install Windows 95 OSR 2.5 (with Winsock 2 update) with Intel i486 DX2 66 Mhz with whopping 8 MB of RAM as the machine. For your information this processor was launched in 1989, a frickin 33 Years Ago on a machine with this look:

486 DX2 Computer

Note: Winsock 2 Update is needed to be able to run our program

I transferred our executable there and run on it and bummer, I got this error:

Invalid Instructions

Invalid instructions, what?

What does it mean? Thankfully, Windows gave us the hint on bytes at CS:EIP part which is.

0f 49 45 94 89 35 20 90 00 c7 04 86 00 00 00

After using a disassembler like ODA Web, we know that the first few bytes 0f 49 45 94 is an opcode for cmovns -0x6c(%ebp), %eax. The problem is CMOVNS is only available for Pentium Pro while our computer is an old i486.

Let's just upgrade

To make this easier to work with, let's just upgrade our board to Pentium Pro with 32 MB Memory and run the program.

Jepretan Layar 2022-05-13 pukul 15.53.49.png

And then, let's run the same executable on this Pentium Pro.

Windows 95 on Pentium Pro

Nice! We're able to run our HTTP client on Windows 95 with Pentium Pro. However we cannot run that on older processors because modern GNU GCC will insist to build the binary for i686 architecture which is a pentium pro architecture.

OpenWatcom makes our software even more backward compatible.

To be able to run on i486 architecture, which is our aim. We'd need a compiler which is able to produce strictly 32-bit code i486 architecture. I used Windows 95 as it's the operating systems which can still run on i386 machine. Surely, there's a way to do that.

The answer is OpenWatcom. A classic compiler, known for its performance when they are closed source and used by Quake to produce their game. Let's see a quote from their wiki retrieved in 2011 about this compiler:

Around 1993-1996, nearly all DOS games were developed with Watcom C, including famous titles such as DOOM, Descent or Duke Nukem 3D.

So, we'll use this compiler to produce a working executable for Windows 95 on i486. You can download it from their site. They alse have a version which runs on Linux. If you're using macOS, I've created a docker image based on Red Hat's ubi8 image. For Windows, just install their installer and run owsetenv.bat.

For docker, you can run and mount your source code on docker volume. For example, all of my projects are in ~/Projects, therefore I mount it to /projects.

docker -v $(pwd):/projects -it ykode/openwatcom:1.9-ubi

After that we're setting up the build environment.

export WATCOM=/usr/local/watcom

source /usr/local/watcom/owsetenv.sh

And then we're generating the Makefile for Watcom's wmake tool.

cd build/watcom

cmake \
  -DCMAKE_C_FLAGS="-bt=nt -4r -oaxt -d2" \
  -DCMAKE_SYSTEM_NAME=Windows \ 
  -G "Watcom WMake" ../..

Which means:

  • CMAKE_C_FLAGS will set the compiler flags. The meaning of each flags is:

    • -bt=nt we're compiling for Win32 environment (NT/Win9x).
    • -4r we're targeting 486 architecture with Watcom's Register calling convention.
    • -oaxt this is a compiler options which means:

      • a relax aliasing constraint
      • x similar to -obmiler -s which means : b enable branch prediction, m inline math functions, iexpand intrinsict functiion, l enable loop optimisation, e expand user function inline,rrearrange instruction for optimal pipeline usage. and -s means that remove stack overflow checking.
      • t favour speed (executing time) over code size.
      • d2 means generate debugging information.
  • CMAKE_SYSTEM_NAME=Windows we're targeting Windows (instead of DOS).
  • -G "Watcom WMake" generate a Makefile applicable for Watcom wmake tool.

And then we run wmake

wmake

You might run on the wall of text of compilation errors like I did.

Open Watcom Make Version 1.9
Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
Scanning dependencies of target binfetch
[ 50%] Building C object CMakeFiles/binfetch.dir/main.c.obj
/projects/retrocoding.net/programs/old_tls/platform.h(20): Error! E1091: "Unsupported platform"
/projects/retrocoding.net/programs/old_tls/platform.h(46): Error! E1011: Symbol 'errno' has not been declared
/projects/retrocoding.net/programs/old_tls/main.c(23): Error! E1011: Symbol 'SOCKET' has not been declared
/projects/retrocoding.net/programs/old_tls/main.c(23): Error! E1009: Expecting ';' but found 'client'
/projects/retrocoding.net/programs/old_tls/main.c(23): Error! E1063: Missing operand
/projects/retrocoding.net/programs/old_tls/main.c(23): Error! E1011: Symbol 'INVALID_SOCKET' has not been declared
/projects/retrocoding.net/programs/old_tls/main.c(24): Error! E1058: Cannot use typedef 'size_t' as a variable
/projects/retrocoding.net/programs/old_tls/main.c(24): Error! E1009: Expecting ';' but found 'i'
/projects/retrocoding.net/programs/old_tls/main.c(24): Error! E1063: Missing operand
/projects/retrocoding.net/programs/old_tls/main.c(26): Error! E1058: Cannot use typedef 'uint8_t' as a variable
/projects/retrocoding.net/programs/old_tls/main.c(26): Error! E1009: Expecting ';' but found 'buf'
/projects/retrocoding.net/programs/old_tls/main.c(26): Error! E1063: Missing operand
/projects/retrocoding.net/programs/old_tls/main.c(26): Warning! W111: Meaningless use of an expression
/projects/retrocoding.net/programs/old_tls/main.c(26): Error! E1009: Expecting ';' but found ']'
/projects/retrocoding.net/programs/old_tls/main.c(27): Error! E1077: Missing '}'
/projects/retrocoding.net/programs/old_tls/main.c(11): Error! E1044: Variable 'addr_item' has incomplete type
/projects/retrocoding.net/programs/old_tls/main.c(12): Error! E1044: Variable 'addr' has incomplete type
/projects/retrocoding.net/programs/old_tls/main.c(27): Warning! W107: Missing return value for function 'main'
/projects/retrocoding.net/programs/old_tls/main.c(29): Error! E1009: Expecting ')' but found 'Starting Up...\n'
/projects/retrocoding.net/programs/old_tls/main.c(29): Error! E1026: Invalid declarator
/projects/retrocoding.net/programs/old_tls/main.c(29): Error! E1009: Expecting ',' but found 'Starting Up...\n'
/projects/retrocoding.net/programs/old_tls/main.c(29): Error! E1026: Invalid declarator
/projects/retrocoding.net/programs/old_tls/main.c(29): Error! E1147: Too many errors: compilation aborted
Error: Compiler returned a bad status compiling '/projects/retrocoding.net/programs/old_tls/main.c'
Error(E42): Last command making (CMakeFiles/binfetch.dir/main.c.obj) returned a bad status
Error(E02): Make execution terminated
Error(E42): Last command making (CMakeFiles/binfetch.dir/all) returned a bad status
Error(E02): Make execution terminated
Error(E42): Last command making (all) returned a bad status
Error(E02): Make execution terminated

This is because we're using different compiler which define different predefined macros. OpenWatcom doesn't define WIN32, it defines __NT__. So we'd need to add this line on the top.

#if defined(__NT__) && !defined(WIN32)
#define WIN32 1
#endif

If we run wmake again, there will be error similar to this:

main.c(32): Error! E1077: Missing '}'

The error is not really clear. This error is caused by OpenWatcom not supporting variable definition in the middle of statement because it's the feature of C99 which OpenWatcom not really support.

// line 32
int res = init_socket();

To make the compiler happy we'd need to move the definition of res to the top. And for completeness, before return-ing we'll call getchar(); so it will stop execution and waiting for your input.

// just after main()
int res = -1;

//.... some code

// change in line 32
res = init_socket();

// just before return
getchar();

return res

Once you change it you can compile using wmake again and you'll get the binfetch.exe which will be compatible with Windows 95 on 486 processor.

> wmake

Open Watcom Make Version 1.9
Portions Copyright (c) 1988-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
See http://www.openwatcom.org/ for details.
Scanning dependencies of target binfetch
[ 50%] Building C object CMakeFiles/binfetch.dir/main.c.obj
[100%] Linking C executable binfetch.exe
[100%] Built target binfetch

Then we can copy the resulting binfetch.exe to the emulator. And run it by double clicking it.

Windows 95 with Watcom

As you can see in this side-by-side comparison of dependencies. It looks like MinGW has hard dependency on MSVCRT.DLL which maybe in turn calls the invalid instructions. OpenWatcom has its own CRT (C-Runtime) in which it will statically link it with the executable. And with -4r flag it won't produce anything above 486.

Side by Side dependency comparison

If we try the binary back on Windows 10 on 64-bits x86_64. It'll still run as it is.

Windows Amazing Compatibility

Conclusion

We're able to build a decent plain-text HTTP client which run on Windows 10 64-bit to Windows 95 running on i486. It shows that the amazing compatibility Microsoft has creatid for Win32 API. The executable runs smoothly from an OS from 30+ years ago to recent OS.

In future installment, we'll integrate mbedtls. First using MinGW on Pentium Pro, and then we'll try to downgrade it to 486 like we do in this article.