/*--------------------------------------------------------------------*/
/*--- valtaxi: The cachetool interface.                vt_main.c ---*/
/*--------------------------------------------------------------------*/
/*

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.

   The GNU General Public License is contained in the file COPYING.
*/

#include "pub_tool_basics.h"
#include "pub_tool_libcassert.h"
#include "pub_tool_tooliface.h"
#include "pub_tool_libcprint.h" 
//#include "pub_tool_hashtable.h"     
#include "pub_tool_libcbase.h"
//#include "pub_tool_threadstate.h" 
//#include "../VEX/pub/libvex_guest_x86.h"
//#include "pub_core_threadstate.h"
#include "../coregrind/pub_core_basics.h"
#include "../coregrind/pub_core_threadstate.h"
#include "pub_tool_libcfile.h"  
#include "pub_tool_options.h"  



#define BUFSIZ 256
int memtrace_ptr,insntrace_ptr;
unsigned long taxi_tag=0;
unsigned long new_insn=1;
char temp_st[BUFSIZ];

long long fastforward=0;
long long x86_instructions=0;
int print_results=1;
int magic_fastforward=0;
long long count_magic=0,count_magic_total=0;

/* Tell Valgrind this function has one parameter */ 
/* write load information to trace file*/


static VG_REGPARM(1) void print_Load (Addr a) {

   if (print_results) {
      if (new_insn) {
         VG_(sprintf)(temp_st,"%x:\n",taxi_tag);
         VG_(write)(memtrace_ptr,temp_st,VG_(strlen)(temp_st));
         taxi_tag++;
      }
      VG_(sprintf)(temp_st,"0x%x 0x%x L\n",(unsigned long)a,0xdeadbeef);
      VG_(write)(memtrace_ptr,temp_st,VG_(strlen)(temp_st));
      new_insn=0;
   }
}



/* write store information to trace file*/
static VG_REGPARM(1) void print_Store (Addr a) {

   if (print_results) {
      if (new_insn) {
         VG_(sprintf)(temp_st,"%x:\n",taxi_tag);
         VG_(write)(memtrace_ptr,temp_st,VG_(strlen)(temp_st));
         taxi_tag++;
      }
      VG_(sprintf)(temp_st,"0x%x 0x%x S\n",(unsigned long)a,0xcafebabe);
      VG_(write)(memtrace_ptr,temp_st,VG_(strlen)(temp_st));
      new_insn=0;
   }
}

static VG_REGPARM(2) void dump_insn2 (Addr address,int len) {

   int tid,i;

   
   x86_instructions++;

   if (!print_results) {
      if ((fastforward) && (x86_instructions>fastforward)) {
         print_results=1;
      }
      if (magic_fastforward) {
	 if (len==2) {
	    //VG_(printf)("Ins= %d %d\n",
	    if ((*((unsigned char *)address)==0x89)
	             && (*((unsigned char *)(address+1))==0xc9)) {

	       count_magic--;
	       if (count_magic==0) {
		  print_results=1;
	          VG_(printf)("\nDetected magic instruction %lld",
			      count_magic_total);
		  VG_(printf)(" after %lld instructions,  starting trace...\n",
			      x86_instructions);
	       }
	    }
	 }
      }      
   }
   else {

      new_insn=1;
      
      tid=VG_(get_running_tid)();

      VG_(sprintf)(temp_st,"%04x:%08x: ",
           VG_(threads)[tid].arch.vex.guest_CS,
	   (unsigned long)address);
      VG_(write)(insntrace_ptr,temp_st,VG_(strlen)(temp_st));
      for(i=0;i<(int)len;i++) {
         VG_(sprintf)(temp_st,"%02x",*(((unsigned char *)address)+i));
	 VG_(write)(insntrace_ptr,temp_st,VG_(strlen)(temp_st));
      }
      VG_(sprintf)(temp_st,": %x: %d 1\n",
 	   LibVEX_GuestX86_get_eflags(&(VG_(threads)[tid].arch.vex)),
	   taxi_tag);
      VG_(write)(insntrace_ptr,temp_st,VG_(strlen)(temp_st));
   }
}


/*------------------------------------------------------------*/
/*--- Our instrumenter                                     ---*/
/*--- Translates the Basic Block passed in as "bb_in"      ---*/
/*---    into a new "instrumented" basic block "bb"        ---*/
/*------------------------------------------------------------*/
//from ac_main.c

static IRBB* vt_instrument ( VgCallbackClosure* closure,
			     IRBB* bb_in, VexGuestLayout* layout,
			     VexGuestExtents* vge,
                             IRType gWordTy, IRType hWordTy ) {

   Int         i, access_size, insn_label;
   IRStmt*     st;
   IRExpr*     data;
   IRExpr      *access_address;
   IRExpr*     guard;
   IRDirty     *di,*di2=NULL;
   Bool        isLoad;
   IRBB*       bb;
   IRExpr      *insn_address,*insn_len;
   
   /* Create a new basic block */
   /* We'll put all of the original instructions, plus our     */
   /* instrumentations into it, and return it back to valgrind */

   /* create an empty basic block */
   bb           = emptyIRBB();

   /* copy over configuration from the original basic block */
   bb->tyenv    = dopyIRTypeEnv(bb_in->tyenv);
   bb->next     = dopyIRExpr(bb_in->next);
   bb->jumpkind = bb_in->jumpkind;


   /* Walk through each statement, */
   /* from first (0) to last (bb_in->stmts_used) */

   for (i = 0; i <  bb_in->stmts_used; i++) {
      insn_label=0;

      st = bb_in->stmts[i];

      /* clear these variables */
      access_size     = 0;
      access_address  = NULL;
      guard  = NULL;
      isLoad = True;

      switch (st->tag) {

	    /* Ist_Tmp means we are copying data into a */
	    /* "Temporary" register                     */
         case Ist_Tmp:
              data = st->Ist.Tmp.data;
	       /* We only care if it's a load instruction */
              if (data->tag == Iex_Load) {
                 access_address  = data->Iex.Load.addr;
                 access_size    = sizeofIRType(data->Iex.Load.ty);
                 isLoad = True;
	               }
	               break;

	    /* Ist_Store means we are storing data */
         case Ist_Store:
	            data  = st->Ist.Store.data;
              access_address = st->Ist.Store.addr;
              access_size = sizeofIRType(typeOfIRExpr(bb_in->tyenv, data));
	            isLoad = False;
              break;
	    
	    /* We ignore these */
         case Ist_Put: /* We are copying some "guest state" */
         case Ist_PutI:/* We are copying some "guest state" */
         case Ist_Exit:/* We are conditionally leaving a basic block  */
         case Ist_NoOp:
	 case Ist_MFence: break;
         case Ist_IMark:

              insn_address=mkIRExpr_HWord (st->Ist.IMark.addr);
	      insn_len =  mkIRExpr_HWord (st->Ist.IMark.len);

              di2 = unsafeIRDirty_0_N( 2, "dump_insn2", &dump_insn2,
                                         mkIRExprVec_2(insn_address,
						       insn_len));

	      insn_label=1;

              break;

	    /* ??????? *//* We are in a "dirty" function? */
         case Ist_Dirty:
            if (st->Ist.Dirty.details->mFx != Ifx_None) {
               /* We classify Ifx_Modify as a load. */
               isLoad = st->Ist.Dirty.details->mFx != Ifx_Write;
               access_size    = st->Ist.Dirty.details->mSize;
               access_address  = st->Ist.Dirty.details->mAddr;
               guard  = st->Ist.Dirty.details->guard;
             }
             break;

	    /* Print an error if an unknown statement type */
         default:
            VG_(printf)("\n");
            ppIRStmt(st);
            VG_(printf)("\n");
            VG_(tool_panic)("unhandled IRStmt");
            break;
      }

      /* If we were a load or store, add a call to print it */
      if (access_address) {

	 if (isLoad) {
	    /* Create a new "instruction" called "di" */
	    /* This is a dirty instruction, meaning it has side effects */
	    /* the "0" means we don't expect a return value */
	    /* the "N" means we can pass many arguments     */
	    /* We pass 1 argument, the name of the function, */
	    /* a pointer to the function, and an "argument vector" */
	    /* which in this case only has one, the address */
            di = unsafeIRDirty_0_N( 1, "print_Load", &print_Load,
                                         mkIRExprVec_1(access_address));
       	 }
         else {
            di = unsafeIRDirty_0_N( 1, "print_Store", &print_Store,
                                          mkIRExprVec_1(access_address));
	       }
	    /* If the call has arisen as a result of a dirty helper which
            references memory, we need to inherit the guard from the
            dirty helper. */ /* ??? */
         if (guard) {
            di->guard = dopyIRExpr(guard);     
	 }

         /* put the helper call into the new Basic Block */
	       /* before the load or store */

         addStmtToIRBB( bb, IRStmt_Dirty(di) );

      }

      if (insn_label) addStmtToIRBB( bb, IRStmt_Dirty(di2) );

      
      /* Make sure the original instruction gets added to the basic block. */
      addStmtToIRBB( bb, st );
   }

   return bb;
}

char mem_filename[BUFSIZ];
char cpu_filename[BUFSIZ];

static void vt_post_clo_init(void)
{

   SysRes sysr;
         
   sysr=VG_(open)(cpu_filename,
		  VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY|VKI_O_LARGEFILE,
		  VKI_S_IRUSR|VKI_S_IWUSR);
   if (sysr.isError) {
      VG_(printf)("file can not be opened");   
   }
   else {
      VG_(printf)("trace file %s opened\n",cpu_filename);                         
   }
   insntrace_ptr=sysr.val;
   
   
   sysr=VG_(open)(mem_filename,
		  VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY|VKI_O_LARGEFILE,
		  VKI_S_IRUSR|VKI_S_IWUSR);
   if (sysr.isError) {
      VG_(printf)("file can not be opened");   
   }
   else {
      VG_(printf)("trace file %s opened\n",mem_filename);                         
   }
   memtrace_ptr=sysr.val;

   VG_(printf)("header written\n");                                         

}
static void vt_fini(Int exitcode)
{   
   VG_(close)(memtrace_ptr);
   VG_(close)(insntrace_ptr);
   
   VG_(printf) ("\n\nValtaxi:Exiting! \n\n");
}



static Bool vt_process_cmd_line_option(Char* arg)
{
   
      // 11 is length of "--tracemem="
   if (VG_CLO_STREQN(11, arg, "--tracemem=")) {
      VG_(sprintf)(mem_filename,"%s",&arg[11]);
   }
   else if (VG_CLO_STREQN(11, arg, "--tracecpu=")) {
      VG_(sprintf)(cpu_filename,"%s",&arg[11]);
   }
   else if (VG_CLO_STREQN(14, arg, "--fastforward=")) {
      fastforward=VG_(atoll)(&arg[14]);
      print_results=0;
      fastforward*=100000000LL;
      VG_(printf)("Fastforwarding %lld instructions...\n",fastforward);
   }
   else if (VG_CLO_STREQN(18, arg, "--magicfastforward")) {
      print_results=0;
      magic_fastforward=1;
      count_magic_total=1;
      count_magic=1;
      VG_(printf)("Waiting until magic instruction...\n");
   }
   else if (VG_CLO_STREQN(13, arg, "--countmagic=")) {
      print_results=0;
      magic_fastforward=1;
      count_magic=VG_(atoll)(&arg[13]);
      count_magic_total=count_magic;
      VG_(printf)("Looking for magic instruction %d times\n",count_magic);
   }
   else { 
      return False;
   }
   
   return True;
}


static void vt_print_usage(void)
{
      VG_(printf)(
	  "    --tracecpu=<file> file for cpu trace file\n"
	  "    --tracemem=<file> file for mem trace file\n"
	  "    --fastforward=<val> number of insns(*100 000 000) to ffwd\n"
          "    --countmagic=<val> ffwd until see the magic insn <val> times\n"
       );
}

static void vt_print_debug_usage(void)
{
      VG_(printf)(
		  "    (none)\n"
		     );
}


static void vt_pre_clo_init(void)
{
   VG_(details_name)            ("valtaxi");
   VG_(details_version)         (NULL);
   VG_(details_description)     ("a frontend for TAXI");
   VG_(details_copyright_author)("Copyright (C) 2006 - Vince Weaver");
   VG_(details_bug_reports_to)  (VG_BUGS_TO);

   
      /* set up default output files */
   VG_(sprintf)(mem_filename,"/tmp/mem_state.txt");
   VG_(sprintf)(cpu_filename,"/tmp/trace_state.txt");
   
   VG_(needs_command_line_options)(vt_process_cmd_line_option,
				   vt_print_usage,
				   vt_print_debug_usage);
   
   
   VG_(basic_tool_funcs)        (vt_post_clo_init,
                                 vt_instrument,
                                 vt_fini);
   

}

VG_DETERMINE_INTERFACE_VERSION(vt_pre_clo_init) 

/*--------------------------------------------------------------------*/
/*--- end                                                          ---*/
/*--------------------------------------------------------------------*/
