[alt.msdos.programmer] Question re: .ASM overlay in .EXE format

venkat@matrix.UUCP (D Venkatrangan) (05/25/90)

In article <935@ashton.UUCP> tomr@ashton.UUCP (Tom Rombouts) writes:
>I am posting this for a friend.  Please no flames if obvious answer:
>
>Does anyone have any experience writing overlays (via DOS EXEC 4Bh AL=3)
>in assembler using an .EXE file format ?. The reference manual I have talks
>about .COM file formats only and just says .EXE formats are handled
>differently. I'd like to see a simple shell of how this is done. Thanks.
>
>Tom Rombouts, Ashton-Tate Torrance,  Voice: (213) 538-7108


The following will load an .EXE file, after allocating its CS and 64K DS using
DOS 48h call.  After loading, you need to perhaps initialize things in the
overlay.  The following code calls the function at the first byte of the
DOS memory segment.  This means that the .EXE file has to be prepared in a
special way.  The following segments of code should be enough to guide you
through some of the steps involved.

D. Venkatrangan, Matrix Computer Systems,  Voice: (603) 888-7790

---------------- CODE TO LOAD OVERLAY ----------------------

ifdef LARGECODE
ovload_text	segment  word public 'code'
		ASSUME	cs:ovload_text, ss:nothing, ds:nothing
else
_text		segment  word public 'code'
		ASSUME	cs:_text, ss:nothing, ds:nothing
endif

;
; Caller:
;	sets up ovload_frame
;
; Returns:	
;	ax = 0, if success
;	ax = error code, if failure
;
; Side effects:
;	causes the .EXE module to be loaded into memory

public	_ov_load

;
; 	_ov_load(char *filename, char *options)
;

ovload_frame	struc
	saved_bp	dw	?
ifdef LARGECODE
	ret_addr	dd	?
else
	ret_addr	dw	?
endif
ifdef LARGEDATA
	filename_seg	dw	?
	filename_off	dw	?
	options		dd	?
else
	filename	dw	?
	options		dw	?
endif
ovload_frame	ends

; parameter block for the Load Overlay call
;

param_block	struc
	start_seg	dw	?
	rel_factor	dw	?
param_block	ends
param_block_size	equ	4

; In some versions of DOS, the load function 4b trashes all registers
; except CS:IP.  Save the registers for this case.

saved_sp	dw	?
saved_ss	dw	?

ifdef LARGECODE
_ov_load	proc	far	
else				
_ov_load	proc	near
endif
		push	bp
		mov	bp, sp
		sub	sp, param_block_size	; param block off stack

		push	ds			; push registers
		push	es
		push	bx
		push	cx
		push	dx
		mov	ax, ss
		mov	cs:saved_ss, ax		; and save ss:sp
		mov	cs:saved_sp, sp

;
; allocate a fixed amount of memory for the overlay.
; after we load the file, the initialization code will shrink this
;

		mov	ah, 48h
		mov	bx, 1000h		; 64K for now
		int	21h
		jnc	alloc_okay
		cmp	ax, 8
		je	retry
		jmp	mcb_error

retry:
		mov	ah, 48h
		int	21h			; failed -- try new size
		jnc	alloc_okay
mcb_error:
		cmp	ax, 8
		jne	mcb_error_1
		mov	ax, OVERR_NOMEM
		jmp	error

mcb_error_1:
		mov	ax, OVERR_MCB
		jmp	error

; here we are, if alloc went off alright.
; NOTE: we may have less than 64K at this point.
; if this is insufficient to load our program, the load call
; should fail.

ifdef not_done
XXX the above fact is not yet completely tested...
endif

alloc_okay:
		mov	bx, bp			; start of param block in bx
		sub	bx, param_block_size
		mov	word ptr [bx].start_seg, ax
		mov	word ptr [bx].rel_factor, ax

ifdef LARGEDATA					; set filename
		mov	dx, word ptr [bp].filename_seg
		mov	ds, dx
		mov	dx, word ptr [bp].filename_off
else
		mov	dx, word ptr [bp].filename
endif
		mov	ax, 4b03h		; load overlay
		int	21h
		jnc	load_okay		; error?
load_error_1:
		cmp	ax, 1
		jne	load_error_2
		mov	ax, OVERR_BUG		; shouldn't happen
		jmp	error
load_error_2:
		cmp	ax, 2
		jne	load_error_5
		mov	ax, OVERR_NOFILE	; file not found
		jmp	error
load_error_5:
		cmp	ax, 5
		jne	load_error_8
		mov	ax, OVERR_ACCESS	; access denied
		jmp	error

load_error_8:
		cmp	ax, 8
		jne	load_error_x
		mov	ax, OVERR_NOMEM	; access denied
		jmp	error
load_error_x:
		mov	ax, OVERR_BUG
		jmp	error

load_okay:
		mov	es, word ptr [bp-param_block_size].start_seg
		cmp	byte ptr es:0, 0eah	; minimal check
		je	valid_code
		mov	ax, OVERR_BADOVFILE	; bad overlay file
		jmp	error

valid_code:
		push	es			; set up the call address
		xor	ax, ax
		push	ax
;
; pass the overlay options address to overlay initialization code
;

ifdef LARGEDATA
		mov	bx, word ptr [bp+2].options	; seg options
		push	bx
else
		push	ds
endif
		mov	bx, word ptr [bp].options	; off options
		push	bx
		mov	bx, sp
		call	dword ptr ss:[bx+4]	; call overlay initializtion
						; it should return status
						; in ax (one of OVERR_)
		add	sp, +8

error:
		mov	bx, cs:saved_ss		; restore regs
		mov	ss, bx
		mov	sp, cs:saved_sp

		pop	dx
		pop	cx
		pop	bx
		pop	es
		pop	ds

		mov	sp, bp
		pop	bp
		ret

_ov_load	endp

ifdef LARGECODE
ovload_text	ends
else
_text		ends
endif

		END

Now, some tips on preparing the .EXE file.

Create a segment with a single jmp instruction to init of overlay.

---------------- START SEGMENT ------------------------

; NOTE: after the overlay is loaded through the 4b03h call, the init code
; in OV is run.  But since the caller of 4b03h does not know the entry
; point of the code to run, the first segment (which is loaded at the
; location the file was loaded) contains a single instruction to jump
; to the location of the overlay initialization code.

START_TEXT	SEGMENT
		db	0eah		; jmp op code
		dw	offset TRANSIENT_TEXT:_init_overlay
		dw	seg TRANSIENT_TEXT
START_TEXT	ENDS

Place this segment as the first segment using an include file such as:
(Avoid linking crt0.obj)

---------------- SEGMENT ORDERING ------------------------

START_SEGMENT	SEGMENT
START_SEGMENT	ENDS

START_TEXT	SEGMENT	word public 'CODE'
START_TEXT	ENDS

_TEXT		SEGMENT	word public 'CODE'
_TEXT		ENDS

_DATA		SEGMENT word public 'DATA'
_DATA		ENDS

STACK		SEGMENT word public 'STACK'
STACK		ENDS

CONST	SEGMENT  WORD PUBLIC 'CONST'
CONST	ENDS

_BSS	SEGMENT  WORD PUBLIC 'BSS'
_BSS	ENDS

DGROUP	GROUP	CONST, _BSS, _DATA

; now place the transient segments

; Segments named TRANSIENT_ contain code and data that are used during the
; initialization of the overlay.  They are not included in the size 
; calculation for the resident part of the 1 code.  If you have C modules
; that you wish to discard after overlay initialization, change their class to
; 'TSTACK', 'TDATA' or 'TTEXT'

TRANSIENT_TEXT	SEGMENT	para public 'TCODE'
TRANSIENT_TEXT	ENDS

TRANSIENT_DATA	SEGMENT	word public 'TDATA'
TRANSIENT_DATA	ENDS


---------------- OVERLAY INITIALIZING ------------------------



; Structure Templates
mcb	STRUC 			; memory control block structure
	TypeMCB	db	?	; block type
	OwnerMCB dw	?	; block owner
	SizeMCB	dw	?	; block size
mcb	ENDS

public __acrtused
public __atopsp
public __aheap
public __asizheap
public __asizds
public __asizovl
public _errno
public __doserrno
public __osversion
public __osmajor
public __osminor

_DATA		segment
;
; define these here, to avoid getting the ones in crt0.obj
;
__acrtused	dw	1
__atopsp	dw	?
__aheap		dw	?
__asizheap	dw	?
__asizds	dw	?
__asizovl	dw	?
_errno		dw	?
__doserrno	dw	?
__osversion	label	word
__osmajor	db	?
__osminor	db	?

_DATA		ends

STACK		segment
		db	OVSTK_SIZE dup (?)
STACK		ends

TRANSIENT_DATA	segment
ifdef sccs
sccsid  db      '%W% %G%'
endif
TRANSIENT_DATA	ends

TRANSIENT_TEXT	segment
		ASSUME	cs:TRANSIENT_TEXT, ss:nothing, ds:nothing

extrn	_locate_int2f:near
extrn	_install_ivt:near

public	_init_overlay

saved_ss	dw	?
saved_sp	dw	?
params		dd	?

_init_overlay	proc	far
		push	bp
		mov	cs:saved_ss, ss
		mov	cs:saved_sp, sp
		mov	bp, sp
		mov	ax, [bp+4]	; we are passed params on stack
		mov word ptr cs:params+2, ax
		mov	ax, [bp+6]
		mov word ptr cs:params, ax

		mov	di, DGROUP	; set up stack
		mov	sp, OVSTK_SIZE

		cli
		mov	ss, di
		add	sp, offset DGROUP:STACK ; ax is now top of stack
		and	sp, 0fffeh	; even boundary
		sub	sp, 2		; leave one word
		sti
		mov	ss:__atopsp, sp 
		mov	si, 1000h	; 64K data segment
		add	si, di		; si now at DS start + 64K
;
; modify memory at start segment to the required size
;
		mov	ax, seg START_SEGMENT
		mov	es, ax		; set up memory block to shrink
		mov	bx, seg START_SEGMENT
		sub	bx, si
		neg	bx			; bx = end of prog - start of prog
		mov	ah, 4ah		; modify memory -- may expand
		int	21h
		jnc	mcb_done	; no errors
		cmp	ax, 8		; insufficient memory
		je	retry
		jmp	mcb_error	; some other mcb error
;
; could not modify memory -- too large a DS?  see if maximum size possible for
; block, which is returned on the call from modify memory is enough to run
; our code.

retry:
		mov	ax, ss:__atopsp
		add	ax, 0fh		; round to next para
		mov	cl, 4
		shr	ax, cl		; ax now is top of stack in paragraphs
		inc	ax		; round to next
		add	ax, di		; ax now at end of DS in para
		add	bx, seg START_SEGMENT
		sub	bx, ax		; we need up to ax atleast
		cmp	bx, 0
		jg	L100
		mov	ax, OVERR_NOMEM
		jmp	done
;
; what we have may be enough to get going, but may fail later
; to provide much dynamic memory.


L100:
		int	21h
		jnc	mcb_done

mcb_error:
		cmp	ax, 7		; memory destroyed
		jne	L200
		mov	ax, OVERR_MCB	; we probably can't recover anyway
		jmp	done

L200:
		cmp	ax, 8		; memory destroyed
		jne	L201
		mov	ax, OVERR_NOMEM
		jmp	done
L201:
		cmp	ax, 9		; incorrect es
		jne	retry
		mov	ax, OVERR_BADES
		jmp	done

;
; we get here if the memory block is adjusted atleast for degraded performance
;

;
; get the actual size of overlay, size of DS and size of heap
mcb_done:
		mov	bx, seg START_SEGMENT	; size of ovlay is size of MCB
		dec	bx		; start of MCB
		mov	es, bx
		xor	di, di	
		mov	bx, es:[di].SizeMCB	; bx is size of ovlay in paragraphs
		mov	ss:__asizovl, bx

		mov	ax, bx
		sub	ax, seg DGROUP
		add	ax, seg START_SEGMENT
		mov	cl, 4
		shl	ax, cl		; ax = size of DS in bytes
		dec	ax			; less one byte to avoid 64K==0
		mov	ss:__asizds, ax

		mov	ax, ss:__atopsp
		add	ax, 2			; two bytes beyond top of stack
		mov	ss:__aheap, ax	; start of heap
		mov	bx, ss:__asizds
		sub	bx, ax			; bx = size of heap
		mov	ss:__asizheap, bx
;
; init DS and SS
;

		push	ss		; set DS and ES
		pop	es
		push	ss
		pop	ds

;
; zero out the _BSS area, ie, all bytes from start of _BSS to STACK
;
		cld
		mov	di, offset DGROUP:_BSS
		mov	cx, offset DGROUP:STACK
		sub	cx, di
		xor	ax, ax
		rep	stosb

; set __osversion
		mov	ah, 30h
		int	21
		mov	word ptr ds:__osversion,ax

		call	_locate_int2f	; gets ISR address
		jnc	done

; if not present proceed to install
; set IVT entry to be our function

not_present:
		call	_install_ivt	; install new IVT, 2f
					; ax set to proper error
					; ... in install_int2f

done:
		mov	ss, cs:saved_ss
		mov	sp, cs:saved_sp
		pop	bp
		ret			; return if present

_init_overlay	endp

TRANSIENT_TEXT	ends

		END