top of page
Writer's picturerehsd

Using C/C++ with my 286

Updated: Mar 26, 2023

Up to this point, all code running on my 286 system has been x86 assembly (real mode). I am hoping to learn how to compile C or C++ code that can run on my system. As I learn more about how to do this, I will capture my learning in this blog post. This post will be updated periodically.


Installing Open Watcom 2.0

I am starting with Open Watcom 2.0. From here I downloaded the Current Release Build (GitHub Release), open-watcom-2_0-c-win-x64.exe (newest as of this writing). I installed Open Watcom, with the following selections, on a Windows 11 host PC.

  • Path of d:\watcom

  • Selective installation

  • Include 16-bit compilers: Small, Medium, Compact, Large

  • Target operation systems: DOS

  • Host operating systems: Windows NT 64-bit (experimental)

  • Toolkits and other components: Include C++ compiler components, Sample programs, Help files for Win32 & PDF.

  • Modify local machine environment variables

  • Modify associations

I updated my Windows environment variables based on d:\watcom\changes.env. I then restarted the system and continued to setup a quick test of the compiler.


To perform a basic test of the compiler, I created a file d:\watcom_projects\helloworld\hello.cpp, as follows.

#include <iostream.h>

void main()
{
	cout << "Hello world" << endl;
}

I then ran the following to build it.

wcl -l=dos hello.cpp

As this stage, I appear to have a compiled executable.


Configuring Visual Studio 2022

In my existing Visual Studio project with my x86 assembly code, I added a file "buildcpp.cmd" with the following.

@echo off
echo Open Watcom Build Environment
PATH D:\WATCOM\BINNT64;D:\WATCOM\BINNT;%PATH%
SET INCLUDE=D:\WATCOM\H;D:\WATCOM\H\NT;D:\WATCOM\H\NT\DIRECTX;D:\WATCOM\H\NT\DDK
SET WATCOM=D:\WATCOM
SET EDPATH=D:\WATCOM\EDDAT
SET WHTMLHELP=D:\WATCOM\BINNT\HELP
SET WIPFC=D:\WATCOM\WIPFC
@echo on
wcl -l=dos %1

Now, I can right click on my project and choose Open in Terminal. In Terminal, I run the following to build a file.

cmd /c buildcpp.cmd .\hello.cpp

So, at this point I can edit the .cpp files in Visual Studio, where I am using Visual Studio as a simple text editor. This will let me manage versions with Git.


DOSBox

Ultimately, I would like to run DOS on my system. With this in mind, I am going to try and develop DOS-compatible .com (or maybe .exe) files. To test my simple helloworld program from above, I installed DOSBox and ran my hello.exe. Success!





Building a COM File

Above, I was able to compile a .exe file. I am using the following wcl syntax to get a .com output.

wcl -2 -mt -bcl=com -lr hello.cpp

The resulting file is 9,152 bytes in size. The file tests fine in DOSBox.


Interrupts

Using Ndisasm, I disassembled the .com file to get a list of interrupts that the application is using. These would be DOS interrupts.

It appears that interrupts 21 and 3 are used. Interrupt 3 appears to be related to calling a debug exception handler. For each 21 interrupt I can look at the assembly code preceding the int call for the value of ah. This gives me a list of interrupts that I *should* try to support in my operating system, listed below (data from Interrupt Services DOS/BIOS/EMS/Mouse (stanislavs.org)). An additional helpful resource: 8086 bios and dos interrupts (IBM PC) (ablmcc.edu.hk).


  • 21, 1 Keyboard Input with Echo

  • 21, 4a Modify Allocated Memory Block (SETBLOCK)

  • 21, 4c Terminate Process with Return Code

  • 21, 30 Get DOS Version Number

  • 21, 3d01 Open File Using Handle (write only)

  • 21, 3e Close File Using Handle

  • 21, 3f Read from File or Device Using Handle

  • 21, 40 Write to File or Device Using Handle

  • 21, 42 Move File Pointer Using Handle

  • 21, 4400 I/O Control - Get Device Information

  • 21, 68 Flush Buffer Using Handle



Next Steps

Additional steps needed (that I know of at this point):

  • Figure out how I would load a .com file into my running 286 system.

  • Implement required support in my "operating system," such as INT21.

  • Identify and implement required support in my "BIOS" (e.g., interrupts). So far, the line between BIOS and operating system has been very blurry in my build. I hope to better delineate the two as I move forward.

  • ...


So Many Questions

The more I learn, the more daunting the task seems to be to implement an IBM compatible BIOS and MS-DOS compatible operating system; both will be required before I can realistically run a compiled C/C++ application. Below, I will add questions/unknowns that come to mind as I work through things. This may not be well organized, but I at least wanted to capture my thoughts. The list will likely grow significantly over time. :)

  • The first BIOS interrupt that I would like to add to my BIOS is INT 10H (video services). Functions (set in AH) that are of initial interest to me are 02h (set cursor position), 03h (get cursor position), 09h (write character with color value), 0Ah (write character only), 0Ch (write pixel), and 13h (write string). 09h uses BL for color, but I am using 16-bit color, not 8-bit color. How would I support more than 8-bit color with 09h? I also need to better understand how pages are used in the context of video interrupts. For example, 0Ch uses BH for page number, CX for x, and DX for y. I will need to determine how this maps to my screen resolution. INT 10H - Wikipedia

  • ...



Additional Resources

The following might be helpful to me as I progress, so I am tracking them here.


Update...

In the following video, I get a C program working (with a little cheating).

Following the above video, I tried a C++ file, too. Here's the source code:

#include <iostream.h>

int main()
{
	cout << "Hello world 3/20" << endl;
	return 0;
}

The result is that it ran fine. I increased the debug logging details. Below is what the C++ code generated for interrupts. The additional columns of logged data are the values of registers AX, BX, CX, DX, CS, DS, and ES (in that order) coming into the interrupt.

The difference with the C++ from the C program is the lack of INT 0x21 Function 0x63 and the addition of Function 0x68 (flush buffer).


Update...

In an earlier video, I had commented that I cheated a bit in the Write function 0x40. The address I was getting in the interrupt (e.g. DX=0x2F94 in the C++ version) for the location of the string to print didn't seem to be working, and I was manually pointing to the location of the string data in loaded COM file. I appear to have resolved the issue. When loading the COM file into memory and far jumping to it, I wasn't setting all of my registers for the COM application. I added that code prior to making the jump, as seen below. Now, the location of the write buffer specified in DS:DX is working.

; CS=DS=ES=SS=PDB pointer, IP=100, SP=max size of block.
mov		ax,		0
mov		ds,		ax
mov		es,		ax
mov		ss,		ax
mov		ax,		0xfffe
mov		sp,		ax
; cs, ip will change with following jmp
jmp 0x2000:0x0100			; Jump to embedded DOS .com at 0x20100



Credit to SLYNYRD for the graphic content for the software sprite.


184 views3 comments

Recent Posts

See All

3 Comments


Unknown member
Feb 25, 2023

I hope you see that all the int21 are not BIOS but MSDOS interrupts. This will not help you with writing a proper BIOS. Best is to look to a MSDOS source to see how MSDOS is using the BIOS. A good starting point are the MSDOS 1.25 sources. Advice to compile it (it works, even in virtual box) can be found here: https://www.betaarchive.com/forum/viewtopic.php?t=40792

But beware all these MSDOS sources are in MASM assembly code.


I love to watch your series about this 80826 CPU whom I have good memories from.

Like
rehsd
rehsd
Feb 25, 2023
Replying to

@bns Unfortunately, I'm unable to access the URL you listed. I'll search for alternate locations. Both my web browsers and firewall block access to that site.

Like
bottom of page