Commit 6dbad63e authored by bellard's avatar bellard
Browse files

added minimal segment support


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@28 c046a42c-6fe2-441c-8c8c-71466251a162
parent 27362c82
- daa/das
- optimize translated cache chaining (DLL PLT like system)
- segment ops (minimal LDT/GDT support for wine)
- optimize translated cache chaining (DLL PLT like system)
- improved 16 bit support
- optimize inverse flags propagation (easy by generating intermediate
micro operation array).
......
......@@ -123,6 +123,20 @@ typedef long double CPU86_LDouble;
typedef double CPU86_LDouble;
#endif
typedef struct SegmentCache {
uint8_t *base;
unsigned long limit;
uint8_t seg_32bit;
} SegmentCache;
typedef struct SegmentDescriptorTable {
uint8_t *base;
unsigned long limit;
/* this is the returned base when reading the register, just to
avoid that the emulated program modifies it */
unsigned long emu_base;
} SegmentDescriptorTable;
typedef struct CPUX86State {
/* standard registers */
uint32_t regs[8];
......@@ -135,9 +149,6 @@ typedef struct CPUX86State {
uint32_t cc_op;
int32_t df; /* D flag : 1 if D = 0, -1 if D = 1 */
/* segments */
uint8_t *segs_base[6];
/* FPU state */
unsigned int fpstt; /* top of stack index */
unsigned int fpus;
......@@ -145,12 +156,19 @@ typedef struct CPUX86State {
uint8_t fptags[8]; /* 0 = valid, 1 = empty */
CPU86_LDouble fpregs[8];
/* segments */
uint32_t segs[6];
/* emulator internal variables */
CPU86_LDouble ft0;
/* segments */
uint32_t segs[6]; /* selector values */
SegmentCache seg_cache[6]; /* info taken from LDT/GDT */
SegmentDescriptorTable gdt;
SegmentDescriptorTable ldt;
SegmentDescriptorTable idt;
/* various CPU modes */
int vm86;
/* exception handling */
jmp_buf jmp_env;
int exception_index;
......@@ -241,9 +259,17 @@ CPUX86State *cpu_x86_init(void);
int cpu_x86_exec(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);
/* internal functions */
#define GEN_FLAG_CODE32_SHIFT 0
#define GEN_FLAG_ADDSEG_SHIFT 1
#define GEN_FLAG_ST_SHIFT 2
int cpu_x86_gen_code(uint8_t *gen_code_buf, int max_code_size,
int *gen_code_size_ptr, uint8_t *pc_start);
int *gen_code_size_ptr, uint8_t *pc_start,
int flags);
void cpu_x86_tblocks_init(void);
#endif /* CPU_I386_H */
......@@ -36,8 +36,10 @@
#define CODE_GEN_MAX_BLOCKS (CODE_GEN_BUFFER_SIZE / 64)
#define CODE_GEN_HASH_BITS 15
#define CODE_GEN_HASH_SIZE (1 << CODE_GEN_HASH_BITS)
typedef struct TranslationBlock {
unsigned long pc; /* simulated PC corresponding to this block */
unsigned int flags; /* flags defining in which context the code was generated */
uint8_t *tc_ptr; /* pointer to the translated code */
struct TranslationBlock *hash_next; /* next matching block */
} TranslationBlock;
......@@ -137,7 +139,8 @@ 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)
static inline TranslationBlock *tb_find_and_alloc(unsigned long pc,
unsigned int flags)
{
TranslationBlock **ptb, *tb;
unsigned int h;
......@@ -148,7 +151,7 @@ static inline TranslationBlock *tb_find_and_alloc(unsigned long pc)
tb = *ptb;
if (!tb)
break;
if (tb->pc == pc)
if (tb->pc == pc && tb->flags == flags)
return tb;
ptb = &tb->hash_next;
}
......@@ -158,6 +161,7 @@ static inline TranslationBlock *tb_find_and_alloc(unsigned long pc)
tb = &tbs[nb_tbs++];
*ptb = tb;
tb->pc = pc;
tb->flags = flags;
tb->tc_ptr = NULL;
tb->hash_next = NULL;
return tb;
......@@ -171,7 +175,8 @@ int cpu_x86_exec(CPUX86State *env1)
void (*gen_func)(void);
TranslationBlock *tb;
uint8_t *tc_ptr;
unsigned int flags;
/* first we save global registers */
saved_T0 = T0;
saved_T1 = T1;
......@@ -187,13 +192,20 @@ int cpu_x86_exec(CPUX86State *env1)
cpu_x86_dump_state();
}
#endif
tb = tb_find_and_alloc((unsigned long)env->pc);
/* we compute the CPU state. We assume it will not
change during the whole generated block. */
flags = env->seg_cache[R_CS].seg_32bit << GEN_FLAG_CODE32_SHIFT;
flags |= (((unsigned long)env->seg_cache[R_DS].base |
(unsigned long)env->seg_cache[R_ES].base |
(unsigned long)env->seg_cache[R_SS].base) != 0) <<
GEN_FLAG_ADDSEG_SHIFT;
tb = tb_find_and_alloc((unsigned long)env->pc, flags);
tc_ptr = tb->tc_ptr;
if (!tb->tc_ptr) {
/* if no translated code available, then translate it now */
tc_ptr = code_gen_ptr;
cpu_x86_gen_code(code_gen_ptr, CODE_GEN_MAX_SIZE,
&code_gen_size, (uint8_t *)env->pc);
&code_gen_size, (uint8_t *)env->pc, flags);
tb->tc_ptr = tc_ptr;
code_gen_ptr = (void *)(((unsigned long)code_gen_ptr + code_gen_size + CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1));
}
......@@ -211,3 +223,13 @@ int cpu_x86_exec(CPUX86State *env1)
env = saved_env;
return ret;
}
void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector)
{
CPUX86State *saved_env;
saved_env = env;
env = s;
load_seg(seg_reg, selector);
env = saved_env;
}
......@@ -27,6 +27,7 @@ typedef struct FILE FILE;
extern FILE *logfile;
extern int loglevel;
extern int fprintf(FILE *, const char *, ...);
extern int printf(const char *, ...);
#ifdef __i386__
register unsigned int T0 asm("ebx");
......@@ -103,3 +104,5 @@ typedef struct CCTable {
} CCTable;
extern CCTable cc_table[];
void load_seg(int seg_reg, int selector);
/*
* emu main
* gemu main
*
* Copyright (c) 2003 Fabrice Bellard
*
......@@ -80,10 +80,28 @@ int cpu_x86_inl(int addr)
return 0;
}
/* default linux values for the selectors */
#define __USER_CS (0x23)
#define __USER_DS (0x2B)
/* XXX: currently we use LDT entries */
#define __USER_CS (0x23|4)
#define __USER_DS (0x2B|4)
void write_dt(void *ptr, unsigned long addr, unsigned long limit,
int seg32_bit)
{
unsigned int e1, e2, limit_in_pages;
limit_in_pages = 0;
if (limit > 0xffff) {
limit = limit >> 12;
limit_in_pages = 1;
}
e1 = (addr << 16) | (limit & 0xffff);
e2 = ((addr >> 16) & 0xff) | (addr & 0xff000000) | (limit & 0x000f0000);
e2 |= limit_in_pages << 23; /* byte granularity */
e2 |= seg32_bit << 22; /* 32 bit segment */
stl((uint8_t *)ptr, e1);
stl((uint8_t *)ptr + 4, e2);
}
uint64_t gdt_table[6];
void usage(void)
{
......@@ -94,6 +112,8 @@ void usage(void)
exit(1);
}
int main(int argc, char **argv)
{
const char *filename;
......@@ -149,6 +169,7 @@ int main(int argc, char **argv)
env = cpu_x86_init();
/* linux register setup */
env->regs[R_EAX] = regs->eax;
env->regs[R_EBX] = regs->ebx;
env->regs[R_ECX] = regs->ecx;
......@@ -157,23 +178,19 @@ int main(int argc, char **argv)
env->regs[R_EDI] = regs->edi;
env->regs[R_EBP] = regs->ebp;
env->regs[R_ESP] = regs->esp;
env->segs[R_CS] = __USER_CS;
env->segs[R_DS] = __USER_DS;
env->segs[R_ES] = __USER_DS;
env->segs[R_SS] = __USER_DS;
env->segs[R_FS] = __USER_DS;
env->segs[R_GS] = __USER_DS;
env->pc = regs->eip;
#if 0
LDT[__USER_CS >> 3].w86Flags = DF_PRESENT | DF_PAGES | DF_32;
LDT[__USER_CS >> 3].dwSelLimit = 0xfffff;
LDT[__USER_CS >> 3].lpSelBase = NULL;
LDT[__USER_DS >> 3].w86Flags = DF_PRESENT | DF_PAGES | DF_32;
LDT[__USER_DS >> 3].dwSelLimit = 0xfffff;
LDT[__USER_DS >> 3].lpSelBase = NULL;
#endif
/* linux segment setup */
env->gdt.base = (void *)gdt_table;
env->gdt.limit = sizeof(gdt_table) - 1;
write_dt(&gdt_table[__USER_CS >> 3], 0, 0xffffffff, 1);
write_dt(&gdt_table[__USER_DS >> 3], 0, 0xffffffff, 1);
cpu_x86_load_seg(env, R_CS, __USER_CS);
cpu_x86_load_seg(env, R_DS, __USER_DS);
cpu_x86_load_seg(env, R_ES, __USER_DS);
cpu_x86_load_seg(env, R_SS, __USER_DS);
cpu_x86_load_seg(env, R_FS, __USER_DS);
cpu_x86_load_seg(env, R_GS, __USER_DS);
for(;;) {
int err;
......@@ -186,7 +203,8 @@ int main(int argc, char **argv)
if (pc[0] == 0xcd && pc[1] == 0x80) {
/* syscall */
env->pc += 2;
env->regs[R_EAX] = do_syscall(env->regs[R_EAX],
env->regs[R_EAX] = do_syscall(env,
env->regs[R_EAX],
env->regs[R_EBX],
env->regs[R_ECX],
env->regs[R_EDX],
......
......@@ -48,7 +48,7 @@ int elf_exec(const char * filename, char ** argv, char ** envp,
void target_set_brk(char *new_brk);
void syscall_init(void);
long do_syscall(int num, long arg1, long arg2, long arg3,
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)));
......
......@@ -69,6 +69,7 @@ struct dirent {
#include "syscall_defs.h"
#ifdef TARGET_I386
#include "cpu-i386.h"
#include "syscall-i386.h"
#endif
......@@ -607,6 +608,124 @@ StructEntry struct_termios_def = {
.align = { __alignof__(struct target_termios), __alignof__(struct host_termios) },
};
#ifdef TARGET_I386
/* NOTE: there is really one LDT for all the threads */
uint8_t *ldt_table;
static int read_ldt(void *ptr, unsigned long bytecount)
{
int size;
if (!ldt_table)
return 0;
size = TARGET_LDT_ENTRIES * TARGET_LDT_ENTRY_SIZE;
if (size > bytecount)
size = bytecount;
memcpy(ptr, ldt_table, size);
return size;
}
/* XXX: add locking support */
static int write_ldt(CPUX86State *env,
void *ptr, unsigned long bytecount, int oldmode)
{
struct target_modify_ldt_ldt_s ldt_info;
int seg_32bit, contents, read_exec_only, limit_in_pages;
int seg_not_present, useable;
uint32_t *lp, entry_1, entry_2;
if (bytecount != sizeof(ldt_info))
return -EINVAL;
memcpy(&ldt_info, ptr, sizeof(ldt_info));
tswap32s(&ldt_info.entry_number);
tswapls((long *)&ldt_info.base_addr);
tswap32s(&ldt_info.limit);
tswap32s(&ldt_info.flags);
if (ldt_info.entry_number >= TARGET_LDT_ENTRIES)
return -EINVAL;
seg_32bit = ldt_info.flags & 1;
contents = (ldt_info.flags >> 1) & 3;
read_exec_only = (ldt_info.flags >> 3) & 1;
limit_in_pages = (ldt_info.flags >> 4) & 1;
seg_not_present = (ldt_info.flags >> 5) & 1;
useable = (ldt_info.flags >> 6) & 1;
if (contents == 3) {
if (oldmode)
return -EINVAL;
if (seg_not_present == 0)
return -EINVAL;
}
/* allocate the LDT */
if (!ldt_table) {
ldt_table = malloc(TARGET_LDT_ENTRIES * TARGET_LDT_ENTRY_SIZE);
if (!ldt_table)
return -ENOMEM;
memset(ldt_table, 0, TARGET_LDT_ENTRIES * TARGET_LDT_ENTRY_SIZE);
env->ldt.base = ldt_table;
env->ldt.limit = 0xffff;
}
/* NOTE: same code as Linux kernel */
/* Allow LDTs to be cleared by the user. */
if (ldt_info.base_addr == 0 && ldt_info.limit == 0) {
if (oldmode ||
(contents == 0 &&
read_exec_only == 1 &&
seg_32bit == 0 &&
limit_in_pages == 0 &&
seg_not_present == 1 &&
useable == 0 )) {
entry_1 = 0;
entry_2 = 0;
goto install;
}
}
entry_1 = ((ldt_info.base_addr & 0x0000ffff) << 16) |
(ldt_info.limit & 0x0ffff);
entry_2 = (ldt_info.base_addr & 0xff000000) |
((ldt_info.base_addr & 0x00ff0000) >> 16) |
(ldt_info.limit & 0xf0000) |
((read_exec_only ^ 1) << 9) |
(contents << 10) |
((seg_not_present ^ 1) << 15) |
(seg_32bit << 22) |
(limit_in_pages << 23) |
0x7000;
if (!oldmode)
entry_2 |= (useable << 20);
/* Install the new entry ... */
install:
lp = (uint32_t *)(ldt_table + (ldt_info.entry_number << 3));
lp[0] = tswap32(entry_1);
lp[1] = tswap32(entry_2);
return 0;
}
/* specific and weird i386 syscalls */
int gemu_modify_ldt(CPUX86State *env, int func, void *ptr, unsigned long bytecount)
{
int ret = -ENOSYS;
switch (func) {
case 0:
ret = read_ldt(ptr, bytecount);
break;
case 1:
ret = write_ldt(env, ptr, bytecount, 1);
break;
case 0x11:
ret = write_ldt(env, ptr, bytecount, 0);
break;
}
return ret;
}
#endif
void syscall_init(void)
{
#define STRUCT(name, list...) thunk_register_struct(STRUCT_ ## name, #name, struct_ ## name ## _def);
......@@ -616,7 +735,7 @@ void syscall_init(void)
#undef STRUCT_SPECIAL
}
long do_syscall(int num, long arg1, long arg2, long arg3,
long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3,
long arg4, long arg5, long arg6)
{
long ret;
......@@ -1095,8 +1214,11 @@ long do_syscall(int num, long arg1, long arg2, long arg3,
/* no need to transcode because we use the linux syscall */
ret = get_errno(sys_uname((struct new_utsname *)arg1));
break;
#ifdef TARGET_I386
case TARGET_NR_modify_ldt:
goto unimplemented;
ret = get_errno(gemu_modify_ldt(cpu_env, arg1, (void *)arg2, arg3));
break;
#endif
case TARGET_NR_adjtimex:
goto unimplemented;
case TARGET_NR_mprotect:
......
......@@ -61,4 +61,3 @@ STRUCT(cdrom_read_audio,
STRUCT(hd_geometry,
TYPE_CHAR, TYPE_CHAR, TYPE_SHORT, TYPE_ULONG)
......@@ -858,6 +858,60 @@ void OPPROTO op_das(void)
CC_SRC = eflags;
}
/* segment handling */
void load_seg(int seg_reg, int selector)
{
SegmentCache *sc;
SegmentDescriptorTable *dt;
int index;
uint32_t e1, e2;
uint8_t *ptr;
env->segs[seg_reg] = selector;
sc = &env->seg_cache[seg_reg];
if (env->vm86) {
sc->base = (void *)(selector << 4);
sc->limit = 0xffff;
sc->seg_32bit = 0;
} else {
if (selector & 0x4)
dt = &env->ldt;
else
dt = &env->gdt;
index = selector & ~7;
if ((index + 7) > dt->limit)
raise_exception(EXCP0D_GPF);
ptr = dt->base + index;
e1 = ldl(ptr);
e2 = ldl(ptr + 4);
sc->base = (void *)((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000));
sc->limit = (e1 & 0xffff) | (e2 & 0x000f0000);
if (e2 & (1 << 23))
sc->limit = (sc->limit << 12) | 0xfff;
sc->seg_32bit = (e2 >> 22) & 1;
#if 0
fprintf(logfile, "load_seg: sel=0x%04x base=0x%08lx limit=0x%08lx seg_32bit=%d\n",
selector, (unsigned long)sc->base, sc->limit, sc->seg_32bit);
#endif
}
}
void OPPROTO op_movl_seg_T0(void)
{
load_seg(PARAM1, T0 & 0xffff);
}
void OPPROTO op_movl_T0_seg(void)
{
T0 = env->segs[PARAM1];
}
void OPPROTO op_addl_A0_seg(void)
{
A0 += *(unsigned long *)((char *)env + PARAM1);
}
/* flags handling */
/* slow jumps cases (compute x86 flags) */
......
......@@ -758,3 +758,14 @@ struct target_termios {
#define TARGET_SOUND_MIXER_WRITE_ENHANCE 0xc0044d1f
#define TARGET_SOUND_MIXER_WRITE_LOUD 0xc0044d1f
#define TARGET_SOUND_MIXER_WRITE_RECSRC 0xc0044dff
#define TARGET_LDT_ENTRIES 8192
#define TARGET_LDT_ENTRY_SIZE 8
struct target_modify_ldt_ldt_s {
unsigned int entry_number;
target_ulong base_addr;
unsigned int limit;
unsigned int flags;
};
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#define xglue(x, y) x ## y
......@@ -612,6 +613,81 @@ void test_bcd(void)
TEST_BCD(aad, 0x12340407, CC_A, (CC_C | CC_P | CC_Z | CC_S | CC_O | CC_A));
}
/**********************************************/
/* segmentation tests */
#include <asm/ldt.h>
#include <linux/unistd.h>
_syscall3(int, modify_ldt, int, func, void *, ptr, unsigned long, bytecount)
uint8_t seg_data1[4096];
uint8_t seg_data2[4096];
#define MK_SEL(n) (((n) << 3) | 4)
/* NOTE: we use Linux modify_ldt syscall */
void test_segs(void)
{
struct modify_ldt_ldt_s ldt;
long long ldt_table[3];
int i, res, res2;
char tmp;
ldt.entry_number = 1;
ldt.base_addr = (unsigned long)&seg_data1;
ldt.limit = (sizeof(seg_data1) + 0xfff) >> 12;
ldt.seg_32bit = 1;
ldt.contents = MODIFY_LDT_CONTENTS_DATA;
ldt.read_exec_only = 0;
ldt.limit_in_pages = 1;
ldt.seg_not_present = 0;
ldt.useable = 1;
modify_ldt(1, &ldt, sizeof(ldt)); /* write ldt entry */
ldt.entry_number = 2;
ldt.base_addr = (unsigned long)&seg_data2;
ldt.limit = (sizeof(seg_data2) + 0xfff) >> 12;
ldt.seg_32bit = 1;
ldt.contents = MODIFY_LDT_CONTENTS_DATA;
ldt.read_exec_only = 0;
ldt.limit_in_pages = 1;
ldt.seg_not_present = 0;
ldt.useable = 1;
modify_ldt(1, &ldt, sizeof(ldt)); /* write ldt entry */
modify_ldt(0, &ldt_table, sizeof(ldt_table)); /* read ldt entries */
for(i=0;i<3;i++)
printf("%d: %016Lx\n", i, ldt_table[i]);
/* do some tests with fs or gs */
asm volatile ("movl %0, %%fs" : : "r" (MK_SEL(1)));
asm volatile ("movl %0, %%gs" : : "r" (MK_SEL(2)));
seg_data1[1] = 0xaa;
seg_data2[1] = 0x55;
asm volatile ("fs movzbl 0x1, %0" : "=r" (res));
printf("FS[1] = %02x\n", res);
asm volatile ("gs movzbl 0x1, %0" : "=r" (res));
printf("GS[1] = %02x\n", res);
/* tests with ds/ss (implicit segment case) */
tmp = 0xa5;
asm volatile ("pushl %%ebp\n\t"
"pushl %%ds\n\t"
"movl %2, %%ds\n\t"
"movl %3, %%ebp\n\t"
"movzbl 0x1, %0\n\t"
"movzbl (%%ebp), %1\n\t"
"popl %%ds\n\t"
"popl %%ebp\n\t"
: "=r" (res), "=r" (res2)
: "r" (MK_SEL(1)), "r" (&tmp));
printf("DS[1] = %02x\n", res);
printf("SS[tmp] = %02x\n", res2);
}
static void *call_end __init_call = NULL;
......@@ -628,8 +704,9 @@ int main(int argc, char **argv)
test_bsx();
test_mul();
test_jcc();
test_lea();
test_floats();
test_bcd();
test_lea();
test_segs();
return 0;
}
......@@ -34,6 +34,10 @@
#include "dis-asm.h"
#endif