wine/loader/dos/dosmod.c

300 lines
6.9 KiB
C

/*
* DOS Virtual Machine
*
* Copyright 1998 Ove Kåven
*/
#if defined(linux) && defined(__i386__)
#include "config.h"
/* apparently ELF images are usually loaded high anyway */
#ifndef __ELF__
/* if not, force dosmod at high addresses */
asm(".org 0x110000");
#endif /* __ELF__ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#ifdef HAVE_SYS_VM86_H
# include <sys/vm86.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#include "dosmod.h"
/* FIXME: hack because libc vm86 may be the old syscall version */
#define SYS_vm86 166
static inline int vm86plus( int func, struct vm86plus_struct *ptr )
{
int res;
#ifdef __PIC__
__asm__ __volatile__( "pushl %%ebx\n\t"
"movl %2,%%ebx\n\t"
"int $0x80\n\t"
"popl %%ebx"
: "=a" (res)
: "0" (SYS_vm86),
"g" (func),
"c" (ptr) );
#else
__asm__ __volatile__("int $0x80"
: "=a" (res)
: "0" (SYS_vm86),
"b" (func),
"c" (ptr) );
#endif /* __PIC__ */
if (res >= 0) return res;
errno = -res;
return -1;
}
int XREAD(int fd,void*buf,int size) {
int res;
while (1) {
res = read(fd, buf, size);
if (res==size)
return res;
if (res==-1) {
if (errno==EINTR)
continue;
perror("dosmod read");
return -1;
}
if (res) /* don't print the EOF condition */
fprintf(stderr,"dosmod read only %d of %d bytes.\n",res,size);
return res;
}
}
int XWRITE(int fd,void*buf,int size) {
int res;
while (1) {
res = write(fd, buf, size);
if (res==size)
return res;
if (res==-1) {
if (errno==EINTR)
continue;
perror("dosmod write");
return -1;
}
fprintf(stderr,"dosmod write only %d of %d bytes.\n",res,size);
return res;
}
}
void set_timer(struct timeval*tim)
{
struct itimerval cur;
cur.it_interval=*tim;
cur.it_value=*tim;
setitimer(ITIMER_REAL,&cur,NULL);
}
void get_timer(struct timeval*tim)
{
struct itimerval cur;
getitimer(ITIMER_REAL,&cur);
*tim=cur.it_value;
}
volatile int sig_pend,sig_fatal=0,sig_alrm=0,sig_cb=0;
void*img;
struct vm86plus_struct VM86;
void sig_handler(int sig)
{
if (sig_pend) fprintf(stderr,"DOSMOD previous signal %d lost\n",sig_pend);
sig_pend=sig;
signal(sig,sig_handler);
}
void bad_handler(int sig)
{
if (sig!=SIGTERM) {
fprintf(stderr,"DOSMOD caught fatal signal %d\n",sig);
fprintf(stderr,"(Last known VM86 CS:IP was %04x:%04lx)\n",VM86.regs.cs,VM86.regs.eip);
}
sig_pend=sig; sig_fatal++;
signal(sig,bad_handler);
}
void alarm_handler(int sig)
{
sig_alrm++;
signal(sig,alarm_handler);
}
void cb_handler(int sig)
{
sig_cb++;
signal(sig,cb_handler);
}
int send_signal(void)
{
int ret=sig_pend; sig_pend=0;
if (!ret) {
if (sig_alrm) {
ret=SIGALRM; sig_alrm--;
} else {
ret=SIGUSR2; sig_cb--;
}
}
if (XWRITE(1,&ret,sizeof(ret))!=sizeof(ret)) return 1;
if (sig_fatal) return 1;
return 0;
}
int send_enter_reply(int ret)
{
if (XWRITE(1,&ret,sizeof(ret))!=sizeof(ret)) return 1;
if (XWRITE(1,&VM86,sizeof(VM86))!=sizeof(VM86)) return 1;
switch (ret&0xff) {
case DOSMOD_SIGNAL:
return send_signal();
}
return 0;
}
int main(int argc,char**argv)
{
int mfd=open(argv[0],O_RDWR);
struct timeval tim;
mprot_info mpr;
int func,ret,idle;
off_t fofs=0;
pid_t ppid=getppid();
/* fprintf(stderr,"main is at %08lx, file is %s, fd=%d\n",(unsigned long)&main,argv[0],mfd); */
if (mfd<0) return 1;
/* Map in our DOS image at the start of the process address space */
if (argv[1]) {
/* Ulrich Weigand suggested mapping in the DOS image directly from the Wine
address space */
fofs=atol(argv[1]);
/* linux currently only allows mapping a process memory if it's being ptraced */
/* Linus doesn't like it, so this probably won't work in the future */
/* it doesn't even work for me right now */
kill(ppid,SIGSTOP);
ptrace(PTRACE_ATTACH,ppid,0,0);
waitpid(ppid,NULL,0);
}
img=mmap(NULL,0x110000,PROT_EXEC|PROT_READ|PROT_WRITE,MAP_FIXED|MAP_SHARED,mfd,fofs);
if (argv[1]) {
ptrace(PTRACE_DETACH,ppid,0,0);
kill(ppid,SIGCONT);
}
if (img==(void*)-1) {
fprintf(stderr,"DOS memory map failed, error=%s\n",strerror(errno));
fprintf(stderr,"in attempt to map %s, offset %08lX, length 110000, to offset 0\n",argv[0],fofs);
return 1;
}
/* initialize signals and system timer */
signal(SIGHUP,sig_handler);
signal(SIGINT,sig_handler);
signal(SIGUSR1,sig_handler);
signal(SIGUSR2,cb_handler);
signal(SIGALRM,alarm_handler);
signal(SIGQUIT,bad_handler);
signal(SIGILL,bad_handler);
signal(SIGBUS,bad_handler);
signal(SIGFPE,bad_handler);
signal(SIGSEGV,bad_handler);
signal(SIGTERM,bad_handler);
#if 0
tim.tv_sec=0; tim.tv_usec=54925;
set_timer(&tim);
#endif
/* report back to the main program that we're ready */
ret=3; /* dosmod protocol revision */
XWRITE(1,&ret,sizeof(ret));
/* context exchange loop */
idle=0;
do {
if (idle) {
while (1) {
int res;
/* parent is idle, transmit any signals (particularly SIGALRM) */
if (sig_pend||sig_alrm||sig_cb) {
ret=DOSMOD_SIGNAL;
if (XWRITE(1,&ret,sizeof(ret))!=sizeof(ret)) return 1;
ret=send_signal();
if (ret) return ret;
break;
}
res = read(0,&func,sizeof(func));
if (res==sizeof(func))
break;
if (res==-1) {
if (errno==EINTR)
continue;
perror("dosmod read");
return 1;
}
if (res) /* don't print the EOF condition */
fprintf(stderr,"dosmod read only %d of %d bytes.\n",res,sizeof(func));
return 1;
}
ret=DOSMOD_LEFTIDLE;
if (XWRITE(1,&ret,sizeof(ret))!=sizeof(ret)) return 1;
idle--;
} else
if (XREAD(0,&func,sizeof(func))!=sizeof(func)) return 1;
if (func<0) break;
switch (func) {
case DOSMOD_SET_TIMER: /* rev 1 */
if (XREAD(0,&tim,sizeof(tim))!=sizeof(tim)) return 1;
set_timer(&tim);
/* no response */
break;
case DOSMOD_GET_TIMER: /* rev 1 */
get_timer(&tim);
if (XWRITE(1,&tim,sizeof(tim))!=sizeof(tim)) return 1;
break;
case DOSMOD_MPROTECT: /* rev 3 */
if (XREAD(0,&mpr,sizeof(mpr))!=sizeof(mpr)) return 1;
mprotect(mpr.addr,mpr.len,mpr.prot);
/* no response */
break;
case DOSMOD_ENTERIDLE: /* rev 3 */
idle++;
break;
case DOSMOD_LEAVEIDLE: /* rev 3 */
break;
case DOSMOD_ENTER: /* rev 0 */
default:
if (XREAD(0,&VM86,sizeof(VM86))!=sizeof(VM86)) return 1;
if (sig_pend||sig_alrm||sig_cb) ret=DOSMOD_SIGNAL; else
ret=vm86plus(func,&VM86);
ret=send_enter_reply(ret);
if (ret) return ret;
}
} while (1);
return 0;
}
#else /* !linux-i386 */
int main(void) {return 1;}
#endif