1. 程式人生 > >deploy the transformation code in an agent

deploy the transformation code in an agent

Sometimes, we cannot touch the source code and place the transformation code in.

In this case, we can use the agent to do the transformation at the class loading time.

An agent should contain a function called premain. The classloader will execute it before executing the main method of the main class. One exemplary premain is:

 public static void premain(String agentArgs, Instrumentation inst) {
    AllocationRecorder.setInstrumentation(inst);
   System.out.println("premain invoked");
    // Force eager class loading here; we need this class to do
    // instrumentation, so if we don't do the eager class loading, we
    // get a ClassCircularityError when trying to load and instrument
    // this class.
    try {     
      Class.forName("sun.security.provider.PolicyFile");
    } catch (Throwable t) {
      // NOP
    }

    if (!inst.isRetransformClassesSupported()) {
      System.err.println("Some JDK classes are already loaded and " +
          "will not be instrumented.");
    }

    // Don't try to rewrite classes loaded by the bootstrap class
    // loader if this class wasn't loaded by the bootstrap class
    // loader.

    // For classes loaded by bootclassloader, getClassLoader()==null.
    // The following tests if the transformation class is loaded by the bootclassloader
    // if not, the transformation code cannot transform the classes loaded by the bootclassloader, which include the main class 
    // and jdk classes. 
    if (AllocationRecorder.class.getClassLoader() != null) {
      canRewriteBootstrap = false;
      // The loggers aren't installed yet, so we use println.
      System.err.println("Class loading breakage: " +
          "Will not be able to instrument JDK classes");
      return;
    }

    canRewriteBootstrap = true;

    inst.addTransformer(new ConstructorInstrumenter(),
        inst.isRetransformClassesSupported());  	  
    inst.addTransformer(new AllocationInstrumenter(),
      inst.isRetransformClassesSupported());

  }
For now, let us ignore the statements at the beginning, which correspond to some checking.

The core part in premain is the last four statements:

   inst.addTransformer(new ConstructorInstrumenter(),
        inst.isRetransformClassesSupported());  	  
    inst.addTransformer(new AllocationInstrumenter(),
      inst.isRetransformClassesSupported());

These statements register the instrumentors. Internally, such instrumentors are stored in the instrumentor manager.

Later, for each class, the manager will apply each instrumentor on it.

Such processing flow is very similar to Soot. First, we register the packs, then we apply the packs on each class.

The dynamic trace discloses more insights:

java.lang.Exception: Stack trace
	at java.lang.Thread.dumpStack(Thread.java:1206)
	at com.google.monitoring.runtime.instrumentation.AllocationInstrumenter.transform(AllocationInstrumenter.java:146)
	at sun.instrument.TransformerManager.transform(TransformerManager.java:169)
	at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:365)
	at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:300)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:252)

Note the TransformerManager is what I mean the instrumentor manager.

Now, the functional code of each instrumentor is implemented in its "transform" method. It can be implemented based on asm library as explained in previous blogs.

For now, we skip the details of how to implement it.

Suppose, we already get all transform methods implemented.
To run the agent, we need an agent jar, which includes the transformation code and the supporting libraries. One simple ant file can be used to produce such agent jar. (put it in the project folder in eclipse's workspace).The destiny jar file is named gen.jar, as specified in the jar task. The gen.jar should also contains the manifest info so that the runtime knows which entry to enter.

Note the Boot-Class-Path attribute in the manifest part. It forces the boot classloader to search the path (e.g., gen.jar) to load classes. In this way, the classes in the gen.jar and the main class are both loaded by the same boot classloader, so that, the transformation is applicable. (Same-class-loader requirement: the transformation code and the code being transformed should be loaded by the same classloader.)

<project name="project" default="default">                             
    <!--property file="ant.settings"/-->     
	<property name="tf.version" value="1.0"/>
        
        <target name="default" depends="agent-jar"/>

        <target name="agent">
        <javac                  
            destdir="bin" 
                source="1.5"    
                target="1.5"    
                debug="true"    
                        debuglevel="lines,vars,source"        
        >                                                     
            <src path="src"/>                                 
            <classpath>
                <pathelement location="lib/asm-3.3.jar"/>
                <pathelement location="lib/asm-analysis-3.3.jar"/>
                <pathelement location="lib/asm-commons-3.3.jar"/>
                <pathelement location="lib/asm-tree-3.3.jar"/>
                <pathelement location="lib/asm-util-3.3.jar"/>
                <pathelement location="lib/guava-r06.jar"/>
                <pathelement location="lib/jarjar-1.0.jar"/>
            </classpath>
        </javac>
    </target>

    <target name="agent-jar" depends="agent">
        <mkdir dir="META-INF"/>
        <manifest file="META-INF/MANIFEST.MF">
           <!--attribute name="Premain-Class" value="com.google.monitoring.runtime.instrumentation.AllocationInstrumenter"/>
           <attribute name="Can-Retransform-Classes" value="true"/>
           <attribute name="Implementation-Version" value="${tf.version}"/-->
          <attribute name="Boot-Class-Path" value="./gen.jar"/>
        <attribute name="Premain-Class" value="com.google.monitoring.runtime.instrumentation.AllocationInstrumenter"/>
        <attribute name="Can-Redefine-Classes" value="true" />
        <attribute name="Can-Retransform-Classes" value="true" />
        <attribute name="Main-Class" value="NotSuitableAsMain" />
        
       </manifest>

        <jar destfile="gen.jar" manifest="META-INF/MANIFEST.MF">
            <fileset dir="bin"/>
            
                <zipfileset  src="lib/asm-3.3.jar"/>
                <zipfileset  src="lib/asm-analysis-3.3.jar"/>
                <zipfileset  src="lib/asm-commons-3.3.jar"/>
                <zipfileset  src="lib/asm-tree-3.3.jar"/>
                <zipfileset  src="lib/asm-util-3.3.jar"/>
                <zipfileset  src="lib/guava-r06.jar"/>
                <zipfileset  src="lib/jarjar-1.0.jar"/>
        </jar>

    </target>


</project>