| /* |
| * DOS Virtual Machine |
| * |
| * Copyright 1998 Ove Kåven |
| * |
| * This code hasn't been completely cleaned up yet. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include "wine/winbase16.h" |
| #include "wine/exception.h" |
| #include "windef.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "winnt.h" |
| #include "wincon.h" |
| |
| #include "callback.h" |
| #include "msdos.h" |
| #include "file.h" |
| #include "miscemu.h" |
| #include "dosexe.h" |
| #include "dosmod.h" |
| #include "stackframe.h" |
| #include "debugtools.h" |
| |
| DECLARE_DEBUG_CHANNEL(int) |
| DECLARE_DEBUG_CHANNEL(module) |
| DECLARE_DEBUG_CHANNEL(relay) |
| |
| #ifdef MZ_SUPPORTED |
| |
| #ifdef HAVE_SYS_VM86_H |
| # include <sys/vm86.h> |
| #endif |
| #ifdef HAVE_SYS_MMAN_H |
| # include <sys/mman.h> |
| #endif |
| |
| #define IF_CLR(ctx) EFL_reg(ctx) &= ~VIF_MASK |
| #define IF_ENABLED(ctx) (EFL_reg(ctx) & VIF_MASK) |
| #define SET_PEND(ctx) EFL_reg(ctx) |= VIP_MASK |
| #define CLR_PEND(ctx) EFL_reg(ctx) &= ~VIP_MASK |
| #define IS_PEND(ctx) (EFL_reg(ctx) & VIP_MASK) |
| |
| #undef TRY_PICRETURN |
| |
| typedef struct _DOSEVENT { |
| int irq,priority; |
| void (*relay)(CONTEXT86*,void*); |
| void *data; |
| struct _DOSEVENT *next; |
| } DOSEVENT, *LPDOSEVENT; |
| |
| static struct _DOSEVENT *pending_event, *current_event; |
| static int sig_sent, entered; |
| |
| /* from module.c */ |
| extern int read_pipe, write_pipe; |
| extern HANDLE hReadPipe; |
| extern pid_t dosmod_pid; |
| |
| static void do_exception( int signal, CONTEXT86 *context ) |
| { |
| EXCEPTION_RECORD rec; |
| if ((signal == SIGTRAP) || (signal == SIGHUP)) |
| { |
| rec.ExceptionCode = EXCEPTION_BREAKPOINT; |
| rec.ExceptionFlags = EXCEPTION_CONTINUABLE; |
| } |
| else |
| { |
| rec.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; /* generic error */ |
| rec.ExceptionFlags = EH_NONCONTINUABLE; |
| } |
| rec.ExceptionRecord = NULL; |
| rec.ExceptionAddress = (LPVOID)EIP_reg(context); |
| rec.NumberParameters = 0; |
| EXC_RtlRaiseException( &rec, context ); |
| } |
| |
| static void DOSVM_Dump( int fn, int sig, struct vm86plus_struct*VM86 ) |
| { |
| BYTE*inst; |
| int x; |
| |
| switch (VM86_TYPE(fn)) { |
| case VM86_SIGNAL: |
| printf("Trapped signal %d\n",sig); break; |
| case VM86_UNKNOWN: |
| printf("Trapped unhandled GPF\n"); break; |
| case VM86_INTx: |
| printf("Trapped INT %02x\n",VM86_ARG(fn)); break; |
| case VM86_STI: |
| printf("Trapped STI\n"); break; |
| case VM86_PICRETURN: |
| printf("Trapped due to pending PIC request\n"); break; |
| case VM86_TRAP: |
| printf("Trapped debug request\n"); break; |
| default: |
| printf("Trapped unknown VM86 type %d arg %d\n",VM86_TYPE(fn),VM86_ARG(fn)); break; |
| } |
| #define REGS VM86->regs |
| fprintf(stderr,"AX=%04lX CX=%04lX DX=%04lX BX=%04lX\n",REGS.eax,REGS.ecx,REGS.edx,REGS.ebx); |
| fprintf(stderr,"SI=%04lX DI=%04lX SP=%04lX BP=%04lX\n",REGS.esi,REGS.edi,REGS.esp,REGS.ebp); |
| fprintf(stderr,"CS=%04X DS=%04X ES=%04X SS=%04X\n",REGS.cs,REGS.ds,REGS.es,REGS.ss); |
| fprintf(stderr,"IP=%04lX EFLAGS=%08lX\n",REGS.eip,REGS.eflags); |
| |
| inst = PTR_REAL_TO_LIN( REGS.cs, REGS.eip ); |
| #undef REGS |
| printf("Opcodes:"); |
| for (x=0; x<8; x++) printf(" %02x",inst[x]); |
| printf("\n"); |
| } |
| |
| static int DOSVM_Int( int vect, CONTEXT86 *context ) |
| { |
| if (vect==0x31) { |
| if (CS_reg(context)==DOSMEM_wrap_seg) { |
| /* exit from real-mode wrapper */ |
| return -1; |
| } |
| /* we could probably move some other dodgy stuff here too from dpmi.c */ |
| } |
| INT_RealModeInterrupt(vect,context); |
| return 0; |
| } |
| |
| static void DOSVM_SimulateInt( int vect, CONTEXT86 *context ) |
| { |
| FARPROC16 handler=INT_GetRMHandler(vect); |
| |
| if (SELECTOROF(handler)==0xf000) { |
| /* if internal interrupt, call it directly */ |
| INT_RealModeInterrupt(vect,context); |
| } else { |
| WORD*stack= PTR_REAL_TO_LIN( context->SegSs, context->Esp ); |
| WORD flag=LOWORD(EFL_reg(context)); |
| |
| if (IF_ENABLED(context)) flag|=IF_MASK; |
| else flag&=~IF_MASK; |
| |
| *(--stack)=flag; |
| *(--stack)=CS_reg(context); |
| *(--stack)=LOWORD(EIP_reg(context)); |
| ESP_reg(context)-=6; |
| CS_reg(context)=SELECTOROF(handler); |
| EIP_reg(context)=OFFSETOF(handler); |
| IF_CLR(context); |
| } |
| } |
| |
| #define SHOULD_PEND(x) \ |
| (x && ((!current_event) || (x->priority < current_event->priority))) |
| |
| static void DOSVM_SendQueuedEvent(CONTEXT86 *context) |
| { |
| LPDOSEVENT event = pending_event; |
| |
| if (SHOULD_PEND(event)) { |
| /* remove from "pending" list */ |
| pending_event = event->next; |
| /* process event */ |
| if (event->irq>=0) { |
| /* it's an IRQ, move it to "current" list */ |
| event->next = current_event; |
| current_event = event; |
| TRACE_(int)("dispatching IRQ %d\n",event->irq); |
| /* note that if DOSVM_SimulateInt calls an internal interrupt directly, |
| * current_event might be cleared (and event freed) in this very call! */ |
| DOSVM_SimulateInt((event->irq<8)?(event->irq+8):(event->irq-8+0x70),context); |
| } else { |
| /* callback event */ |
| TRACE_(int)("dispatching callback event\n"); |
| (*event->relay)(context,event->data); |
| free(event); |
| } |
| } |
| if (!SHOULD_PEND(pending_event)) { |
| TRACE_(int)("clearing Pending flag\n"); |
| CLR_PEND(context); |
| } |
| } |
| |
| static void DOSVM_SendQueuedEvents(CONTEXT86 *context) |
| { |
| /* we will send all queued events as long as interrupts are enabled, |
| * but IRQ events will disable interrupts again */ |
| while (IS_PEND(context) && IF_ENABLED(context)) |
| DOSVM_SendQueuedEvent(context); |
| } |
| |
| void DOSVM_QueueEvent( int irq, int priority, void (*relay)(CONTEXT86*,void*), void *data) |
| { |
| LPDOSEVENT event, cur, prev; |
| |
| if (entered) { |
| event = malloc(sizeof(DOSEVENT)); |
| if (!event) { |
| ERR_(int)("out of memory allocating event entry\n"); |
| return; |
| } |
| event->irq = irq; event->priority = priority; |
| event->relay = relay; event->data = data; |
| |
| /* insert event into linked list, in order *after* |
| * all earlier events of higher or equal priority */ |
| cur = pending_event; prev = NULL; |
| while (cur && cur->priority<=priority) { |
| prev = cur; |
| cur = cur->next; |
| } |
| event->next = cur; |
| if (prev) prev->next = event; |
| else pending_event = event; |
| |
| /* get dosmod's attention to the new event, if necessary */ |
| if (!sig_sent) { |
| TRACE_(int)("new event queued, signalling dosmod\n"); |
| kill(dosmod_pid,SIGUSR2); |
| sig_sent++; |
| } else { |
| TRACE_(int)("new event queued\n"); |
| } |
| } else { |
| /* DOS subsystem not running */ |
| /* (this probably means that we're running a win16 app |
| * which uses DPMI to thunk down to DOS services) */ |
| if (irq<0) { |
| /* callback event, perform it with dummy context */ |
| CONTEXT86 context; |
| memset(&context,0,sizeof(context)); |
| (*relay)(&context,data); |
| } else { |
| ERR_(int)("IRQ without DOS task: should not happen"); |
| } |
| } |
| } |
| |
| #define CV CP(eax,EAX); CP(ecx,ECX); CP(edx,EDX); CP(ebx,EBX); \ |
| CP(esi,ESI); CP(edi,EDI); CP(esp,ESP); CP(ebp,EBP); \ |
| CP(cs,CS); CP(ds,DS); CP(es,ES); \ |
| CP(ss,SS); CP(fs,FS); CP(gs,GS); \ |
| CP(eip,EIP); CP(eflags,EFL) |
| |
| static int DOSVM_Process( int fn, int sig, struct vm86plus_struct*VM86 ) |
| { |
| CONTEXT86 context; |
| int ret=0; |
| |
| #define CP(x,y) y##_reg(&context) = VM86->regs.x |
| CV; |
| #undef CP |
| if (VM86_TYPE(fn)==VM86_UNKNOWN) { |
| ret=INSTR_EmulateInstruction(&context); |
| #define CP(x,y) VM86->regs.x = y##_reg(&context) |
| CV; |
| #undef CP |
| if (ret) return 0; |
| ret=0; |
| } |
| #ifdef TRY_PICRETURN |
| if (VM86->vm86plus.force_return_for_pic) { |
| SET_PEND(&context); |
| } |
| #else |
| /* linux doesn't preserve pending flag on return */ |
| if (SHOULD_PEND(pending_event)) { |
| SET_PEND(&context); |
| } |
| #endif |
| |
| switch (VM86_TYPE(fn)) { |
| case VM86_SIGNAL: |
| TRACE_(int)("DOS module caught signal %d\n",sig); |
| if ((sig==SIGALRM) || (sig==SIGUSR2)) { |
| if (sig==SIGALRM) { |
| sig_sent++; |
| DOSVM_QueueEvent(0,DOS_PRIORITY_REALTIME,NULL,NULL); |
| } |
| if (pending_event) { |
| TRACE_(int)("setting Pending flag, interrupts are currently %s\n", |
| IF_ENABLED(&context) ? "enabled" : "disabled"); |
| SET_PEND(&context); |
| DOSVM_SendQueuedEvents(&context); |
| } else { |
| TRACE_(int)("no events are pending, clearing Pending flag\n"); |
| CLR_PEND(&context); |
| } |
| sig_sent--; |
| } |
| else if ((sig==SIGHUP) || (sig==SIGILL) || (sig==SIGSEGV)) { |
| do_exception( sig, &context ); |
| } else { |
| DOSVM_Dump(fn,sig,VM86); |
| ret=-1; |
| } |
| break; |
| case VM86_UNKNOWN: /* unhandled GPF */ |
| DOSVM_Dump(fn,sig,VM86); |
| do_exception( SIGSEGV, &context ); |
| break; |
| case VM86_INTx: |
| if (TRACE_ON(relay)) |
| DPRINTF("Call DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip); |
| ret=DOSVM_Int(VM86_ARG(fn),&context); |
| if (TRACE_ON(relay)) |
| DPRINTF("Ret DOS int 0x%02x (EAX=%08lx) ret=%04lx:%04lx\n",VM86_ARG(fn),context.Eax,context.SegCs,context.Eip); |
| break; |
| case VM86_STI: |
| case VM86_PICRETURN: |
| TRACE_(int)("DOS task enabled interrupts with events pending, sending events\n"); |
| DOSVM_SendQueuedEvents(&context); |
| break; |
| case VM86_TRAP: |
| do_exception( SIGTRAP, &context ); |
| break; |
| default: |
| DOSVM_Dump(fn,sig,VM86); |
| ret=-1; |
| } |
| |
| #define CP(x,y) VM86->regs.x = y##_reg(&context) |
| CV; |
| #undef CP |
| #ifdef TRY_PICRETURN |
| VM86->vm86plus.force_return_for_pic = IS_PEND(&context) ? 1 : 0; |
| CLR_PEND(&context); |
| #endif |
| return ret; |
| } |
| |
| static void DOSVM_ProcessConsole(void) |
| { |
| INPUT_RECORD msg; |
| DWORD res; |
| BYTE scan; |
| |
| if (ReadConsoleInputA(GetStdHandle(STD_INPUT_HANDLE),&msg,1,&res)) { |
| switch (msg.EventType) { |
| case KEY_EVENT: |
| scan = msg.Event.KeyEvent.wVirtualScanCode; |
| if (!msg.Event.KeyEvent.bKeyDown) scan |= 0x80; |
| |
| /* check whether extended bit is set, |
| * and if so, queue the extension prefix */ |
| if (msg.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) { |
| INT_Int09SendScan(0xE0,0); |
| } |
| INT_Int09SendScan(scan,msg.Event.KeyEvent.uChar.AsciiChar); |
| break; |
| default: |
| FIXME_(int)("unhandled console event: %d\n", msg.EventType); |
| } |
| } |
| } |
| |
| static void DOSVM_ProcessMessage(MSG *msg) |
| { |
| BYTE scan = 0; |
| |
| TRACE_(int)("got message %04x, wparam=%08x, lparam=%08lx\n",msg->message,msg->wParam,msg->lParam); |
| if ((msg->message>=WM_MOUSEFIRST)&& |
| (msg->message<=WM_MOUSELAST)) { |
| INT_Int33Message(msg->message,msg->wParam,msg->lParam); |
| } else { |
| switch (msg->message) { |
| case WM_KEYUP: |
| scan = 0x80; |
| case WM_KEYDOWN: |
| scan |= (msg->lParam >> 16) & 0x7f; |
| |
| /* check whether extended bit is set, |
| * and if so, queue the extension prefix */ |
| if (msg->lParam & 0x1000000) { |
| /* FIXME: some keys (function keys) have |
| * extended bit set even when they shouldn't, |
| * should check for them */ |
| INT_Int09SendScan(0xE0,0); |
| } |
| INT_Int09SendScan(scan,0); |
| break; |
| } |
| } |
| } |
| |
| void DOSVM_Wait( int read_pipe, HANDLE hObject ) |
| { |
| MSG msg; |
| DWORD waitret; |
| HANDLE objs[2]; |
| int objc; |
| BOOL got_msg = FALSE; |
| |
| objs[0]=GetStdHandle(STD_INPUT_HANDLE); |
| objs[1]=hObject; |
| objc=hObject?2:1; |
| do { |
| /* check for messages (waste time before the response check below) */ |
| while (Callout.PeekMessageA(&msg,0,0,0,PM_REMOVE|PM_NOYIELD)) { |
| /* got a message */ |
| DOSVM_ProcessMessage(&msg); |
| /* we don't need a TranslateMessage here */ |
| Callout.DispatchMessageA(&msg); |
| got_msg = TRUE; |
| } |
| if (!got_msg) { |
| /* check for console input */ |
| INPUT_RECORD msg; |
| DWORD num; |
| if (PeekConsoleInputA(objs[0],&msg,1,&num) && num) { |
| DOSVM_ProcessConsole(); |
| got_msg = TRUE; |
| } |
| } |
| if (read_pipe == -1) { |
| if (got_msg) break; |
| } else { |
| fd_set readfds; |
| struct timeval timeout={0,0}; |
| /* quick check for response from dosmod |
| * (faster than doing the full blocking wait, if data already available) */ |
| FD_ZERO(&readfds); FD_SET(read_pipe,&readfds); |
| if (select(read_pipe+1,&readfds,NULL,NULL,&timeout)>0) |
| break; |
| } |
| /* nothing yet, block while waiting for something to do */ |
| waitret=Callout.MsgWaitForMultipleObjects(objc,objs,FALSE,INFINITE,QS_ALLINPUT); |
| if (waitret==(DWORD)-1) { |
| ERR_(module)("dosvm wait error=%ld\n",GetLastError()); |
| } |
| if ((read_pipe != -1) && hObject) { |
| if (waitret==(WAIT_OBJECT_0+1)) break; |
| } |
| if (waitret==WAIT_OBJECT_0) { |
| DOSVM_ProcessConsole(); |
| } |
| } while (TRUE); |
| } |
| |
| int DOSVM_Enter( CONTEXT86 *context ) |
| { |
| struct vm86plus_struct VM86; |
| int stat,len,sig; |
| |
| #define CP(x,y) VM86.regs.x = y##_reg(context) |
| CV; |
| #undef CP |
| if (VM86.regs.eflags & IF_MASK) |
| VM86.regs.eflags |= VIF_MASK; |
| |
| /* main exchange loop */ |
| entered++; |
| do { |
| TRACE_(module)("thread is: %lx\n",GetCurrentThreadId()); |
| stat = VM86_ENTER; |
| errno = 0; |
| /* transmit VM86 structure to dosmod task */ |
| if (write(write_pipe,&stat,sizeof(stat))!=sizeof(stat)) { |
| ERR_(module)("dosmod sync lost, errno=%d, fd=%d, pid=%d\n",errno,write_pipe,getpid()); |
| return -1; |
| } |
| if (write(write_pipe,&VM86,sizeof(VM86))!=sizeof(VM86)) { |
| ERR_(module)("dosmod sync lost, errno=%d\n",errno); |
| return -1; |
| } |
| /* wait for response, doing other things in the meantime */ |
| DOSVM_Wait(read_pipe, hReadPipe); |
| /* read response */ |
| while (1) { |
| if ((len=read(read_pipe,&stat,sizeof(stat)))==sizeof(stat)) break; |
| if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) { |
| WARN_(module)("rereading dosmod return code due to errno=%d, result=%d\n",errno,len); |
| continue; |
| } |
| ERR_(module)("dosmod sync lost reading return code, errno=%d, result=%d\n",errno,len); |
| return -1; |
| } |
| TRACE_(module)("dosmod return code=%d\n",stat); |
| if (stat==DOSMOD_LEFTIDLE) { |
| continue; |
| } |
| while (1) { |
| if ((len=read(read_pipe,&VM86,sizeof(VM86)))==sizeof(VM86)) break; |
| if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) { |
| WARN_(module)("rereading dosmod VM86 structure due to errno=%d, result=%d\n",errno,len); |
| continue; |
| } |
| ERR_(module)("dosmod sync lost reading VM86 structure, errno=%d, result=%d\n",errno,len); |
| return -1; |
| } |
| if ((stat&0xff)==DOSMOD_SIGNAL) { |
| while (1) { |
| if ((len=read(read_pipe,&sig,sizeof(sig)))==sizeof(sig)) break; |
| if (((errno==EINTR)||(errno==EAGAIN))&&(len<=0)) { |
| WARN_(module)("rereading dosmod signal due to errno=%d, result=%d\n",errno,len); |
| continue; |
| } |
| ERR_(module)("dosmod sync lost reading signal, errno=%d, result=%d\n",errno,len); |
| return -1; |
| } while (0); |
| } else sig=0; |
| /* got response */ |
| } while (DOSVM_Process(stat,sig,&VM86)>=0); |
| entered--; |
| |
| if (context) { |
| #define CP(x,y) y##_reg(context) = VM86.regs.x |
| CV; |
| #undef CP |
| } |
| return 0; |
| } |
| |
| void DOSVM_PIC_ioport_out( WORD port, BYTE val) |
| { |
| LPDOSEVENT event; |
| |
| if ((port==0x20) && (val==0x20)) { |
| if (current_event) { |
| /* EOI (End Of Interrupt) */ |
| TRACE_(int)("received EOI for current IRQ, clearing\n"); |
| event = current_event; |
| current_event = event->next; |
| if (event->relay) |
| (*event->relay)(NULL,event->data); |
| free(event); |
| |
| if (pending_event && |
| !sig_sent) { |
| /* another event is pending, which we should probably |
| * be able to process now, so tell dosmod about it */ |
| TRACE_(int)("another event pending, signalling dosmod\n"); |
| kill(dosmod_pid,SIGUSR2); |
| sig_sent++; |
| } |
| } else { |
| WARN_(int)("EOI without active IRQ\n"); |
| } |
| } else { |
| FIXME_(int)("unrecognized PIC command %02x\n",val); |
| } |
| } |
| |
| void DOSVM_SetTimer( unsigned ticks ) |
| { |
| int stat=DOSMOD_SET_TIMER; |
| struct timeval tim; |
| |
| if (MZ_Current()) { |
| /* the PC clocks ticks at 1193180 Hz */ |
| tim.tv_sec=0; |
| tim.tv_usec=((unsigned long long)ticks*1000000)/1193180; |
| /* sanity check */ |
| if (!tim.tv_usec) tim.tv_usec=1; |
| |
| if (write(write_pipe,&stat,sizeof(stat))!=sizeof(stat)) { |
| ERR_(module)("dosmod sync lost, errno=%d\n",errno); |
| return; |
| } |
| if (write(write_pipe,&tim,sizeof(tim))!=sizeof(tim)) { |
| ERR_(module)("dosmod sync lost, errno=%d\n",errno); |
| return; |
| } |
| /* there's no return */ |
| } |
| } |
| |
| unsigned DOSVM_GetTimer( void ) |
| { |
| int stat=DOSMOD_GET_TIMER; |
| struct timeval tim; |
| |
| if (MZ_Current()) { |
| if (write(write_pipe,&stat,sizeof(stat))!=sizeof(stat)) { |
| ERR_(module)("dosmod sync lost, errno=%d\n",errno); |
| return 0; |
| } |
| /* read response */ |
| while (1) { |
| if (read(read_pipe,&tim,sizeof(tim))==sizeof(tim)) break; |
| if ((errno==EINTR)||(errno==EAGAIN)) continue; |
| ERR_(module)("dosmod sync lost, errno=%d\n",errno); |
| return 0; |
| } |
| return ((unsigned long long)tim.tv_usec*1193180)/1000000; |
| } |
| return 0; |
| } |
| |
| #else /* !MZ_SUPPORTED */ |
| |
| int DOSVM_Enter( CONTEXT86 *context ) |
| { |
| ERR_(module)("DOS realmode not supported on this architecture!\n"); |
| return -1; |
| } |
| |
| void DOSVM_Wait( int read_pipe, HANDLE hObject) {} |
| void DOSVM_PIC_ioport_out( WORD port, BYTE val) {} |
| void DOSVM_SetTimer( unsigned ticks ) {} |
| unsigned DOSVM_GetTimer( void ) { return 0; } |
| void DOSVM_QueueEvent( int irq, int priority, void (*relay)(CONTEXT86*,void*), void *data) |
| { |
| if (irq<0) { |
| /* callback event, perform it with dummy context */ |
| CONTEXT86 context; |
| memset(&context,0,sizeof(context)); |
| (*relay)(&context,data); |
| } else { |
| ERR_(int)("IRQ without DOS task: should not happen"); |
| } |
| } |
| |
| #endif |