Use exceptions instead of ctx_debug_call.
[wine.git] / loader / dos / dosvm.c
blob155c0ee58f8d2b52de93374b623a17e936aee1cf
1 /*
2 * DOS Virtual Machine
4 * Copyright 1998 Ove Kåven
6 * This code hasn't been completely cleaned up yet.
7 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <unistd.h>
16 #include <sys/time.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
20 #include "wine/winbase16.h"
21 #include "winuser.h"
22 #include "winnt.h"
23 #include "sig_context.h"
24 #include "msdos.h"
25 #include "file.h"
26 #include "miscemu.h"
27 #include "debugger.h"
28 #include "module.h"
29 #include "task.h"
30 #include "ldt.h"
31 #include "dosexe.h"
32 #include "dosmod.h"
33 #include "stackframe.h"
34 #include "debugtools.h"
36 DECLARE_DEBUG_CHANNEL(int)
37 DECLARE_DEBUG_CHANNEL(module)
38 DECLARE_DEBUG_CHANNEL(relay)
40 #ifdef MZ_SUPPORTED
42 #include <sys/mman.h>
43 #include <sys/vm86.h>
45 #define IF_CLR(ctx) EFL_reg(ctx) &= ~VIF_MASK
46 #define IF_ENABLED(ctx) (EFL_reg(ctx) & VIF_MASK)
47 #define SET_PEND(ctx) EFL_reg(ctx) |= VIP_MASK
48 #define CLR_PEND(ctx) EFL_reg(ctx) &= ~VIP_MASK
49 #define IS_PEND(ctx) (EFL_reg(ctx) & VIP_MASK)
51 #undef TRY_PICRETURN
53 static void do_exception( int signal, CONTEXT *context )
55 EXCEPTION_RECORD rec;
56 extern void WINAPI REGS_FUNC(RtlRaiseException)( EXCEPTION_RECORD *rec,
57 CONTEXT *context );
58 if ((signal == SIGTRAP) || (signal == SIGHUP))
60 rec.ExceptionCode = EXCEPTION_BREAKPOINT;
61 rec.ExceptionFlags = EXCEPTION_CONTINUABLE;
63 else
65 rec.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; /* generic error */
66 rec.ExceptionFlags = EH_NONCONTINUABLE;
68 rec.ExceptionRecord = NULL;
69 rec.ExceptionAddress = (LPVOID)EIP_reg(context);
70 rec.NumberParameters = 0;
71 REGS_FUNC(RtlRaiseException)( &rec, context );
74 static void DOSVM_Dump( LPDOSTASK lpDosTask, int fn, int sig,
75 struct vm86plus_struct*VM86 )
77 unsigned iofs;
78 BYTE*inst;
79 int x;
81 switch (VM86_TYPE(fn)) {
82 case VM86_SIGNAL:
83 printf("Trapped signal %d\n",sig); break;
84 case VM86_UNKNOWN:
85 printf("Trapped unhandled GPF\n"); break;
86 case VM86_INTx:
87 printf("Trapped INT %02x\n",VM86_ARG(fn)); break;
88 case VM86_STI:
89 printf("Trapped STI\n"); break;
90 case VM86_PICRETURN:
91 printf("Trapped due to pending PIC request\n"); break;
92 case VM86_TRAP:
93 printf("Trapped debug request\n"); break;
94 default:
95 printf("Trapped unknown VM86 type %d arg %d\n",VM86_TYPE(fn),VM86_ARG(fn)); break;
97 #define REGS VM86->regs
98 fprintf(stderr,"AX=%04lX CX=%04lX DX=%04lX BX=%04lX\n",REGS.eax,REGS.ecx,REGS.edx,REGS.ebx);
99 fprintf(stderr,"SI=%04lX DI=%04lX SP=%04lX BP=%04lX\n",REGS.esi,REGS.edi,REGS.esp,REGS.ebp);
100 fprintf(stderr,"CS=%04X DS=%04X ES=%04X SS=%04X\n",REGS.cs,REGS.ds,REGS.es,REGS.ss);
101 fprintf(stderr,"IP=%04lX EFLAGS=%08lX\n",REGS.eip,REGS.eflags);
103 iofs=((DWORD)REGS.cs<<4)+REGS.eip;
104 #undef REGS
105 inst=(BYTE*)lpDosTask->img+iofs;
106 printf("Opcodes:");
107 for (x=0; x<8; x++) printf(" %02x",inst[x]);
108 printf("\n");
111 static int DOSVM_Int( int vect, PCONTEXT context, LPDOSTASK lpDosTask )
113 extern UINT16 DPMI_wrap_seg;
115 if (vect==0x31) {
116 if (CS_reg(context)==DPMI_wrap_seg) {
117 /* exit from real-mode wrapper */
118 return -1;
120 /* we could probably move some other dodgy stuff here too from dpmi.c */
122 INT_RealModeInterrupt(vect,context);
123 return 0;
126 static void DOSVM_SimulateInt( int vect, PCONTEXT context, LPDOSTASK lpDosTask )
128 FARPROC16 handler=INT_GetRMHandler(vect);
130 if (SELECTOROF(handler)==0xf000) {
131 /* if internal interrupt, call it directly */
132 INT_RealModeInterrupt(vect,context);
133 } else {
134 WORD*stack=(WORD*)(V86BASE(context)+(((DWORD)SS_reg(context))<<4)+SP_reg(context));
135 WORD flag=FL_reg(context);
137 if (IF_ENABLED(context)) flag|=IF_MASK;
138 else flag&=~IF_MASK;
140 *(--stack)=flag;
141 *(--stack)=CS_reg(context);
142 *(--stack)=IP_reg(context);
143 SP_reg(context)-=6;
144 CS_reg(context)=SELECTOROF(handler);
145 IP_reg(context)=OFFSETOF(handler);
146 IF_CLR(context);
150 #define SHOULD_PEND(x) \
151 (x && ((!lpDosTask->current) || (x->priority < lpDosTask->current->priority)))
153 static void DOSVM_SendQueuedEvent(PCONTEXT context, LPDOSTASK lpDosTask)
155 LPDOSEVENT event = lpDosTask->pending;
157 if (SHOULD_PEND(event)) {
158 /* remove from "pending" list */
159 lpDosTask->pending = event->next;
160 /* process event */
161 if (event->irq>=0) {
162 /* it's an IRQ, move it to "current" list */
163 event->next = lpDosTask->current;
164 lpDosTask->current = event;
165 TRACE_(int)("dispatching IRQ %d\n",event->irq);
166 /* note that if DOSVM_SimulateInt calls an internal interrupt directly,
167 * lpDosTask->current might be cleared (and event freed) in this very call! */
168 DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context,lpDosTask);
169 } else {
170 /* callback event */
171 TRACE_(int)("dispatching callback event\n");
172 (*event->relay)(lpDosTask,context,event->data);
173 free(event);
176 if (!SHOULD_PEND(lpDosTask->pending)) {
177 TRACE_(int)("clearing Pending flag\n");
178 CLR_PEND(context);
182 static void DOSVM_SendQueuedEvents(PCONTEXT context, LPDOSTASK lpDosTask)
184 /* we will send all queued events as long as interrupts are enabled,
185 * but IRQ events will disable interrupts again */
186 while (IS_PEND(context) && IF_ENABLED(context))
187 DOSVM_SendQueuedEvent(context,lpDosTask);
190 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,PCONTEXT,void*), void *data)
192 LPDOSTASK lpDosTask = MZ_Current();
193 LPDOSEVENT event, cur, prev;
195 if (lpDosTask) {
196 event = malloc(sizeof(DOSEVENT));
197 if (!event) {
198 ERR_(int)("out of memory allocating event entry\n");
199 return;
201 event->irq = irq; event->priority = priority;
202 event->relay = relay; event->data = data;
204 /* insert event into linked list, in order *after*
205 * all earlier events of higher or equal priority */
206 cur = lpDosTask->pending; prev = NULL;
207 while (cur && cur->priority<=priority) {
208 prev = cur;
209 cur = cur->next;
211 event->next = cur;
212 if (prev) prev->next = event;
213 else lpDosTask->pending = event;
215 /* get dosmod's attention to the new event, except for irq==0 where we already have it */
216 if (irq && !lpDosTask->sig_sent) {
217 TRACE_(int)("new event queued, signalling dosmod\n");
218 kill(lpDosTask->task,SIGUSR2);
219 lpDosTask->sig_sent++;
220 } else {
221 TRACE_(int)("new event queued\n");
226 #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \
227 CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \
228 CP(cs,CS); CP(ds,DS); CP(es,ES); \
229 CP(ss,SS); CP(fs,FS); CP(gs,GS); \
230 CP(eip,EIP); CP(eflags,EFL)
232 static int DOSVM_Process( LPDOSTASK lpDosTask, int fn, int sig,
233 struct vm86plus_struct*VM86 )
235 SIGCONTEXT sigcontext;
236 CONTEXT context;
237 int ret=0;
239 if (VM86_TYPE(fn)==VM86_UNKNOWN) {
240 /* INSTR_EmulateInstruction needs a SIGCONTEXT, not a CONTEXT... */
241 #define CP(x,y) y##_sig(&sigcontext) = VM86->regs.x
243 #undef CP
244 ret=INSTR_EmulateInstruction(&sigcontext);
245 #define CP(x,y) VM86->regs.x = y##_sig(&sigcontext)
247 #undef CP
248 if (ret) return 0;
249 ret=0;
251 #define CP(x,y) y##_reg(&context) = VM86->regs.x
253 #undef CP
254 (void*)V86BASE(&context)=lpDosTask->img;
255 #ifdef TRY_PICRETURN
256 if (VM86->vm86plus.force_return_for_pic) {
257 SET_PEND(&context);
259 #else
260 /* linux doesn't preserve pending flag on return */
261 if (SHOULD_PEND(lpDosTask->pending)) {
262 SET_PEND(&context);
264 #endif
266 switch (VM86_TYPE(fn)) {
267 case VM86_SIGNAL:
268 TRACE_(int)("DOS module caught signal %d\n",sig);
269 if ((sig==SIGALRM) || (sig==SIGUSR2)) {
270 if (sig==SIGALRM) {
271 DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL);
273 if (lpDosTask->pending) {
274 TRACE_(int)("setting Pending flag, interrupts are currently %s\n",
275 IF_ENABLED(&context) ? "enabled" : "disabled");
276 SET_PEND(&context);
277 DOSVM_SendQueuedEvents(&context,lpDosTask);
278 } else {
279 TRACE_(int)("no events are pending, clearing Pending flag\n");
280 CLR_PEND(&context);
282 if (sig==SIGUSR2) lpDosTask->sig_sent--;
284 else if ((sig==SIGHUP) || (sig==SIGILL) || (sig==SIGSEGV)) {
285 do_exception( sig, &context );
286 } else {
287 DOSVM_Dump(lpDosTask,fn,sig,VM86);
288 ret=-1;
290 break;
291 case VM86_UNKNOWN: /* unhandled GPF */
292 DOSVM_Dump(lpDosTask,fn,sig,VM86);
293 do_exception( SIGSEGV, &context );
294 break;
295 case VM86_INTx:
296 if (TRACE_ON(relay))
297 DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
298 ret=DOSVM_Int(VM86_ARG(fn),&context,lpDosTask);
299 if (TRACE_ON(relay))
300 DPRINTF("Ret DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip);
301 break;
302 case VM86_STI:
303 case VM86_PICRETURN:
304 TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n");
305 DOSVM_SendQueuedEvents(&context,lpDosTask);
306 break;
307 case VM86_TRAP:
308 do_exception( SIGTRAP, &context );
309 break;
310 default:
311 DOSVM_Dump(lpDosTask,fn,sig,VM86);
312 ret=-1;
315 #define CP(x,y) VM86->regs.x = y##_reg(&context)
317 #undef CP
318 #ifdef TRY_PICRETURN
319 VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0;
320 CLR_PEND(&context);
321 #endif
322 return ret;
325 void DOSVM_ProcessMessage(LPDOSTASK lpDosTask,MSG *msg)
327 BYTE scan = 0;
329 fprintf(stderr,"got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam);
330 if ((msg->message>=WM_MOUSEFIRST)&&
331 (msg->message<=WM_MOUSELAST)) {
332 INT_Int33Message(msg->message,msg->wParam,msg->lParam);
333 } else {
334 switch (msg->message) {
335 case WM_KEYUP:
336 scan = 0x80;
337 case WM_KEYDOWN:
338 scan |= (msg->lParam >> 16) & 0x7f;
340 /* check whether extended bit is set,
341 * and if so, queue the extension prefix */
342 if (msg->lParam & 0x1000000) {
343 /* FIXME: some keys (function keys) have
344 * extended bit set even when they shouldn't,
345 * should check for them */
346 INT_Int09SendScan(0xE0);
348 INT_Int09SendScan(scan);
349 break;
354 void DOSVM_Wait( int read_pipe, HANDLE hObject )
356 LPDOSTASK lpDosTask = MZ_Current();
357 MSG msg;
358 DWORD waitret;
359 BOOL got_msg = FALSE;
361 do {
362 /* check for messages (waste time before the response check below) */
363 while (PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) {
364 /* got a message */
365 DOSVM_ProcessMessage(lpDosTask,&msg);
366 /* we don't need a TranslateMessage here */
367 DispatchMessageA(&msg);
368 got_msg = TRUE;
370 if (read_pipe == -1) {
371 if (got_msg) break;
372 } else {
373 fd_set readfds;
374 struct timeval timeout={0,0};
375 /* quick check for response from dosmod
376 * (faster than doing the full blocking wait, if data already available) */
377 FD_ZERO(&readfds); FD_SET(read_pipe,&readfds);
378 if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0)
379 break;
381 /* check for data from win32 console device */
383 /* nothing yet, block while waiting for something to do */
384 waitret=MsgWaitForMultipleObjects(1,&hObject,FALSE,INFINITE,QS_ALLINPUT);
385 if (waitret==(DWORD)-1) {
386 ERR_(module)("dosvm wait error=%ld\n",GetLastError());
388 if (read_pipe != -1) {
389 if (waitret==WAIT_OBJECT_0) break;
391 } while (TRUE);
394 int DOSVM_Enter( PCONTEXT context )
396 LPDOSTASK lpDosTask = MZ_Current();
397 struct vm86plus_struct VM86;
398 int stat,len,sig;
400 if (!lpDosTask) {
401 /* MZ_CreateProcess or MZ_AllocDPMITask should have been called first */
402 ERR_(module)("dosmod has not been initialized!");
403 return -1;
406 if (context) {
407 #define CP(x,y) VM86.regs.x = y##_reg(context)
409 #undef CP
410 if (VM86.regs.eflags & IF_MASK)
411 VM86.regs.eflags |= VIF_MASK;
412 } else {
413 /* initial setup */
414 /* allocate standard DOS handles */
415 FILE_InitProcessDosHandles();
416 /* registers */
417 memset(&VM86,0,sizeof(VM86));
418 VM86.regs.cs=lpDosTask->init_cs;
419 VM86.regs.eip=lpDosTask->init_ip;
420 VM86.regs.ss=lpDosTask->init_ss;
421 VM86.regs.esp=lpDosTask->init_sp;
422 VM86.regs.ds=lpDosTask->psp_seg;
423 VM86.regs.es=lpDosTask->psp_seg;
424 VM86.regs.eflags=VIF_MASK;
425 /* hmm, what else do we need? */
428 /* main exchange loop */
429 do {
430 stat = VM86_ENTER;
431 errno = 0;
432 /* transmit VM86 structure to dosmod task */
433 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
434 ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,lpDosTask->write_pipe,getpid());
435 return -1;
437 if (write(lpDosTask->write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) {
438 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
439 return -1;
441 /* wait for response, doing other things in the meantime */
442 DOSVM_Wait(lpDosTask->read_pipe, lpDosTask->hReadPipe);
443 /* read response */
444 while (1) {
445 if ((len=read(lpDosTask->read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break;
446 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
447 WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len);
448 continue;
450 ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len);
451 return -1;
453 TRACE_(module)("dosmod return code=%d\n",stat);
454 while (1) {
455 if ((len=read(lpDosTask->read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break;
456 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
457 WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len);
458 continue;
460 ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len);
461 return -1;
463 if ((stat&0xff)==DOSMOD_SIGNAL) {
464 while (1) {
465 if ((len=read(lpDosTask->read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break;
466 if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) {
467 WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len);
468 continue;
470 ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len);
471 return -1;
472 } while (0);
473 } else sig=0;
474 /* got response */
475 } while (DOSVM_Process(lpDosTask,stat,sig,&VM86)>=0);
477 if (context) {
478 #define CP(x,y) y##_reg(context) = VM86.regs.x
480 #undef CP
482 return 0;
485 void DOSVM_PIC_ioport_out( WORD port, BYTE val)
487 LPDOSTASK lpDosTask = MZ_Current();
488 LPDOSEVENT event;
490 if (lpDosTask) {
491 if ((port==0x20) && (val==0x20)) {
492 if (lpDosTask->current) {
493 /* EOI (End Of Interrupt) */
494 TRACE_(int)("received EOI for current IRQ, clearing\n");
495 event = lpDosTask->current;
496 lpDosTask->current = event->next;
497 if (event->relay)
498 (*event->relay)(lpDosTask,NULL,event->data);
499 free(event);
501 if (lpDosTask->pending &&
502 !lpDosTask->sig_sent) {
503 /* another event is pending, which we should probably
504 * be able to process now, so tell dosmod about it */
505 TRACE_(int)("another event pending, signalling dosmod\n");
506 kill(lpDosTask->task,SIGUSR2);
507 lpDosTask->sig_sent++;
509 } else {
510 WARN_(int)("EOI without active IRQ\n");
512 } else {
513 FIXME_(int)("unrecognized PIC command %02x\n",val);
518 void DOSVM_SetTimer( unsigned ticks )
520 LPDOSTASK lpDosTask = MZ_Current();
521 int stat=DOSMOD_SET_TIMER;
522 struct timeval tim;
524 if (lpDosTask) {
525 /* the PC clocks ticks at 1193180 Hz */
526 tim.tv_sec=0;
527 tim.tv_usec=((unsigned long long)ticks*1000000)/1193180;
528 /* sanity check */
529 if (!tim.tv_usec) tim.tv_usec=1;
531 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
532 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
533 return;
535 if (write(lpDosTask->write_pipe,&tim,sizeof(tim))!=sizeof(tim)) {
536 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
537 return;
539 /* there's no return */
543 unsigned DOSVM_GetTimer( void )
545 LPDOSTASK lpDosTask = MZ_Current();
546 int stat=DOSMOD_GET_TIMER;
547 struct timeval tim;
549 if (lpDosTask) {
550 if (write(lpDosTask->write_pipe,&stat,sizeof(stat))!=sizeof(stat)) {
551 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
552 return 0;
554 /* read response */
555 while (1) {
556 if (read(lpDosTask->read_pipe,&tim,sizeof(tim))==sizeof(tim)) break;
557 if ((errno==EINTR)||(errno==EAGAIN)) continue;
558 ERR_(module)("dosmod sync lost, errno=%d\n",errno);
559 return 0;
561 return ((unsigned long long)tim.tv_usec*1193180)/1000000;
563 return 0;
566 void DOSVM_SetSystemData( int id, void *data )
568 LPDOSTASK lpDosTask = MZ_Current();
569 DOSSYSTEM *sys, *prev;
571 if (lpDosTask) {
572 sys = lpDosTask->sys;
573 prev = NULL;
574 while (sys && (sys->id != id)) {
575 prev = sys;
576 sys = sys->next;
578 if (sys) {
579 free(sys->data);
580 sys->data = data;
581 } else {
582 sys = malloc(sizeof(DOSSYSTEM));
583 sys->id = id;
584 sys->data = data;
585 sys->next = NULL;
586 if (prev) prev->next = sys;
587 else lpDosTask->sys = sys;
589 } else free(data);
592 void* DOSVM_GetSystemData( int id )
594 LPDOSTASK lpDosTask = MZ_Current();
595 DOSSYSTEM *sys;
597 if (lpDosTask) {
598 sys = lpDosTask->sys;
599 while (sys && (sys->id != id))
600 sys = sys->next;
601 if (sys)
602 return sys->data;
604 return NULL;
607 #else /* !MZ_SUPPORTED */
609 int DOSVM_Enter( PCONTEXT context )
611 ERR_(module)("DOS realmode not supported on this architecture!\n");
612 return -1;
615 void DOSVM_Wait( int read_pipe, HANDLE hObject) {}
616 void DOSVM_PIC_ioport_out( WORD port, BYTE val) {}
617 void DOSVM_SetTimer( unsigned ticks ) {}
618 unsigned DOSVM_GetTimer( void ) { return 0; }
619 void DOSVM_SetSystemData( int id, void *data ) { free(data); }
620 void* DOSVM_GetSystemData( int id ) { return NULL; }
621 void DOSVM_QueueEvent( int irq, int priority, void (*relay)(LPDOSTASK,PCONTEXT,void*), void *data) {}
623 #endif