diff options
-rw-r--r-- | TODO | 8 | ||||
-rw-r--r-- | cpu-i386.h | 13 | ||||
-rw-r--r-- | exec-i386.c | 173 | ||||
-rw-r--r-- | exec-i386.h | 4 | ||||
-rw-r--r-- | linux-user/elfload.c | 3 | ||||
-rw-r--r-- | linux-user/ioctls.h | 6 | ||||
-rw-r--r-- | linux-user/main.c | 52 | ||||
-rw-r--r-- | linux-user/qemu.h | 33 | ||||
-rw-r--r-- | linux-user/signal.c | 244 | ||||
-rw-r--r-- | linux-user/syscall.c | 115 | ||||
-rw-r--r-- | linux-user/syscall_defs.h | 11 | ||||
-rw-r--r-- | op-i386.c | 48 | ||||
-rw-r--r-- | syscall-i386.h | 46 | ||||
-rw-r--r-- | tests/testsig.c | 75 | ||||
-rw-r--r-- | translate-i386.c | 17 |
15 files changed, 642 insertions, 206 deletions
@@ -1,11 +1,9 @@ -- asynchronous signal interrupt / clear synchronous signal handling -- add eflags restore in emulator -- finish signal handing (fp87 state) -- verify thread support (clone() and various locks) - optimize translated cache chaining (DLL PLT-like system) +- more syscalls (in particular all 64 bit ones, IPCs, fix 64 bit issues) +- finish signal handing (fp87 state, more siginfo conversions) +- verify thread support (clone() and various locks) - vm86 syscall support - overrides/16bit for string ops -- more syscalls (in particular all 64 bit ones) - make it self runnable (use same trick as ld.so : include its own relocator and libc) - improved 16 bit support - fix FPU exceptions (in particular: gen_op_fpush not before mem load) @@ -68,7 +68,7 @@ #define EXCP11_ALGN 18 #define EXCP12_MCHK 19 -#define EXCP_SIGNAL 256 /* async signal */ +#define EXCP_INTERRUPT 256 /* async interruption */ enum { CC_OP_DYNAMIC, /* must use dynamic code to get cc_op */ @@ -170,9 +170,10 @@ typedef struct CPUX86State { /* various CPU modes */ int vm86; - /* exception handling */ + /* exception/interrupt handling */ jmp_buf jmp_env; int exception_index; + int interrupt_request; } CPUX86State; /* all CPU memory access use these macros */ @@ -383,11 +384,19 @@ int cpu_x86_inl(int addr); CPUX86State *cpu_x86_init(void); int cpu_x86_exec(CPUX86State *s); +void cpu_x86_interrupt(CPUX86State *s); void cpu_x86_close(CPUX86State *s); /* needed to load some predefinied segment registers */ void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector); +/* you can call these signal handler from you SIGBUS and SIGSEGV + signal handlers to inform the virtual CPU of exceptions. non zero + is returned if the signal was handled by the virtual CPU. */ +struct siginfo; +int cpu_x86_signal_handler(int host_signum, struct siginfo *info, + void *puc); + /* internal functions */ #define GEN_FLAG_CODE32_SHIFT 0 diff --git a/exec-i386.c b/exec-i386.c index 573ba0a..269a1e4 100644 --- a/exec-i386.c +++ b/exec-i386.c @@ -21,6 +21,7 @@ //#define DEBUG_EXEC #define DEBUG_FLUSH +//#define DEBUG_SIGNAL /* main execution loop */ @@ -98,7 +99,41 @@ void cpu_unlock(void) global_cpu_lock = 0; } -#ifdef DEBUG_EXEC +/* exception support */ +/* NOTE: not static to force relocation generation by GCC */ +void raise_exception(int exception_index) +{ + /* NOTE: the register at this point must be saved by hand because + longjmp restore them */ +#ifdef reg_EAX + env->regs[R_EAX] = EAX; +#endif +#ifdef reg_ECX + env->regs[R_ECX] = ECX; +#endif +#ifdef reg_EDX + env->regs[R_EDX] = EDX; +#endif +#ifdef reg_EBX + env->regs[R_EBX] = EBX; +#endif +#ifdef reg_ESP + env->regs[R_ESP] = ESP; +#endif +#ifdef reg_EBP + env->regs[R_EBP] = EBP; +#endif +#ifdef reg_ESI + env->regs[R_ESI] = ESI; +#endif +#ifdef reg_EDI + env->regs[R_EDI] = EDI; +#endif + env->exception_index = exception_index; + longjmp(env->jmp_env, 1); +} + +#if defined(DEBUG_EXEC) static const char *cc_op_str[] = { "DYNAMIC", "EFLAGS", @@ -132,15 +167,16 @@ static const char *cc_op_str[] = { "SARL", }; -static void cpu_x86_dump_state(void) +static void cpu_x86_dump_state(FILE *f) { int eflags; eflags = cc_table[CC_OP].compute_all(); eflags |= (DF & DIRECTION_FLAG); - fprintf(logfile, + fprintf(f, "EAX=%08x EBX=%08X ECX=%08x EDX=%08x\n" "ESI=%08x EDI=%08X EBP=%08x ESP=%08x\n" - "CCS=%08x CCD=%08x CCO=%-8s EFL=%c%c%c%c%c%c%c\n", + "CCS=%08x CCD=%08x CCO=%-8s EFL=%c%c%c%c%c%c%c\n" + "EIP=%08x\n", env->regs[R_EAX], env->regs[R_EBX], env->regs[R_ECX], env->regs[R_EDX], env->regs[R_ESI], env->regs[R_EDI], env->regs[R_EBP], env->regs[R_ESP], env->cc_src, env->cc_dst, cc_op_str[env->cc_op], @@ -150,10 +186,10 @@ static void cpu_x86_dump_state(void) eflags & CC_Z ? 'Z' : '-', eflags & CC_A ? 'A' : '-', eflags & CC_P ? 'P' : '-', - eflags & CC_C ? 'C' : '-' - ); + eflags & CC_C ? 'C' : '-', + env->eip); #if 1 - fprintf(logfile, "ST0=%f ST1=%f ST2=%f ST3=%f\n", + fprintf(f, "ST0=%f ST1=%f ST2=%f ST3=%f\n", (double)ST0, (double)ST1, (double)ST(2), (double)ST(3)); #endif } @@ -185,10 +221,11 @@ static void tb_flush(void) } /* find a translation block in the translation cache. If not found, - allocate a new one */ -static inline TranslationBlock *tb_find_and_alloc(unsigned long pc, - unsigned long cs_base, - unsigned int flags) + return NULL and the pointer to the last element of the list in pptb */ +static inline TranslationBlock *tb_find(TranslationBlock ***pptb, + unsigned long pc, + unsigned long cs_base, + unsigned int flags) { TranslationBlock **ptb, *tb; unsigned int h; @@ -203,16 +240,19 @@ static inline TranslationBlock *tb_find_and_alloc(unsigned long pc, return tb; ptb = &tb->hash_next; } + *pptb = ptb; + return NULL; +} + +/* allocate a new translation block. flush the translation buffer if + too many translation blocks or too much generated code */ +static inline TranslationBlock *tb_alloc(void) +{ + TranslationBlock *tb; if (nb_tbs >= CODE_GEN_MAX_BLOCKS || (code_gen_ptr - code_gen_buffer) >= CODE_GEN_BUFFER_MAX_SIZE) tb_flush(); tb = &tbs[nb_tbs++]; - *ptb = tb; - tb->pc = pc; - tb->cs_base = cs_base; - tb->flags = flags; - tb->tc_ptr = NULL; - tb->hash_next = NULL; return tb; } @@ -246,7 +286,7 @@ int cpu_x86_exec(CPUX86State *env1) #endif int code_gen_size, ret; void (*gen_func)(void); - TranslationBlock *tb; + TranslationBlock *tb, **ptb; uint8_t *tc_ptr, *cs_base, *pc; unsigned int flags; @@ -289,12 +329,21 @@ int cpu_x86_exec(CPUX86State *env1) EDI = env->regs[R_EDI]; #endif + /* put eflags in CPU temporary format */ + T0 = env->eflags; + op_movl_eflags_T0(); + CC_OP = CC_OP_EFLAGS; + env->interrupt_request = 0; + /* prepare setjmp context for exception handling */ if (setjmp(env->jmp_env) == 0) { for(;;) { + if (env->interrupt_request) { + raise_exception(EXCP_INTERRUPT); + } #ifdef DEBUG_EXEC if (loglevel) { - cpu_x86_dump_state(); + cpu_x86_dump_state(logfile); } #endif /* we compute the CPU state. We assume it will not @@ -307,28 +356,43 @@ int cpu_x86_exec(CPUX86State *env1) GEN_FLAG_ADDSEG_SHIFT; cs_base = env->seg_cache[R_CS].base; pc = cs_base + env->eip; - tb = tb_find_and_alloc((unsigned long)pc, (unsigned long)cs_base, - flags); - tc_ptr = tb->tc_ptr; - if (!tb->tc_ptr) { + tb = tb_find(&ptb, (unsigned long)pc, (unsigned long)cs_base, + flags); + if (!tb) { /* if no translated code available, then translate it now */ /* XXX: very inefficient: we lock all the cpus when generating code */ cpu_lock(); tc_ptr = code_gen_ptr; - cpu_x86_gen_code(code_gen_ptr, CODE_GEN_MAX_SIZE, - &code_gen_size, pc, cs_base, flags); + ret = cpu_x86_gen_code(code_gen_ptr, CODE_GEN_MAX_SIZE, + &code_gen_size, pc, cs_base, flags); + /* if invalid instruction, signal it */ + if (ret != 0) { + cpu_unlock(); + raise_exception(EXCP06_ILLOP); + } + tb = tb_alloc(); + *ptb = tb; + tb->pc = (unsigned long)pc; + tb->cs_base = (unsigned long)cs_base; + tb->flags = flags; tb->tc_ptr = tc_ptr; + tb->hash_next = NULL; code_gen_ptr = (void *)(((unsigned long)code_gen_ptr + code_gen_size + CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1)); cpu_unlock(); } /* execute the generated code */ + tc_ptr = tb->tc_ptr; gen_func = (void *)tc_ptr; gen_func(); } } ret = env->exception_index; + /* restore flags in standard format */ + op_movl_T0_eflags(); + env->eflags = T0; + /* restore global registers */ #ifdef reg_EAX EAX = saved_EAX; @@ -361,6 +425,12 @@ int cpu_x86_exec(CPUX86State *env1) return ret; } +void cpu_x86_interrupt(CPUX86State *s) +{ + s->interrupt_request = 1; +} + + void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector) { CPUX86State *saved_env; @@ -370,3 +440,56 @@ void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector) load_seg(seg_reg, selector); env = saved_env; } + +#undef EAX +#undef ECX +#undef EDX +#undef EBX +#undef ESP +#undef EBP +#undef ESI +#undef EDI +#undef EIP +#include <signal.h> +#include <sys/ucontext.h> + +static inline int handle_cpu_signal(unsigned long pc, + sigset_t *old_set) +{ +#ifdef DEBUG_SIGNAL + printf("gemu: SIGSEGV pc=0x%08lx oldset=0x%08lx\n", + pc, *(unsigned long *)old_set); +#endif + if (pc >= (unsigned long)code_gen_buffer && + pc < (unsigned long)code_gen_buffer + CODE_GEN_BUFFER_SIZE) { + /* the PC is inside the translated code. It means that we have + a virtual CPU fault */ + /* we restore the process signal mask as the sigreturn should + do it */ + sigprocmask(SIG_SETMASK, old_set, NULL); + /* XXX: need to compute virtual pc position by retranslating + code. The rest of the CPU state should be correct. */ + raise_exception(EXCP0D_GPF); + /* never comes here */ + return 1; + } else { + return 0; + } +} + +int cpu_x86_signal_handler(int host_signum, struct siginfo *info, + void *puc) +{ +#if defined(__i386__) + struct ucontext *uc = puc; + unsigned long pc; + sigset_t *pold_set; + + pc = uc->uc_mcontext.gregs[EIP]; + pold_set = &uc->uc_sigmask; + return handle_cpu_signal(pc, pold_set); +#else +#warning No CPU specific signal handler: cannot handle target SIGSEGV events + return 0; +#endif +} diff --git a/exec-i386.h b/exec-i386.h index 0bcfd22..d6cac3b 100644 --- a/exec-i386.h +++ b/exec-i386.h @@ -141,3 +141,7 @@ extern CCTable cc_table[]; void load_seg(int seg_reg, int selector); void cpu_lock(void); void cpu_unlock(void); +void raise_exception(int exception_index); + +void OPPROTO op_movl_eflags_T0(void); +void OPPROTO op_movl_T0_eflags(void); diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 43d989e..285d8f5 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -261,6 +261,9 @@ unsigned long setup_arg_pages(unsigned long p, struct linux_binprm * bprm, /* Create enough stack to hold everything. If we don't use * it for args, we'll use it for something else... */ + /* XXX: on x86 MAP_GROWSDOWN only works if ESP <= address + 32, so + we allocate a bigger stack. Need a better solution, for example + by remapping the process stack directly at the right place */ if(x86_stack_size > MAX_ARG_PAGES*X86_PAGE_SIZE) { if((long)mmap4k((void *)(X86_STACK_TOP-x86_stack_size), x86_stack_size + X86_PAGE_SIZE, PROT_READ | PROT_WRITE, diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h index eeefcac..f075aff 100644 --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h @@ -1,7 +1,7 @@ /* emulated ioctl list */ IOCTL(TCGETS, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios))) - IOCTL(TCGETS, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) + IOCTL(TCSETS, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) IOCTL(TCSETSF, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) IOCTL(TCSETSW, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) IOCTL(TIOCGWINSZ, IOC_R, MK_PTR(MK_STRUCT(STRUCT_winsize))) @@ -199,8 +199,12 @@ IOCTL(SNDCTL_TMR_METRONOME, IOC_W, MK_PTR(TYPE_INT)) IOCTL(SNDCTL_TMR_SELECT, IOC_W, MK_PTR(TYPE_INT)) IOCTL(SNDCTL_TMR_SOURCE, IOC_RW, MK_PTR(TYPE_INT)) +#if 0 + /* we invalidate these defines because they have a same number as + termios ioctls */ IOCTL(SNDCTL_TMR_START, 0, TYPE_NULL) IOCTL(SNDCTL_TMR_STOP, 0, TYPE_NULL) +#endif IOCTL(SNDCTL_TMR_TEMPO, IOC_RW, MK_PTR(TYPE_INT)) IOCTL(SNDCTL_TMR_TIMEBASE, IOC_RW, MK_PTR(TYPE_INT)) diff --git a/linux-user/main.c b/linux-user/main.c index bcaa4be..fba23a0 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -33,7 +33,10 @@ FILE *logfile = NULL; int loglevel; -unsigned long x86_stack_size; +/* XXX: on x86 MAP_GROWSDOWN only works if ESP <= address + 32, so + we allocate a bigger stack. Need a better solution, for example + by remapping the process stack directly at the right place */ +unsigned long x86_stack_size = 512 * 1024; unsigned long stktop; void gemu_log(const char *fmt, ...) @@ -102,10 +105,11 @@ uint64_t gdt_table[6]; void cpu_loop(struct CPUX86State *env) { + int err; + uint8_t *pc; + target_siginfo_t info; + for(;;) { - int err; - uint8_t *pc; - err = cpu_x86_exec(env); pc = env->seg_cache[R_CS].base + env->eip; switch(err) { @@ -122,12 +126,42 @@ void cpu_loop(struct CPUX86State *env) env->regs[R_EDI], env->regs[R_EBP]); } else { - goto trap_error; + /* XXX: more precise info */ + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = 0; + info._sifields._sigfault._addr = 0; + queue_signal(info.si_signo, &info); } break; + case EXCP00_DIVZ: + /* division by zero */ + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_code = TARGET_FPE_INTDIV; + info._sifields._sigfault._addr = env->eip; + queue_signal(info.si_signo, &info); + break; + case EXCP04_INTO: + case EXCP05_BOUND: + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = 0; + info._sifields._sigfault._addr = 0; + queue_signal(info.si_signo, &info); + break; + case EXCP06_ILLOP: + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = TARGET_ILL_ILLOPN; + info._sifields._sigfault._addr = env->eip; + queue_signal(info.si_signo, &info); + break; + case EXCP_INTERRUPT: + /* just indicate that signals should be handled asap */ + break; default: - trap_error: - fprintf(stderr, "0x%08lx: Unknown exception %d, aborting\n", + fprintf(stderr, "0x%08lx: Unknown exception CPU %d, aborting\n", (long)pc, err); abort(); } @@ -144,6 +178,9 @@ void usage(void) exit(1); } +/* XXX: currently only used for async signals (see signal.c) */ +CPUX86State *global_env; + int main(int argc, char **argv) { const char *filename; @@ -199,6 +236,7 @@ int main(int argc, char **argv) signal_init(); env = cpu_x86_init(); + global_env = env; /* linux register setup */ env->regs[R_EAX] = regs->eax; diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 77e9eca..8626955 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -3,30 +3,12 @@ #include "thunk.h" -#ifdef TARGET_I386 - -/* default linux values for the selectors */ -#define __USER_CS (0x23) -#define __USER_DS (0x2B) - -struct target_pt_regs { - long ebx; - long ecx; - long edx; - long esi; - long edi; - long ebp; - long eax; - int xds; - int xes; - long orig_eax; - long eip; - int xcs; - long eflags; - long esp; - int xss; -}; +#include <signal.h> +#include "syscall_defs.h" +#ifdef TARGET_I386 +#include "cpu-i386.h" +#include "syscall-i386.h" #endif /* This struct is used to hold certain information about the image. @@ -59,9 +41,10 @@ void syscall_init(void); long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6); void gemu_log(const char *fmt, ...) __attribute__((format(printf,1,2))); -struct CPUX86State; -void cpu_loop(struct CPUX86State *env); +extern CPUX86State *global_env; +void cpu_loop(CPUX86State *env); void process_pending_signals(void *cpu_env); void signal_init(void); +int queue_signal(int sig, target_siginfo_t *info); #endif diff --git a/linux-user/signal.c b/linux-user/signal.c index 61baf99..3e792ae 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -27,13 +27,6 @@ #include "gemu.h" -#include "syscall_defs.h" - -#ifdef TARGET_I386 -#include "cpu-i386.h" -#include "syscall-i386.h" -#endif - /* signal handling inspired from em86. */ //#define DEBUG_SIGNAL @@ -42,7 +35,7 @@ struct sigqueue { struct sigqueue *next; - siginfo_t info; + target_siginfo_t info; }; struct emulated_sigaction { @@ -101,20 +94,66 @@ void target_to_host_old_sigset(sigset_t *sigset, *(unsigned long *)sigset = tswapl(*old_sigset); } -/* XXX: finish it */ -void host_to_target_siginfo(target_siginfo_t *tinfo, siginfo_t *info) +/* siginfo conversion */ + +static inline void host_to_target_siginfo_noswap(target_siginfo_t *tinfo, + const siginfo_t *info) { - tinfo->si_signo = tswap32(info->si_signo); + int sig; + sig = host_to_target_signal(info->si_signo); + tinfo->si_signo = sig; + tinfo->si_errno = 0; + tinfo->si_code = 0; + if (sig == SIGILL || sig == SIGFPE || sig == SIGSEGV || sig == SIGBUS) { + /* should never come here, but who knows. The information for + the target is irrelevant */ + tinfo->_sifields._sigfault._addr = 0; + } else if (sig >= TARGET_SIGRTMIN) { + tinfo->_sifields._rt._pid = info->si_pid; + tinfo->_sifields._rt._uid = info->si_uid; + /* XXX: potential problem if 64 bit */ + tinfo->_sifields._rt._sigval.sival_ptr = + (target_ulong)info->si_value.sival_ptr; + } +} + +static void tswap_siginfo(target_siginfo_t *tinfo, + const target_siginfo_t *info) +{ + int sig; + sig = info->si_signo; + tinfo->si_signo = tswap32(sig); tinfo->si_errno = tswap32(info->si_errno); tinfo->si_code = tswap32(info->si_code); + if (sig == SIGILL || sig == SIGFPE || sig == SIGSEGV || sig == SIGBUS) { + tinfo->_sifields._sigfault._addr = + tswapl(info->_sifields._sigfault._addr); + } else if (sig >= TARGET_SIGRTMIN) { + tinfo->_sifields._rt._pid = tswap32(info->_sifields._rt._pid); + tinfo->_sifields._rt._uid = tswap32(info->_sifields._rt._uid); + tinfo->_sifields._rt._sigval.sival_ptr = + tswapl(info->_sifields._rt._sigval.sival_ptr); + } +} + + +void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info) +{ + host_to_target_siginfo_noswap(tinfo, info); + tswap_siginfo(tinfo, tinfo); } -/* XXX: finish it */ -void target_to_host_siginfo(siginfo_t *info, target_siginfo_t *tinfo) +/* XXX: we support only POSIX RT signals are used. */ +/* XXX: find a solution for 64 bit (additionnal malloced data is needed) */ +void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo) { info->si_signo = tswap32(tinfo->si_signo); info->si_errno = tswap32(tinfo->si_errno); info->si_code = tswap32(tinfo->si_code); + info->si_pid = tswap32(tinfo->_sifields._rt._pid); + info->si_uid = tswap32(tinfo->_sifields._rt._uid); + info->si_value.sival_ptr = + (void *)tswapl(tinfo->_sifields._rt._sigval.sival_ptr); } void signal_init(void) @@ -122,8 +161,9 @@ void signal_init(void) struct sigaction act; int i; - /* set all host signal handlers */ - sigemptyset(&act.sa_mask); + /* set all host signal handlers. ALL signals are blocked during + the handlers to serialize them. */ + sigfillset(&act.sa_mask); act.sa_flags = SA_SIGINFO; act.sa_sigaction = host_signal_handler; for(i = 1; i < NSIG; i++) { @@ -155,56 +195,40 @@ static inline void free_sigqueue(struct sigqueue *q) first_free = q; } -static int queue_signal(struct emulated_sigaction *k, int sig, siginfo_t *info) -{ - struct sigqueue *q, **pq; - - pq = &k->first; - if (!k->pending || sig < TARGET_SIGRTMIN) { - /* first signal or non real time signal */ - q = &k->info; - } else { - q = alloc_sigqueue(); - if (!q) - return -EAGAIN; - while (*pq != NULL) - pq = &(*pq)->next; - } - *pq = q; - q->info = *info; - q->next = NULL; - k->pending = 1; - /* signal that a new signal is pending */ - signal_pending = 1; - return 0; -} - -void force_sig(int sig) +/* abort execution with signal */ +void __attribute((noreturn)) force_sig(int sig) { int host_sig; - /* abort execution with signal */ host_sig = target_to_host_signal(sig); fprintf(stderr, "gemu: uncaught target signal %d (%s) - exiting\n", sig, strsignal(host_sig)); +#if 1 _exit(-host_sig); +#else + { + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = SIG_DFL; + sigaction(SIGABRT, &act, NULL); + abort(); + } +#endif } - -static void host_signal_handler(int host_signum, siginfo_t *info, - void *puc) +/* queue a signal so that it will be send to the virtual CPU as soon + as possible */ +int queue_signal(int sig, target_siginfo_t *info) { struct emulated_sigaction *k; - int sig; + struct sigqueue *q, **pq; target_ulong handler; - /* get target signal number */ - sig = host_to_target_signal(host_signum); - if (sig < 1 || sig > TARGET_NSIG) - return; - k = &sigact_table[sig - 1]; -#ifdef DEBUG_SIGNAL - fprintf(stderr, "gemu: got signal %d\n", sig); +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "queue_sigal: sig=%d\n", + sig); #endif + k = &sigact_table[sig - 1]; handler = k->sa._sa_handler; if (handler == TARGET_SIG_DFL) { /* default handler : ignore some signal. The other are fatal */ @@ -212,13 +236,96 @@ static void host_signal_handler(int host_signum, siginfo_t *info, sig != TARGET_SIGURG && sig != TARGET_SIGWINCH) { force_sig(sig); + } else { + return 0; /* indicate ignored */ } } else if (handler == TARGET_SIG_IGN) { /* ignore signal */ + return 0; } else if (handler == TARGET_SIG_ERR) { force_sig(sig); } else { - queue_signal(k, sig, info); + pq = &k->first; + if (sig < TARGET_SIGRTMIN) { + /* if non real time signal, we queue exactly one signal */ + if (!k->pending) + q = &k->info; + else + return 0; + } else { + if (!k->pending) { + /* first signal */ + q = &k->info; + } else { + q = alloc_sigqueue(); + if (!q) + return -EAGAIN; + while (*pq != NULL) + pq = &(*pq)->next; + } + } + *pq = q; + q->info = *info; + q->next = NULL; + k->pending = 1; + /* signal that a new signal is pending */ + signal_pending = 1; + return 1; /* indicates that the signal was queued */ + } +} + +#if defined(DEBUG_SIGNAL) +#ifdef __i386__ +static void dump_regs(struct ucontext *uc) +{ + fprintf(stderr, + "EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n" + "ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n" + "EFL=%08x EIP=%08x\n", + uc->uc_mcontext.gregs[EAX], + uc->uc_mcontext.gregs[EBX], + uc->uc_mcontext.gregs[ECX], + uc->uc_mcontext.gregs[EDX], + uc->uc_mcontext.gregs[ESI], + uc->uc_mcontext.gregs[EDI], + uc->uc_mcontext.gregs[EBP], + uc->uc_mcontext.gregs[ESP], + uc->uc_mcontext.gregs[EFL], + uc->uc_mcontext.gregs[EIP]); +} +#else +static void dump_regs(struct ucontext *uc) +{ +} +#endif + +#endif + +static void host_signal_handler(int host_signum, siginfo_t *info, + void *puc) +{ + int sig; + target_siginfo_t tinfo; + + /* the CPU emulator uses some host signals to detect exceptions, + we we forward to it some signals */ + if (host_signum == SIGSEGV || host_signum == SIGBUS) { + if (cpu_x86_signal_handler(host_signum, info, puc)) + return; + } + + /* get target signal number */ + sig = host_to_target_signal(host_signum); + if (sig < 1 || sig > TARGET_NSIG) + return; +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "gemu: got signal %d\n", sig); + dump_regs(puc); +#endif + host_to_target_siginfo_noswap(&tinfo, info); + if (queue_signal(sig, &tinfo) == 1) { + /* interrupt the virtual CPU as soon as possible */ + cpu_x86_interrupt(global_env); } } @@ -388,9 +495,10 @@ struct rt_sigframe 0;\ }) -static inline int copy_siginfo_to_user(target_siginfo_t *tinfo, siginfo_t *info) +static inline int copy_siginfo_to_user(target_siginfo_t *tinfo, + const target_siginfo_t *info) { - host_to_target_siginfo(tinfo, info); + tswap_siginfo(tinfo, info); return 0; } @@ -531,7 +639,8 @@ give_sigsegv: force_sig(TARGET_SIGSEGV /* , current */); } -static void setup_rt_frame(int sig, struct emulated_sigaction *ka, siginfo_t *info, +static void setup_rt_frame(int sig, struct emulated_sigaction *ka, + target_siginfo_t *info, target_sigset_t *set, CPUX86State *env) { struct rt_sigframe *frame; @@ -734,7 +843,8 @@ void process_pending_signals(void *cpu_env) { int sig; target_ulong handler; - target_sigset_t set; + sigset_t set, old_set; + target_sigset_t target_old_set; struct emulated_sigaction *k; struct sigqueue *q; @@ -774,12 +884,24 @@ void process_pending_signals(void *cpu_env) } else if (handler == TARGET_SIG_ERR) { force_sig(sig); } else { - set = k->sa.sa_mask; - /* send the signal to the CPU */ + /* compute the blocked signals during the handler execution */ + target_to_host_sigset(&set, &k->sa.sa_mask); + /* SA_NODEFER indicates that the current signal should not be + blocked during the handler */ + if (!(k->sa.sa_flags & TARGET_SA_NODEFER)) + sigaddset(&set, target_to_host_signal(sig)); + + /* block signals in the handler using Linux */ + sigprocmask(SIG_BLOCK, &set, &old_set); + /* save the previous blocked signal state to restore it at the + end of the signal execution (see do_sigreturn) */ + host_to_target_sigset(&target_old_set, &old_set); + + /* prepare the stack frame of the virtual CPU */ if (k->sa.sa_flags & TARGET_SA_SIGINFO) - setup_rt_frame(sig, k, &q->info, &set, cpu_env); + setup_rt_frame(sig, k, &q->info, &target_old_set, cpu_env); else - setup_frame(sig, k, &set, cpu_env); + setup_frame(sig, k, &target_old_set, cpu_env); if (k->sa.sa_flags & TARGET_SA_RESETHAND) k->sa._sa_handler = TARGET_SIG_DFL; } diff --git a/linux-user/syscall.c b/linux-user/syscall.c index dbe3de3..ab78fbb 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -38,6 +38,7 @@ #include <sched.h> #include <sys/socket.h> #include <sys/uio.h> +#include <sys/poll.h> //#include <sys/user.h> #define termios host_termios @@ -68,15 +69,8 @@ #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2]) #define VFAT_IOCTL_READDIR_SHORT _IOR('r', 2, struct dirent [2]) -#include "syscall_defs.h" - -#ifdef TARGET_I386 -#include "cpu-i386.h" -#include "syscall-i386.h" -#endif - -void host_to_target_siginfo(target_siginfo_t *tinfo, siginfo_t *info); -void target_to_host_siginfo(siginfo_t *info, target_siginfo_t *tinfo); +void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info); +void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo); long do_sigreturn(CPUX86State *env); long do_rt_sigreturn(CPUX86State *env); @@ -106,6 +100,9 @@ _syscall2(int,sys_fstatfs,int,fd,struct kernel_statfs *,buf) _syscall3(int,sys_rt_sigqueueinfo,int,pid,int,sig,siginfo_t *,uinfo) extern int personality(int); +extern int flock(int, int); +extern int setfsuid(int); +extern int setfsgid(int); static inline long get_errno(long ret) { @@ -437,7 +434,7 @@ static long do_ioctl(long fd, long cmd, long arg) ie++; } arg_type = ie->arg_type; -#ifdef DEBUG +#if defined(DEBUG) gemu_log("ioctl: cmd=0x%04lx (%s)\n", cmd, ie->name); #endif switch(arg_type[0]) { @@ -1244,9 +1241,30 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, ret = get_errno(sethostname((const char *)arg1, arg2)); break; case TARGET_NR_setrlimit: - goto unimplemented; + { + /* XXX: convert resource ? */ + int resource = arg1; + struct target_rlimit *target_rlim = (void *)arg2; + struct rlimit rlim; + rlim.rlim_cur = tswapl(target_rlim->rlim_cur); + rlim.rlim_max = tswapl(target_rlim->rlim_max); + ret = get_errno(setrlimit(resource, &rlim)); + } + break; case TARGET_NR_getrlimit: - goto unimplemented; + { + /* XXX: convert resource ? */ + int resource = arg1; + struct target_rlimit *target_rlim = (void *)arg2; + struct rlimit rlim; + + ret = get_errno(getrlimit(resource, &rlim)); + if (!is_error(ret)) { + target_rlim->rlim_cur = tswapl(rlim.rlim_cur); + target_rlim->rlim_max = tswapl(rlim.rlim_max); + } + } + break; case TARGET_NR_getrusage: goto unimplemented; case TARGET_NR_gettimeofday: @@ -1317,6 +1335,27 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, case TARGET_NR_munmap: ret = get_errno(munmap((void *)arg1, arg2)); break; + case TARGET_NR_mprotect: + ret = get_errno(mprotect((void *)arg1, arg2, arg3)); + break; + case TARGET_NR_mremap: + ret = get_errno((long)mremap((void *)arg1, arg2, arg3, arg4)); + break; + case TARGET_NR_msync: + ret = get_errno(msync((void *)arg1, arg2, arg3)); + break; + case TARGET_NR_mlock: + ret = get_errno(mlock((void *)arg1, arg2)); + break; + case TARGET_NR_munlock: + ret = get_errno(munlock((void *)arg1, arg2)); + break; + case TARGET_NR_mlockall: + ret = get_errno(mlockall(arg1)); + break; + case TARGET_NR_munlockall: + ret = get_errno(munlockall()); + break; case TARGET_NR_truncate: ret = get_errno(truncate((const char *)arg1, arg2)); break; @@ -1506,9 +1545,6 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, #endif case TARGET_NR_adjtimex: goto unimplemented; - case TARGET_NR_mprotect: - ret = get_errno(mprotect((void *)arg1, arg2, arg3)); - break; case TARGET_NR_create_module: case TARGET_NR_init_module: case TARGET_NR_delete_module: @@ -1532,9 +1568,11 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, case TARGET_NR_afs_syscall: goto unimplemented; case TARGET_NR_setfsuid: - goto unimplemented; + ret = get_errno(setfsuid(arg1)); + break; case TARGET_NR_setfsgid: - goto unimplemented; + ret = get_errno(setfsgid(arg1)); + break; case TARGET_NR__llseek: { int64_t res; @@ -1596,10 +1634,31 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, ret = do_select(arg1, (void *)arg2, (void *)arg3, (void *)arg4, (void *)arg5); break; + case TARGET_NR_poll: + { + struct target_pollfd *target_pfd = (void *)arg1; + unsigned int nfds = arg2; + int timeout = arg3; + struct pollfd *pfd; + int i; + + pfd = alloca(sizeof(struct pollfd) * nfds); + for(i = 0; i < nfds; i++) { + pfd->fd = tswap32(target_pfd->fd); + pfd->events = tswap16(target_pfd->events); + } + ret = get_errno(poll(pfd, nfds, timeout)); + if (!is_error(ret)) { + for(i = 0; i < nfds; i++) { + target_pfd->revents = tswap16(pfd->revents); + } + } + } + break; case TARGET_NR_flock: - goto unimplemented; - case TARGET_NR_msync: - ret = get_errno(msync((void *)arg1, arg2, arg3)); + /* NOTE: the flock constant seems to be the same for every + Linux platform */ + ret = get_errno(flock(arg1, arg2)); break; case TARGET_NR_readv: { @@ -1638,18 +1697,6 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, goto unimplemented; case TARGET_NR__sysctl: goto unimplemented; - case TARGET_NR_mlock: - ret = get_errno(mlock((void *)arg1, arg2)); - break; - case TARGET_NR_munlock: - ret = get_errno(munlock((void *)arg1, arg2)); - break; - case TARGET_NR_mlockall: - ret = get_errno(mlockall(arg1)); - break; - case TARGET_NR_munlockall: - ret = get_errno(munlockall()); - break; case TARGET_NR_sched_setparam: goto unimplemented; case TARGET_NR_sched_getparam: @@ -1681,12 +1728,10 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, } break; - case TARGET_NR_mremap: case TARGET_NR_setresuid: case TARGET_NR_getresuid: case TARGET_NR_vm86: case TARGET_NR_query_module: - case TARGET_NR_poll: case TARGET_NR_nfsservctl: case TARGET_NR_setresgid: case TARGET_NR_getresgid: @@ -1800,7 +1845,7 @@ long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, goto unimplemented; default: unimplemented: - gemu_log("Unsupported syscall: %d\n", num); + gemu_log("gemu: Unsupported syscall: %d\n", num); ret = -ENOSYS; break; } diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index b83aeac..9b2f42a 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -150,6 +150,17 @@ struct target_sigaction; int do_sigaction(int sig, const struct target_sigaction *act, struct target_sigaction *oact); +struct target_rlimit { + target_ulong rlim_cur; + target_ulong rlim_max; +}; + +struct target_pollfd { + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +}; + /* Networking ioctls */ #define TARGET_SIOCADDRT 0x890B /* add routing table entry */ #define TARGET_SIOCDELRT 0x890C /* delete routing table entry */ @@ -119,40 +119,6 @@ static inline int lshift(int x, int n) return x >> (-n); } -/* exception support */ -/* NOTE: not static to force relocation generation by GCC */ -void raise_exception(int exception_index) -{ - /* NOTE: the register at this point must be saved by hand because - longjmp restore them */ -#ifdef reg_EAX - env->regs[R_EAX] = EAX; -#endif -#ifdef reg_ECX - env->regs[R_ECX] = ECX; -#endif -#ifdef reg_EDX - env->regs[R_EDX] = EDX; -#endif -#ifdef reg_EBX - env->regs[R_EBX] = EBX; -#endif -#ifdef reg_ESP - env->regs[R_ESP] = ESP; -#endif -#ifdef reg_EBP - env->regs[R_EBP] = EBP; -#endif -#ifdef reg_ESI - env->regs[R_ESI] = ESI; -#endif -#ifdef reg_EDI - env->regs[R_EDI] = EDI; -#endif - env->exception_index = exception_index; - longjmp(env->jmp_env, 1); -} - /* we define the various pieces of code used by the JIT */ #define REG EAX @@ -391,13 +357,15 @@ void OPPROTO op_imull_T0_T1(void) } /* division, flags are undefined */ -/* XXX: add exceptions for overflow & div by zero */ +/* XXX: add exceptions for overflow */ void OPPROTO op_divb_AL_T0(void) { unsigned int num, den, q, r; num = (EAX & 0xffff); den = (T0 & 0xff); + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den) & 0xff; r = (num % den) & 0xff; EAX = (EAX & 0xffff0000) | (r << 8) | q; @@ -409,6 +377,8 @@ void OPPROTO op_idivb_AL_T0(void) num = (int16_t)EAX; den = (int8_t)T0; + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den) & 0xff; r = (num % den) & 0xff; EAX = (EAX & 0xffff0000) | (r << 8) | q; @@ -420,6 +390,8 @@ void OPPROTO op_divw_AX_T0(void) num = (EAX & 0xffff) | ((EDX & 0xffff) << 16); den = (T0 & 0xffff); + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den) & 0xffff; r = (num % den) & 0xffff; EAX = (EAX & 0xffff0000) | q; @@ -432,6 +404,8 @@ void OPPROTO op_idivw_AX_T0(void) num = (EAX & 0xffff) | ((EDX & 0xffff) << 16); den = (int16_t)T0; + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den) & 0xffff; r = (num % den) & 0xffff; EAX = (EAX & 0xffff0000) | q; @@ -445,6 +419,8 @@ void OPPROTO op_divl_EAX_T0(void) num = EAX | ((uint64_t)EDX << 32); den = T0; + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den); r = (num % den); EAX = q; @@ -458,6 +434,8 @@ void OPPROTO op_idivl_EAX_T0(void) num = EAX | ((uint64_t)EDX << 32); den = T0; + if (den == 0) + raise_exception(EXCP00_DIVZ); q = (num / den); r = (num % den); EAX = q; diff --git a/syscall-i386.h b/syscall-i386.h index 39bba1a..5878ccd 100644 --- a/syscall-i386.h +++ b/syscall-i386.h @@ -359,7 +359,7 @@ struct target_sigaction { typedef union target_sigval { int sival_int; - void *sival_ptr; + target_ulong sival_ptr; } target_sigval_t; #define TARGET_SI_MAX_SIZE 128 @@ -389,7 +389,7 @@ typedef struct target_siginfo { struct { pid_t _pid; /* sender's pid */ uid_t _uid; /* sender's uid */ - sigval_t _sigval; + target_sigval_t _sigval; } _rt; /* SIGCHLD */ @@ -403,7 +403,7 @@ typedef struct target_siginfo { /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ struct { - void *_addr; /* faulting insn/memory ref. */ + target_ulong _addr; /* faulting insn/memory ref. */ } _sigfault; /* SIGPOLL */ @@ -414,6 +414,46 @@ typedef struct target_siginfo { } _sifields; } target_siginfo_t; +/* + * SIGILL si_codes + */ +#define TARGET_ILL_ILLOPN (2) /* illegal operand */ + +/* + * SIGFPE si_codes + */ +#define TARGET_FPE_INTDIV (1) /* integer divide by zero */ +#define TARGET_FPE_INTOVF (2) /* integer overflow */ +#define TARGET_FPE_FLTDIV (3) /* floating point divide by zero */ +#define TARGET_FPE_FLTOVF (4) /* floating point overflow */ +#define TARGET_FPE_FLTUND (5) /* floating point underflow */ +#define TARGET_FPE_FLTRES (6) /* floating point inexact result */ +#define TARGET_FPE_FLTINV (7) /* floating point invalid operation */ +#define TARGET_FPE_FLTSUB (8) /* subscript out of range */ +#define TARGET_NSIGFPE 8 + +/* default linux values for the selectors */ +#define __USER_CS (0x23) +#define __USER_DS (0x2B) + +struct target_pt_regs { + long ebx; + long ecx; + long edx; + long esi; + long edi; + long ebp; + long eax; + int xds; + int xes; + long orig_eax; + long eip; + int xcs; + long eflags; + long esp; + int xss; +}; + /* ioctls */ /* diff --git a/tests/testsig.c b/tests/testsig.c index 59af54f..d93f428 100644 --- a/tests/testsig.c +++ b/tests/testsig.c @@ -1,7 +1,13 @@ +#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> +#include <string.h> #include <signal.h> #include <unistd.h> +#include <setjmp.h> +#include <sys/ucontext.h> + +jmp_buf jmp_env; void alarm_handler(int sig) { @@ -9,15 +15,82 @@ void alarm_handler(int sig) alarm(1); } +void dump_regs(struct ucontext *uc) +{ + printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n" + "ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n" + "EFL=%08x EIP=%08x\n", + uc->uc_mcontext.gregs[EAX], + uc->uc_mcontext.gregs[EBX], + uc->uc_mcontext.gregs[ECX], + uc->uc_mcontext.gregs[EDX], + uc->uc_mcontext.gregs[ESI], + uc->uc_mcontext.gregs[EDI], + uc->uc_mcontext.gregs[EBP], + uc->uc_mcontext.gregs[ESP], + uc->uc_mcontext.gregs[EFL], + uc->uc_mcontext.gregs[EIP]); +} + +void sig_handler(int sig, siginfo_t *info, void *puc) +{ + struct ucontext *uc = puc; + + printf("%s: si_signo=%d si_errno=%d si_code=%d si_addr=0x%08lx\n", + strsignal(info->si_signo), + info->si_signo, info->si_errno, info->si_code, + (unsigned long)info->si_addr); + dump_regs(uc); + longjmp(jmp_env, 1); +} + +int v1; + int main(int argc, char **argv) { struct sigaction act; + int i; + + /* test division by zero reporting */ + if (setjmp(jmp_env) == 0) { + act.sa_sigaction = sig_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO | SA_ONESHOT; + sigaction(SIGFPE, &act, NULL); + + /* now divide by zero */ + v1 = 0; + v1 = 2 / v1; + } + + /* test illegal instruction reporting */ + if (setjmp(jmp_env) == 0) { + act.sa_sigaction = sig_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO | SA_ONESHOT; + sigaction(SIGILL, &act, NULL); + + /* now execute an invalid instruction */ + asm volatile("ud2"); + } + + /* test SEGV reporting */ + if (setjmp(jmp_env) == 0) { + act.sa_sigaction = sig_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO | SA_ONESHOT; + sigaction(SIGSEGV, &act, NULL); + + /* now store in an invalid address */ + *(char *)0x1234 = 1; + } + act.sa_handler = alarm_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); alarm(1); - for(;;) { + for(i = 0;i < 2; i++) { sleep(1); } return 0; diff --git a/translate-i386.c b/translate-i386.c index b7a7cdc..e7e91f5 100644 --- a/translate-i386.c +++ b/translate-i386.c @@ -22,6 +22,7 @@ #include <stdio.h> #include <string.h> #include <inttypes.h> +#include <signal.h> #include <assert.h> #define DEBUG_DISAS @@ -3487,7 +3488,8 @@ static void dump_ops(const uint16_t *opc_buf) static uint16_t gen_opc_buf[OPC_BUF_SIZE]; static uint32_t gen_opparam_buf[OPPARAM_BUF_SIZE]; -/* return the next pc */ +/* return non zero if the very first instruction is invalid so that + the virtual CPU can trigger an exception. */ int cpu_x86_gen_code(uint8_t *gen_code_buf, int max_code_size, int *gen_code_size_ptr, uint8_t *pc_start, uint8_t *cs_base, int flags) @@ -3519,9 +3521,13 @@ int cpu_x86_gen_code(uint8_t *gen_code_buf, int max_code_size, do { ret = disas_insn(dc, pc_ptr); if (ret == -1) { - fprintf(stderr, "unknown instruction at PC=0x%08lx B=%02x %02x %02x", - (long)pc_ptr, pc_ptr[0], pc_ptr[1], pc_ptr[2]); - abort(); + /* we trigger an illegal instruction operation only if it + is the first instruction. Otherwise, we simply stop + generating the code just before it */ + if (pc_ptr == pc_start) + return -1; + else + break; } pc_ptr = (void *)ret; } while (!dc->is_jmp && gen_opc_ptr < gen_opc_end); @@ -3640,8 +3646,7 @@ CPUX86State *cpu_x86_init(void) env->fptags[i] = 1; env->fpuc = 0x37f; /* flags setup */ - env->cc_op = CC_OP_EFLAGS; - env->df = 1; + env->eflags = 0; /* init various static tables */ if (!inited) { |