[Soot-list] need help with method references

John Jorgensen jorgnsn at lcd.uregina.ca
Sat May 13 15:11:24 EDT 2006


You've probably answered your question yourself by now, but just
in case...

>>>>> "kdean1" == Kevin Dean <kdean1 at gmu.edu> writes:
    kdean1> What I am trying to do is create an external class which accepts  
    kdean1> blocks and adds them to a vector (there is an algorithm here). I want  
    kdean1> to instrument the code so that for every statement that is the head  
    kdean1> of a block, there should be a method reference to my external  
    kdean1> class... and to pass that method the current block. 

    (omitting example code for instrumenting blocks)

    kdean1> I know its failing because v is not a "Value" but a
    kdean1> block.... but maybe I am not using the right class
    kdean1> here? Is InvokeExpr the right way to do this? 

I think the underlying difficulty is that you need to get values
from one domain---references to soot.Block objects created by
Soot's analysis---into another domain---values which the
instrumented program may pass as parameters during its execution.  

Just in case the distinction I'm trying to make isn't clear,
imagine a case where you're first instrumenting the analyzed
classes using Soot, then running the instrumented classes
afterwards, outside of Soot, in a separate execution of the
JVM. In that case, the soot.Block objects identified by Soot
would not even exist at the time the instrumented program ran, so
even if Java let you manipulate arbitrary pointer values like C
does, the pointers recorded by the instrumentation run would no
longer be valid when you executed the instrumented program.

If you are performing the instrumentation and executing the
instrumented code in the same JVM run---so that the soot.Block
objects created by the analysis do still exist when the
instrumented code executes---I suspect that Java's type safety
rules will still prevent you from injecting a reference to an
arbitrary object into bytecode that you generate.

What the instrumenter could do would be to store the Block
objects in some Map, and add to the instrumented code calls to
retrieve the objects from that map, using the hashCodes of the
objects (or some other key that Soot will let you turn into an
immediate value).  Something like this:

  blockRecorder.add(block.hashCode(), block);
  SootMethodRef ref = // make a reference to a method of
                      // blockRecorder which increments
                      // the execution count for the block 
                      // corresponding to a passed hashCode.
  Value incrExpr = 
        Jimple.v().newStaticInvokeExpr(ref, IntConstant.v(b.hashCode()));
  Unit incrStmt = Jimple.v().newInvokeStmt(incrExpr);
  unitChain.insertBefore(incrStmt, b.getHead())

With serialization, you could even do this in separate
instrumentation and execution runs.

I hacked together some code to prove to myself that this is
feasible (my Map only stores some information identifying the
blocks, rather than the complete Block object, since serializing
Blocks would end up serializing a great deal of Soot state). I'll
attach my code here, but be warned that it is very ugly; I just
threw the ideas together into a single file as they occurred to
me, and I was teaching myself about serialization on the fly, so
this is the polar opposite of a comprehensible, maintainable
solution).

-------------- next part --------------
package sootexperiment;

import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import soot.Body;
import soot.BodyTransformer;
import soot.BriefUnitPrinter;
import soot.G;
import soot.IntType;
import soot.LabeledUnitPrinter;
import soot.Local;
import soot.Main;
import soot.SootMethodRef;
import soot.PackManager;
import soot.PatchingChain;
import soot.Scene;
import soot.RefType;
import soot.SootClass;
import soot.Transform;
import soot.Type;
import soot.Unit;
import soot.UnitPrinter;
import soot.Value;
import soot.jimple.AssignStmt;
import soot.jimple.IntConstant;
import soot.jimple.Jimple;
import soot.toolkits.graph.Block;
import soot.toolkits.graph.BlockGraph;
import soot.toolkits.graph.ExceptionalBlockGraph;
import soot.util.Chain;


/**
 * A class for instrumenting block executions, as an exercise
 * in adding instrumentation that refers to entities, in this case
 * basic blocks, identified by Soot.
 */
  
public class BlockInstrumenter extends BodyTransformer {

  private static final String packToJoin = "jtp";
  private static final String phaseSubname = "blockinstrument"; // even duller
								// than a
								// blunt
								// instrument.
  private static final String phaseFullname = packToJoin + '.' + phaseSubname;

  private static final String recorderSerializationFile = "/tmp/blockRecorder";

  private static BlockRecorder globalRecorder = null;

  private SootMethodRef incrementRef = null;

  private void initIncrementRef() {
    SootClass classOfBlockInstrumenter = Scene.v().getSootClass("sootexperiment.BlockInstrumenter");
    classOfBlockInstrumenter.setLibraryClass();
    List parameterTypes = new ArrayList();
    parameterTypes.add(soot.IntType.v());
    incrementRef = Scene.v().makeMethodRef(classOfBlockInstrumenter,
					   "incrementBlockExecutionCount",
					   parameterTypes, soot.VoidType.v(),
					   true);
  }

  static BlockRecorder unserializeRecorder(String serializedFile) 
  throws ClassNotFoundException, IOException {
    InputStream is = new FileInputStream(serializedFile);
    ObjectInputStream ois = new ObjectInputStream(is);
    return (BlockRecorder) ois.readObject();
  }

  public static void incrementBlockExecutionCount(int blockHashCode)
  throws ClassNotFoundException, IOException {
    if (globalRecorder == null) {
      globalRecorder = unserializeRecorder(recorderSerializationFile);
    }
    globalRecorder.incrementExecutionCount(blockHashCode);
  }


  /**
   * Class for recording information about a single block
   */
  static class BlockRecord implements Serializable {
    private static final long serialVersionUID = 0x1234;
    private int blockHashCode;
    private String blockId;
    private String blockContent;
    private int executionCount;

    BlockRecord(Block block, String methodSignature, LabeledUnitPrinter printer) {
      blockHashCode = block.hashCode();

      blockId = new StringBuffer(methodSignature).append(' ').append(block.toShortString()).toString();

      // Record the code inside the block.
      printer.output().setLength(0);
      for (Iterator unitIter = block.iterator(); unitIter.hasNext(); ) {
	Unit u = (Unit) unitIter.next();
	String targetLabel = (String) printer.labels().get(u);
	if (targetLabel != null) {
	  printer.output().append(targetLabel).append(":\n");
	}
	u.toString(printer);
	printer.newline();
      }
      blockContent = printer.toString();

      executionCount = 0;
    }

    void incrementExecutionCount() {
      executionCount++;
    }

    int getExecutionCount() {
      return executionCount;
    }

    public String toString() {
      StringBuffer buf = new StringBuffer(blockId);
      buf.append("\n");
      buf.append(blockContent);
      buf.append("Execution Count: ");
      buf.append(executionCount);
      buf.append('\n');
      return buf.toString();
    }
  }


  /**
   * Class for recording BlockRecords for all the blocks we care about.
   */
  public static class BlockRecorder implements Serializable, Runnable {
    private static final long serialVersionUID = 0x4321;
    private transient HashMap blockHashCodeToRecord = new HashMap();

    private void  init() {
      // Code that must be executed by all constructors and by
      // any deserializer.  Ensures that the BlockRecorder's content
      // will be written to a file when the JVM exits.
      Runtime.getRuntime().addShutdownHook(new Thread(this));
    }

    BlockRecorder() {
      init();
    }

    void add(Block b, String methodSig, LabeledUnitPrinter p) {
      blockHashCodeToRecord.put(b.hashCode(), new BlockRecord(b, methodSig, p));
    }

    void incrementExecutionCount(int blockHashCode) {
      ((BlockRecord) blockHashCodeToRecord.get(blockHashCode)).incrementExecutionCount();
    }

    Iterator iterator() {
      return blockHashCodeToRecord.values().iterator();
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
      s.defaultWriteObject();
      s.writeInt(blockHashCodeToRecord.size());
      for (Iterator recIter = blockHashCodeToRecord.values().iterator();
	   recIter.hasNext(); ) {
	s.writeObject(recIter.next());
      }
    }

    /**
     * run as a shutdown hook, to record the state of the
     * BlockRecorder on JVM exit.
     */
    public void run() {
      try {
	OutputStream os = new FileOutputStream(recorderSerializationFile);
	ObjectOutputStream oos = new ObjectOutputStream(os);
	oos.writeObject(this);
      } catch (IOException e) {
	throw new RuntimeException("Rethrowing IOException to avoid having to declare it", e);
      }
    }

    private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
      s.defaultReadObject();
      int size = s.readInt();
      blockHashCodeToRecord = new HashMap(size+17);
      for (int i = 0; i < size; i++) {
	BlockRecord record = (BlockRecord) s.readObject();
	blockHashCodeToRecord.put(record.blockHashCode, record);
      }
      init();
    }

    public String toString() {
      StringBuffer buf = new StringBuffer();
      for (Iterator recIter = blockHashCodeToRecord.values().iterator();
	   recIter.hasNext(); ) {
	buf.append(recIter.next().toString());
	buf.append('\n');
      }
      return buf.toString();
    }
  }


  /**
   * The Transformation which records information about Blocks, and
   * adds instrumentation to the beginning of each Block.
   */
  protected void internalTransform(Body body, String phase, Map options) {

    if (incrementRef == null) {
      initIncrementRef();
    }
    String signature = body.getMethod().getSubSignature();
    BlockGraph g = new ExceptionalBlockGraph(body);
    List blocks = g.getBlocks();
    LabeledUnitPrinter printer = new BriefUnitPrinter(body); 
    PatchingChain unitChain = body.getUnits();

    for (Iterator blockIter = blocks.iterator(); blockIter.hasNext(); ) {
      Block b = (Block) blockIter.next();
      globalRecorder.add(b, signature, printer);
      Value incrExpr = Jimple.v().newStaticInvokeExpr(incrementRef, 
						      IntConstant.v(b.hashCode()));
      Unit incrStmt = Jimple.v().newInvokeStmt(incrExpr);
      unitChain.insertBefore(incrStmt, b.getHead());
    }
  }

  /**
   * Run as "java BlockInstrumenter undump" to print the contents of
   * the BlockRecorder in our hardcoded serialization file.  Run as
   * "java BlockInstrumenter classname" to record the blocks in
   * classname, and instrument them so that they will record execution
   * counts in that serialization file.
   */

  public static void main(String[] args) 
    throws IOException, ClassNotFoundException {
    if (args.length == 1 && args[0].equals("undump")) {
      globalRecorder = unserializeRecorder(recorderSerializationFile);
      G.v().out.println(globalRecorder.toString());
    } else {
      globalRecorder = new BlockRecorder();
      BlockInstrumenter instrumenter = new BlockInstrumenter();
      Transform blockTransform = new Transform(phaseFullname, instrumenter);
      blockTransform.setDeclaredOptions("enabled ");
      blockTransform.setDefaultOptions("enabled ");
      PackManager.v().getPack(packToJoin).add(blockTransform);
      Scene.v().addBasicClass("sootexperiment.BlockInstrumenter", SootClass.SIGNATURES);
      soot.Main.main(args);
    }
  }
}
-------------- next part --------------
public class BlockInstrumenterTest {
  public static void main(String[] args) {
    int iterations = 0;
    if (args.length > 0) {
      iterations = Integer.parseInt(args[0]);
    }
    // Test BlockInstrumenter by setting up blocks which will
    // execute a controllable number of times.
    for (int i = 0; i < iterations; i++) {
      System.out.print("Iteration " + i);
      if (i % 2 == 0) {
	System.out.print(" (mod 2)");
	if (i % 4 == 0) {
	  System.out.print(" (mod 4)");
	  if (i % 8 == 0) {
	    System.out.print(" (mod 8)");
	  }
	}
      }
      System.out.println();
    }
  }
}


More information about the Soot-list mailing list