1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
|
;
; 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 <bp+argbase+0>
a_prog equ <bp+argbase+2>
a_tail equ <bp+argbase+6>
a_env equ <bp+argbase+10>
a_tmp equ <bp+argbase+12>
a_handle equ <bp+argbase>
; 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
|