smash_insts.c 9.75 KB
/*
 * Copyright (C) 1996-1998 by the Board of Trustees
 *    of Leland Stanford Junior University.
 * 
 * This file is part of the SimOS distribution. 
 * See LICENSE file for terms of the license. 
 *
 */

/*
 * smashinst.c
 *
 * A function to be called from tcl when it is time to
 * smash an instruction at a given address for fault injection
 * experiments.
 *
 * We do this in C since we need to decompose instructions to
 * figure out what to do.
 *
 * Let tcl pass us the address to smash and the random number that
 * controls what we do, so the experiments are repeatable if needed.
 */
#include <stdlib.h>
#include "simtypes.h"
#include "cpu.h"
#include "cpu_interface.h"
#include "mips_arch.h"
#include "stdio.h"
#include "sim_error.h"
#include "string.h"
#include "math.h"

/* how random number is interpreted.
 * modify dest of ALU op: change register
 * modify dest of branch op: change < to <=
 */

struct randomsmash {
   uint fill             : 23;
   uint regnum           : 5;   /* random register number */
   uint modify_or_delete : 2;   /* 1,2,3 = modify instr, 0 = delete it */
   uint source_or_dest   : 1;   /* 1 = modify source, 0 = modify dest */
   uint rs_or_rt         : 1;   /* 1 = modify source rs, 0 = mod source rt */
};

#define DELETE_INST    0
#define MODIFY_DEST    0
#define MODIFY_SOURCE  0

typedef enum {change_none, 
              change_rs, 
              change_rt, 
              change_rd} changetype;

#if !defined(__alpha) && !defined(__linux__)
extern long random(void);
#endif

Result
smashinst(int dosmash, int cpunum, VA addr, int rnd, char* errbuf)
{
   uint  op;         /* extracted instruction opcode */
   uint nreg;
   Inst instr = 0;      
#ifdef PRINT_INSTRUCTIONS
   Inst orig_instr = 0;
#endif
   Result r = SUCCESS;
   struct randomsmash* smashtype = (struct randomsmash*) &rnd;
   changetype ct = change_none;
   uint64 field0;
   char fnamebuf[64];

   strncpy(fnamebuf, errbuf, 63);

   r = CPUVec.GetMemory(cpunum, addr, INST_SIZE, (char*) &instr);
#ifdef PRINT_INSTRUCTIONS
   orig_instr = instr;
#endif

   if (r != SUCCESS) {
      sprintf(errbuf, "Can't read instruction at address 0x%llx", (Reg64)addr);
      return FAILURE;
   }

   if (instr == 0) {
      /* can't do anything to a nop */
      goto retry;
   }

   if (smashtype->modify_or_delete == DELETE_INST) {
      instr = 0;
      goto writeback;
   }

   /* going to modify the inst if possible */
   op = MAJOR_OP(instr);

   if (op == spec_op) {
      uint specialFunc = FUNC(instr);

      switch(specialFunc) {
            
      case add_op:
      case addu_op:
      case and_op:
      case nor_op:
      case or_op:
      case sll_op:
      case sllv_op:
      case slt_op:
      case sltu_op:
      case sra_op:
      case srav_op:
      case srl_op:
      case srlv_op:
      case sub_op: 
      case subu_op:
      case xor_op:
         if (smashtype->source_or_dest == MODIFY_DEST)
            ct = change_rd;
         else
            ct = (smashtype->rs_or_rt) ? change_rs : change_rt;
         break;

      case div_op:
      case divu_op:
      case mult_op:
      case multu_op:
         ct = (smashtype->rs_or_rt) ? change_rs : change_rt;
         break;

      case mfhi_op:
      case mflo_op:
         ct = change_rd;
         break;

      case mthi_op:
      case mtlo_op:
         ct = change_rs;
         break;

      case break_op:
      case sync_op:
      case jr_op:
      case jalr_op: 
      case syscall_op: 
         goto retry;

      default:
         CPUWarning("Illegal inst at %#x on CPU %d\n", addr, cpunum);
         ASSERT(0);
         break;
      }
   } else {                  
      /* Not SPECIAL */
      if (op == bcond_op) { 

         if (smashtype->source_or_dest == MODIFY_SOURCE) {
            ct = change_rs;
         } else {
            uint bcondFunc   = RT(instr);
            int newop = -1;
            switch(bcondFunc) {
            case bgez_op:
               newop = bgtz_op;
               break;
            case bgezl_op:
               newop = bgtzl_op;
               break;
            case bltz_op:
               newop = blez_op;
               break;
            case bltzl_op:
               newop = blezl_op;
               break;

               /* there are no off-by-one branch-and-link.  Luckily
                * the kernel doesn't use these ops much
                */
            case bgezal_op:
               bcondFunc = bltzal_op;
               break;
            case bgezall_op:
               bcondFunc = bltzall_op;
               break;
            case bltzal_op:
               bcondFunc = bgezal_op;
               break;
            case bltzall_op:
               bcondFunc = bgezall_op;
               break;

            default:
               CPUWarning("Illegal inst at %#x on CPU %d\n", addr, cpunum);
               ASSERT(0);
               break;
            }
            if (newop != -1) {
               instr = (newop << 26) | RS(instr) | IMMED(instr);
            } else {
               instr = (bcond_op << 26) | (RS(instr) << 21)
                  | (bcondFunc << 16) | IMMED(instr);
            }
         }
      } else { /* Not a SPECIAL or REGIMM instruction */

         switch(op) {

         case addi_op:
         case addiu_op:
         case andi_op:
         case slti_op:
         case sltiu_op:
         case xori_op:
            ct = (smashtype->source_or_dest == MODIFY_DEST)
                  ? change_rt : change_rs;
            break;

         case beq_op:
            if ((RS(instr) == 0) && (RT(instr) == 0)) {
               /* this is a b in disguise */
               goto retry;
            }
            /* FALL THROUGH */

         case beql_op:
         case bne_op:
         case bnel_op:
            if (smashtype->source_or_dest == MODIFY_SOURCE) {
               ct = (smashtype->rs_or_rt) ? change_rs : change_rt;
            } else {
               uint newop = 0;
               switch (op) {
               case beq_op:  newop = bne_op;  break;
               case beql_op: newop = bnel_op; break;
               case bne_op:  newop = beq_op;  break;
               case bnel_op: newop = beql_op; break;
               }
               instr = (newop << 26) | TARGET(instr);
            }
            break;

         case bgtz_op:
         case bgtzl_op:
         case blez_op:
         case blezl_op:
            if (smashtype->source_or_dest == MODIFY_SOURCE) {
               ct = change_rs;
            } else {
               uint bcondFunc = 0;
               switch (op) {
               case bgtz_op:  bcondFunc = bgez_op;  break;
               case bgtzl_op: bcondFunc = bgezl_op;  break;
               case blez_op:  bcondFunc = bltz_op;  break;
               case blezl_op: bcondFunc = bltzl_op;  break;
               }
               instr = (bcond_op << 26) | (RS(instr) << 21)
                  | (bcondFunc << 16) | IMMED(instr);
            }
            break;

         case cache_op:
         case j_op:
         case jal_op: 
         case pref_op:
         case lui_op:
         case ori_op:
            goto retry;

            /* pointer base register in rs, dest is rt */
         case lb_op:
         case lbu_op:
         case lh_op:
         case lhu_op:
         case ll_op: 
         case lw_op:
         case ld_op: 
         case ldc2_op:
         case lwl_op:
         case lwr_op:
            goto retry;   /* don't handle loads/stores yet */

            /* rs is base, rt is dest in coprocessor 1 */
         case lwc1_op:
         case ldc1_op:
            goto retry;

            /* rs is base, rt is source of data to store */
         case sb_op:
         case sh_op:
         case sc_op: 
         case sw_op:
         case sd_op:
         case sdc2_op:
         case swc2_op:
         case swl_op:
         case swr_op:
            goto retry;

            /* rs is base, rt is source in coproc 1 */
         case sdc1_op:
         case swc1_op:
            goto retry;

         case cop0_op:
         case cop1_op:
            goto retry;
         }
      }
   }

   nreg = smashtype->regnum;

   switch (ct) {
   case change_none:
      break;

   case change_rs:
      if (nreg == RS(instr)) {
         nreg = (nreg + 1) % 32;
      }
      instr = 
         (MAJOR_OP(instr) << 26) 
         | (nreg << 21)
         | (RT(instr) << 16)
         | IMMED(instr);
      break;

   case change_rt:
      if (nreg == RT(instr)) {
         nreg = (nreg + 1) % 32;
      }
      instr = 
         (MAJOR_OP(instr) << 26) 
         | (RS(instr) << 21)
         | (nreg << 16)
         | IMMED(instr);
      break;

   case change_rd:
      if (nreg == RD(instr)) {
         nreg = (nreg + 1) % 32;
      }
      instr = 
         (MAJOR_OP(instr) << 26) 
         | (RS(instr) << 21)
         | (RT(instr) << 16)
         | (nreg << 11)
         | (instr & 0x7ff);
      break;
   }

writeback:
   if (dosmash) {
      r = CPUVec.PutMemory(cpunum, addr, INST_SIZE, (char*) &instr);
      if (r != SUCCESS) {
         sprintf(errbuf, "Can't write instruction at address 0x%llx", (Reg64)addr);
         return FAILURE;
      }
      field0 = CPUVec.CycleCount(cpunum);
   } else {
      field0 = random();
   }

#ifdef PRINT_INSTRUCTIONS
   {
      extern void SprintInstruction(Inst inst, char* buf);
      sprintf(errbuf, "0x%x 0x%x %s (", addr, rnd, fnamebuf);
      SprintInstruction(orig_instr, errbuf+strlen(errbuf));
      sprintf(errbuf+strlen(errbuf), ") -> (");
      SprintInstruction(instr, errbuf+strlen(errbuf));
      CPUPrint("%lld %s %s)\n", field0,
               dosmash ? "SMASH" : "testSMASH",
               errbuf);
   }
#else
   CPUPrint("%lld %s 0x%x 0x%x %s\n", field0,
            dosmash ? "SMASH" : "testSMASH",
            addr, rnd, fnamebuf);

#endif

   sprintf(errbuf, "OK");
   return SUCCESS;

retry:
   /* no modification possible on this instr.  tell TCL no go. */
   sprintf(errbuf, "RETRY");
   return FAILURE;
}