; ; DESCRIPTION ; This code is a model independent version of DOS exec that will swap ; the calling process out to secondary storage prior to running the ; child. The prototype for calling the exec function is below. ; ; exec( int swap, char far *program, char far *cmdtail, ; int environment_seg, char far *tmpfilename ); ; ; ; To assemble this file issue the command: ; ; tasm /mx /t /dmmodel exec.asm ; ; where 'model' is one of {small, compact, medium, large}, you may ; also use MASM 5.1 to assemble this file, in this case simply replace ; 'tasm' with 'masm' in the above command line. ; ; AUTHOR ; Dennis Vadura, dvadura@watdragon.uwaterloo.ca ; CS DEPT, University of Waterloo, Waterloo, Ont., Canada ; ; COPYRIGHT ; Copyright (c) 1990 by Dennis Vadura. All rights reserved. ; ; This program is free software; you can redistribute it and/or ; modify it under the terms of the GNU General Public License ; (version 1), as published by the Free Software Foundation, and ; found in the file 'LICENSE' included with this distribution. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warrant of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ; ifdef have286 .286 ; define have286 with -D for 80286 processor or better mpusha Macro pusha Endm mpopa Macro popa Endm else ; 8088/8086 compatible mpusha Macro push ax push cx push dx push bx push sp push bp push si push di Endm mpopa Macro pop di pop si pop bp add sp,2 pop bx pop dx pop cx pop ax Endm endif ifdef msmall .model small argbase equ 4 endif ifdef mcompact .model compact argbase equ 4 endif ifdef mmedium .model medium argbase equ 6 endif ifdef mlarge .model large argbase equ 6 endif a_swap equ a_prog equ a_tail equ a_env equ a_tmp equ a_handle equ ; Define all useful equ's swap_xms equ 0 ; we swapped it out to xms swap_ems equ 2 ; we swapped it out to ems swap_file equ 4 ; we swapped it out to a file seg_no_alloc equ 0 ; this is part of a segment seg_alloc equ 1 ; this is a full segment header seg_data equ 2 ; this is data for part of a segment ; Define any global/external variables that we will be accessing from here. .data extrn _errno:word ; Set to dos ret code from exec public _Interrupted ; Set to 1 if interrupted 0 _Interrupted dw 0 ; otherwise .code assume cs:@code, ds:@code, ss:@code, es:@code even execstack dw 64 dup (?) ; put the temporary exec stack right exec_sp label word ; at the start. old_ss dw ? ; save stack seg across exec old_sp dw ? ; save stack ptr across exec progsize dw ? ; original size of the program rootsize dw ? ; size of base root kept during swap resend dw ? ; paragraph where resident code ends envseg dw ? ; paragraph of environment segment psp dw ? ; our own psp swap dw ? ; swapping selection flag eretcode dw ? ; return code from exec interrupted dw ? ; interrupted flag for exec arenahead dw ? ; start of memory block list alstr dw ? ; allocation strategy save spot in_exec dw 0 ; flag, 1 ==> in exec cmdpath db 65 dup(?) ; file to exec cmdtail db 129 dup(?) ; its command tail fcb db 37 dup(0) ; dummy fcb tmpseg db 7 dup(?) ; block header buffer tmpname db 65 dup(0) ; name of temporary file resource even tmphandle dw ? ; handle for temporary file real_21h dd 0 ; will be DOS's 21h vector if doing -C std_fil_handle dw ? ; file handle for -C file std_fil_number db ? ; system file number for -C file our_stdout db ? ; sys file number our stdout handle error_rhdr db "exec: Failure reading header block", 0DH, 0AH, '$' error_rseg db "exec: Failure reading segment data", 0DH, 0AH, '$' error_resize db "exec: Failure on resize", 0DH, 0AH, '$' error_free db "exec: Failure to free a block", 0DH, 0AH, '$' error_string db "exec: Program swap failure", 0DH, 0AH, '$' error_alloc db "exec: Memory blocks don't match", 0DH, 0AH, '$' even write_header label word whdr_xms_ptr dw word ptr whdr_xms whdr_ems_ptr dw word ptr whdr_ems whdr_file_ptr dw word ptr whdr_file write_seg label word wseg_xms_ptr dw word ptr wseg_xms wseg_ems_ptr dw word ptr wseg_ems wseg_file_ptr dw word ptr wseg_file read_header label word rhdr_xms_ptr dw word ptr rhdr_xms rhdr_ems_ptr dw word ptr rhdr_ems rhdr_file_ptr dw word ptr rhdr_file read_seg label word rseg_xms_ptr dw word ptr rseg_xms rseg_ems_ptr dw word ptr rseg_ems rseg_file_ptr dw word ptr rseg_file free_resource label word free_xms_ptr dw word ptr free_xms_resource free_ems_ptr dw word ptr free_ems_resource free_file_ptr dw word ptr free_file_resource reset_resource label word reset_xms_ptr dw word ptr reset_xms_resource reset_ems_ptr dw word ptr reset_ems_resource reset_file_ptr dw word ptr reset_file_resource old_ctl_brk label dword old_ctl_brk_off dw ? old_ctl_brk_seg dw ? old_crit_err label dword old_crit_err_off dw ? old_crit_err_seg dw ? exec_block label word ex_envseg dw ? ; env seg, use parent's if 0 ex_cmdtail dd ? ; command tail for exec ex_fcb1 dd far ptr fcb ; fcb's aren't used by dmake ex_fcb2 dd far ptr fcb ex_ss dw ? ; saved ss for exec ex_sp dw ? ; saved sp for exec ex_error dw 0 ; error code for dos exec ; Special 21h (DOS call) handler to tee stdout/stderr writes to the -C file. ; Ignore 21h calls that aren't writes to 1 or 2; i.e., pass them to DOS handler. ; If write call was from this process, it's pretty simple to duplicate it ; to the -C file. If it's from another process, we try to write to its ; inherited handle. Worst case is where the handle wasn't inherited: someone ; closed it. In that instance we have to switch to dmake's PSP to do the ; duplicate write. ; Subprocesses do not get their stdout/stderr teed to the -C file if ; their stdout/stderr no longer points to the file/device that dmake's ; stdout points to. This is tested by looking at the process's job ; file table, which is a table that maps process handles to DOS system file ; table numbers. (The far pointer to the JFT is at the PSP offset 34h.) ; The JFT is also queried to see if the -C file was inherited. ; O_BINARY, O_TEXT problems are ignored here. These are fudged by the ; C library before it calls DOS; since we're working below that level ; we don't have to worry about it. simulate_21h Macro pushf ;; direct call to DOS call cs:[real_21h] Endm assume cs:@code, ds:nothing, es:nothing, ss:nothing our_21h_handler proc far pushf cmp ah,40h ; is this a write? jne call_dos ; --no cmp bx,1 ; write on handle 1 (stdout?) je duplicate_it cmp bx,2 ; stderr? je duplicate_it call_dos: popf jmp [real_21h] ; far jump to real handler, which will do the sys call ; and return to the original caller duplicate_it: mpusha push ds push es mov bp,sp mov di,std_fil_handle ; handle of the -C file If @CodeSize eq 0 ; Small/compact models allow for quick test of us versus subprocess. ; False negative (it's us with a different CS) will be picked ; up by code just below. (Might happen due to call from C library.) ; False positives would be bad, but can't happen. mov ax,[bp+24] ; caller's CS cmp ax,@code ; same as us? je call_from_dmake Endif mov ah,51h ; get PSP ("undocumented version" works in DOS 2.0+) simulate_21h ; PSP segment returned in BX cmp bx,psp ; our PSP? je call_from_dmake ; --yes, no PSP changing needed mov es,bx ; set ES to current (caller's) PSP lds bx,es:[34h] ; set DS:BX pointing to caller's job file table mov si,[bp+12] ; file handle caller passed in (known to be 1 or 2) mov al,[bx+si] ; system file number corresponding to caller's handle cmp al,our_stdout ; same as our stdout? jne do_real_write ; no--subprocess must have redirected it mov al,[bx+di] ; see if caller has dup of -C file still open cmp al,std_fil_number je use_dup ; yes--we can write using caller's PSP ; Calling process (or some intermediate process) has closed ; the -C descriptor. We'll use dmake's (our) -C descriptor, but ; to do so we'll have to change the PSP. Disable BREAK handling ; so that ^break doesn't kill the wrong process. mov ax,3300h ; get BREAK flag simulate_21h mov si,dx ; save BREAK state in SI sub dx,dx ; now turn break flag off mov ax,3301h simulate_21h ; don't want ^Break recoginized while PSP changed mov bx,psp ; set dmake's PSP mov ah,50h simulate_21h mov bx,di ; handle of -C file ; CX still has caller's count mov ds,[bp+2] ; restore caller's DS mov dx,[bp+14] ; DS:DX again points to caller's buffer mov ah,40h simulate_21h ; write the copy mov bx,es ; caller's PSP mov ah,50h ; set PSP simulate_21h ; restore caller's PSP mov dx,si ; break state before we changed it mov ax,3301h simulate_21h ; restore break state jmp short do_real_write use_dup: mov ds,[bp+2] ; restore caller's DS mov dx,[bp+14] ; DS:DX again points to caller's buffer call_from_dmake: mov bx,di ; handle of -C file mov ah,40h ; write ; CX still has caller's count simulate_21h ; write to the file do_real_write: pop es pop ds mpopa popf jmp [real_21h] ; far jump to real handler, which will do the sys call ; and return to the original caller our_21h_handler endp assume cs:@code, ds:@code, ss:@code, es:@code ;----------------------------------------------------------------------------- ; First define the critical-error and control-brk handlers. ; The critical error handler simply pops the machine state and returns an ; access denied result code. crit_err_handler proc far add sp, 6 ; ip/cs/flags ... pop ax pop bx pop cx pop dx pop si pop di pop bp pop ds pop es push bp ; fix up the return flags mov bp, sp xchg ax, [bp+6] ; get the flag byte. or ax, 1 ; set the carry bit xchg ax, [bp+6] ; put it back. pop bp mov ax, 5 ; access denied iret crit_err_handler endp ;----------------------------------------------------------------------------- ; Here we set the interrupted flag, and terminate the currently running ; process. ctl_brk_handler proc far clc ; make sure carry is clear inc cs:interrupted ; set the flag ; Make certain it isn't us that is going to get terminated. ; There is a small window where the in_exec flag is set but the child is ; not running yet, I assume that DOS doesn't test for ctl_brk at that time ; as it is bussily creating a new process. cmp cs:in_exec,0 je just_return ; note this implies CF == 0 stc ; set CF to abort child just_return: iret ctl_brk_handler endp ;----------------------------------------------------------------------------- ; Something really nasty happened, so abort the exec call and exit. ; This kills the calling process altogether, and is a very nasty way of ; termination since files may still be open etc. abort_exec_rhdr label near mov dx, offset error_rhdr jmp print_it abort_exec_rseg label near mov dx, offset error_rseg jmp print_it abort_exec_resize label near mov dx, offset error_resize jmp print_it abort_exec_free label near mov dx, offset error_free jmp print_it abort_exec_alloc label near mov dx, offset error_alloc jmp print_it abort_exec proc near mov dx, offset error_string print_it: push dx mov bx, [swap] call [free_resource+bx] mov ax, cs mov ds, ax pop dx mov ah, 9 int 21H kill_program: mov ax, 04cffH ; nuke it! int 21H abort_exec endp ;----------------------------------------------------------------------------- ; lodsw/stosw loop to copy data. Called only for word copy operations. ; ds:si - point at source ; es:di - point at destination ; cx - count of bytes to copy. copy_data proc near shr cx, 1 ; convert to word count jnc copy_words movsb copy_words: rep movsw ; copy the words. ret copy_data endp ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ XMS RECORDS. ;============================================================================= rhdr_xms proc near ret rhdr_xms endp rseg_xms proc near ret rseg_xms endp reset_xms_resource proc near ret reset_xms_resource endp free_xms_resource proc near ret free_xms_resource endp ;============================================================================= ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ EMS RECORDS. ;============================================================================= rhdr_ems proc near ret rhdr_ems endp rseg_ems proc near ret rseg_ems endp reset_ems_resource proc near ret reset_ems_resource endp free_ems_resource proc near ret free_ems_resource endp ;============================================================================= ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ FILE RECORDS. ;============================================================================= ; This routine reads a segment header from a file. ; The header is a seven byte record formatted as follows: ; segment address - of data ; offset address - of data ; length in paragraphs - of data ; mode - 1 => segment header (allocate seg on read) ; 0 => subsegment, don't allocate on read. ; The information is placed into the tmpseg data area in the code segment. ; The routine aborts if an error is detected. rhdr_file proc near mov dx, offset tmpseg ; read the header record out mov cx, 7 mov bx, [tmphandle] mov ah, 03fH int 21H jnc rhdr_done ; make sure it worked jmp abort_exec_rhdr rhdr_done: cmp ax, 7 je exit_rhdr_file or ax, ax je signal_eof jmp abort_exec_rhdr signal_eof: stc exit_rhdr_file: ret rhdr_file endp ;----------------------------------------------------------------------------- ; Read a segment from the temporary file whose handle is in cs:tmphandle. ; The routine aborts if an error is detected. rseg_file proc near push ds mov ds, word ptr cs:tmpseg; Now read the whole segment mov dx, word ptr cs:tmpseg+2 mov cx, word ptr cs:tmpseg+4 mov bx, cs:tmphandle mov ah, 03fH int 21H pop ds jnc rseg_done jmp abort_exec_rseg rseg_done: cmp ax, [word ptr tmpseg+4] je exit_rseg_file jmp abort_exec_rseg ; If we didn't get read full exit_rseg_file: ret ; segment then abort rseg_file endp ;----------------------------------------------------------------------------- ; Seek to the beginning of the file. reset_file_resource proc near mov bx, [tmphandle] xor cx, cx mov dx, cx mov ax, 04200H ; seek to begining of file int 21H ret reset_file_resource endp ;----------------------------------------------------------------------------- ; unlink the temporary file allocated for swapping. ; We close the file first, and then delete it. We ignore errors here since ; we can't do anything about them anyway. free_file_resource proc near mov bx, [tmphandle] ; get the file handle mov ah, 03eH ; close the file int 21H mov dx, offset tmpname ; Now delete the temp file mov ah, 041H int 21H ret free_file_resource endp ;============================================================================= ;============================================================================= ; CODE TO SWAP THE IMAGE IN FROM SECONDARY STORAGE ;============================================================================= swap_in proc near mov bx, [alstr] ; get previous alloc strategy mov ax, 5801H ; and set it back int 21H mov bx, [swap] ; get type of resource call [reset_resource+bx] ; reset the resource mov es, [psp] ; resize the program back mov bx, [progsize] ; to original size mov ah, 04AH int 21H jnc read_seg_loop jmp abort_exec read_seg_loop: mov bx, [swap] ; get type of resource call [read_header+bx] ; get seg header jc exit_swap_in ; all done mov al, [tmpseg+6] cmp al, seg_no_alloc ; see if dummy segment header je read_seg_loop cmp al, seg_alloc ; do we need to do an alloc? jne read_data ; nope ; Allocate back the memory for a segment that is not the [psp], note that this ; must come back to the same segment we had previously since other segments ; may have pointers stored in their variables that point to this segment using ; segment:offset long pointers. mov bx, [word ptr tmpseg+4] ; get count of paragraphs mov ah, 048H ; dos_alloc int 21H jc alloc_error ; oops! cmp ax, [word ptr tmpseg] ; did we get the same segment? je read_seg_loop ; yup! alloc_error: jmp abort_exec_alloc read_data: mov bx, [swap] call [read_seg+bx] ; this must succeed, if fail jmp read_seg_loop ; we never come back here exit_swap_in: mov bx, [swap] ; all done, so free resource call [free_resource+bx] ret swap_in endp ;============================================================================= ; CODE TO SWAP THE IMAGE OUT TO SECONDARY STORAGE ;============================================================================= ; This routine is called to swap the non-resident portion of the program ; out to the resource specified by the value of [cs:swap]. If the swap out ; fails, then appropriate routines are called to free the resources allocated ; up to that point. ; ; The steps used to swap the program out are as follows: ; - calculate new size of program to remain resident and size to swap ; out. ; - write out non-resident portion of current segment ; - walk DOS allocation chain and write out all other segments owned by ; the current program that are contiguous with the _psp segment ; - copy the environment down to low memory ; - resize the current _psp segment to savesize ; - free all segments belonging to program except current _psp segment swap_out proc near mov ax, 05800H ; get memory alocation strategy int 021H mov [alstr], ax ; and save it for future restoration. mov di, [psp] ; compute length of program to current mov bx, cs ; value of cs, and find program size sub bx, di ; by looking at length stored in mov ax, di ; arena header found in front of psp dec ax mov es, ax mov si, es:3 ; si is size of program in paragraphs mov [progsize], si ; progsize now contains the size. ; Now compute length of program segment to save. ; Length is: cs - psp + (offset overlay_code_here+15 >> 4) mov ax, offset overlay_code_here+15 shr ax, 1 shr ax, 1 shr ax, 1 shr ax, 1 add bx, ax ; bx is size of program to keep sub si, bx ; si is # of paragraphs to save. add di, bx ; di is paragraph to start at mov rootsize, bx mov resend, di ; cs:resend is saved start para mov al, seg_no_alloc ; set no allocation for segment call write_segment jc abort_swap_out ; We have now saved the portion of the program segment that will not remain ; resident during the exec. We should now walk the DOS allocation chain and ; write out all other segments owned by the current process. save_segments: mov ax, [psp] dec ax mov es, ax mov bx, offset write_segment_data call walk_arena_chain jc abort_swap_out ; Now we must walk the chain of allocated memory blocks again and free ; all those that are owned by the current process, except the one that is ; the current process' psp. free_segments: mov ax, [psp] dec ax mov es,ax mov bx, offset free_dos_segment call walk_arena_chain jnc resize_program jmp abort_exec_free ; can't fix it up now. ; We now resize the program to the size specified by cs:rootsize. This will ; free most of the memory taken up by the current program segment. resize_program: mov es, [psp] ; es is segment to resize. mov bx, [rootsize] ; bx is size of segment. mov ah, 04aH ; resize memory block int 21H jnc swap_out_ok jmp abort_exec_resize ; disaster swap_out_ok: ret ; The swap out failed for some reason, so free any allocated resources ; and set the carry bit. abort_swap_out: mov bx, [swap] call [free_resource+bx] xor ax, ax mov [swap], ax ; clear the swap flag stc ret swap_out endp ;============================================================================= ; CODE TO SET-UP FOR AND EXEC THE CHILD PROCESS ;============================================================================= ; Actually execute the program. If cs:swap is set, this code will invoke the ; swap-out/swap-in code as required. do_exec proc near cmp [swap], 0 ; does the user want to swap? je no_swap_out ; nope call init_swap ; figger out where to swap to jc no_swap_out ; if carry set then don't swap call swap_out no_swap_out: cmp [interrupted], 0 ; were we interrupted? jne leave_exec ; yep, so clean up, don't exec ; free passed in environment block if it is non zero. ; This way the parent program does not need to free it. mov ax, [envseg] or ax, ax je setup_block push ax mov es, ax mov ah, 49H int 21H pop ax ; set up the parameter block for the DOS exec call. ; offset contents ; 00 segment address of environment to be passed, ; 0 => use parents env. ; 02 pointer to command tail for new process. ; 06 pointer to fcb1 ; 0a pointer to fcb2 setup_block: mov ax, [envseg] mov [ex_envseg], ax mov cx, cs mov [word ptr ex_cmdtail], offset cmdtail mov [word ptr ex_cmdtail+2], cx ; set up registers for exec call ; ds:dx - pointer to pathname of program to execute ; es:bx - pointer to above parameter block mov dx, offset cmdpath mov es, cx mov bx, offset exec_block ; Under DOS 2.x exec is notorious for clobbering registers and guarantees ; to preserve only cs:ip. push ds mov [ex_sp], sp mov [ex_ss], ss mov [ex_error], 0 ; clear exec error code inc [in_exec] ; set internal flag mov ax, 04b00H int 21H ; returned from exec, so restore possibly clobbered registers. mov ss, cs:ex_ss mov sp, cs:ex_sp pop ds ; check to make certain the exec call worked. jnc it_worked ; exec call failed. Save return code from msdos. mov [ex_error], ax jmp leave_exec it_worked: mov ah, 04dH ; get the return code int 21H cmp ah,1 ; check if terminated by ^C jnz nosigint inc interrupted ; yes so set flag nosigint: xor ah, ah ; 8-bit return code, so clear ah mov [eretcode], ax leave_exec: cmp [swap], 0 ; check swap, if non-zero swap back in je no_swap_in call swap_in ; Clear the in_exec after the swap back in. This way we are guaranteed to ; get parent in and the resources freed should a ^C be hit when we are reading ; the image in. no_swap_in: mov [in_exec], 0 ret do_exec endp ;============================================================================== ; Everything past this point is overwriten with the environment and new ; program after the currently executing program is swapped out. ;============================================================================== overlay_code_here label word ;----------------------------------------------------------------------------- ; Figure out where we can swap to and initialize the resource we are going to ; use. We try XMS, EMS, and a tempfile (if specified), in that order. We set ; [cs:swap] to the correct value based on which of the resources exists. ; If none can be used, then [cs:swap] is set to 0, and no swap takes place. ; The exec code will still attempt to execute the child in this instance, but ; may fail due to lack of resources. Each swap_out_* routine must provide ; its own clean-up handler should it not be able to write all program ; segments to the swap resource. init_swap proc near mov [swap], 0 ;call init_xms ;jnc init_done ;call init_ems ;jnc init_done call init_file init_done: ret init_swap endp ;----------------------------------------------------------------------------- ; This routine is used to walk the DOS allocated memory block chain ; starting at address supplied in the es register. For each block it ; calls the routine specified by the bx register with the segment length ; in si, and its address in di. It does not apply the routine to the ; segment if the segment is the same as the current program's [cs:psp] value. memheader struc magic db ? ; either 'Z' for end or 'M' for allocated owner dw ? ; psp of owner block len dw ? ; length in paragraphs of segment memheader ends walk_arena_chain proc near mov si, word ptr es:3 ; get length mov di, es inc di mov ax, word ptr es:1 ; Stop the search if the block is NOT owned by us. Ignore our own psp block ; and our environment segment block. cmp ax, cs:psp ; is it owned by us? jne walk_done ; NOPE! -- all done cmp di, cs:envseg ; skip our environment je next_block cmp di, cs:psp ; skip our psp je next_block ; Now save state and call the routine pointed at by [bx]. push di push si push bx call bx pop bx pop si pop di jc exit_walk ; if error then stop mov al, byte ptr es:0 ; check if at end cmp al, 'Z' je walk_done next_block: add di, si ; go on to next segment mov es, di jmp walk_arena_chain walk_done: clc exit_walk: ret walk_arena_chain endp ;----------------------------------------------------------------------------- ; This routine takes a dos segment found in the di register and free's it. free_dos_segment proc near mov es, di ; free dos memory block mov ah, 49H int 21H ret free_dos_segment endp ;----------------------------------------------------------------------------- ; Called to invoke write_segment with proper values in the al register. Only ; ever called from walk_arena_chain, and so al should be set to seg_alloc. write_segment_data label near mov al, seg_alloc ; and fall through into write_segment ;----------------------------------------------------------------------------- ; This routine writes a segment as a block of data segments if the number of ; paragraphs to write exceeds 0x0fff (rarely the case). ; It stuffs the info into tmpseg, and then calls wheader and wseg to get the ; data out. ; ; di:dx segment:offset of segment; offset is ALWAYS zero. ; si number of paragraphs to write. ; al mode of header to write write_segment proc near push di push si xor dx,dx mov bx, [swap] call [write_header+bx] pop si pop di jc exit_wseg do_io_loop: cmp si, 0 ; are we done yet? je exit_wseg ; yup so leave. mov cx, si ; # of paragraphs to move cmp cx, 0fffH ; see if we have lots to move? jle do_io mov cx, 0fffH ; reset to max I/O size do_io: push cx ; save # of paragraphs we are writing shl cx, 1 ; shift cx by four to the left shl cx, 1 shl cx, 1 shl cx, 1 push di ; save the start, and count left push si mov si, cx xor dx,dx mov al, seg_data mov bx, [swap] push bx call [write_header+bx] pop bx call [write_seg+bx] pop si pop di pop dx ; original paragraph count in dx jc exit_wseg ; it failed so exit. add di, dx ; adjust the pointers, and continue. sub si, dx jmp do_io_loop exit_wseg: ret write_segment endp ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE XMS RECORDS. ;============================================================================= init_xms proc near ret init_xms endp whdr_xms proc near ret whdr_xms endp wseg_xms proc near ret wseg_xms endp ;============================================================================= ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE EMS RECORDS. ;============================================================================= init_ems proc near ret init_ems endp whdr_ems proc near ret whdr_ems endp wseg_ems proc near ret wseg_ems endp ;============================================================================= ;============================================================================= ; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE FILES. ;============================================================================= ;----------------------------------------------------------------------------- ; Attempt to create a temporary file. If the tempfile name is NIL then return ; with the cary flag set. init_file proc near mov al, [tmpname] or al, al je err_init_file mov dx, offset tmpname xor cx, cx mov ah, 03cH int 21H jc err_init_file ; if carry set then failure mov [tmphandle], ax ; init swapping mov [swap], swap_file jmp exit_init_file err_init_file: stc exit_init_file: ret init_file endp ;----------------------------------------------------------------------------- ; This routine writes a segment header to a file. ; The header is a seven byte record formatted as follows: ; segment address - of data ; offset address - of data ; length in paragraphs - of data ; mode - 1 => segment header (allocate seg on read) ; 0 => subsegment, don't allocate on read. ; Routine takes three arguments: ; di:dx segment:offset of segment ; si number of paragraphs to write. ; al mode of header to write whdr_file proc near mov [word ptr tmpseg], di ; save the segment/offset mov [word ptr tmpseg+2], dx mov [word ptr tmpseg+4], si ; save the segment length mov [tmpseg+6], al mov dx, offset tmpseg ; write the header record out mov cx, 7 mov bx, [tmphandle] mov ah, 040H int 21H jc exit_whdr_file ; make sure it worked cmp ax, 7 je exit_whdr_file ; oh oh, disk is full! err_whdr_file: stc exit_whdr_file: ret whdr_file endp ;----------------------------------------------------------------------------- ; Write a segment to the temporary file whose handle is in cs:tmphandle ; Parameters for the write are assumed to be stored in the tmpseg data area. ; function returns carry set if failed, carry clear otherwise. wseg_file proc near push ds mov ds, word ptr cs:tmpseg ; Now write the whole segment mov dx, word ptr cs:tmpseg+2 mov cx, word ptr cs:tmpseg+4 mov bx, cs:tmphandle mov ah, 040H int 21H pop ds jc exit_wseg_file ; make sure it worked cmp ax, [word ptr tmpseg+4] je exit_wseg_file err_wseg_file: stc ; it failed (usually disk full) exit_wseg_file: ret wseg_file endp ;============================================================================= ;============================================================================= ; _exec: THIS IS THE MAIN ENTRY ROUTINE TO THIS MODULE ;============================================================================= ; This is the main entry routine into the swap code and corresponds to the ; following C function call: ; ; exec( int swap, char far *program, char far *cmdtail, int environment_seg, ; char far *tmpfilename ); ; ; Exec performs the following: ; 1. set up the local code segment copies of arguments to the exec call. ; 2. switch to a local stack frame so that we don't clobber the user ; stack. ; 3. save old interrupt vectors for ctrl-brk. ; 4. install our own handler for the ctrl-brk interrupt, our handler ; terminates the current running process, and returns with non-zero ; status code. ; 5. get our psp ; 6. setup arguments for exec call ; 7. exec the program, save result code on return. ; 8. restore previous ctrl-brk and crit-error handler. ; 9. restore previous process stack, and segment registers. ; 10. return from exec with child result code in AX ; and global _Interrupted flag set to true if child execution was ; interrupted. ; NOTE: When first called the segments here assume the standard segment ; settings. assume cs:@code, ds:DGROUP,es:DGROUP,ss:DGROUP public _exec _exec proc push bp ; set up the stack frame mov bp, sp push si ; save registers we shouldn't step on. push di push ds ; set up for copying of parameters passed in with long pointers. push cs ; going to use lodsb/stosb, set up es pop es ; as destination. assume es:@code ; let the assembler know :-) cld ; make sure direction is right ; Copy all parameters into the bottom of the code segment. After doing so we ; will immediately switch stacks, so that the user stack is preserved intact. mov ax, ss:[a_swap] ; save swap mov es:swap, ax mov ax, ss:[a_env] ; save env seg to use mov es:envseg, ax mov di, offset cs:cmdpath ; copy the command lds si, ss:[a_prog] ; 65 bytes worth mov cx, 65 call copy_data mov di, offset cs:cmdtail ; copy the command tail lds si, ss:[a_tail] ; 129 bytes worth mov cx, 129 call copy_data mov di, offset cs:tmpname ; copy the temp file name lds si, ss:[a_tmp] ; 65 bytes worth. mov cx, 65 call copy_data ; Now we save the current ss:sp stack pointer and swap stack to our temporary ; stack located in the current code segment. At the same time we reset the ; segment pointers to point into the code segment only. swap_stacks: mov ax, ss mov es:old_ss, ax mov es:old_sp, sp mov ax, cs mov ds, ax mov ss, ax ; set ss first, ints are then mov sp, offset cs:exec_sp ; disabled for this instr too assume ds:@code, ss:@code ; let the assembler know :-) ; Now we save the old control break and critical error handler addresses. ; We replace them by our own routines found in the resident portion of the ; swapping exec code. set_handlers: mov [interrupted], 0 ; clear interrupted flag mov [eretcode], 0 ; clear the return code mov ax, 03523H ; get int 23 handler address int 21H mov cs:old_ctl_brk_off, bx mov cs:old_ctl_brk_seg, es mov dx, offset ctl_brk_handler mov ax, 02523H ; set int 23 handler address int 21H mov ax, 03524H ; get int 24 handler address int 21H mov cs:old_crit_err_off, bx mov cs:old_crit_err_seg, es mov dx, offset crit_err_handler mov ax, 02524H ; set int 24 handler address int 21H ; Go and execute the child, we've set up all of its parameters. The do_exec ; routine will attempt to perform a swap of the code if requested to do so by ; a non-zero value in the variable cs:swap. mov ah, 051H ; get the psp int 21H mov cs:psp, bx call do_exec ; We're back from the exec, so fix things up the way they were. ; Restore the old control-break and critical-error handlers. lds dx, cs:old_ctl_brk mov ax, 02523H int 21H lds dx, cs:old_crit_err mov ax, 02524H int 21H ; Restore previous program stack segment registers, and data segment. mov ax, cs:old_ss mov ss, ax ; mov into ss first, that way mov sp, cs:old_sp ; no interrupts in this instr. pop ds ; Tell the assembler we have swaped segments again. assume ds:DGROUP,es:DGROUP,ss:DGROUP ; Set the global Interrupted flag so that parent can tell it was interrupted. mov ax, seg DGROUP:_Interrupted mov es, ax mov ax, cs:interrupted mov es:_Interrupted, ax ; Set the global errno value to reflect the success/failure of the DOS ; exec call. mov ax, seg DGROUP:_errno mov es, ax mov ax, cs:ex_error mov es:_errno, ax ; Fetch the child's return code, pop rest of stuff off of the stack ; and return to the caller. mov ax, cs:eretcode pop di pop si pop bp ret _exec endp ; void do_hook_std_writes(int handle); ; This saves the 21h interrupt vector and changes it to point ; into this code. Argument is the file handle of the -C file. public _do_hook_std_writes _do_hook_std_writes proc push bp mov bp,sp push di mov di, ss:[a_handle] ; handle of -C file mov std_fil_handle, di mov ah, 51h ; request our PSP int 21h mov [psp], bx ; save it mov es, bx les bx, es:[34h] ; pointer to job file table mov al, es:[bx+1] ; system file # of our stdout mov [our_stdout], al mov al, es:[bx+di] ; system file number of -C file mov std_fil_number, al mov ax,3521h ; request vector 21h int 21h ; it's returned in ES:BX mov word ptr [real_21h], bx mov word ptr [real_21h+2], es push ds mov ax,cs mov ds,ax lea dx,our_21h_handler ; DS:DX is the new vector mov ax,2521h ; set vector 21h int 21h pop ds pop di pop bp ret _do_hook_std_writes endp ; void do_unhook_std_writes(void); ; This restores the 21h interrupt vector. ; The saved vector is zero if it wasn't changed (no -C option). public _do_unhook_std_writes _do_unhook_std_writes proc push ds lds dx, [real_21h] ; put saved vector into DS:DX mov ax, ds or ax, dx jz unhook_return ; zero means we didn't hook 21h mov ax,2521h ; set vector 21h simulate_21h unhook_return: pop ds ret _do_unhook_std_writes endp end