Home > database >  IBM 5150 - int 21h corrupts segment registers
IBM 5150 - int 21h corrupts segment registers

Time:09-04

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...

CodePudding user response:

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.


Notes

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.

  • Related