/*
 * iretvm.c — direct probe: does a CPL3 iretq with EFLAGS.VM=1 (long mode)
 * #GP on this CPU?  Bypasses every kernel path; pure hardware semantics
 * under the current QEMU/KVM configuration.
 *
 * build: cc -O -o iretvm iretvm.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
#include <stdlib.h>

static sigjmp_buf jb;
static volatile int faulted;

static void
fh(int sig)
{
	faulted = sig;
	siglongjmp(jb, 1);
}

static unsigned long rflags_live(void)
{
	unsigned long rf;
	__asm __volatile("pushfq; popq %0" : "=r" (rf));
	return rf;
}

unsigned long mycs, myss, myrsp, myrip;

int
main(int ac, char **av)
{
	unsigned long rf0 = rflags_live();
	unsigned long sp, out_rf = 0;
	int rc;
	int vmflag = (ac > 1) ? atoi(av[1]) : 1;

	signal(SIGSEGV, fh);
	signal(SIGBUS, fh);
	signal(SIGILL, fh);

	__asm __volatile(
	    "movq %%cs,%0\n"
	    "movq %%ss,%1\n"
	    : "=r"(mycs), "=r"(myss));

	printf("iretvm: live rflags=%lx cs=%lx ss=%lx vm=%d\n",
	       rf0, mycs, myss, vmflag);
	fflush(stdout);

	if (sigsetjmp(jb, 1) != 0) {
		printf("iretvm: iretq with VM FAULTED: sig=%d\n", faulted);
		fflush(stdout);
		return (2);
	}

	/* build the iret frame and execute it */
	__asm __volatile(
	    "leaq 1f(%%rip),%%rax\n"		/* rip       */
	    "movq %%rsp,%%rdx\n"		/* rsp       */
	    "subq $128,%%rdx\n"
	    "movq %%rax,%0\n"
	    "movq %%rdx,%1\n"
	    : "=m"(myrip), "=m"(myrsp)
	    :
	    : "rax", "rdx");
	{
		unsigned long frame[5];
		frame[0] = myrip;			/* rip */
		frame[1] = mycs;			/* cs  (RPL3) */
		frame[2] = 0x246 | (vmflag ? 0x20000 : 0); /* rflags */
		frame[3] = myrsp;			/* rsp */
		frame[4] = myss;			/* ss  (RPL3) */
		/* long-mode iretq pops rip,cs,rflags,rsp,ss (5 slots,
		 * unconditionally): 5 pushes, 5 pops -> stack balanced */
		__asm __volatile(
		    "pushq %4\n"
		    "pushq %3\n"
		    "pushq %2\n"
		    "pushq %1\n"
		    "pushq %0\n"
		    "iretq\n"
		    "1:\n"
		    :
		    : "r"(frame[0]), "r"(frame[1]), "r"(frame[2]),
		      "r"(frame[3]), "r"(frame[4])
		    : "memory");
	}
	printf("iretvm: iretq %s RETURNED normally; live rflags=%lx "
	       "(vm=%d)\n", vmflag ? "with VM" : "no-VM control",
	       rflags_live(),
	       (int)((rflags_live() & 0x20000) ? 1 : 0));
	fflush(stdout);
	rc = ((rflags_live() & 0x20000) == 0);
	printf("iretvm: RESULT=%s\n", rc ? "VM-SILENTLY-CLEARED" : "VM-SET?!");
	return (0);
}
