I've been playing around with assembly on an emulated IBM 5150, PC-DOS 1.0, IBM Macro Assembler 1.0. (86box 3.7.1)
I started with a simple 'Hello World':
stack segment stack
db 80h dup(?)
stack ends
data segment
msg db 'Hello World!',13,10,'$'
data ends
code segment
assume cs:code,ds:data
mov dx, seg msg
mov ds, dx
mov dx, offset msg
mov ah, 09h
int 21h
int 20h
code ends
end
Sadly, the program crashes after output. Through debug I found, that after the 21h interrupt the DS, CS and IP registers are corrupted.
a:debug hello.exe
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=0080 BP=0000 SI=0000 DI=0000
DS=049F ES=049F SS=04B1 CS=04AF IP=0000 NV UP DI PL NZ NA PO NC
04AF:0000 BAB004 MOV DX,04B0
-t
AX=0000 BX=0000 CX=0000 DX=04B0 SP=0080 BP=0000 SI=0000 DI=0000
DS=049F ES=049F SS=04B1 CS=04AF IP=0003 NV UP DI PL NZ NA PO NC
04AF:0003 8EDA MOV DS,DX
-t
AX=0000 BX=0000 CX=0000 DX=04B0 SP=0080 BP=0000 SI=0000 DI=0000
DS=04B0 ES=049F SS=04B1 CS=04AF IP=0005 NV UP DI PL NZ NA PO NC
04AF:0005 BA0000 MOV DX,0000
-t
AX=0000 BX=0000 CX=0000 DX=0000 SP=0080 BP=0000 SI=0000 DI=0000
DS=04B0 ES=049F SS=04B1 CS=04AF IP=0008 NV UP DI PL NZ NA PO NC
04AF:0008 B409 MOV AH,09
-t
AX=0900 BX=0000 CX=0000 DX=0000 SP=0080 BP=0000 SI=0000 DI=0000
DS=04B0 ES=049F SS=04B1 CS=04AF IP=000A NV UP DI PL NZ NA PO NC
04AF:000A CD21 INT 21
-t
Hello World!
AX=0040 BX=0000 CX=0000 DX=0000 SP=FD24 BP=0000 SI=0000 DI=0000
DS=0040 ES=049F SS=04B1 CS=FFFF IP=FFFF OV DN EI NG ZR AC PE CY
FFFF:FFFF 00EA ADD 21
Could someone explain to me why that is?
I wrote a similar program for PC-DOS 2.0 and it worked. (Just with 21h, 4Ch to terminate, which isn't supported by PC-DOS 1.0)
PS: I do this out of historical curiosity, so...
I can answer one part of your question definitively: How can you properly exit a DOS .EXE program when Int 21h/AH=4Ch
is unavailable in DOS 1.0?
The primary way is to use Int 20h
, however there is a caveat. When you use Int 20h
you must set CS to the segment that the Program Segment Prefix (PSP) is at. The problem is that in an EXE program CS doesn't point to the PSP segment, it points to a separate code segment!
The general solution for this is to use the fact that DS (and ES) points to the PSP segment in an EXE program at startup. You can also use the fact that there is an Int 20h
instruction in the first word of the PSP. We can use a FAR Return to PSP_Segment:0000h to execute the Int 20h
in the PSP to exit the program.
Your code for terminating a DOS .EXE program could look something like:
stack segment stack
db 80h dup(?)
stack ends
data segment
data ends
code segment
main proc far ; Marking this `far` will make the
; assembler convert RET to a FAR RETURN
push ds ; DS = PSP, save PSP segment on stack
; Insert program code here.
; Make sure the stack is balanced when finished
; -----------------
assume cs:code,ds:data
mov dx, data
mov ds, dx
; -----------------
; Exit EXE program with FAR Return to PSP_Segment:0000h where
; an Into 20h resides in the word at offset 0000h in the PSP
xor ax, ax ; AX = 0
push ax ; Push 0 on the stack
; Note: `PUSH` with an immediate value
; wasn't a valid instruction on
; Intel 8088/8086 processors
; At this point the DS (PSP) pushed at the beginning of the program
; and the value 0000h forms a CS:IP FAR pointer on the stack
; A FAR return is done since
; procedure main was declared as FAR
ret ; Return to PSP_Segment:0000h via FAR pointer
; on the stack and execute
; the Int 20h at that address
main endp ; End of procedure main
code ends
end
I believe there might be a bug in the original version of DEBUG that is causing a problem with tracing, but I can't be certain and I don't have a PC-DOS 1.0 environment handy to try it out. I am beginning to think that trace is tracing more than one instruction and possibly doing Int 21h
and then Int 20h
together. The int 20h
is failing for the reason given in the prior section.
In COM programs CS is the segment that has the PSP and your code. The PSP is the first 256 bytes of the segment and why COM programs have an origin point (ORG) of 100h. This is why Int 20h
works in this environment assuming you don't change CS yourself within the code.