Text of WWN article "The Registry".
[wine.git] / loader / dos / dosvm.c
blob0c1af5998f0dbe86cc4526c9a76a38de40a2f264
1 /*
2 * DOS Virtual Machine
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/time.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
22 #include "wine/winbase16.h"
23 #include "wine/exception.h"
24 #include "windef.h"
25 #include "wingdi.h"
26 #include "winuser.h"
27 #include "winnt.h"
29 #include "callback.h"
30 #include "msdos.h"
31 #include "file.h"
32 #include "miscemu.h"
33 #include "dosexe.h"
34 #include "dosmod.h"
35 #include "stackframe.h"
36 #include "debugtools.h"
38 DECLARE_DEBUG_CHANNEL(int)
39 DECLARE_DEBUG_CHANNEL(module)
40 DECLARE_DEBUG_CHANNEL(relay)
42 #ifdef MZ_SUPPORTED
44 #ifdef HAVE_SYS_VM86_H
45 # include <sys/vm86.h>
46 #endif
47 #ifdef HAVE_SYS_MMAN_H
48 # include <sys/mman.h>
49 #endif
51 #define IF_CLR(ctx) EFL_reg(ctx) &= ~VIF_MASK
52 #define IF_ENABLED(ctx) (EFL_reg(ctx) & VIF_MASK)
53 #define SET_PEND(ctx) EFL_reg(ctx) |= VIP_MASK
54 #define CLR_PEND(ctx) EFL_reg(ctx) &= ~VIP_MASK
55 #define IS_PEND(ctx) (EFL_reg(ctx) & VIP_MASK)
57 #undef TRY_PICRETURN
59 static void do_exception( int signal, CONTEXT86 *context )
61 EXCEPTION_RECORD rec;
62 if ((signal == SIGTRAP) || (signal == SIGHUP))
64 rec.ExceptionCode = EXCEPTION_BREAKPOINT;
65 rec.ExceptionFlags = EXCEPTION_CONTINUABLE;
67 else
69 rec.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; /* generic error */
70 rec.ExceptionFlags = EH_NONCONTINUABLE;
72 rec.ExceptionRecord = NULL;
73 rec.ExceptionAddress = (LPVOID)EIP_reg(context);
74 rec.NumberParameters = 0;
75 EXC_RtlRaiseException( &rec, context );
78 static void DOSVM_Dump( LPDOSTASK lpDosTask, int fn, int sig,
79 struct vm86plus_struct*VM86 )
81 unsigned iofs;
82 BYTE*inst;
83 int x;
85 switch (VM86_TYPE(fn)) {
86 case VM86_SIGNAL:
87 printf("Trapped signal %d\n",sig); break;
88 case VM86_UNKNOWN:
89 printf("Trapped unhandled GPF\n"); break;
90 case VM86_INTx:
91 printf("Trapped INT %02x\n",VM86_ARG(fn)); break;
92 case VM86_STI:
93 printf("Trapped STI\n"); break;
94 case VM86_PICRETURN:
95 printf("Trapped due to pending PIC request\n"); break;
96 case VM86_TRAP:
97 printf("Trapped debug request\n"); break;
98 default:
99 printf("Trapped unknown VM86 type %d arg %d\n",VM86_TYPE(fn),VM86_ARG(fn)); break;
101 #define REGS VM86->regs
102 fprintf(stderr,"AX=%04lX CX=%04lX DX=%04lX BX=%04lX\n",REGS.eax,REGS.ecx,REGS.edx,REGS.ebx);
103 fprintf(stderr,"SI=%04lX DI=%04lX SP=%04lX BP=%04lX\n",REGS.esi,REGS.edi,REGS.esp,REGS.ebp);
104 fprintf(stderr,"CS=%04X DS=%04X ES=%04X SS=%04X\n",REGS.cs,REGS.ds,REGS.es,REGS.ss);
105 fprintf(stderr,"IP=%04lX EFLAGS=%08lX\n",REGS.eip,REGS.eflags);
107 iofs=((DWORD)REGS.cs<<4)+REGS.eip;
108 #undef REGS
109 inst=(BYTE*)lpDosTask->img+iofs;
110 printf("Opcodes:");
111 for (x=0; x<8; x++) printf(" %02x",inst[x]);
112 printf("\n");
115 static int DOSVM_Int( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
117 extern UINT16 DPMI_wrap_seg;
119 if (vect==0x31) {
120 if (CS_reg(context)==DPMI_wrap_seg) {
121 /* exit from real-mode wrapper */
122 return -1;
124 /* we could probably move some other dodgy stuff here too from dpmi.c */
126 INT_RealModeInterrupt(vect,context);
127 return 0;
130 static void DOSVM_SimulateInt( int vect, CONTEXT86 *context, LPDOSTASK lpDosTask )
132 FARPROC16 handler=INT_GetRMHandler(vect);
134 if (SELECTOROF(handler)==0xf000) {
135 /* if internal interrupt, call it directly */
136 INT_RealModeInterrupt(vect,context);
137 } else {
138 WORD*stack=(WORD*)(V86BASE(context)+(((DWORD)SS_reg(context))<<4)+LOWORD(ESP_reg(context)));
139 WORD flag=LOWORD(EFL_reg(context));
141 if (IF_ENABLED(context)) flag|=IF_MASK;
142 else flag&=~IF_MASK;
144 *(--stack)=flag;
145 *(--stack)=CS_reg(context);
146 *(--stack)=LOWORD(EIP_reg(context));
147 ESP_reg(context)-=6;
148 CS_reg(context)=SELECTOROF(handler);
149 EIP_reg(context)=OFFSETOF(handler);
150 IF_CLR(context);
154 #define SHOULD_PEND(x) \
155 (x && ((!lpDosTask->current) || (x->priority < lpDosTask->current->priority)))
157 static void DOSVM_SendQueuedEvent(CONTEXT86 *context, LPDOSTASK lpDosTask)
159 LPDOSEVENT event = lpDosTask->pending;
161 if (SHOULD_PEND(event)) {
162 /* remove from "pending" list */
163 lpDosTask->pending = event->next;
164 /* process event */
165 if (event->irq>=0) {
166 /* it's an IRQ, move it to "current" list */
167 event->next = lpDosTask->current;
168 lpDosTask->current = event;
169 TRACE_(int)("dispatching IRQ %d\n",event->irq);
170 /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
171 * lpDosTask->current might be cleared (and event freed) in this very call! */
172 DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context,lpDosTask);
173 } else {
174 /* callback event */
175 TRACE_(int)("dispatching callback event\n");
176 (*event->relay)(lpDosTask,context,event->data);
177 free(event);
180 if (!SHOULD_PEND(lpDosTask->pending)) {
181 TRACE_(int)("clearing Pending flag\n");
182 CLR_PEND(context);
186 static void DOSVM_SendQueuedEvents(CONTEXT86 *context, LPDOSTASK lpDosTask)
188 /* we will send all queued events as long as interrupts are enabled,
189 * but IRQ events will disable interrupts again */
190 while (IS_PEND(context) && IF_ENABLED(context))
191 DOSVM_SendQueuedEvent(context,lpDosTask);
194 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data)
196 LPDOSTASK lpDosTask = MZ_Current();
197 LPDOSEVENT event, cur, prev;
199 if (lpDosTask) {
200 event = malloc(sizeof(DOSEVENT));
201 if (!event) {
202 ERR_(int)("out of memory allocating event entry\n");
203 return;
205 event->irq = irq; event->priority = priority;
206 event->relay = relay; event->data = data;
208 /* insert event into linked list, in order *after*
209 * all earlier events of higher or equal priority */
210 cur = lpDosTask->pending; prev = NULL;
211 while (cur && cur->priority<=priority) {
212 prev = cur;
213 cur = cur->next;
215 event->next = cur;
216 if (prev) prev->next = event;
217 else lpDosTask->pending = event;
219 /* get dosmod's attention to the new event, except for irq==0 where we already have it */
220 if (irq && !lpDosTask->sig_sent) {
221 TRACE_(int)("new event queued, signalling dosmod\n");
222 kill(lpDosTask->task,SIGUSR2);
223 lpDosTask->sig_sent++;
224 } else {
225 TRACE_(int)("new event queued\n");
230 #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \
231 CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \
232 CP(cs,CS); CP(ds,DS); CP(es,ES); \
233 CP(ss,SS); CP(fs,FS); CP(gs,GS); \
234 CP(eip,EIP); CP(eflags,EFL)
236 static int DOSVM_Process( LPDOSTASK lpDosTask, int fn, int sig,
237 struct vm86plus_struct*VM86 )
239 CONTEXT86 context;
240 int ret=0;
242 #define CP(x,y) y##_reg(&context) = VM86->regs.x
244 #undef CP
245 if (VM86_TYPE(fn)==VM86_UNKNOWN) {
246 ret=INSTR_EmulateInstruction(&context);
247 #define CP(x,y) VM86->regs.x = y##_reg(&context)
249 #undef CP
250 if (ret) return 0;
251 ret=0;
253 (void*)V86BASE(&context)=lpDosTask->img;
254 #ifdef TRY_PICRETURN
255 if (VM86->vm86plus.force_return_for_pic) {
256 SET_PEND(&context);
258 #else
259 /* linux doesn't preserve pending flag on return */
260 if (SHOULD_PEND(lpDosTask->pending)) {
261 SET_PEND(&context);
263 #endif
265 switch (VM86_TYPE(fn)) {
266 case VM86_SIGNAL:
267 TRACE_(int)("DOS module caught signal %d\n",sig);
268 if ((sig==SIGALRM) || (sig==SIGUSR2)) {
269 if (sig==SIGALRM) {
270 DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
272 if (lpDosTask->pending) {
273 TRACE_(int)("setting Pending flag, interrupts are currently %s\n",
274 IF_ENABLED(&context) ? "enabled" : "disabled");
275 SET_PEND(&context);
276 DOSVM_SendQueuedEvents(&context,lpDosTask);
277 } else {
278 TRACE_(int)("no events are pending, clearing Pending flag\n");
279 CLR_PEND(&context);
281 if (sig==SIGUSR2) lpDosTask->sig_sent--;
283 else if ((sig==SIGHUP) || (sig==SIGILL) || (sig==SIGSEGV)) {
284 do_exception( sig, &context );
285 } else {
286 DOSVM_Dump(lpDosTask,fn,sig,VM86);
287 ret=-1;
289 break;
290 case VM86_UNKNOWN: /* unhandled GPF */
291 DOSVM_Dump(lpDosTask,fn,sig,VM86);
292 do_exception( SIGSEGV, &context );
293 break;
294 case VM86_INTx:
295 if (TRACE_ON(relay))
296 DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
297 ret=DOSVM_Int(VM86_ARG(fn),&context,lpDosTask);
298 if (TRACE_ON(relay))
299 DPRINTF("Ret DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
300 break;
301 case VM86_STI:
302 case VM86_PICRETURN:
303 TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n");
304 DOSVM_SendQueuedEvents(&context,lpDosTask);
305 break;
306 case VM86_TRAP:
307 do_exception( SIGTRAP, &context );
308 break;
309 default:
310 DOSVM_Dump(lpDosTask,fn,sig,VM86);
311 ret=-1;
314 #define CP(x,y) VM86->regs.x = y##_reg(&context)
316 #undef CP
317 #ifdef TRY_PICRETURN
318 VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0;
319 CLR_PEND(&context);
320 #endif
321 return ret;
324 void DOSVM_ProcessMessage(LPDOSTASK lpDosTask,MSG *msg)
326 BYTE scan = 0;
328 fprintf(stderr,"got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
329 if ((msg->message>=WM_MOUSEFIRST)&&
330 (msg->message<=WM_MOUSELAST)) {
331 INT_Int33Message(msg->message,msg->wParam,msg->lParam);
332 } else {
333 switch (msg->message) {
334 case WM_KEYUP:
335 scan = 0x80;
336 case WM_KEYDOWN:
337 scan |= (msg->lParam >> 16) & 0x7f;
339 /* check whether extended bit is set,
340 * and if so, queue the extension prefix */
341 if (msg->lParam & 0x1000000) {
342 /* FIXME: some keys (function keys) have
343 * extended bit set even when they shouldn't,
344 * should check for them */
345 INT_Int09SendScan(0xE0);
347 INT_Int09SendScan(scan);
348 break;
353 void DOSVM_Wait( int read_pipe, HANDLE hObject )
355 LPDOSTASK lpDosTask = MZ_Current();
356 MSG msg;
357 DWORD waitret;
358 BOOL got_msg = FALSE;
360 do {
361 /* check for messages (waste time before the response check below) */
362 while (Callout.PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
363 /* got a message */
364 DOSVM_ProcessMessage(lpDosTask,&msg);
365 /* we don't need a TranslateMessage here */
366 Callout.DispatchMessageA(&msg);
367 got_msg = TRUE;
369 if (read_pipe == -1) {
370 if (got_msg) break;
371 } else {
372 fd_set readfds;
373 struct timeval timeout={0,0};
374 /* quick check for response from dosmod
375 * (faster than doing the full blocking wait, if data already available) */
376 FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
377 if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
378 break;
380 /* check for data from win32 console device */
382 /* nothing yet, block while waiting for something to do */
383 waitret=MsgWaitForMultipleObjects(1,&hObject,FALSE,INFINITE,QS_ALLINPUT);
384 if (waitret==(DWORD)-1) {
385 ERR_(module)("dosvm wait error=%ld\n",GetLastError());
387 if (read_pipe != -1) {
388 if (waitret==WAIT_OBJECT_0) break;
390 } while (TRUE);
393 int DOSVM_Enter( CONTEXT86 *context )
395 LPDOSTASK lpDosTask = MZ_Current();
396 struct vm86plus_struct VM86;
397 int stat,len,sig;
399 if (!lpDosTask) {
400 /* MZ_CreateProcess or MZ_AllocDPMITask should have been called first */
401 ERR_(module)("dosmod has not been initialized!");
402 return -1;
405 if (context) {
406 #define CP(x,y) VM86.regs.x = y##_reg(context)
408 #undef CP
409 if (VM86.regs.eflags & IF_MASK)
410 VM86.regs.eflags |= VIF_MASK;
411 } else {
412 /* initial setup */
413 /* allocate standard DOS handles */
414 FILE_InitProcessDosHandles();
415 /* registers */
416 memset(&VM86,0,sizeof(VM86));
417 VM86.regs.cs=lpDosTask->init_cs;
418 VM86.regs.eip=lpDosTask->init_ip;
419 VM86.regs.ss=lpDosTask->init_ss;
420 VM86.regs.esp=lpDosTask->init_sp;
421 VM86.regs.ds=lpDosTask->psp_seg;
422 VM86.regs.es=lpDosTask->psp_seg;
423 VM86.regs.eflags=VIF_MASK;
424 /* hmm, what else do we need? */
427 /* main exchange loop */
428 do {
429 stat = VM86_ENTER;
430 errno = 0;
431 /* transmit VM86 structure to dosmod task */
432 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
433 ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,lpDosTask->write_pipe,getpid());
434 return -1;
436 if (write(lpDosTask->write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) {
437 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
438 return -1;
440 /* wait for response, doing other things in the meantime */
441 DOSVM_Wait(lpDosTask->read_pipe, lpDosTask->hReadPipe);
442 /* read response */
443 while (1) {
444 if ((len=read(lpDosTask->read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break;
445 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
446 WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len);
447 continue;
449 ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len);
450 return -1;
452 TRACE_(module)("dosmod return code=%d\n",stat);
453 while (1) {
454 if ((len=read(lpDosTask->read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break;
455 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
456 WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len);
457 continue;
459 ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len);
460 return -1;
462 if ((stat&0xff)==DOSMOD_SIGNAL) {
463 while (1) {
464 if ((len=read(lpDosTask->read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break;
465 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
466 WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len);
467 continue;
469 ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len);
470 return -1;
471 } while (0);
472 } else sig=0;
473 /* got response */
474 } while (DOSVM_Process(lpDosTask,stat,sig,&VM86)>=0);
476 if (context) {
477 #define CP(x,y) y##_reg(context) = VM86.regs.x
479 #undef CP
481 return 0;
484 void DOSVM_PIC_ioport_out( WORD port, BYTE val)
486 LPDOSTASK lpDosTask = MZ_Current();
487 LPDOSEVENT event;
489 if (lpDosTask) {
490 if ((port==0x20) && (val==0x20)) {
491 if (lpDosTask->current) {
492 /* EOI (End Of Interrupt) */
493 TRACE_(int)("received EOI for current IRQ, clearing\n");
494 event = lpDosTask->current;
495 lpDosTask->current = event->next;
496 if (event->relay)
497 (*event->relay)(lpDosTask,NULL,event->data);
498 free(event);
500 if (lpDosTask->pending &&
501 !lpDosTask->sig_sent) {
502 /* another event is pending, which we should probably
503 * be able to process now, so tell dosmod about it */
504 TRACE_(int)("another event pending, signalling dosmod\n");
505 kill(lpDosTask->task,SIGUSR2);
506 lpDosTask->sig_sent++;
508 } else {
509 WARN_(int)("EOI without active IRQ\n");
511 } else {
512 FIXME_(int)("unrecognized PIC command %02x\n",val);
517 void DOSVM_SetTimer( unsigned ticks )
519 LPDOSTASK lpDosTask = MZ_Current();
520 int stat=DOSMOD_SET_TIMER;
521 struct timeval tim;
523 if (lpDosTask) {
524 /* the PC clocks ticks at 1193180 Hz */
525 tim.tv_sec=0;
526 tim.tv_usec=((unsigned long long)ticks*1000000)/1193180;
527 /* sanity check */
528 if (!tim.tv_usec) tim.tv_usec=1;
530 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
531 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
532 return;
534 if (write(lpDosTask->write_pipe,&tim,sizeof(tim))!=sizeof(tim)) {
535 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
536 return;
538 /* there's no return */
542 unsigned DOSVM_GetTimer( void )
544 LPDOSTASK lpDosTask = MZ_Current();
545 int stat=DOSMOD_GET_TIMER;
546 struct timeval tim;
548 if (lpDosTask) {
549 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
550 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
551 return 0;
553 /* read response */
554 while (1) {
555 if (read(lpDosTask->read_pipe,&tim,sizeof(tim))==sizeof(tim)) break;
556 if ((errno==EINTR)||(errno==EAGAIN)) continue;
557 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
558 return 0;
560 return ((unsigned long long)tim.tv_usec*1193180)/1000000;
562 return 0;
565 void DOSVM_SetSystemData( int id, void *data )
567 LPDOSTASK lpDosTask = MZ_Current();
568 DOSSYSTEM *sys, *prev;
570 if (lpDosTask) {
571 sys = lpDosTask->sys;
572 prev = NULL;
573 while (sys && (sys->id != id)) {
574 prev = sys;
575 sys = sys->next;
577 if (sys) {
578 free(sys->data);
579 sys->data = data;
580 } else {
581 sys = malloc(sizeof(DOSSYSTEM));
582 sys->id = id;
583 sys->data = data;
584 sys->next = NULL;
585 if (prev) prev->next = sys;
586 else lpDosTask->sys = sys;
588 } else free(data);
591 void* DOSVM_GetSystemData( int id )
593 LPDOSTASK lpDosTask = MZ_Current();
594 DOSSYSTEM *sys;
596 if (lpDosTask) {
597 sys = lpDosTask->sys;
598 while (sys && (sys->id != id))
599 sys = sys->next;
600 if (sys)
601 return sys->data;
603 return NULL;
606 #else /* !MZ_SUPPORTED */
608 int DOSVM_Enter( CONTEXT86 *context )
610 ERR_(module)("DOS realmode not supported on this architecture!\n");
611 return -1;
614 void DOSVM_Wait( int read_pipe, HANDLE hObject) {}
615 void DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
616 void DOSVM_SetTimer( unsigned ticks ) {}
617 unsigned DOSVM_GetTimer( void ) { return 0; }
618 void DOSVM_SetSystemData( int id, void *data ) { free(data); }
619 void* DOSVM_GetSystemData( int id ) { return NULL; }
620 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,CONTEXT86*,void*), void *data) {}
622 #endif