381 lines · 10.3 KB · C
RawDownload
// Code specific to x86 hosts running Linux. #define _GNU_SOURCE#include <stdio.h>#include <string.h>#include <signal.h>#include <assert.h>#include <ucontext.h>#include <sys/ucontext.h>#include <asm/ldt.h>#include <errno.h> #include "vx32.h"#include "vx32impl.h"#include "os.h" extern int modify_ldt(int, void*, unsigned long); int vxemu_map(vxemu *emu, vxmmap *mm){	struct vxproc *vxp;	struct user_desc desc;	uint ldt[2];#ifdef __x86_64	static int didflat;#endif 	vxp = emu->proc;	emu->datasel = vxp->vxpno * 16 + 16 + 4 + 3;	// 4=LDT, 3=RPL	emu->emusel = emu->datasel + 8; 	if (emu->ldt_base != (uintptr_t)mm->base || emu->ldt_size != mm->size) {		// Set up the process's data segment selector (for DS,ES,SS).		memset(&desc, 0, sizeof(desc));		desc.seg_32bit = 1;		desc.read_exec_only = 0;		desc.limit_in_pages = 1;		desc.seg_not_present = 0;		desc.useable = 1; 		desc.entry_number = emu->datasel / 8;		desc.base_addr = (uintptr_t)mm->base;		desc.limit = (mm->size - 1) >> VXPAGESHIFT;		desc.contents = MODIFY_LDT_CONTENTS_DATA;		if (modify_ldt(1, &desc, sizeof(desc)) < 0)			return -1;			// Set up the process's vxemu segment selector (for FS).		desc.entry_number = emu->emusel / 8;		desc.base_addr = (uintptr_t)emu;		desc.limit = (VXCODEBUFSIZE - 1) >> VXPAGESHIFT;		desc.contents = MODIFY_LDT_CONTENTS_DATA;		if (modify_ldt(1, &desc, sizeof(desc)) < 0)			return -1;				emu->ldt_base = (uintptr_t)mm->base;		emu->ldt_size = mm->size;	} #ifdef __x86_64	// Set up 32-bit mode code and data segments (not vxproc-specific),	// giving access to the full low 32 bits of linear address space.	// The code segment is necessary to get into 32-bit compatibility mode;	// the data segment is needed because Linux for x86-64	// doesn't give 64-bit processes a "real" data segment by default	// but instead just loads zero into the data segment selectors!	emu->runptr.sel = FLATCODE; 	if (!didflat) {		didflat = 1;		memset(&desc, 0, sizeof(desc));		desc.seg_32bit = 1;		desc.read_exec_only = 0;		desc.limit_in_pages = 1;		desc.seg_not_present = 0;		desc.useable = 1; 		desc.entry_number = FLATCODE / 8;		desc.base_addr = 0;		desc.limit = 0xfffff;		desc.contents = MODIFY_LDT_CONTENTS_CODE;		if (modify_ldt(1, &desc, sizeof(desc)) < 0)			return -1;				/*		 * Linux 2.6.27 has a bug: it does not load the L (long mode)		 * bit from desc.lm when copying desc into its own		 * copy of the LDT entry on the kernel stack.		 * Instead, it leaves L uninitialized, picking up whatever		 * random bit was left on the kernel stack by the		 * previous call sequence.  We need L to be 0.		 * If it ends up 1, the *ljmpq in run64.S will GP fault.		 * Luckily, we can look for this by asking to read		 * back the raw LDT bytes.  If we observe this problem,		 * try to fix it by doing a modify_ldt with base = limit = 0,		 * which clears the entire stack ldt structure, and then		 * quickly do another modify_ldt with desc, hoping that		 * the bit will still be zero when we get there for the		 * second modify_ldt.  I wish I were making this up.		 * This is fixed in Linus's git repository, but the Ubuntu		 * git repositories are still out of date.  See for example		 * 	http://swtch.com/go/ubuntu-ldt		 *	http://swtch.com/go/linus-ldt		 *		 * Remember, folks, Free Software is only free if your		 * time has no value.		 */		if(modify_ldt(0, ldt, sizeof ldt) < 0)			return -1;		if(ldt[1] & 0x00200000) {			if (vx32_debugxlate)				vxprint("FLATCODE LDT=%08x %08x; working around\n", ldt[0], ldt[1]);			desc.limit = 0;			modify_ldt(1, &desc, sizeof desc);			desc.limit = 0xfffff;			modify_ldt(1, &desc, sizeof desc);			modify_ldt(0, ldt, sizeof ldt);			if(ldt[1] & 0x00200000) {				vxprint("cannot work around Linux FLATCODE bug\n");				errno = EBADE;				return -1;			}			if (vx32_debugxlate)				vxprint("FLATCODE LDT=%08x %08x\n", ldt[0], ldt[1]);		} 		desc.entry_number = FLATDATA / 8;		desc.base_addr = 0;		desc.limit = 0xfffff;		desc.contents = MODIFY_LDT_CONTENTS_DATA;		if (modify_ldt(1, &desc, sizeof(desc)) < 0)			return -1;	} 	// Set up a far return vector in emu->retptr	// for getting back into 64-bit long mode.	extern void vxrun_return();	asm volatile("movw %%cs,%0" : "=r" (emu->retptr.sel));	emu->retptr.ofs = (uint32_t)(intptr_t)vxrun_return;#endif 	return 0;} static void dumpsigcontext(struct sigcontext *ctx){#ifdef i386	printf(		"eax %08lx  ebx %08lx\necx %08lx  edx %08lx  "		"rsi %08lx  rdi %08lx\nrbp %08lx  rsp %08lx\n"		"eip %08lx  efl %08lx  cs %04x\n"		"err %08lx  trapno %08lx  cr2 %08lx\n",		ctx->eax, ctx->ebx, ctx->ecx, ctx->edx,		ctx->esi, ctx->edi, ctx->ebp, ctx->esp,		ctx->eip, ctx->eflags, ctx->cs,		ctx->err, ctx->trapno, ctx->cr2);#else	printf(		"rax %016lx  rbx %016lx\nrcx %016lx  rdx %016lx\n"		"rsi %016lx  rdi %016lx\nrbp %016lx  rsp %016lx\n"		"r8  %016lx  r9  %016lx\nr10 %016lx  r11 %016lx\n"		"r12 %016lx  r13 %016lx\nr14 %016lx  r15 %016lx\n"		"rip %016lx  efl %016lx  cs %04x  ss %04x\n"		"err %016lx  trapno %016lx  cr2 %016lx\n",		ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx,		ctx->rsi, ctx->rdi, ctx->rbp, ctx->rsp,		ctx->r8, ctx->r9, ctx->r10, ctx->r11,		ctx->r12, ctx->r13, ctx->r14, ctx->r15,		ctx->rip, ctx->eflags, ctx->cs, ctx->__pad0,		ctx->err, ctx->trapno, ctx->cr2);#endif} #ifdef i386#define	VX32_BELIEVE_EIP	(ctx->ds == vs - 8)#define	ctxeip eip#else#define	VX32_BELIEVE_EIP	(ctx->cs == FLATCODE) // On x86-64, make x86 names work for ctx->xxx.#define	eax rax#define	ebx rbx#define	ecx rcx#define	edx rdx#define	esi rsi#define	edi rdi#define	esp rsp#define	ebp rbp#define	ctxeip rip#endif static voidfprestore(struct _fpstate *s){	asm volatile("frstor 0(%%eax); fwait\n" : : "a" (s) : "memory");} int vx32_sighandler(int signo, siginfo_t *si, void *v){	uint32_t trapeip;	uint32_t magic;	uint16_t vs;	vxproc *vxp;	vxemu *emu;	struct sigcontext *ctx;	ucontext_t *uc;	mcontext_t *mc;	int r; 	uc = v;	mc = &uc->uc_mcontext; 	// same layout, and sigcontext is more convenient...	ctx = (struct sigcontext*)mc; 	// We can't be sure that vxemu is running,	// and thus that %VSEG is actually mapped to a	// valid vxemu.  The only way to tell is to look at %VSEG. 	// First sanity check vxproc segment number.	asm("movw %"VSEGSTR",%0"		: "=r" (vs));		if(vx32_debugxlate) vxprint("vx32_sighandler signo=%d eip=%#x esp=%#x vs=%#x\n",		signo, ctx->ctxeip, ctx->esp, vs);	if(vx32_debugxlate) dumpsigcontext(ctx); 	if ((vs & 15) != 15)	// 8 (emu), LDT, RPL=3		return 0; 	// Okay, assume mapped; check for vxemu.	asm("movl %"VSEGSTR":%1,%0"		: "=r" (magic)		: "m" (((vxemu*)0)->magic));	if (magic != VXEMU_MAGIC)		return 0; 	// Okay, we're convinced. 	// Find current vxproc and vxemu.	asm("mov %"VSEGSTR":%1,%0"		: "=r" (vxp)		: "m" (((vxemu*)0)->proc));	emu = vxp->emu; 	// Get back our regular host segment register state,	// so that thread-local storage and such works.	vxrun_cleanup(emu); 	// dumpsigcontext(ctx); 	if (VX32_BELIEVE_EIP)		trapeip = ctx->ctxeip;	else		trapeip = 0xffffffff; 	int newtrap;	switch(signo){	case SIGSEGV:	case SIGBUS:		newtrap = VXTRAP_PAGEFAULT;		break;		case SIGFPE:		newtrap = VXTRAP_FLOAT;		break;		case SIGVTALRM:		newtrap = VXTRAP_IRQ + VXIRQ_TIMER;		break; 	case SIGTRAP:		// Linux sends SIGTRAP when it gets a processor 		// debug exception, which is caused by single-stepping		// with the TF bit, among other things.  The processor		// turns off the TF bit before generating the trap, but		// it appears that Linux turns it back on for us.		// Let's use it to confirm that this is a single-step trap.		if (ctx->eflags & EFLAGS_TF){			newtrap = VXTRAP_SINGLESTEP;			ctx->eflags &= ~EFLAGS_TF;		}else{			vxprint("Unexpected sigtrap eflags=%#x\n", ctx->eflags);			newtrap = VXTRAP_SIGNAL + signo;		}		break; 	default:		newtrap = VXTRAP_SIGNAL + signo;		break;	}		int replaced_trap = 0;	if (emu->cpu_trap) {		// There's already a pending trap!		// Handle the new trap, and assume that when it		// finishes, restarting the code at cpu.eip will trigger		// the old trap again.		// Have to fix up eip for int 0x30 and syscall instructions.		if (emu->cpu_trap == VXTRAP_SYSCALL ||				(emu->cpu_trap&VXTRAP_CATEGORY) == VXTRAP_SOFT)			emu->cpu.eip -= 2;		replaced_trap = emu->cpu_trap;	}	emu->cpu_trap = newtrap; 	r = vxemu_sighandler(emu, trapeip); 	if (r == VXSIG_SINGLESTEP){		// Vxemu_sighandler wants us to single step.		// Execution state is in intermediate state - don't touch.		ctx->eflags |= EFLAGS_TF;		// x86 TF (single-step) bit		vxrun_setup(emu);		return 1;	} 	// Copy execution state into emu.	if ((r & VXSIG_SAVE_ALL) == VXSIG_SAVE_ALL) {		emu->cpu.reg[EAX] = ctx->eax;		emu->cpu.reg[EBX] = ctx->ebx;		emu->cpu.reg[ECX] = ctx->ecx;		emu->cpu.reg[EDX] = ctx->edx;		emu->cpu.reg[ESI] =  ctx->esi;		emu->cpu.reg[EDI] = ctx->edi;		emu->cpu.reg[ESP] = ctx->esp;	// or esp_at_signal ???		emu->cpu.reg[EBP] = ctx->ebp;		emu->cpu.eflags = ctx->eflags;	} else if (r & VXSIG_SAVE_ALL) {		if (r & VXSIG_SAVE_EAX)			emu->cpu.reg[EAX] = ctx->eax;		if (r & VXSIG_SAVE_EBX)			emu->cpu.reg[EBX] = ctx->ebx;		if (r & VXSIG_SAVE_ECX)			emu->cpu.reg[ECX] = ctx->ecx;		if (r & VXSIG_SAVE_EDX)			emu->cpu.reg[EDX] = ctx->edx;		if (r & VXSIG_SAVE_ESI)			emu->cpu.reg[ESI] =  ctx->esi;		if (r & VXSIG_SAVE_EDI)			emu->cpu.reg[EDI] = ctx->edi;		if (r & VXSIG_SAVE_ESP)			emu->cpu.reg[ESP] = ctx->esp;	// or esp_at_signal ???		if (r & VXSIG_SAVE_EBP)			emu->cpu.reg[EBP] = ctx->ebp;		if (r & VXSIG_SAVE_EFLAGS)			emu->cpu.eflags = ctx->eflags;	}	r &= ~VXSIG_SAVE_ALL; 	if (r & VXSIG_SAVE_EBX_AS_EIP)		emu->cpu.eip = ctx->ebx;	r &= ~VXSIG_SAVE_EBX_AS_EIP; 	if (r & VXSIG_ADD_COUNT_TO_ESP) {		emu->cpu.reg[ESP] += (uint16_t)(r >> VXSIG_COUNT_SHIFT);		r &= ~VXSIG_ADD_COUNT_TO_ESP;		r &= ~(0xFFFF << VXSIG_COUNT_SHIFT);	}		if (r &  VXSIG_INC_ECX) {		emu->cpu.reg[ECX]++;		r &= ~VXSIG_INC_ECX;	} 	if (r == VXSIG_TRAP) {		if (emu->trapenv == NULL)			return 0;		emu->cpu.traperr = ctx->err;		// Usually, ctx->cr2 == si->si_addr.		// But on a segmentation fault (as opposed to a paging fault),		// cr2 is not updated and the kernel sends an si_addr == 0.		// Be sure to use si_addr, not cr2.		emu->cpu.trapva = (uint32_t)(uintptr_t)si->si_addr;		memmove(mc->gregs, emu->trapenv->gregs, sizeof emu->trapenv->gregs);				return 1;	} 	// The signal handler is confused; so are we.	return 0;}