OSI Executables

From Open Watcom

Jump to: navigation, search

Contents

History

In late 1980s, Watcom C and F77 compilers (no C++ yet) ran on DOS, were implemented as 16-bit executables, and everything was simple. Then 32-bit DOS came along, then 32-bit OS/2, later Win32. The compiler developers switched to 32-bit code even for DOS, which meant that the code base for all these host platforms was almost identical.

In Watcom C/C++ version 9.5 (1993), support for NT host platform was added, in addition to the existing support for DOS and OS/2 hosts. There were now three versions of all the major 32-bit executables (wcc386.exe, wpp386.exe etc.), although the code was largely identical.

The engineers at Watcom started wondering if there might be a way to reduce build times and disk space requirements, perhaps especially distribution media requirements (software was still distributed on floppies, remember?). Dual-mode executables such as 'bound' OS/2 Family API (FAPI) programs or TNT dual-mode Win32 and DOS programs already existed. Why not take this idea and extend it a little to support DOS, Win32 and OS/2, all in one executable?

The next release of Watcom C/C++, version 10.0 (1994), used a new technology called 'OSI' (OS Independent) to deliver only one executable to be shared on the three above mentioned host platform. This technology was successfully used for the 10.x line of compilers without most users noticing that it was even there.

For version 11.0, the OSI technology was still used, but somewhat obsolete. That's because the compilers for Windows and OS/2 were now implemented as platform-specific DLLs, and the compiler executables such as wcc386.exe for NT and OS/2 were only small stub programs. The DOS executables were still OSI, though most likely only due to inertia.

Implementation

Although the OSI technology was never marketed and has somewhat limited scope, it is relatively easy to implement for applications that only use the console and files for I/O.

The user-written code of OSI applications looks perfectly ordinary. It uses the standard C library to interact with the host operating system. All the magic (what little there is) is hidden in the C runtime and in the loader. The application is built as a PharLap REX executable and a special stub program is prepended to it.

Library

The OSI runtime library is derived from the DOS library. Probably for historical reasons, the OSI interface is modeled on the DOS int 21h interface. The biggest difference is that instead of int instructions, the OSI library uses call instructions.

On DOS, the library simply forwards the calls to the actual software interrupt. On OS/2 and Win32, this is of course not possible, and an attempt to invoke int 21h would simply terminate the application. On these platforms, the loader (more about which later) implements a simple DOS API emulation using standard C functions. The entire DOS API is obviously not covered, only the most important (and also most portable) aspects. This includes file I/O, console I/O, and memory management. This code is not part of the actual OSI executable, it is part of the loader.

Loader

The OSI loader is a stub program concatenated with the actual application, only there are actually three loaders.

Each OSI application contains a stub program which contains a DOS (MZ) pre-loader and the actual OS/2 (LX) loader. On OS/2, the operating system will skip the DOS MZ executable and find and load the OS/2 LX executable. This LX executable is the OSI loader for OS/2.

The loader consists of two major parts: the loader proper and the DOS API emulation. The loader itself is relatively simple, because the REX format is simple. To load an executable, the relevant part of the file is loaded into memory in one go, relocations are applied, and the loader jumps to the application's entry point. The slightly larger part of the loader is the DOS API emulation which the OSI application calls through the DOS-like system call interface.

On DOS and Win32, the situation is slightly more involved. It's not possible to have both OS/2 (LX) and Win32 (PE) images in the same executable, and because the OS/2 loader is already part of the OSI stub, the Win32 loader had to go somewhere else. This is a file called w32run.exe (where 'w32' presumably stands for 'Watcom 32').

The only catch is that there are actually two w32run.exe files. The actual DOS (MZ) stub program which is part of each OSI executable only determines whether it's running on Win32 or plain DOS, and spawns w32run.exe. There is w32run.exe for DOS and another one for Win32, in a different directory.

The Win32 version of w32run.exe is very similar to the OS/2 loader. It is delivered in a separate executable for technical reasons, but the implementation is not substantially different.

Now the DOS version of w32run.exe is somewhat trickier, because it also has to contain a DOS extender. And just to make matters slightly more complicated, three versions were actually shipped with Watcom C/C++ 10.x: d4grun.exe, tntrun.exe, and x32run.exe. These were versions using the DOS/4G extender, PharLap TNT, and FlashTek X32, respectively. By default, w32run.exe was a copy of x32run.exe, but users could copy over one of the other two. This was perhaps done in an attempt to work around compatibility issues.

The DOS OSI loader obviously has to first start a DOS extender. After this, loading of the OSI application is the same as on OS/2 or Win32. However, the system call interface is significantly different. On DOS hosts, int 21h memory management calls are translated to DPMI calls, but almost everything is passed directly to the underlying OS.

Load Sequence

The picture may become clearer when the load sequence on each host platform is executed. Let us assume that foo.exe is the OSI application with built-in OSI stub programs.

On OS/2, the sequence will be:

  • Operating system runs the LX stub OSI loader embedded in foo.exe
  • OSI loader reads OSI content from foo.exe and jumps to its entry point
  • OSI loader handles callbacks to the OS initiated by the application

On Win32, the sequence will be:

  • Operating system runs the MZ stub embedded in foo.exe
  • The stub spawns binnt\w32run.exe, passing it the foo.exe pathname
  • w32run reads OSI content from foo.exe and jumps to its entry point
  • w32run handles callbacks to the OS initiated by the application

On DOS, the sequence will be:

  • Operating system runs the MZ stub embedded in foo.exe
  • The stub spawns binw\w32run.exe, passing it the foo.exe pathname
  • w32run starts a DOS extender
  • w32run reads OSI content from foo.exe and jumps to its entry point
  • w32run forwards most OS callbacks to the underlying DOS int 21h handler

If w32run is missing, the following message will be printed:

This program requires W32RUN.EXE to be in your PATH

Possibilities

The OSI technology could theoretically be extended to other platforms, such as Linux. A Linux-hosted OSI loader would not be fundamentally different from either the OS/2 or the Win32 loader.

Personal tools