This seems to work:
DATA SEGMENT PARA 'Data'
Path db 'C:\asm\callext4.exe',0 ;asciiz command string
Parms dw 0 ;parent env block is ok
dw OFFSET CmdTail ;command tail address
dw SEG CmdTail
dw 0,0 ;fcb pointers don't matter
dw 0,0 ;ditto
CmdTail db 3 ;length of command tail
db 'C:\',13 ;actual command tail
SaveSS DW 0 ;temporary storage
SaveSP dw 0
Error dw 0
Message1 db 'Press any key to enter child program...$'
Message2 db 13,10,'Back from child program',13,10,'$'
Message3 db 'Ending parent program'
CRLF DB 13,10,'$'
ErrMsg db 'Error during EXEC function: $'
ErrorTable dw offset ErrorX
dw offset Error1
dw offset Error2
dw offset Error3
dw offset Error4
dw offset Error5
dw offset ErrorX
dw offset ErrorX
dw offset Error8
dw offset ErrorX
dw offset ErrorA
dw offset ErrorB
Error1 db ' (invalid function)$'
Error2 db ' (file not found)$'
Error3 db ' (path not found)$'
Error4 db ' (too many open files)$'
Error5 db ' (access denied)$'
Error8 db ' (not enough memory)$'
ErrorA db ' (bad environment)$'
ErrorB db ' (bad Format)$'
ErrorX db ' (unknown error value)$'
sscsmsg db 'cs reg == ss reg! I',027h,'m amazed!$'
ssltcsmsg db 'ss is less than cs!$'
ssltdsmsg db 'ss is less than ds!$'
CR EQU 0DH
LF EQU 0AH
BEL EQU 07H
q db 'Register values at entry',CR,LF,'cs:ip '
csstr db 'xxxx:'
ipstr db 'xxxx ds '
dsstr db 'xxxx:xxxx ss:sp '
ssstr db 'xxxx:'
spstr db 'xxxx es '
esstr db 'xxxx:xxxx ',CR,LF
db 'Program Data Segment at '
newds db 'xxxx:xxxx',CR,LF,'$'
oldds dw 0 ;DS register on entry
oldss dw 0 ;SS register on entry
oldcs dw 0 ;CS register on entry
oldip dw 0 ;IP register on entry
oldes dw 0 ;ES register on entry
end_of_ds equ $
DATA ENDS
CODE SEGMENT PARA 'Code'
ASSUME CS:CODE,DS

ATA,ES:CODE,SS:STACK
Exec1 proc far
call get_ip ;get instruction pointer into ax
jmp CONV_START ;don't mess with the stack yet
callext4: ;return here from register display
mov ax,ss ;SS should be > ds
mov bx,ds
cmp bx,ax ;ss>ds sets cy
jz ss_eq_ds ;original code assumes this
jc ss_gt_ds
mov dx,offset ssltdsmsg ;ss < ds don't expect this
mov ah,9
int 21h
jmp exit ;give up
ss_gt_ds: ;SS > DS
mov ax,ss ;stack seg
push cs
pop bx ;code seg
cmp bx,ax ;ss>cs sets cy
jz ss_eq_cs_gt_ds ;this is very unlikely
jc ss_gt_cs_gt_ds ;this is what the code originally expected
mov dx,offset ssltcsmsg ;ss < cs don't expect this
mov ah,9 ;prn string
int 21h ;call dos
jmp exit ;give up
ss_eq_cs_gt_ds:
mov dx,offset sscsmsg
mov ah,9 ;print msg
int 21h
jmp exit ;give up
ss_gt_cs_gt_ds:
mov bx,ss ;this is the most likely
mov ax,es
sub bx,ax ;get number of paragraphs
;between psp and bottom of stack
jmp add_stack_len
ss_eq_ds: ;ss == ds
mov bx,ds ;point to start of data segment
mov ax,es ;point to start of psp
sub bx,ax ;number of segments for code and data
add_stack_len:
mov ax,sp ;points to top of stack (byte pointer)
mov cl,4
shr ax,cl ;bytes to paragraphs
add bx,ax ;bx = paragraphs needed
mov ah,4ah ;modify memory allocation of this program to give space to use exec
int 21h ;call dos
mov dx,offset Message1 ;starting message
mov ah,9 ;display string
int 21h ;call dos
mov ah,0 ;read kb chr
int 16h ;call bios
cmp al,27 ;esc?
jnz not_esc
jmp exit ;ESC: terminate program & return to dos
not_esc:
mov dx,offset CRLF
mov ah,9
int 21h ;print CRLF
mov Error, 0 ;clr Error
mov ah,4bh ;exec function
mov al,0 ;load & execute
mov dx,OFFSET Path ;address of the asciiz command string in the data seg
push ds ;get ds segment
pop es ;into es for int21 call
mov bx,OFFSET Parms ;parameter block address
mov SaveSS,SS ;save SS reg
mov SaveSP,SP ;save SP reg
int 21h ;call dos
jnc no_error ;carry clear if no error
mov Error,ax ;save error
no_error:
mov SS,SaveSS ;restore SP:SS (may be corrupted by dos)
mov SP,SaveSP
mov dx,offset Message2 ;point to return message
mov ah,9 ;dos print string
int 21h ;call dos
cmp Error,0 ;any error
je ok ;no error
mov dx,offset ErrMsg ;point to error message
mov ah,9
int 21h ;dos prn string
mov ax,Error ;get error valuve
Call PrtDec ;and print it
mov bx,Error ;get error value
shl bx,1 ;times 2
mov dx,ErrorTable[BX] ;get the error message address
mov ah,9
int 21h ;dos print string
mov dx,offset CRLF
mov ah,9
int 21h ;dos print string
ok:
mov dx,offset Message3 ;exit message
mov ah,9
int 21h ;dos print string
exit:
mov ax,4c01h ;exit with error 0
int 21h ;return to dos
Exec1 endp
PrtDec proc near
push ax
push cx
push dx
mov cx,0ffffh ;ending flag
push cx
mov cx,10
pd1:
mov dx,0
div cx ;divide by 10
add dl,30h ;to ascii
push dx
cmp ax,0 ;all done?
jne pd1
pd2:
pop dx ;get chr
cmp dx,0ffffh ;end?
je pd3
mov ah,02h ;prn chr
int 21h ;call dos
jmp pd2 ;do next chr
pd3:
pop dx
pop cx
pop ax
ret
PrtDec endp
cs_end equ $
; CONVPROG displays the contents of CS:IP, DS:xx ES:xx
; and SS:SP
CONVPROG proc
CONV_START:
push ax ;save the ip value held in ax
push cs ;save cs reg
push ds ;save ds reg
MOV AX,DATA ;initialize DS to data segment
MOV DS,AX
pop ax ;get original ds
mov oldds,ax ;save original ds
mov ax,es ;save es
mov oldes,ax
mov ax,ss ;save ss
mov oldss,ax
pop ax ;get cs
mov oldcs,ax ;save cs
pop ax ;get original ip
mov oldip, ax
mov bx,offset csstr ;pointer to CS address in message
mov ax,oldcs
call hex4asc ;hex to 4 ascii chrs
mov bx,offset ipstr ;ptr to IP address in message
mov ax,oldip
call hex4asc ;hex to 4 ascii chrs
mov bx,offset dsstr ;ptr to DS address in msg
mov ax,oldds
call hex4asc ;hex to 4 ascii chrs
mov bx, offset ssstr ;ptr to SS address in msg
mov ax,oldss
call hex4asc ;hex to 4 ascii chrs
mov bx,offset esstr
mov ax,oldes
call hex4asc ;hex to 4 ascii chrs
mov ax,sp
mov bx, offset spstr
call hex4asc ;hex to 4 ascii chrs
push ds
pop ax
mov bx,offset newds
call hex4asc ;hex to 4 ascii chrs
MOV DX,OFFSET q ;register values msg
MOV AH,9 ;display it
INT 21H ;call dos
jmp callext4 ;return to "caller"
CONVPROG endp ;add end proc
get_ip proc near
pop ax ;get return address into ax
push ax
sub ax,3 ;and subtract 3 to get orignal ip
ret
get_ip endp
; hex1asc converts the ls nibble of AL into ascii & stores it [bx]
; bumps BX.
; in: AL and BX
; out: AL corrupted, BX bumped
;
hex1asc proc near ;convert ls nibble in AL to ascii & store at [bx]
and al,0fh
add al,'0'
cmp al,'9'
jle not_alpha
add al,'a'-'9'-1
not_alpha:
mov [ds:bx],al ;store at ds:bx
inc bx ;bump pointer bx
ret
hex1asc endp
; hex2asc converts AL to two ascii chrs at [BX]
; in: AL and BX
; out: AL unchanged, BX=BX+2
;
hex2asc proc near ;convert AL to two ascii chrs and store at [bx]
push ax
and al,0f0h
shr al,1
shr al,1
shr al,1
shr al,1
call hex1asc ;convert ms nibble to ascii
pop ax
push ax
call hex1asc ;convert ls nibble to ascii
pop ax
ret
hex2asc endp
; hex4asc converts AX to 4 ascii chars and stores at [BX]
; in: AX with BX pointing to output string
; out: AX unchanged with BX=BX+4
;
hex4asc proc near ;convert AX to ascii and store [bx]
push ax
mov al,ah
call hex2asc ;convert 2 ms digits to ascii
pop ax ;restore ax
push ax
call hex2asc ;convert 2 ls digits to ascii
pop ax
ret
hex4asc endp
CODE ENDS
STACK segment para stack 'STACK'
db 1024 dup ('x')
STACK ENDS
END Exec1 ;start address
Assemble it to an exe file.
It shows its own loading address and register contents, then spawns another version of itself ad infinitum, or until you hit ESC, at which time it exits.
Hope that's of some help
rgds
Zeit.