mirror of https://github.com/mchr3k/org.intrace
- Improve comments in the agent package
- Cleanup code in agent package
This commit is contained in:
parent
2b4c9862c3
commit
e3cf53ac0f
|
|
@ -4,5 +4,6 @@
|
|||
<classpathentry kind="lib" path="lib/traceagent.jar" sourcepath="/org.intrace"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="C:/Program Files/Java/jdk1.6.0/lib/tools.jar"/>
|
||||
<classpathentry kind="lib" path="genbin"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package org.intrace.agent;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
import org.intrace.agent.server.AgentServer;
|
||||
|
|
@ -10,57 +8,66 @@ import org.intrace.output.callers.CallersOutput;
|
|||
import org.intrace.output.trace.TraceOutput;
|
||||
|
||||
/**
|
||||
* Trace Agent: Installs a Class Transformer to add trace lines.
|
||||
* InTrace Agent: Installs a Class Transformer to instrument class bytecode.
|
||||
*/
|
||||
public class Agent
|
||||
{
|
||||
/**
|
||||
* Agent called on JVM init.
|
||||
* Entry point when loaded using -agent command line arg.
|
||||
*
|
||||
* @param agentArgs
|
||||
* @param inst
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void premain(String agentArgs, Instrumentation inst) throws IOException
|
||||
public static void premain(String agentArgs, Instrumentation inst)
|
||||
{
|
||||
initialize(agentArgs, inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent called after JVM init.
|
||||
* Entry point when loaded into running JVM.
|
||||
*
|
||||
* @param agentArgs
|
||||
* @param inst
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void agentmain(String agentArgs, Instrumentation inst) throws IOException
|
||||
public static void agentmain(String agentArgs, Instrumentation inst)
|
||||
{
|
||||
initialize(agentArgs, inst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common init function.
|
||||
*
|
||||
* @param agentArgs
|
||||
* @param inst
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void initialize(String agentArgs, Instrumentation inst) throws IOException
|
||||
private static void initialize(String agentArgs, Instrumentation inst)
|
||||
{
|
||||
System.out.println("Loaded Tracing Agent.");
|
||||
System.out.println("## Loaded InTrace Agent.");
|
||||
|
||||
if (agentArgs == null) agentArgs = "";
|
||||
if (agentArgs == null)
|
||||
{
|
||||
agentArgs = "";
|
||||
}
|
||||
|
||||
// Setup the output handlers
|
||||
AgentHelper.outputHandlers.put(new TraceOutput(), new Object());
|
||||
AgentHelper.outputHandlers.put(new CallersOutput(), new Object());
|
||||
|
||||
// Parse startup args
|
||||
AgentSettings args = new AgentSettings(agentArgs);
|
||||
AgentHelper.getResponses(agentArgs);
|
||||
|
||||
// Construct Transformer
|
||||
ClassTransformer t = new ClassTransformer(inst, args);
|
||||
inst.addTransformer(t, true);
|
||||
t.traceLoadedClasses();
|
||||
|
||||
// Ensure loaded classes are traced
|
||||
t.instrumentLoadedClasses();
|
||||
|
||||
// Start Server thread
|
||||
Thread traceServer = new Thread(new AgentServer(t));
|
||||
traceServer.setName("TraceServer");
|
||||
traceServer.setDaemon(true);
|
||||
traceServer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import org.intrace.shared.AgentConfigConstants;
|
|||
public class AgentSettings
|
||||
{
|
||||
private Pattern classRegex = Pattern.compile(".*");
|
||||
private boolean tracingEnabled = false;
|
||||
private boolean instruEnabled = false;
|
||||
private boolean saveTracedClassfiles = false;
|
||||
private boolean verboseMode = false;
|
||||
private boolean allowJarsToBeTraced = false;
|
||||
|
|
@ -29,7 +29,7 @@ public class AgentSettings
|
|||
public AgentSettings(AgentSettings oldInstance)
|
||||
{
|
||||
classRegex = oldInstance.getClassRegex();
|
||||
tracingEnabled = oldInstance.isTracingEnabled();
|
||||
instruEnabled = oldInstance.isInstrumentationEnabled();
|
||||
saveTracedClassfiles = oldInstance.saveTracedClassfiles();
|
||||
verboseMode = oldInstance.isVerboseMode();
|
||||
allowJarsToBeTraced = oldInstance.allowJarsToBeTraced();
|
||||
|
|
@ -54,13 +54,13 @@ public class AgentSettings
|
|||
{
|
||||
verboseMode = false;
|
||||
}
|
||||
else if (arg.toLowerCase().equals(AgentConfigConstants.TRACING_ENABLED + "true"))
|
||||
else if (arg.toLowerCase().equals(AgentConfigConstants.INSTRU_ENABLED + "true"))
|
||||
{
|
||||
tracingEnabled = true;
|
||||
instruEnabled = true;
|
||||
}
|
||||
else if (arg.toLowerCase().equals(AgentConfigConstants.TRACING_ENABLED + "false"))
|
||||
else if (arg.toLowerCase().equals(AgentConfigConstants.INSTRU_ENABLED + "false"))
|
||||
{
|
||||
tracingEnabled = false;
|
||||
instruEnabled = false;
|
||||
}
|
||||
else if (arg.toLowerCase().equals(AgentConfigConstants.SAVE_TRACED_CLASSFILES + "true"))
|
||||
{
|
||||
|
|
@ -90,9 +90,9 @@ public class AgentSettings
|
|||
return classRegex;
|
||||
}
|
||||
|
||||
public boolean isTracingEnabled()
|
||||
public boolean isInstrumentationEnabled()
|
||||
{
|
||||
return tracingEnabled;
|
||||
return instruEnabled;
|
||||
}
|
||||
|
||||
public boolean saveTracedClassfiles()
|
||||
|
|
@ -115,7 +115,7 @@ public class AgentSettings
|
|||
{
|
||||
String currentSettings = "";
|
||||
currentSettings += "Class Regex : " + classRegex + "\n";
|
||||
currentSettings += "Tracing Enabled : " + tracingEnabled + "\n";
|
||||
currentSettings += "Tracing Enabled : " + instruEnabled + "\n";
|
||||
currentSettings += "Save Traced Class Files : " + saveTracedClassfiles + "\n";
|
||||
currentSettings += "Trace Classes in JAR Files : " + allowJarsToBeTraced + "\n";
|
||||
return currentSettings;
|
||||
|
|
@ -124,7 +124,7 @@ public class AgentSettings
|
|||
public Map<String,String> getSettingsMap()
|
||||
{
|
||||
Map<String,String> settingsMap = new HashMap<String, String>();
|
||||
settingsMap.put(AgentConfigConstants.TRACING_ENABLED, Boolean.toString(tracingEnabled));
|
||||
settingsMap.put(AgentConfigConstants.INSTRU_ENABLED, Boolean.toString(instruEnabled));
|
||||
settingsMap.put(AgentConfigConstants.CLASS_REGEX, classRegex.pattern());
|
||||
settingsMap.put(AgentConfigConstants.ALLOW_JARS_TO_BE_TRACED, Boolean.toString(allowJarsToBeTraced));
|
||||
settingsMap.put(AgentConfigConstants.VERBOSE_MODE, Boolean.toString(verboseMode));
|
||||
|
|
|
|||
|
|
@ -12,56 +12,104 @@ import org.objectweb.asm.MethodVisitor;
|
|||
import org.objectweb.asm.commons.EmptyVisitor;
|
||||
|
||||
/**
|
||||
* First pass analysis phase
|
||||
* InTrace uses ASM to instrument class files. However, ASM traverses bytecode
|
||||
* linearly which means some information is not available when we need it. This
|
||||
* class implements a first pass analysis phase to collect information which we
|
||||
* can't collect during the transformation phase.
|
||||
* <p>
|
||||
* This analysis collects two sets of data.
|
||||
* <ul>
|
||||
* <li>Reverse GOTO Lines
|
||||
* <li>Method Entry Line
|
||||
* </ul>
|
||||
* <h1>Reverse GOTO Lines</h1> This analysis records the target line number of
|
||||
* GOTOs that jump backwards in the code.
|
||||
*
|
||||
* <h2>Example:</h2>
|
||||
*
|
||||
* <pre>
|
||||
* 1. public void method()
|
||||
* 2. {
|
||||
* 3. do
|
||||
* 4. {
|
||||
* 5. // Branch C
|
||||
* 6. // Branch C
|
||||
* 7. }
|
||||
* 8. while (condition)
|
||||
* 9. }
|
||||
* </pre>
|
||||
*
|
||||
* We want to capture line 5 and line 9. Example 1 gets transformed into the
|
||||
* following bytecode:
|
||||
*
|
||||
* <pre>
|
||||
* 5. label1:
|
||||
* 5. // Branch C
|
||||
* 6. // Branch C
|
||||
* 8. if (condition) goto label1
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Line 5:</h3>
|
||||
* <ul>
|
||||
* <li>{@link ClassBranchLineAnalysis#visitLineNumber(int, Label)} is called and
|
||||
* we record a mapping from label1 to line 5.
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Line 8:</h3>
|
||||
* <ul>
|
||||
* <li>{@link ClassBranchLineAnalysis#visitJumpInsn(int, Label)} is called with
|
||||
* a GOTO instruction so we check whether we have already seen the target label.
|
||||
* In this case, we have so we mark the target line number as a reverse GOTO.
|
||||
* </ul>
|
||||
*
|
||||
* <h1>Method Entry Line</h1> This analysis records the first source line of a
|
||||
* method. This is necessary so that the transformation phase can add an entry
|
||||
* trace line in the call to {@link InstrumentedMethodWriter#visitCode()} and
|
||||
* know the source line.
|
||||
*/
|
||||
public class ClassBranchLineAnalysis extends EmptyVisitor
|
||||
{
|
||||
public final Map<String, Set<Integer>> methodBranchTraceLines = new HashMap<String, Set<Integer>>();
|
||||
public final Map<String, Integer> methodEntryLine = new HashMap<String, Integer>();
|
||||
private Set<Integer> branchTraceLines = new HashSet<Integer>();
|
||||
private final Map<Label,Integer> methodLabelLineNos = new HashMap<Label,Integer>();
|
||||
private String methodSig;
|
||||
private boolean traceThisLine = false;
|
||||
private boolean recordedMethodEntryLine = false;
|
||||
// Output of this analysis
|
||||
public final Map<String, Set<Integer>> methodReverseGOTOLines = new HashMap<String, Set<Integer>>();
|
||||
public final Map<String, Integer> methodEntryLines = new HashMap<String, Integer>();
|
||||
|
||||
// Intermediate fields
|
||||
private Set<Integer> currentMethod_reverseGOTOLines = new HashSet<Integer>();
|
||||
private final Map<Label, Integer> currentMethod_labelLineNos = new HashMap<Label, Integer>();
|
||||
private String currentMethod_sig;
|
||||
private boolean currentMethod_recordedEntryLine = false;
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
|
||||
public MethodVisitor visitMethod(int access, String name, String desc,
|
||||
String signature, String[] exceptions)
|
||||
{
|
||||
methodLabelLineNos.clear();
|
||||
methodSig = name + desc;
|
||||
recordedMethodEntryLine = false;
|
||||
currentMethod_labelLineNos.clear();
|
||||
currentMethod_sig = name + desc;
|
||||
currentMethod_recordedEntryLine = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLineNumber(int xiLineNo, Label xiLabel)
|
||||
{
|
||||
if (!recordedMethodEntryLine)
|
||||
if (!currentMethod_recordedEntryLine)
|
||||
{
|
||||
methodEntryLine.put(methodSig, xiLineNo);
|
||||
recordedMethodEntryLine = true;
|
||||
}
|
||||
methodLabelLineNos.put(xiLabel, xiLineNo);
|
||||
if (traceThisLine)
|
||||
{
|
||||
branchTraceLines.add(xiLineNo);
|
||||
traceThisLine = false;
|
||||
methodEntryLines.put(currentMethod_sig, xiLineNo);
|
||||
currentMethod_recordedEntryLine = true;
|
||||
}
|
||||
|
||||
currentMethod_labelLineNos.put(xiLabel, xiLineNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int xiOpCode, Label xiBranchLabel)
|
||||
{
|
||||
if (xiOpCode != GOTO)
|
||||
if (xiOpCode == GOTO)
|
||||
{
|
||||
traceThisLine= true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Integer lineNo = methodLabelLineNos.get(xiBranchLabel);
|
||||
Integer lineNo = currentMethod_labelLineNos.get(xiBranchLabel);
|
||||
if (lineNo != null)
|
||||
{
|
||||
branchTraceLines.add(lineNo);
|
||||
currentMethod_reverseGOTOLines.add(lineNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,11 +117,12 @@ public class ClassBranchLineAnalysis extends EmptyVisitor
|
|||
@Override
|
||||
public void visitEnd()
|
||||
{
|
||||
if (methodSig != null)
|
||||
if (currentMethod_sig != null)
|
||||
{
|
||||
methodBranchTraceLines.put(methodSig, branchTraceLines);
|
||||
methodSig = null;
|
||||
branchTraceLines = new HashSet<Integer>();
|
||||
methodReverseGOTOLines.put(currentMethod_sig,
|
||||
currentMethod_reverseGOTOLines);
|
||||
currentMethod_sig = null;
|
||||
currentMethod_reverseGOTOLines = new HashSet<Integer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,20 +18,28 @@ import org.intrace.output.AgentHelper;
|
|||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
/**
|
||||
* Uses ASM2 to transform class files to add Trace output.
|
||||
* Uses ASM2 to transform class files to add Trace instrumentation.
|
||||
*/
|
||||
public class ClassTransformer implements ClassFileTransformer
|
||||
{
|
||||
/**
|
||||
* Map of modified class names to their original bytes
|
||||
*/
|
||||
private final Set<String> modifiedClasses =
|
||||
new ConcurrentSkipListSet<String>();
|
||||
private final Set<String> modifiedClasses = new ConcurrentSkipListSet<String>();
|
||||
|
||||
/**
|
||||
* Instrumentation interface.
|
||||
*/
|
||||
private final Instrumentation inst;
|
||||
private final AgentSettings args;
|
||||
|
||||
/**
|
||||
* Settings for this Transformer
|
||||
*/
|
||||
private final AgentSettings settings;
|
||||
|
||||
/**
|
||||
* cTor
|
||||
*
|
||||
* @param xiInst
|
||||
* @param xiEnableTracing
|
||||
* @param xiClassRegex
|
||||
|
|
@ -42,81 +50,41 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
public ClassTransformer(Instrumentation xiInst, AgentSettings xiArgs)
|
||||
{
|
||||
inst = xiInst;
|
||||
args = xiArgs;
|
||||
if (args.isVerboseMode())
|
||||
settings = xiArgs;
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println(args.toString());
|
||||
System.out.println(settings.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle whether tracing is enabled
|
||||
* @param xiTracingEnabled
|
||||
* Generate and return instrumented class bytes.
|
||||
*
|
||||
* @param xiClassName
|
||||
* @param classfileBuffer
|
||||
* @return Instrumented class bytes
|
||||
*/
|
||||
public void setTracingEnabled(boolean xiOldTracingEnabled)
|
||||
private byte[] getInstrumentedClassBytes(String xiClassName,
|
||||
byte[] classfileBuffer)
|
||||
{
|
||||
if (args.isTracingEnabled() && !xiOldTracingEnabled)
|
||||
{
|
||||
traceLoadedClasses();
|
||||
}
|
||||
else if (!args.isTracingEnabled() && xiOldTracingEnabled)
|
||||
{
|
||||
recheckModifiedClasses();
|
||||
}
|
||||
ClassReader cr = new ClassReader(classfileBuffer);
|
||||
ClassBranchLineAnalysis analysis = new ClassBranchLineAnalysis();
|
||||
cr.accept(analysis, false);
|
||||
InstrumentedClassWriter writer = new InstrumentedClassWriter(xiClassName,
|
||||
cr, analysis);
|
||||
cr.accept(writer, false);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Trace transformation to loaded classes.
|
||||
* Retransform all modified classes.
|
||||
* <p>
|
||||
* Iterates over all loaded classes and retransforms those which we know we
|
||||
* have modified.
|
||||
*
|
||||
* @param xiInst
|
||||
*/
|
||||
public void traceLoadedClasses()
|
||||
{
|
||||
Class<?>[] loadedClasses = inst.getAllLoadedClasses();
|
||||
for (Class<?> loadedClass : loadedClasses)
|
||||
{
|
||||
if (loadedClass.isAnnotation())
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring annotation class: " + loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (loadedClass.isSynthetic())
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring synthetic class: " + loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (!inst.isModifiableClass(loadedClass))
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring unmodifiable class: " + loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (args.isTracingEnabled() &&
|
||||
isToBeConsideredForCoverage(loadedClass.getName(), loadedClass.getProtectionDomain()))
|
||||
{
|
||||
try
|
||||
{
|
||||
inst.retransformClasses(loadedClass);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
// Write exception to stdout
|
||||
System.out.println(loadedClass.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore original class bytes
|
||||
* @param xiInst
|
||||
*/
|
||||
private void recheckModifiedClasses()
|
||||
private void retransformModifiedClasses()
|
||||
{
|
||||
Class<?>[] loadedClasses = inst.getAllLoadedClasses();
|
||||
for (Class<?> loadedClass : loadedClasses)
|
||||
|
|
@ -136,16 +104,40 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isToBeConsideredForCoverage(String className,
|
||||
ProtectionDomain protectionDomain)
|
||||
/**
|
||||
* Determine whether a given className is eligible for modification. Any of
|
||||
* the following conditions will make a class ineligible for instrumentation.
|
||||
* <ul>
|
||||
* <li>Class name which begins with "org.intrace"
|
||||
* <li>Class name which begins with "org.objectweb.asm"
|
||||
* <li>The class has already been modified
|
||||
* <li>Class name ends with "Test"
|
||||
* <li>Class name doesn't match the regex
|
||||
* <li>Class is in a JAR and JAR instrumention is disabled
|
||||
* </ul>
|
||||
*
|
||||
* @param className
|
||||
* @param protectionDomain
|
||||
* @return True if the Class with name className should be instrumented.
|
||||
*/
|
||||
private boolean isToBeConsideredForInstrumentation(
|
||||
String className,
|
||||
ProtectionDomain protectionDomain)
|
||||
{
|
||||
// Don't modify self
|
||||
if (className.startsWith("org.intrace") ||
|
||||
className.startsWith("org.objectweb.asm"))
|
||||
// Don't modify anything if tracing is disabled
|
||||
if (!settings.isInstrumentationEnabled())
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't modify self
|
||||
if (className.startsWith("org.intrace")
|
||||
|| className.startsWith("org.objectweb.asm"))
|
||||
{
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring class in gb.instrument package: " + className);
|
||||
System.out.println("Ignoring class in gb.instrument package: "
|
||||
+ className);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -153,7 +145,7 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
// Don't modify a class which is already modified
|
||||
if (modifiedClasses.contains(className))
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring class already modified: " + className);
|
||||
}
|
||||
|
|
@ -165,7 +157,7 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
if (className.endsWith("Test") || p > 0
|
||||
&& className.substring(0, p).endsWith("Test"))
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring class name ending in Test: " + className);
|
||||
}
|
||||
|
|
@ -173,54 +165,63 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
}
|
||||
|
||||
// Don't modify classes which fail to match the regex
|
||||
if ((args.getClassRegex() == null) ||
|
||||
!args.getClassRegex().matcher(className).matches())
|
||||
if ((settings.getClassRegex() == null)
|
||||
|| !settings.getClassRegex().matcher(className).matches())
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring class not matching the active regex: " + className);
|
||||
System.out.println("Ignoring class not matching the active regex: "
|
||||
+ className);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't modify a class from a JAR file unless this is allowed
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
if (!args.allowJarsToBeTraced() &&
|
||||
(codeSource != null) &&
|
||||
codeSource.getLocation().getPath().endsWith(".jar"))
|
||||
if (!settings.allowJarsToBeTraced() && (codeSource != null)
|
||||
&& codeSource.getLocation().getPath().endsWith(".jar"))
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring class in a JAR: " + className);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// All checks passed - class can be instrumented
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* java.lang.instrument Entry Point
|
||||
* <p>
|
||||
* Optionally transform a class file to add instrumentation.
|
||||
* {@link ClassTransformer#isToBeConsideredForInstrumentation(String, ProtectionDomain)}
|
||||
* determines whether a class is eligible for instrumentation.
|
||||
*/
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader,
|
||||
String internalClassName,
|
||||
public byte[] transform(ClassLoader loader, String internalClassName,
|
||||
Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain,
|
||||
byte[] originalClassfile)
|
||||
throws IllegalClassFormatException
|
||||
throws IllegalClassFormatException
|
||||
{
|
||||
String className = internalClassName.replace('/', '.');
|
||||
if (args.isTracingEnabled() &&
|
||||
isToBeConsideredForCoverage(className, protectionDomain))
|
||||
if (isToBeConsideredForInstrumentation(className, protectionDomain))
|
||||
{
|
||||
if (args.isVerboseMode())
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("!! Instrumenting class: " + className);
|
||||
}
|
||||
byte[] newBytes = readAndModifyClassForTracing(className, originalClassfile);
|
||||
|
||||
if (args.saveTracedClassfiles())
|
||||
byte[] newBytes = getInstrumentedClassBytes(className, originalClassfile);
|
||||
|
||||
if (settings.saveTracedClassfiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
File classOut = new File("./genbin/" + internalClassName + "_gen.class");
|
||||
File classOut = new File("./genbin/" + internalClassName
|
||||
+ "_gen.class");
|
||||
File parentDir = classOut.getParentFile();
|
||||
boolean dirExists = parentDir.exists();
|
||||
if (!dirExists)
|
||||
|
|
@ -236,8 +237,8 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Can't create directory " + parentDir +
|
||||
" for saving traced classfiles.");
|
||||
System.out.println("Can't create directory " + parentDir
|
||||
+ " for saving traced classfiles.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -256,58 +257,134 @@ public class ClassTransformer implements ClassFileTransformer
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] readAndModifyClassForTracing(String xiClassName,
|
||||
byte[] classfileBuffer)
|
||||
/**
|
||||
* Consider loaded classes for transformation. Any of the following reasons
|
||||
* would prevent a loaded class from being eligible for instrumentation.
|
||||
* <ul>
|
||||
* <li>Class is an annotation
|
||||
* <li>Class is synthetic
|
||||
* <li>Class is not modifiable
|
||||
* <li>Class is rejected by
|
||||
* {@link ClassTransformer#isToBeConsideredForInstrumentation(String, ProtectionDomain)}
|
||||
* </ul>
|
||||
*/
|
||||
public void instrumentLoadedClasses()
|
||||
{
|
||||
ClassReader cr = new ClassReader(classfileBuffer);
|
||||
ClassBranchLineAnalysis analysis = new ClassBranchLineAnalysis();
|
||||
cr.accept(analysis, false);
|
||||
InstrumentedClassWriter writer = new InstrumentedClassWriter(xiClassName,
|
||||
cr,
|
||||
analysis);
|
||||
cr.accept(writer, false);
|
||||
return writer.toByteArray();
|
||||
Class<?>[] loadedClasses = inst.getAllLoadedClasses();
|
||||
for (Class<?> loadedClass : loadedClasses)
|
||||
{
|
||||
if (loadedClass.isAnnotation())
|
||||
{
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring annotation class: "
|
||||
+ loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (loadedClass.isSynthetic())
|
||||
{
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring synthetic class: "
|
||||
+ loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (!inst.isModifiableClass(loadedClass))
|
||||
{
|
||||
if (settings.isVerboseMode())
|
||||
{
|
||||
System.out.println("Ignoring unmodifiable class: "
|
||||
+ loadedClass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
else if (isToBeConsideredForInstrumentation(
|
||||
loadedClass.getName(),
|
||||
loadedClass
|
||||
.getProtectionDomain()))
|
||||
{
|
||||
try
|
||||
{
|
||||
inst.retransformClasses(loadedClass);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
// Write exception to stdout
|
||||
System.out.println(loadedClass.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle whether instrumentation is enabled
|
||||
*
|
||||
* @param xiTracingEnabled
|
||||
*/
|
||||
public void setInstrumentationEnabled(boolean xiInstrumentationEnabled)
|
||||
{
|
||||
if (xiInstrumentationEnabled)
|
||||
{
|
||||
instrumentLoadedClasses();
|
||||
}
|
||||
else if (!xiInstrumentationEnabled)
|
||||
{
|
||||
retransformModifiedClasses();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The currently active settings.
|
||||
*/
|
||||
public Map<String, String> getSettings()
|
||||
{
|
||||
return settings.getSettingsMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a message and return a response.
|
||||
*
|
||||
* @param message
|
||||
* @return Response or null if there is no response.
|
||||
*/
|
||||
public List<String> getResponse(String message)
|
||||
{
|
||||
AgentSettings oldSettings = new AgentSettings(args);
|
||||
args.parseArgs(message);
|
||||
AgentSettings oldSettings = new AgentSettings(settings);
|
||||
settings.parseArgs(message);
|
||||
|
||||
if (args.isVerboseMode() &&
|
||||
(oldSettings.isVerboseMode() != args.isVerboseMode()))
|
||||
if (settings.isVerboseMode()
|
||||
&& (oldSettings.isVerboseMode() != settings.isVerboseMode()))
|
||||
{
|
||||
System.out.println(args.toString());
|
||||
System.out.println(settings.toString());
|
||||
}
|
||||
else if (oldSettings.isTracingEnabled() != args.isTracingEnabled())
|
||||
else if (oldSettings.isInstrumentationEnabled() != settings
|
||||
.isInstrumentationEnabled())
|
||||
{
|
||||
System.out.println("## Settings Changed");
|
||||
setTracingEnabled(oldSettings.isTracingEnabled());
|
||||
setInstrumentationEnabled(settings.isInstrumentationEnabled());
|
||||
}
|
||||
else if (!oldSettings.getClassRegex().pattern().equals(args.getClassRegex().pattern()))
|
||||
else if (!oldSettings.getClassRegex().pattern()
|
||||
.equals(settings.getClassRegex().pattern()))
|
||||
{
|
||||
System.out.println("## Settings Changed");
|
||||
recheckModifiedClasses();
|
||||
traceLoadedClasses();
|
||||
retransformModifiedClasses();
|
||||
instrumentLoadedClasses();
|
||||
}
|
||||
else if (oldSettings.allowJarsToBeTraced() != args.allowJarsToBeTraced())
|
||||
else if (oldSettings.allowJarsToBeTraced() != settings
|
||||
.allowJarsToBeTraced())
|
||||
{
|
||||
System.out.println("## Settings Changed");
|
||||
recheckModifiedClasses();
|
||||
traceLoadedClasses();
|
||||
retransformModifiedClasses();
|
||||
instrumentLoadedClasses();
|
||||
}
|
||||
else if (oldSettings.saveTracedClassfiles() != args.saveTracedClassfiles())
|
||||
else if (oldSettings.saveTracedClassfiles() != settings
|
||||
.saveTracedClassfiles())
|
||||
{
|
||||
System.out.println("## Settings Changed");
|
||||
recheckModifiedClasses();
|
||||
traceLoadedClasses();
|
||||
retransformModifiedClasses();
|
||||
instrumentLoadedClasses();
|
||||
}
|
||||
|
||||
return AgentHelper.getResponses(message);
|
||||
}
|
||||
|
||||
public Map<String, String> getSettings()
|
||||
{
|
||||
return args.getSettingsMap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ public class InstrumentedClassWriter extends ClassWriter
|
|||
{
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
|
||||
exceptions);
|
||||
Set<Integer> branchTraceLines = analysis.methodBranchTraceLines.get(name + desc);
|
||||
Integer entryLine = analysis.methodEntryLine.get(name + desc);
|
||||
Set<Integer> branchTraceLines = analysis.methodReverseGOTOLines.get(name + desc);
|
||||
Integer entryLine = analysis.methodEntryLines.get(name + desc);
|
||||
if (branchTraceLines == null)
|
||||
{
|
||||
branchTraceLines = new HashSet<Integer>();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import org.objectweb.asm.Opcodes;
|
|||
import org.objectweb.asm.Type;
|
||||
|
||||
/**
|
||||
* ASM2 MethodVisitor used to add trace to methods.
|
||||
* ASM2 MethodVisitor used to instrument methods.
|
||||
*/
|
||||
public class InstrumentedMethodWriter extends MethodAdapter
|
||||
{
|
||||
|
|
@ -29,6 +29,7 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
|
||||
/**
|
||||
* cTor
|
||||
*
|
||||
* @param xiMethodVisitor
|
||||
* @param xiClassName
|
||||
* @param xiMethodName
|
||||
|
|
@ -37,11 +38,8 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
* @param entryLine
|
||||
*/
|
||||
public InstrumentedMethodWriter(MethodVisitor xiMethodVisitor,
|
||||
String xiClassName,
|
||||
String xiMethodName,
|
||||
String xiDesc,
|
||||
Set<Integer> xiBranchTraceLines,
|
||||
Integer xiEntryLine)
|
||||
String xiClassName, String xiMethodName, String xiDesc,
|
||||
Set<Integer> xiBranchTraceLines, Integer xiEntryLine)
|
||||
{
|
||||
super(xiMethodVisitor);
|
||||
className = xiClassName;
|
||||
|
|
@ -54,12 +52,13 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
@Override
|
||||
public void visitCode()
|
||||
{
|
||||
generateCallToWriteBranchTrace(TraceType.BEGIN,((entryLine != null ? entryLine : -1)));
|
||||
traceArgs();
|
||||
generateCallToWriteTrace(TraceType.BEGIN, ((entryLine != null ? entryLine
|
||||
: -1)));
|
||||
traceMethodArgs();
|
||||
super.visitCode();
|
||||
}
|
||||
|
||||
private void traceArgs()
|
||||
private void traceMethodArgs()
|
||||
{
|
||||
Type[] argTypes = Type.getArgumentTypes(methodDescriptor);
|
||||
for (int ii = 0; ii < argTypes.length; ii++)
|
||||
|
|
@ -70,8 +69,8 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
{
|
||||
typeDescriptor = "Ljava/lang/Object;";
|
||||
}
|
||||
else if ((argTypes[ii].getSort() == Type.ARRAY) &&
|
||||
(argTypes[ii].getDescriptor().startsWith("[L")))
|
||||
else if ((argTypes[ii].getSort() == Type.ARRAY)
|
||||
&& (argTypes[ii].getDescriptor().startsWith("[L")))
|
||||
{
|
||||
typeDescriptor = "[Ljava/lang/Object;";
|
||||
}
|
||||
|
|
@ -79,10 +78,9 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
mv.visitLdcInsn(className);
|
||||
mv.visitLdcInsn(methodName);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, ii);
|
||||
mv.visitMethodInsn(INVOKESTATIC,
|
||||
HELPER_CLASS,
|
||||
"arg",
|
||||
"(Ljava/lang/String;Ljava/lang/String;" + typeDescriptor + ")V");
|
||||
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "arg",
|
||||
"(Ljava/lang/String;Ljava/lang/String;"
|
||||
+ typeDescriptor + ")V");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,14 +88,10 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
public void visitLineNumber(int xiLineNumber, Label label)
|
||||
{
|
||||
lineNumber = xiLineNumber;
|
||||
if (writeTraceLine ||
|
||||
branchTraceLines.contains(xiLineNumber))
|
||||
if (writeTraceLine || branchTraceLines.contains(xiLineNumber))
|
||||
{
|
||||
generateCallToWriteBranchTrace(TraceType.BRANCH, lineNumber);
|
||||
if (writeTraceLine)
|
||||
{
|
||||
writeTraceLine = false;
|
||||
}
|
||||
generateCallToWriteTrace(TraceType.BRANCH, lineNumber);
|
||||
writeTraceLine = false;
|
||||
}
|
||||
super.visitLineNumber(xiLineNumber, label);
|
||||
}
|
||||
|
|
@ -107,7 +101,7 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
{
|
||||
if (xiOpCode == RETURN)
|
||||
{
|
||||
generateCallToWriteBranchTrace(TraceType.END, lineNumber);
|
||||
generateCallToWriteTrace(TraceType.END, lineNumber);
|
||||
}
|
||||
super.visitInsn(xiOpCode);
|
||||
}
|
||||
|
|
@ -119,53 +113,38 @@ public class InstrumentedMethodWriter extends MethodAdapter
|
|||
super.visitJumpInsn(xiOpCode, xiBranchLabel);
|
||||
}
|
||||
|
||||
private void generateCallToWriteBranchTrace(TraceType traceType,
|
||||
int lineNumber)
|
||||
private void generateCallToWriteTrace(TraceType traceType, int lineNumber)
|
||||
{
|
||||
mv.visitLdcInsn(className);
|
||||
mv.visitLdcInsn(methodName);
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, lineNumber);
|
||||
switch (traceType)
|
||||
{
|
||||
case BEGIN:
|
||||
{
|
||||
mv.visitLdcInsn(className);
|
||||
mv.visitLdcInsn(methodName);
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, lineNumber);
|
||||
mv.visitMethodInsn(INVOKESTATIC,
|
||||
HELPER_CLASS,
|
||||
"enter",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "enter",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case BRANCH:
|
||||
{
|
||||
mv.visitLdcInsn(className);
|
||||
mv.visitLdcInsn(methodName);
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, lineNumber);
|
||||
mv.visitMethodInsn(INVOKESTATIC,
|
||||
HELPER_CLASS,
|
||||
"branch",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "branch",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case END:
|
||||
{
|
||||
mv.visitLdcInsn(className);
|
||||
mv.visitLdcInsn(methodName);
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, lineNumber);
|
||||
mv.visitMethodInsn(INVOKESTATIC,
|
||||
HELPER_CLASS,
|
||||
"exit",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "exit",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private enum TraceType
|
||||
{
|
||||
BEGIN,
|
||||
BRANCH,
|
||||
END
|
||||
BEGIN, BRANCH, END
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ParsedSettingsData
|
|||
|
||||
classRegex = settingsMap.get(AgentConfigConstants.CLASS_REGEX);
|
||||
|
||||
if ("true".equals(settingsMap.get(AgentConfigConstants.TRACING_ENABLED)))
|
||||
if ("true".equals(settingsMap.get(AgentConfigConstants.INSTRU_ENABLED)))
|
||||
{
|
||||
instrEnabled = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ import java.util.Set;
|
|||
|
||||
public class AgentConfigConstants
|
||||
{
|
||||
public static final String CLASS_REGEX = "[regex-";
|
||||
public static final String TRACING_ENABLED = "[instru-";
|
||||
public static final String SAVE_TRACED_CLASSFILES = "[saveinstru-";
|
||||
public static final String VERBOSE_MODE = "[verbose-";
|
||||
public static final String CLASS_REGEX = "[regex-";
|
||||
public static final String INSTRU_ENABLED = "[instru-";
|
||||
public static final String SAVE_TRACED_CLASSFILES = "[saveinstru-";
|
||||
public static final String VERBOSE_MODE = "[verbose-";
|
||||
public static final String ALLOW_JARS_TO_BE_TRACED = "[instrujars-";
|
||||
public static final Set<String> COMMANDS = new HashSet<String>();
|
||||
static
|
||||
{
|
||||
COMMANDS.add(CLASS_REGEX + "<regex>");
|
||||
COMMANDS.add(TRACING_ENABLED + "<true/false>");
|
||||
COMMANDS.add(INSTRU_ENABLED + "<true/false>");
|
||||
COMMANDS.add(SAVE_TRACED_CLASSFILES + "<true/false>");
|
||||
COMMANDS.add(VERBOSE_MODE + "<true/false>");
|
||||
COMMANDS.add(ALLOW_JARS_TO_BE_TRACED + "<true/false>");
|
||||
|
|
|
|||
Loading…
Reference in New Issue