Something COMmon About MS-DOS and CP/M
Bill Wilkinson
This document was updated on Saturday, 2 April 2005.
Internal changes only; no changes to the content.
(From Remark magazine, 1987. See the
Notes at the end for details.)
Here's an interesting programming technique I've discovered.
I doubt that I was the first one to discover it, but I haven't
seen it on any of the bulletin boards nor in any of the computer
magazines. Basically, the technique lets you write COM-type
programs that will run unmodified under both MS-DOS and CP/M.
The program can do this because of two facts: First, COM
files in both CP/M and MS-DOS start at address 100 hex. Second,
there exists certain bit patterns common to both the 8080 and
8088 instruction set that cause both CPUs to behave
predictably. All that's necessary is that this bit pattern be
placed at the start of the program. As a specific example,
consider the following binary sequence.
11001101 00010001 00100100 11111111
For the 8088, this pattern translates to the following opcodes:
INT 11H
AND AL,0FFH
For the 8080, the first three bytes translate to:
CALL 2411H
and the fourth byte is ignored.
See where this is heading? The 8088 instructions tell
MS-DOS to return the I/O configuration and AND it with
0FFH, which, of course, does nothing to the returned
data. Your MS-DOS program can immediately follow.
Depending on what it does, you may or may not want to
use the data returned by the interrupt instruction.
The 8080 sees the first three bytes as an unconditional
call to address 2411 hex, so that's where the CP/M code
should start. This gives about 8K of memory for MS-DOS
and whatever length is necessary for CP/M. Of course,
if you need more memory for your MS-DOS code, you can
always jump around the CP/M portion.
To use this technique requires two computers, one that
runs CP/M and a PC machine that runs MS-DOS. (Note that
this program may not work on the 8088-side of the
original H/Z-100 series. Unless I overlooked it while
reading version 2 of the Programmer's Utility Pack, the
H/Z-100 does not recognize INT 11H.) The machines I
developed and tested this program on were an H8/H19 and
an HF-241. I transferred data between the two machines by
hooking up a cable between the two serial ports and
using XMODEM. I used CPS on the HF-241 side
and a public domain version of Christensen's
MODEM7 program on the H-8.
I was tempted to follow up on that INT 11H instruction
and make the program display a large number of facts
about the operating system it was running under, but decided
to keep it simple. So the example program, DOSINFO.COM,
will only print a sign-on message on the screen and then
state whether it's running under MSDOS or CP/M.
DOSINFO.COM must be built out of two binary files called
DCPM.BIN and DMSDOS.BIN. These in turn are assembled from
their associated ASM files shown in listings one and two.
In the CP/M code
(Listing 1), the
call start instruction is
there for testing the program before transferring it to
MS-DOS. However, it doesn't make any difference whether or
not that command is there when you merge the program with
DMSDOS.BIN. The remainder of the code begins at 2411 hex
as defined by the
org 2411h statement.
When start: is
called, the stack pointer puts the return address in the
stack. Since the CP/M program will never return to
main:,
the pop h
instruction removes this address from the stack.
The rest of the program is the standard CP/M procedure for
printing text to the display. The DE register
points to
the start of the text while the C register
has the
"print text" command. Calling BDOS performs the action,
then jmp 0 causes the
program to exit to CP/M.
The source code for DMSDOS
(Listing 2) is similar to
that in DCPM. After performing the
int 11h
and the and al,11h
instructions, the program points the DX register
to the text, loads the "print text" command into
the AH register, then prints it by performing an
interrupt 21H. The program exits to MS-DOS when it
encounters int 20h.
To write the program, use your favorite text editor and
enter Listing 1 on the CP/M machine.
Assuming that A: is
your default drive and all your programs are there,
assemble DCPM by typing:
ASM DCPM.AAZ
LOAD CPM
Transfer the resulting COM file to MS-DOS with XMODEM
(or other error-checking protocol) and name it DCPM.BIN.
Then, on the MS-DOS side, type in the code in
Listing 2 and
assemble it as follows:
MASM DMSDOS;
LINK DMSDOS;
EXE2BIN DMSDOS
MASM is part of the Programmer's Utility Pack,
while LINK and EXE2BIN come with MS-DOS.
Note that without
the semicolons at the end of the first two commands,
MASM and LINK will stop to ask you a bunch of
unnecessary questions. EXE2BIN will automatically
name the assembled program to DMSDOS.BIN.
Use DEBUG to merge DCPM.BIN and DMSDOS.BIN.
The following
steps show how. (Do not enter the comments in parenthesis.
That's just for your information.)
DEBUG DMSDOS.BIN (Load the MS-DOS code first.)
R (Note the value in the CX register pair. It should
be 93 for this program.)
M100 L 93 8000 (Move the program to 8000H. If CX was a different
number in the previous step, use it instead of 93.)
NDCPM.BIN (Load the CP/M file.)
L
M8000 L 93 100 (Move the MS-DOS program back to the beginning.)
NDOSINFO.COM (Write the merged programs to DOSINFO.COM.
It's not necessary to calculate the
W length, since the CX register was set up
properly when DCPM.BIN was loaded.)
Q (Exit to MS-DOS.)
You should now have a runable copy of DOSINFO.COM on
your disk.
Instead of DEBUG, you can use the C program shown in
Listing 3. I wrote this to avoid
continually
repeating the above procedure while developing
DOSINFO. The code is heavily commented, so if you
don't have a C compiler, you shouldn't have too much
of a problem translating it to your preferred language.
Basically, DMERGE will copy DMSDOS.BIN to
DOSINFO.COM
and write zeros in the area between the end of the MS-DOS
code and the start of the CP/M code. Next, it opens
DCPM.BIN, jumps to the start of the code at 2411 hex,
and copies it to DOSINFO.COM. Then
DMERGE closes all open
files, exits to MS-DOS, and DOSINFO is ready to run.
Once you're satisfied that it runs properly on MS-DOS,
transfer it to your CP/M system and test it there. This
time, it will tell you that it's running under CP/M.
As you see the program run, you may be thinking,
"it's clever, but what use is it?"
Beats me.
I got a kick out of the concept, but can't think of
any way to get rich off it. It lets you write programs
that are transportable between the two disk operating
systems, but you have to write twice as much code.
In such a case, writing your program in a high-level
language and compiling it twice is more practical.
The only useful thing I can think of is as a protection
device for public domain and shareware programs. For
example, somebody downloads CPMCHESS.COM from the
local bulletin board and tries to run it on an MS-DOS
machine. The program states "Sorry, I'm not an MS-DOS
program" then performs an orderly exit.
Other than that, the DOSINFO technique may either
be totally useless or is a solution looking for a
problem. Any ideas?
(Source code follows.)
Listing 1
;
; DCPM.ASM
;
;
; Disk Operating System Informatinn Version 1.0
; William A. Wilkinson 10-Nov-86
;
; Assemble under CP/M then transfer the binary code
; to MS-DOS.
;
cr equ 0dh ; Carriage return.
if equ 0ah ; Line feed.
tab equ 9 ; Tab.
bdos equ 5 ; Function entry point.
org l00h
;
; MS-DOS thinks that the following instruction is
; a "get i/o configuration" command (INT 11H).
main:
call start
org 2411h ; CP/M program starts here.
start:
pop h ; Clean up. We're not returning.
lxi d,cptxt ; Point to the CP/M message.
mvi c,9 ; Print string function.
call bdos
jmp 0 ; Exit.
cptxt: db cr,lf,lf
db tab,tab,tab
db 'DOS Information Version 1.0',cr,lf
db tab,tab,tab
db ' William A. Wilkinson',cr,lf
db tab,tab,tab
db ' 10-Nov-86',cr,lf,lf
db tab,tab
db ' This program is running under CF/M.'
db cr,lf,lf
db '$'
end
Listing 2
;
; DMSDOS.ASM
;
; Disk Operating System Information Version 1.0
; William A. Wilkinson 10-Nov-86
;
; MS-DOS code.
;
cr equ 0dh ; Carriage return.
lf equ 0ah ; Line feed.
tab equ 9 ; Tab.
dosinfo segment
org l00h
assume cs:dosinfo,ds:dosinfo
main proc near
; CP/M thinks that the following 1-1/2 instructions
; form an unconditional call to 2411H.
start:
int llh ; Get the I/O configuration.
and al,0ffh ; Do nothing with it.
mov dx,offset mstxt ; Tell 'em MS-DOS.
mov ah,9
int 21h
int 20h ; Goodbye.
mstxt db cr,lf,lf
db tab,tab,tab
db 'DOS Information Version 1.0',cr,lf
db tab,tab,tab
db ' William A. Wilkinson',cr,lf
db tab,tab,tab
db ' 10-Nov-86',cr,lf,lf
db tab,tab
db ' This program is running under MS-DOS.'
db cr,lf,lf
db '$'
main endp ; End of main procedure
dosinfo ends ; Close the segment.
end main ; Program starts at main.
Listing 3
/*
DMERGE.C Ver.l.0
Written in Toolworks C by William A. Wilkinson,
15 November 1986.
This program merges the two binary files used in the
DOSINFO MSDOS-CP/M demonstration program. The MS-DOS
file should be named DMSDOS.BIN and the CP/M filename
should be DCPM.BIN. DMERGE will first transfer
DMSDOS.BIN to DOSINFO.COM. Next it will pad the
remainder of DOSINFO.COM with nulls up to address
0x2310. DMERGE will then read DCPM.BIN, skipping the
first 0x2310 bytes. At byte number 0x2311 (0x2411 with
the 0x100 offset), it will transfer DCPM.BIN to
DOSINFO.COM.
*/
#include <stdio.h>
#include <math.h> /* Comment out if not using Toolworks C or */
/* don't have the Mathpak option. */
main()
{
FILE *fp0, *fp1, *o_file();
int c, i;
long cpmstart; /* Change to int if using Toolworks C */
/* without the Mathpak. */
cpmstart = 0x2411 - 0x100; /* Start of CP/M code. */
fputs("DMERGE Ver.l.0 by Bill Wilkinson 15 November 1986\n", stdout);
fp0 = o_file("dosinfo.com", "wb"); /* Create DOSINFO.COM. */
fp1 = o_file("dmsdos.bin", "rb"); /* Open the MS-DOS binary file. */
fputs("\nTransferring DMSDOS.BIN to DOSINFO.COM...", stdout);
i=0x100; /* Initialize counter to standard *.COM file offset. */
while ((c = fgetc(fp1)) != EOF) { /* Transfer DMSDOS.BIN */
fputc(c, fp0); /* to DOSINFO.COM. */
i++; /* Point to the next empty byte.*/
}
fputs("\nPadding with nulls...", stdout);
while (i < 0x2411) { /* Pad with nulls. */
fputs(NULL, fp0);
i++;
}
o_file(fp1); /* Close DMSDOS.BIN. */
fp1 = o_file("dcpm.bin", "rb"); /* Now open CP/M binary file. */
fputs("\nSkipping 0x2310 bytes in DCPM.BIN...", stdout);
fseek(fp1, cpmstart, SEEK_SET);
fputs("\nTransferring DCPM.BIN to DOSINFO.COM...", stdout);
while ((c = fgetc(fp1)) != EOF) /* Transfer rest to DOSINFO.COM.*/
fputc(c, fp0);
c_file(fp1); /* Done! Close the files. */
c_file(fp0);
fputs("\n\nDone!\n\n", stdout);
exit(0); /* Return to MS-DOS. */
}
/*
Open the file (fn) for read or write (type). Return
the file pointer (fp). Exit to MS-DOS if can't open.
*/
FILE *o_file(fn, type)
char *fn, *type;
{
FILE *fp, *fopen();
if ((fp = fopen(fn, type)) == NULL) {
fputs("\nCannot open file!\n", stderr);
exit(1);
}
return(fp);
}
/*
Close the file pointed to by the file pointer (fp).
Do not return anything. Exit to MS-DOS if can't
close.
*/
c_file(fp)
FILE *fp;
{
int c;
if ((c = fclose(fp)) != NULL) {
fputs("\nCannot close file!\n", stderr);
exit(l);
}
}
Notes About the 1999 HTML Version of This Article
I wrote this article in late 1986 and it was published
in the February, 1987, issue of Remark magazine.
(Remark was the official publication of Heath User's
Group--a group devoted to Heath/Zenith computers.)
I've lost the original electronic copy of the article, so
I've scanned it in from the magazine. The OCR software
introduced some typos, but I think I've found and corrected
them all. I've also made minor corrections to the orginal
text (but not the source code). If you discover an error,
however, please email me.
Bill Wilkinson
16 February 1999
The original copy of my article appeared in Remark magazine, which is Copyright (C) 1987, Heath/Zenith User's Group.
This HTML version of my article is Copyright © 1999, 2003, William Albert Wilkinson. All rights reserved.
Go to
Bill Wilkinson's Heath Company Page.