Convert DOS (MZ) binary loader to the new DOS process model.
[wine.git] / loader / dos / module.c
blob8fa92518da5514d457140a1c6eb4b0854cdaffd1
1 /*
2 * DOS (MZ) loader
4 * Copyright 1998 Ove Kåven
6 * This code hasn't been completely cleaned up yet.
7 */
9 #include "config.h"
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <signal.h>
17 #include <unistd.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/time.h>
21 #include "windef.h"
22 #include "wine/winbase16.h"
23 #include "winerror.h"
24 #include "module.h"
25 #include "neexe.h"
26 #include "task.h"
27 #include "selectors.h"
28 #include "file.h"
29 #include "ldt.h"
30 #include "process.h"
31 #include "miscemu.h"
32 #include "debugtools.h"
33 #include "dosexe.h"
34 #include "dosmod.h"
35 #include "options.h"
36 #include "server.h"
37 #include "vga.h"
39 DEFAULT_DEBUG_CHANNEL(module)
41 #ifdef MZ_SUPPORTED
43 #ifdef HAVE_SYS_MMAN_H
44 # include <sys/mman.h>
45 #endif
47 /* define this to try mapping through /proc/pid/mem instead of a temp file,
48 but Linus doesn't like mmapping /proc/pid/mem, so it doesn't work for me */
49 #undef MZ_MAPSELF
51 #define BIOS_DATA_SEGMENT 0x40
52 #define START_OFFSET 0
53 #define PSP_SIZE 0x10
55 #define SEG16(ptr,seg) ((LPVOID)((BYTE*)ptr+((DWORD)(seg)<<4)))
56 #define SEGPTR16(ptr,segptr) ((LPVOID)((BYTE*)ptr+((DWORD)SELECTOROF(segptr)<<4)+OFFSETOF(segptr)))
58 static LPDOSTASK dos_current;
60 static void MZ_Launch(void);
62 static void MZ_CreatePSP( LPVOID lpPSP, WORD env )
64 PDB16*psp=lpPSP;
66 psp->int20=0x20CD; /* int 20 */
67 /* some programs use this to calculate how much memory they need */
68 psp->nextParagraph=0x9FFF;
69 psp->environment=env;
70 /* FIXME: more PSP stuff */
73 static void MZ_FillPSP( LPVOID lpPSP, LPCSTR cmdline )
75 PDB16*psp=lpPSP;
76 const char*cmd=cmdline?strchr(cmdline,' '):NULL;
78 /* copy parameters */
79 if (cmd) {
80 #if 0
81 /* command.com doesn't do this */
82 while (*cmd == ' ') cmd++;
83 #endif
84 psp->cmdLine[0]=strlen(cmd);
85 strcpy(psp->cmdLine+1,cmd);
86 psp->cmdLine[psp->cmdLine[0]+1]='\r';
87 } else psp->cmdLine[1]='\r';
88 /* FIXME: more PSP stuff */
91 /* default INT 08 handler: increases timer tick counter but not much more */
92 static char int08[]={
93 0xCD,0x1C, /* int $0x1c */
94 0x50, /* pushw %ax */
95 0x1E, /* pushw %ds */
96 0xB8,0x40,0x00, /* movw $0x40,%ax */
97 0x8E,0xD8, /* movw %ax,%ds */
98 #if 0
99 0x83,0x06,0x6C,0x00,0x01, /* addw $1,(0x6c) */
100 0x83,0x16,0x6E,0x00,0x00, /* adcw $0,(0x6e) */
101 #else
102 0x66,0xFF,0x06,0x6C,0x00, /* incl (0x6c) */
103 #endif
104 0xB0,0x20, /* movb $0x20,%al */
105 0xE6,0x20, /* outb %al,$0x20 */
106 0x1F, /* popw %ax */
107 0x58, /* popw %ax */
108 0xCF /* iret */
111 static void MZ_InitHandlers( LPDOSTASK lpDosTask )
113 WORD seg;
114 LPBYTE start=DOSMEM_GetBlock(sizeof(int08),&seg);
115 memcpy(start,int08,sizeof(int08));
116 /* INT 08: point it at our tick-incrementing handler */
117 ((SEGPTR*)(lpDosTask->img))[0x08]=PTR_SEG_OFF_TO_SEGPTR(seg,0);
118 /* INT 1C: just point it to IRET, we don't want to handle it ourselves */
119 ((SEGPTR*)(lpDosTask->img))[0x1C]=PTR_SEG_OFF_TO_SEGPTR(seg,sizeof(int08)-1);
122 static char enter_xms[]={
123 /* XMS hookable entry point */
124 0xEB,0x03, /* jmp entry */
125 0x90,0x90,0x90, /* nop;nop;nop */
126 /* entry: */
127 /* real entry point */
128 /* for simplicity, we'll just use the same hook as DPMI below */
129 0xCD,0x31, /* int $0x31 */
130 0xCB /* lret */
133 static void MZ_InitXMS( LPDOSTASK lpDosTask )
135 LPBYTE start=DOSMEM_GetBlock(sizeof(enter_xms),&(lpDosTask->xms_seg));
136 memcpy(start,enter_xms,sizeof(enter_xms));
139 static char enter_pm[]={
140 0x50, /* pushw %ax */
141 0x52, /* pushw %dx */
142 0x55, /* pushw %bp */
143 0x89,0xE5, /* movw %sp,%bp */
144 /* get return CS */
145 0x8B,0x56,0x08, /* movw 8(%bp),%dx */
146 /* just call int 31 here to get into protected mode... */
147 /* it'll check whether it was called from dpmi_seg... */
148 0xCD,0x31, /* int $0x31 */
149 /* we are now in the context of a 16-bit relay call */
150 /* need to fixup our stack;
151 * 16-bit relay return address will be lost, but we won't worry quite yet */
152 0x8E,0xD0, /* movw %ax,%ss */
153 0x66,0x0F,0xB7,0xE5, /* movzwl %bp,%esp */
154 /* set return CS */
155 0x89,0x56,0x08, /* movw %dx,8(%bp) */
156 0x5D, /* popw %bp */
157 0x5A, /* popw %dx */
158 0x58, /* popw %ax */
159 0xCB /* lret */
162 static void MZ_InitDPMI( LPDOSTASK lpDosTask )
164 unsigned size=sizeof(enter_pm);
165 LPBYTE start=DOSMEM_GetBlock(size,&(lpDosTask->dpmi_seg));
167 lpDosTask->dpmi_sel = SELECTOR_AllocBlock( start, size, SEGMENT_CODE, FALSE, FALSE );
169 memcpy(start,enter_pm,sizeof(enter_pm));
172 static WORD MZ_InitEnvironment( LPDOSTASK lpDosTask, LPCSTR env, LPCSTR name )
174 unsigned sz=0;
175 WORD seg;
176 LPSTR envblk;
178 if (env) {
179 /* get size of environment block */
180 while (env[sz++]) sz+=strlen(env+sz)+1;
181 } else sz++;
182 /* allocate it */
183 envblk=DOSMEM_GetBlock(sz+sizeof(WORD)+strlen(name)+1,&seg);
184 /* fill it */
185 if (env) {
186 memcpy(envblk,env,sz);
187 } else envblk[0]=0;
188 /* DOS 3.x: the block contains 1 additional string */
189 *(WORD*)(envblk+sz)=1;
190 /* being the program name itself */
191 strcpy(envblk+sz+sizeof(WORD),name);
192 return seg;
195 static BOOL MZ_InitMemory( LPDOSTASK lpDosTask )
197 if (lpDosTask->img) return TRUE; /* already allocated */
199 /* allocate 1MB+64K shared memory */
200 lpDosTask->img_ofs=START_OFFSET;
201 #ifdef MZ_MAPSELF
202 lpDosTask->img=VirtualAlloc(NULL,0x110000,MEM_COMMIT,PAGE_READWRITE);
203 /* make sure mmap accepts it */
204 ((char*)lpDosTask->img)[0x10FFFF]=0;
205 #else
206 tmpnam(lpDosTask->mm_name);
207 /* strcpy(lpDosTask->mm_name,"/tmp/mydosimage"); */
208 lpDosTask->mm_fd=open(lpDosTask->mm_name,O_RDWR|O_CREAT /* |O_TRUNC */,S_IRUSR|S_IWUSR);
209 if (lpDosTask->mm_fd<0) ERR("file %s could not be opened\n",lpDosTask->mm_name);
210 /* expand file to 1MB+64K */
211 ftruncate(lpDosTask->mm_fd,0x110000);
212 /* map it in */
213 lpDosTask->img=mmap(NULL,0x110000-START_OFFSET,PROT_READ|PROT_WRITE,MAP_SHARED,lpDosTask->mm_fd,0);
214 #endif
215 if (lpDosTask->img==(LPVOID)-1) {
216 ERR("could not map shared memory, error=%s\n",strerror(errno));
217 return FALSE;
219 TRACE("DOS VM86 image mapped at %08lx\n",(DWORD)lpDosTask->img);
221 /* initialize the memory */
222 TRACE("Initializing DOS memory structures\n");
223 DOSMEM_Init(TRUE);
224 MZ_InitHandlers(lpDosTask);
225 MZ_InitXMS(lpDosTask);
226 MZ_InitDPMI(lpDosTask);
227 return TRUE;
230 BOOL MZ_LoadImage( HMODULE module, HANDLE hFile, LPCSTR filename )
232 LPDOSTASK lpDosTask = dos_current;
233 IMAGE_NT_HEADERS *win_hdr = PE_HEADER(module);
234 IMAGE_DOS_HEADER mz_header;
235 DWORD image_start,image_size,min_size,max_size,avail;
236 BYTE*psp_start,*load_start;
237 int x, old_com=0, alloc=0;
238 SEGPTR reloc;
239 WORD env_seg;
240 DWORD len;
242 win_hdr->OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI;
243 win_hdr->OptionalHeader.AddressOfEntryPoint = (LPBYTE)MZ_Launch - (LPBYTE)module;
245 if (!lpDosTask) {
246 alloc=1;
247 lpDosTask = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSTASK));
248 lpDosTask->mm_fd = -1;
249 dos_current = lpDosTask;
252 SetFilePointer(hFile,0,NULL,FILE_BEGIN);
253 if ( !ReadFile(hFile,&mz_header,sizeof(mz_header),&len,NULL)
254 || len != sizeof(mz_header)
255 || mz_header.e_magic != IMAGE_DOS_SIGNATURE) {
256 old_com=1; /* assume .COM file */
257 image_start=0;
258 image_size=GetFileSize(hFile,NULL);
259 min_size=0x10000; max_size=0x100000;
260 mz_header.e_crlc=0;
261 mz_header.e_ss=0; mz_header.e_sp=0xFFFE;
262 mz_header.e_cs=0; mz_header.e_ip=0x100;
263 } else {
264 /* calculate load size */
265 image_start=mz_header.e_cparhdr<<4;
266 image_size=mz_header.e_cp<<9; /* pages are 512 bytes */
267 if ((mz_header.e_cblp!=0)&&(mz_header.e_cblp!=4)) image_size-=512-mz_header.e_cblp;
268 image_size-=image_start;
269 min_size=image_size+((DWORD)mz_header.e_minalloc<<4)+(PSP_SIZE<<4);
270 max_size=image_size+((DWORD)mz_header.e_maxalloc<<4)+(PSP_SIZE<<4);
273 MZ_InitMemory(lpDosTask);
275 /* allocate environment block */
276 env_seg=MZ_InitEnvironment(lpDosTask,GetEnvironmentStringsA(),filename);
278 /* allocate memory for the executable */
279 TRACE("Allocating DOS memory (min=%ld, max=%ld)\n",min_size,max_size);
280 avail=DOSMEM_Available();
281 if (avail<min_size) {
282 ERR("insufficient DOS memory\n");
283 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
284 goto load_error;
286 if (avail>max_size) avail=max_size;
287 psp_start=DOSMEM_GetBlock(avail,&lpDosTask->psp_seg);
288 if (!psp_start) {
289 ERR("error allocating DOS memory\n");
290 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
291 goto load_error;
293 lpDosTask->load_seg=lpDosTask->psp_seg+(old_com?0:PSP_SIZE);
294 load_start=psp_start+(PSP_SIZE<<4);
295 MZ_CreatePSP(psp_start, env_seg);
297 /* load executable image */
298 TRACE("loading DOS %s image, %08lx bytes\n",old_com?"COM":"EXE",image_size);
299 SetFilePointer(hFile,image_start,NULL,FILE_BEGIN);
300 if (!ReadFile(hFile,load_start,image_size,&len,NULL) || len != image_size) {
301 SetLastError(ERROR_BAD_FORMAT);
302 goto load_error;
305 if (mz_header.e_crlc) {
306 /* load relocation table */
307 TRACE("loading DOS EXE relocation table, %d entries\n",mz_header.e_crlc);
308 /* FIXME: is this too slow without read buffering? */
309 SetFilePointer(hFile,mz_header.e_lfarlc,NULL,FILE_BEGIN);
310 for (x=0; x<mz_header.e_crlc; x++) {
311 if (!ReadFile(hFile,&reloc,sizeof(reloc),&len,NULL) || len != sizeof(reloc)) {
312 SetLastError(ERROR_BAD_FORMAT);
313 goto load_error;
315 *(WORD*)SEGPTR16(load_start,reloc)+=lpDosTask->load_seg;
319 lpDosTask->init_cs=lpDosTask->load_seg+mz_header.e_cs;
320 lpDosTask->init_ip=mz_header.e_ip;
321 lpDosTask->init_ss=lpDosTask->load_seg+mz_header.e_ss;
322 lpDosTask->init_sp=mz_header.e_sp;
324 TRACE("entry point: %04x:%04x\n",lpDosTask->init_cs,lpDosTask->init_ip);
326 if (!MZ_InitTask(lpDosTask)) {
327 MZ_KillTask(lpDosTask);
328 SetLastError(ERROR_GEN_FAILURE);
329 return FALSE;
332 return TRUE;
334 load_error:
335 if (alloc) {
336 dos_current = NULL;
337 if (lpDosTask->mm_name[0]!=0) {
338 if (lpDosTask->img!=NULL) munmap(lpDosTask->img,0x110000-START_OFFSET);
339 if (lpDosTask->mm_fd>=0) close(lpDosTask->mm_fd);
340 unlink(lpDosTask->mm_name);
341 } else
342 if (lpDosTask->img!=NULL) VirtualFree(lpDosTask->img,0x110000,MEM_RELEASE);
345 return FALSE;
348 LPDOSTASK MZ_AllocDPMITask( void )
350 LPDOSTASK lpDosTask = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSTASK));
352 if (lpDosTask) {
353 lpDosTask->mm_fd = -1;
354 dos_current = lpDosTask;
355 MZ_InitMemory(lpDosTask);
357 return lpDosTask;
360 static void MZ_InitTimer( LPDOSTASK lpDosTask, int ver )
362 if (ver<1) {
363 /* can't make timer ticks */
364 } else {
365 int func;
366 struct timeval tim;
368 /* start dosmod timer at 55ms (18.2Hz) */
369 func=DOSMOD_SET_TIMER;
370 tim.tv_sec=0; tim.tv_usec=54925;
371 write(lpDosTask->write_pipe,&func,sizeof(func));
372 write(lpDosTask->write_pipe,&tim,sizeof(tim));
376 BOOL MZ_InitTask( LPDOSTASK lpDosTask )
378 int write_fd[2],x_fd;
379 pid_t child;
380 char *fname,*farg,arg[16],fproc[64],path[256],*fpath;
381 struct get_read_fd_request *r_req = get_req_buffer();
382 struct get_write_fd_request *w_req = get_req_buffer();
384 if (!lpDosTask) return FALSE;
385 /* create pipes */
386 if (!CreatePipe(&(lpDosTask->hReadPipe),&(lpDosTask->hXPipe),NULL,0)) return FALSE;
387 if (pipe(write_fd)<0) {
388 CloseHandle(lpDosTask->hReadPipe);
389 CloseHandle(lpDosTask->hXPipe);
390 return FALSE;
393 r_req->handle = lpDosTask->hReadPipe;
394 server_call_fd( REQ_GET_READ_FD, -1, &lpDosTask->read_pipe );
395 w_req->handle = lpDosTask->hXPipe;
396 server_call_fd( REQ_GET_WRITE_FD, -1, &x_fd );
398 TRACE("win32 pipe: read=%d, write=%d, unix pipe: read=%d, write=%d\n",
399 lpDosTask->hReadPipe,lpDosTask->hXPipe,lpDosTask->read_pipe,x_fd);
400 TRACE("outbound unix pipe: read=%d, write=%d, pid=%d\n",write_fd[0],write_fd[1],getpid());
402 lpDosTask->write_pipe=write_fd[1];
404 lpDosTask->hConInput=GetStdHandle(STD_INPUT_HANDLE);
405 lpDosTask->hConOutput=GetStdHandle(STD_OUTPUT_HANDLE);
407 /* if we have a mapping file, use it */
408 fname=lpDosTask->mm_name; farg=NULL;
409 if (!fname[0]) {
410 /* otherwise, map our own memory image */
411 sprintf(fproc,"/proc/%d/mem",getpid());
412 sprintf(arg,"%ld",(unsigned long)lpDosTask->img);
413 fname=fproc; farg=arg;
416 TRACE("Loading DOS VM support module\n");
417 if ((child=fork())<0) {
418 close(write_fd[0]);
419 close(lpDosTask->read_pipe);
420 close(lpDosTask->write_pipe);
421 close(x_fd);
422 CloseHandle(lpDosTask->hReadPipe);
423 CloseHandle(lpDosTask->hXPipe);
424 return FALSE;
426 if (child!=0) {
427 /* parent process */
428 int ret;
430 close(write_fd[0]);
431 close(x_fd);
432 lpDosTask->task=child;
433 /* wait for child process to signal readiness */
434 while (1) {
435 if (read(lpDosTask->read_pipe,&ret,sizeof(ret))==sizeof(ret)) break;
436 if ((errno==EINTR)||(errno==EAGAIN)) continue;
437 /* failure */
438 ERR("dosmod has failed to initialize\n");
439 if (lpDosTask->mm_name[0]!=0) unlink(lpDosTask->mm_name);
440 return FALSE;
442 /* the child has now mmaped the temp file, it's now safe to unlink.
443 * do it here to avoid leaving a mess in /tmp if/when Wine crashes... */
444 if (lpDosTask->mm_name[0]!=0) unlink(lpDosTask->mm_name);
445 /* start simulated system timer */
446 MZ_InitTimer(lpDosTask,ret);
447 if (ret<2) {
448 ERR("dosmod version too old! Please install newer dosmod properly\n");
449 ERR("If you don't, the new dosmod event handling system will not work\n");
451 /* all systems are now go */
452 } else {
453 /* child process */
454 close(lpDosTask->read_pipe);
455 close(lpDosTask->write_pipe);
456 /* put our pipes somewhere dosmod can find them */
457 dup2(write_fd[0],0); /* stdin */
458 dup2(x_fd,1); /* stdout */
459 /* now load dosmod */
460 /* check argv[0]-derived paths first, since the newest dosmod is most likely there
461 * (at least it was once for Andreas Mohr, so I decided to make it easier for him) */
462 fpath=strrchr(strcpy(path,full_argv0),'/');
463 if (fpath) {
464 strcpy(fpath,"/dosmod");
465 execl(path,fname,farg,NULL);
466 strcpy(fpath,"/loader/dos/dosmod");
467 execl(path,fname,farg,NULL);
469 /* okay, it wasn't there, try in the path */
470 execlp("dosmod",fname,farg,NULL);
471 /* last desperate attempts: current directory */
472 execl("dosmod",fname,farg,NULL);
473 /* and, just for completeness... */
474 execl("loader/dos/dosmod",fname,farg,NULL);
475 /* if failure, exit */
476 ERR("Failed to spawn dosmod, error=%s\n",strerror(errno));
477 exit(1);
479 return TRUE;
482 static void MZ_Launch(void)
484 LPDOSTASK lpDosTask = MZ_Current();
485 BYTE *psp_start = (BYTE*)lpDosTask->img + ((DWORD)lpDosTask->psp_seg << 4);
487 MZ_FillPSP(psp_start, GetCommandLineA());
489 DOSVM_Enter(NULL);
492 void MZ_KillTask( LPDOSTASK lpDosTask )
494 DOSEVENT *event,*p_event;
495 DOSSYSTEM *sys,*p_sys;
497 TRACE("killing DOS task\n");
498 VGA_Clean();
499 if (lpDosTask->mm_name[0]!=0) {
500 munmap(lpDosTask->img,0x110000-START_OFFSET);
501 close(lpDosTask->mm_fd);
502 } else VirtualFree(lpDosTask->img,0x110000,MEM_RELEASE);
503 close(lpDosTask->read_pipe);
504 close(lpDosTask->write_pipe);
505 CloseHandle(lpDosTask->hReadPipe);
506 CloseHandle(lpDosTask->hXPipe);
507 kill(lpDosTask->task,SIGTERM);
508 /* free memory allocated for events and systems */
509 #define DFREE(var,pvar,svar) \
510 var = lpDosTask->svar; \
511 while (var) { \
512 if (var->data) free(var->data); \
513 pvar = var->next; free(var); var = pvar; \
516 DFREE(event,p_event,pending)
517 DFREE(event,p_event,current)
518 DFREE(sys,p_sys,sys)
520 #undef DFREE
522 #if 0
523 /* FIXME: this seems to crash */
524 if (lpDosTask->dpmi_sel)
525 SELECTOR_FreeBlock(lpDosTask->dpmi_sel, 1);
526 #endif
529 LPDOSTASK MZ_Current( void )
531 return dos_current;
534 #else /* !MZ_SUPPORTED */
536 BOOL MZ_CreateProcess( HANDLE hFile, LPCSTR filename, LPCSTR cmdline, LPCSTR env,
537 LPSECURITY_ATTRIBUTES psa, LPSECURITY_ATTRIBUTES tsa,
538 BOOL inherit, DWORD flags, LPSTARTUPINFOA startup,
539 LPPROCESS_INFORMATION info )
541 WARN("DOS executables not supported on this architecture\n");
542 SetLastError(ERROR_BAD_FORMAT);
543 return FALSE;
546 LPDOSTASK MZ_Current( void )
548 return NULL;
551 #endif