Returning from the EXEC Function

In MS-DOS version 2, the EXEC function destroys the contents of all registers except the code segment (CS) and instruction pointer (IP). Therefore, before making the EXEC call, the parent program must push the contents of any other registers that are important onto the stack and then save the stack segment (SS) and stack pointer (SP) registers in variables. Upon return from a successful EXEC call (that is, the child program has finished executing), the parent program should reload SS and SP from the variables where they were saved and then pop the other saved registers off the stack. In MS-DOS versions 3.0 and later, the stack and other registers are preserved across the EXEC call in the usual fashion.

Finally, the parent can use Int 21H Function 4DH to obtain the termination type and return code of the child program.

The EXEC function will fail under the following conditions:

Not enough unallocated memory is available to load and execute the requested program file.

The requested program can't be found on the disk.

The transient portion of COMMAND.COM in highest RAM (which contains the actual loader) has been destroyed and not enough free memory is available to reload it (PC-DOS version 2 only).

Figure 12-1 summarizes the calling convention for function 4BH. Figure 12-2 shows a skeleton of a typical EXEC call. This particular example uses the EXEC function to load and run the MS-DOS utility CHKDSK.COM. The SHELL.ASM program listing later in this chapter (Figure 12-5) presents a more complete example that includes the use of Int 21H Function 4AH to free unneeded memory.

Called with:

AH = 4BH AL = function type 00 = load and execute program 03 = load overlay ES:BX = segment:offset of parameter block DS:DX = segment:offset of program specification

Returns:

If call succeeded

Carry flag clear. In MS-DOS version 2, all registers except for CS:IP may be destroyed. In MS-DOS versions 3.0 and later, registers are preserved in the usual fashion.

If call failed

Carry flag set and AX = error code.

Parameter block format:

If AL = 0 (load and execute program)

Bytes 0—1 = segment pointer, environment block

Bytes 2—3 = offset of command-line tail

Bytes 4—5 = segment of command-line tail

Bytes 6—7 = offset of first file control block to be copied

into new PSP + 5CH

Bytes 8—9 = segment of first file control block

Bytes 10—11 = offset of second file control block to be copied

into new PSP + 6CH

Bytes 12—13 = segment of second file control block

If AL = 3 (load overlay)

Bytes 0—1 = segment address where file will be loaded

Bytes 2—3 = relocation factor to apply to loaded image

Figure 12-1. Calling convention for the EXEC function (Int 21H Function 4BH).

cr egu 0dh ; ASCII carriage return

.

.

.

mov stkseg,ss ; save stack pointer

mov stkptr,sp

mov dx,offset pname ; DS:DX = program name

mov bx,offset pars ; ES:BX = param block

mov ax,4b00h ; function 4bh, subfunction 00h

int 21h ; transfer to MS-DOS

mov ax,_DATA ; make our data segment

mov ds,ax ; addressable again

mov es,ax

cli ; (for bug in some 8088s)

mov ss,stkseg ; restore stack pointer

mov sp,stkptr

sti ; (for bug in some 8088s)

jc error ; jump if EXEC failed

.

.

.

stkseg dw 0 ; original SS contents

stkptr dw 0 ; original SP contents

pname db '\CHKDSK.COM',0 ; pathname of child program

pars dw envir ; environment segment

dd cmdline ; command line for child

dd fcb1 ; file control block #1

dd fcb2 ; file control block #2

cmdline db 4,' *.*',cr ; command line for child

fcb1 db 0 ; file control block #1

db 11 dup ('?')

db 25 dup (0)

fcb2 db 0 ; file control block #2

db 11 dup (' ')

db 25 dup (0)

envir segment para 'ENVIR' ; environment segment

db 'PATH=',0 ; empty search path

; location of COMMAND.COM

db 'COMSPEC=A:\COMMAND.COM',0

db 0 ; end of environment

envir ends

Figure 12-2. A brief example of the use of the MS-DOS EXEC call, with all necessary variables and command blocks. Note the protection of the registers for MS-DOS version 2 and the masking of interrupts during loading of SS:SP to circumvent a bug in some early 8088 CPUs.