|
@@ -92,6 +92,109 @@ static void gen_signal(CPUX86State *env, int sig, int code, abi_ptr addr)
|
|
queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
|
|
queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#ifdef TARGET_X86_64
|
|
|
|
+static bool write_ok_or_segv(CPUX86State *env, abi_ptr addr, size_t len)
|
|
|
|
+{
|
|
|
|
+ /*
|
|
|
|
+ * For all the vsyscalls, NULL means "don't write anything" not
|
|
|
|
+ * "write it at address 0".
|
|
|
|
+ */
|
|
|
|
+ if (addr == 0 || access_ok(VERIFY_WRITE, addr, len)) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ env->error_code = PG_ERROR_W_MASK | PG_ERROR_U_MASK;
|
|
|
|
+ gen_signal(env, TARGET_SIGSEGV, TARGET_SEGV_MAPERR, addr);
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Since v3.1, the kernel traps and emulates the vsyscall page.
|
|
|
|
+ * Entry points other than the official generate SIGSEGV.
|
|
|
|
+ */
|
|
|
|
+static void emulate_vsyscall(CPUX86State *env)
|
|
|
|
+{
|
|
|
|
+ int syscall;
|
|
|
|
+ abi_ulong ret;
|
|
|
|
+ uint64_t caller;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Validate the entry point. We have already validated the page
|
|
|
|
+ * during translation to get here; now verify the offset.
|
|
|
|
+ */
|
|
|
|
+ switch (env->eip & ~TARGET_PAGE_MASK) {
|
|
|
|
+ case 0x000:
|
|
|
|
+ syscall = TARGET_NR_gettimeofday;
|
|
|
|
+ break;
|
|
|
|
+ case 0x400:
|
|
|
|
+ syscall = TARGET_NR_time;
|
|
|
|
+ break;
|
|
|
|
+ case 0x800:
|
|
|
|
+ syscall = TARGET_NR_getcpu;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ goto sigsegv;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Validate the return address.
|
|
|
|
+ * Note that the kernel treats this the same as an invalid entry point.
|
|
|
|
+ */
|
|
|
|
+ if (get_user_u64(caller, env->regs[R_ESP])) {
|
|
|
|
+ goto sigsegv;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Validate the the pointer arguments.
|
|
|
|
+ */
|
|
|
|
+ switch (syscall) {
|
|
|
|
+ case TARGET_NR_gettimeofday:
|
|
|
|
+ if (!write_ok_or_segv(env, env->regs[R_EDI],
|
|
|
|
+ sizeof(struct target_timeval)) ||
|
|
|
|
+ !write_ok_or_segv(env, env->regs[R_ESI],
|
|
|
|
+ sizeof(struct target_timezone))) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case TARGET_NR_time:
|
|
|
|
+ if (!write_ok_or_segv(env, env->regs[R_EDI], sizeof(abi_long))) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case TARGET_NR_getcpu:
|
|
|
|
+ if (!write_ok_or_segv(env, env->regs[R_EDI], sizeof(uint32_t)) ||
|
|
|
|
+ !write_ok_or_segv(env, env->regs[R_ESI], sizeof(uint32_t))) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ g_assert_not_reached();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Perform the syscall. None of the vsyscalls should need restarting.
|
|
|
|
+ */
|
|
|
|
+ ret = do_syscall(env, syscall, env->regs[R_EDI], env->regs[R_ESI],
|
|
|
|
+ env->regs[R_EDX], env->regs[10], env->regs[8],
|
|
|
|
+ env->regs[9], 0, 0);
|
|
|
|
+ g_assert(ret != -TARGET_ERESTARTSYS);
|
|
|
|
+ g_assert(ret != -TARGET_QEMU_ESIGRETURN);
|
|
|
|
+ if (ret == -TARGET_EFAULT) {
|
|
|
|
+ goto sigsegv;
|
|
|
|
+ }
|
|
|
|
+ env->regs[R_EAX] = ret;
|
|
|
|
+
|
|
|
|
+ /* Emulate a ret instruction to leave the vsyscall page. */
|
|
|
|
+ env->eip = caller;
|
|
|
|
+ env->regs[R_ESP] += 8;
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ sigsegv:
|
|
|
|
+ /* Like force_sig(SIGSEGV). */
|
|
|
|
+ gen_signal(env, TARGET_SIGSEGV, TARGET_SI_KERNEL, 0);
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|
|
void cpu_loop(CPUX86State *env)
|
|
void cpu_loop(CPUX86State *env)
|
|
{
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
CPUState *cs = env_cpu(env);
|
|
@@ -141,6 +244,11 @@ void cpu_loop(CPUX86State *env)
|
|
env->regs[R_EAX] = ret;
|
|
env->regs[R_EAX] = ret;
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
|
|
+#endif
|
|
|
|
+#ifdef TARGET_X86_64
|
|
|
|
+ case EXCP_VSYSCALL:
|
|
|
|
+ emulate_vsyscall(env);
|
|
|
|
+ break;
|
|
#endif
|
|
#endif
|
|
case EXCP0B_NOSEG:
|
|
case EXCP0B_NOSEG:
|
|
case EXCP0C_STACK:
|
|
case EXCP0C_STACK:
|