; ; EXE file loader ; ; David Lindauer, March 25, 1997 ; ; This should run MOST EXE files, if not all of them. The only ; especially tricky part is marking the newly allocated memory ; segments with an appropriate owner so that the memory will be ; released properly when the program exits. ; ; Note: there are some aspects of DOS compatability I haven't taken ; care of here; for example the program name should show up right after ; the environment with a fully qualified path; and it should show up ; again right after the command line without the path. I may have missed ; other stuff as well... ; ; .model tiny .386 ;_____________________________________________________________________ ; ; Header struct. You need to read 1ch bytes to get this info ; headstruct struc sig dw ? ; EXE signature- we exit if not 'MZ' mlength dw ? ; length modulo 512 pages dw ? ; length in pages, rounded UP relocs dw ? ; Number of relocation items headsize dw ? ; Size of header in paragraphs ; We will later assume this is less than 1000h ; paragraphs but you probably should fix that minalloc dw ? ; Minimum memory needed maxalloc dw ? ; Maximum memory desired dispss dw ? ; starting stack basesp dw ? ; checksum dw ? ; We'll ignore the checksum. Dos ignores it too ; if it is zero baseip dw ? ; Starting CS:IP dispcs dw ? relocofs dw ? ; Offset to relocation table overlay dw ? ; I never saw a program with the overlay field set ; so I don't know what happens here... headstruct ends .code org 100h start: jmp go db 0C4h ; word aligning the stack ;_____________________________________________________________________ ; ; Internal stack ; dw 128 DUP ('?') tos: ;_____________________________________________________________________ ; ; Internal variables ; header headstruct ? relox dd ? handle dw ? query db 10,13,"Please enter name of file to execute: ",'$' response db 40,0,40 DUP (?) ;_____________________________________________________________________ ; ; Failure comes here ; errmsg db 10,13,"failed.",10,13,'$' failure: push cs ; print a message if something fails pop ds mov dx,offset errmsg mov ah,9 int 21h mov ax,4c01h int 21h ;_____________________________________________________________________ ; ; Prepare an empty environment. COMMAND.COM copies the environment of ; the parent to a new hunk of memory and throws some data in after it; ; We just make an empty environment ; prepare_env: mov bx,1 ; need one paragraph mov ah,48h int 21h jc failure mov es,ax ; Zero it out sub di,di sub ax,ax mov cx,8 rep stosw ret ;_____________________________________________________________________ ; ; Allocate useable memory as indicated by header ; allocate_allmem: mov bx,[header.maxalloc]; find out how much mem push bx mov ah,48h int 21h jnc finish pop ax cmp bx,[header.minalloc] jb failure push bx ; Allocate it mov ah,48h int 21h finish: mov es,ax pop bx ; returning size of block jc failure ret ;_____________________________________________________________________ ; ; Initialize the new PSP ; ; Note: this should handle most DOS .COM files. Of course it ; makes little sense to go to all this trouble if we aren't running DOS ; programs :). BTW this should let them run, but it may keep them from ; important information such as command lines and environments ; prepare_psp: push ax ; start by zeroing it out mov cx,128 sub ax,ax sub di,di rep stosw pop ax mov word ptr es:[2ch],ax ; Pointer to our environment mov word ptr es:[0],20cdh ; null return to DOS mov ax,es add ax,bx mov word ptr es:[2],ax ; Next free block (usually A000) mov byte ptr es:[5],9ah ; CALLF ; ; Now we grab stuff from the original PSP. Usually DOS initializes ; this stuff with values out of the appropriate vectors but I got lazy... ; mov si,6 ; vectors 21h-24h copy from old PSP mov di,6 mov cx,8 rep movsw mov ax,ds:[16h] ; ; This next field would typically be set to point at itself or ; the parent which did the spawning; I set it to the parent of ; this loader program ; mov word ptr es:[16h],ax ; Common parent mov byte ptr es:[18h],1 ; Handle table mov byte ptr es:[19h],1 mov byte ptr es:[1ah],1 mov byte ptr es:[1bh],0 mov byte ptr es:[1ch],2 mov di, 1dh ; blank out unused entries (handles) mov al,0ffh mov cx,15 rep stosb mov word ptr es:[32h],20 ; 20 handles available mov word ptr es:[34h],18h ; Point at handle table mov word ptr es:[36h],ES ; mov dword ptr es:[38h],-1 ; reserved mov word ptr es:[50h],21CDh ; dummy DOS call mov byte ptr es:[52h],0CBH ; ; The rest of this stuff has to be modified if you put in command line support ; mov di,5dh ; Now blank out the FCB file names mov al,20h mov cx,8 rep stosb mov di,6dh mov cx,8 rep stosb mov word ptr es:[80h],0D00h ; Indicate no command line ret ;_____________________________________________________________________ ; ; Reclassify the memory for the PSP and ENV as being owned by the PSP ; If we don't do this the memory may not get freed up... which typically ; results in a lockup. ; rename_arena: push es push es mov ax,es:[2ch] dec ax mov es,ax pop ax mov es:[1],ax dec ax mov es,ax pop ax mov es:[1],ax mov es,ax ret ;_____________________________________________________________________ ; ; Load the program into memory ; load_program: mov ax,[header.pages] ; calculate size of prog dec ax sub cx,cx mov cl,ah mov ah,al mov al,0 add ax,ax adc cx,cx add ax,[header.mlength] adc cx,0 mov dx,es ; Point at the load address add dx,10h mov ds,dx push cx push ax push ds mov dx,cs:[header.headsize] ; finish adjusting size shl dx,4 sub ax,dx sbb cx,0 ; ; At this point we probably OUGHT to check that the program will fit ; in the allocated memory, just in case someone hand-built an EXE header ; and got it wrong... ; push cx ; point at program data push ax sub cx,cx mov ax,4200h mov bx,cs:[handle] int 21h pop ax pop cx jc failure mov bx,cs:[handle] ldlp: or cx,cx ; Less than 64K left? jz last ; Yep, load it sub ax,8000h ; Otherwise we load 32K worth sbb cx,0 ; push cx push ax mov cx,ax sub dx,dx mov ax,3f00h int 21h pop ax pop cx jc failure mov ax,ds add ax,800h mov ds,ax jmp ldlp last: mov cx,ax ; Load the remainder mov ax,3f00h sub dx,dx int 21h jc failure pop ds pop cx pop ax sub cx,200h ; adjust length the way DOS does it sbb ax,0 ret ;_____________________________________________________________________ ; ; Load the relocs load_relocs: test cs:[header.relocs],-1 ; get out if no relocs jz lfexit sub cx,cx mov dx,cs:[header.relocofs] ; position to start of reloc table mov ax,4200h int 21h jc failure mov dx,ds ; DX = start of prog push cs pop ds mov cx,[header.relocs] ; Get number of relocs mov bx,[handle] relolp: push cx ; Load a reloc push dx mov cx,4 mov dx, offset relox mov ah,3fh int 21h pop dx pop cx jc failure add word ptr [relox+2],dx ; Adjust to phys address push ds lds si,[relox] ; Readjust the reloc add [si],dx pop ds loop relolp ; continue till done lfexit: mov ds,dx ret ; ;_____________________________________________________________________ ; ; Program entry point ; go: mov sp,offset tos ; Switch to internal stack ; ; This is the standard ASM prelude for freeing up memory the program ; isn't using ; mov bx,offset eop ; Free up unused memory add bx,15 shr bx,4 mov ah,4ah int 21h ; ; Everything up to here is to adjust for the fact we are already a com file ; ; ; ; Put a prompt and get file name ; mov dx,offset query ; Get the file name mov ah,9 int 21h mov dx,offset response mov ah,10 int 21h mov bh,0 mov bl,[response+1] mov [bx+response+2],0 ; ; Open the file ; mov ax,3d00h ; Open the file mov dx,offset response+2 int 21h mov bx,ax mov [handle],ax jc failure ; ; Read the basic part of the header ; mov ax,3f00h ; Read the file mov dx,offset header mov cx,1ch int 21h jc failure mov ax,[header.sig] ; Fail if not an EXE cmp ax,"ZM" jnz failure ; First we make the environment. We are making an empty one but it should ; get copied either from the master environment or from the parent's ; environment call prepare_env ; Memory initialize ; ; Allocate program memory. We are going to allocate anything we can ; get our hands on. We HAVE to allocate this rather than just default ; to the pre-allocated memory because some programs depend on the PSP ; being at the beginning of an arena block ; push es call allocate_allmem pop ax ; ; Now initialize all relevant fields of the PSP ; call prepare_psp ; ; Retag the arena entries for the memory we have allocated so it will ; be freed properly ; call rename_arena ; rename the arena entries ; ; Load the program ; call load_program ; load prog push cx push ax push es mov ax,ds ; adjust header SS and CS add cs:[header.dispss],ax add cs:[header.dispcs],ax call load_relocs mov ax,3e00h ; close the file mov bx,cs:[handle] int 21h jc failure ; ; Now we free the resources of this loader program. Note this will ; leave a memory gap- a better solution would to have been to free it ; BEFORE allocating memory. Of course then we have to move ourselves ; out of the way ; mov es,cs:[2ch] ; free our environment mov ah,49h int 21h push cs ; Free our program seg pop es mov ah,49h int 21h ; ; Now we have to inform DOS that the active PSP has changed. It is normal ; to eventually restore the PSP in TSRs but we won't be coming back ; later. ; pop es mov bx,es ; Inform DOS about the new PSP mov ds,bx mov ah,50h int 21h pop bx pop cx ; ; Now we set up the segment registers and such. The registers we ; are initializing are as follows: ; ; DS,ES,SS,Sp,CS,IP ; BX:CX ; ; AFAIK that is all that has to be initialized. But maybe you ought to ; write a short program to dump the registers at startup of a com file ; and see? Don't depend on DEBUG... it zeros the registers but DOS ; doesn't do that. cli ; Set stack pointer to new process mov ss,cs:[header.dispss] mov sp,cs:[header.basesp] sti jmp dword ptr cs:[header.baseip] ; branch to process eop: end start