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), thecall 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 theorg 2411h
statement. Whenstart:
is called, the stack pointer puts the return address in the stack. Since the CP/M program will never return tomain:
, thepop 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, thenjmp 0
causes the program to exit to CP/M. The source code for DMSDOS (Listing 2) is similar to that in DCPM. After performing theint 11h
and theand 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 encountersint 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.)
; ; 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
; ; 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.
/* 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); } }
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