// http://charette.no-ip.com:81/programming/2009-12-30_Virtualization/
// $Id: 2009-12-30_Virtualization.c 13 2010-01-01 15:38:51Z stephane $
//
// Code to walk through the CPUs/cores and gcc inline assembly is loosely
// based on this:  http://nibbles.tuxfamily.org/?tag=gdt
//
// *** PLEASE NOTE ***
//
//	1)	This code assumes it is being compiled for AMD64.  If compiling for
//		i386 then you'll need to edit the "dt" structure.  See documentation.
//
//	2)	This "C" file has inline assembly written for gcc v4.4.1.  If using
//		a different compiler, you'll neeed to modify the source.

#define _GNU_SOURCE	// needed for CPU_... macros
#include <sched.h>
#include <errno.h>
#include <stdio.h>


typedef struct
{
	// Lots of *really bad* examples on the web where they use char[2]
	// to store the output of SIDT and SGDT.  This is wrong!
	//
	// In 32-bit mode, the output is 6 bytes, where the first 2 is
	// the limit and the upper 4 bytes is the 32-bit base address.
	//
	// In 64-bit mode, the output is 10 bytes, where the first 2 is
	// the limit and the upper 8 bytes is the 64-bit base address.
	//
	// This is described on page 299 of AMD64 Architecture Programmer's
	// Manual Volume 3: General-Purpose and System Instructions:
	// http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/24594.pdf

	unsigned long	limit : 16;
	unsigned long	base  : 64;
} __attribute__((packed)) dt;


unsigned long getIDT( void )
{
	// Interrupt Descriptor Table Register
	//
	// This is the original "red pill" and "scoopy_doo" method of determining
	// whether or not we're running in a VM.  The check would be against the
	// base address returned by SIDT.  If the table was not at the usual
	// location, then it was assumed we were running in a VM.  However, this
	// check can return false-positives with multi-core and multi-processor,
	// since the table is per-cpu/core.

	dt idt = {0, 0};
	asm volatile( "sidt %0" : "=m"(idt) );
	printf( "IDT: limit=%04d, base=%016lx\n", idt.limit, idt.base );

	return idt.base;
}


unsigned long getGDT( void )
{
	// Global Descriptor Table Register
	//
	// This is the "no pill" variation on the original "red pill".

	dt gdt = {0, 0};
	asm volatile( "sgdt %0" : "=m"(gdt) );
	printf( "GDT: limit=%04d, base=%016lx\n", gdt.limit, gdt.base );

	return gdt.base;
}


unsigned short getLDT( void )
{
	// Local Descriptor Table Register
	//
	// This one is slightly different than IDT and GDT.  It returns to us just
	// the 16-bit segment selector.  This is always a 16-bit value, regardless
	// of whether or not we're running in legacy i386 or in AMD64.

	unsigned long ldt = -1;	// -1 will help us "see" this is a 16-bit value
	asm volatile( "sldt %0" : "=m"(ldt) );
	printf( "LDT: %04lx\n", ldt );

	return ldt;
}


unsigned short getSTR( void )
{
	// Task Register Selector
	//
	// Apparently this can be used to determine if this is within or outside
	// the "Matrix, but I get the same value whether I'm running natively or
	// within VirtualBox.
	// http://www.s21sec.com/descargas/vmware-eng.pdf
	unsigned long str = -1;	// -1 will help us "see" this is a 16-bit value
	asm volatile( "str %0" : "=m"(str) );
	printf( "STR: %04lx\n", str );

	return str;
}

 
int main( void )
{
	int result	= 0;
	int cpu		= 0;
	int cpu2	= 0;
	cpu_set_t cs, cs2;

	if( sched_getaffinity( getpid(), sizeof( cs ), &cs ) != 0 )
	{
		printf( "sched_getaffinity() failed\n" );
		return -1;
	}

	// loop through all of the CPUs
	while( CPU_COUNT( &cs ) != 0 )
	{
		// if we have this CPU...
		if ( CPU_ISSET( cpu, &cs ) )
		{
			printf("\nCPU: %d\n", cpu );

			// set affinity -- schedule us to run on this specific CPU
			CPU_ZERO( &cs2 );
			CPU_SET( cpu, &cs2 );
			if( sched_setaffinity( getpid(), sizeof( cs2 ), &cs2 ) != 0)
			{
				printf( "setaffinity(%d) failed\n", cpu);
				result = -1;
			}

			// determine on which CPU we're running
			cpu2 = sched_getcpu ();
			if (cpu2 == -1 && errno == ENOSYS)
			{
				printf( "getcpu syscall not implemented" );
				return -1;
			}

			// ensure we're running on the CPU we think we should be using
			if (cpu2 != cpu)
			{
				printf( "getcpu result is %d; expected %d\n", cpu2, cpu );
				result = 1;
			}

			// display the results
			getIDT();
			getGDT();
			getLDT();
			getSTR();

			// clear affinity
			CPU_CLR(cpu, &cs);
		}

		// move on to the next CPU
		cpu ++;
	}

	return 0;
}

