/*
* JCarder -- cards Java programs to keep threads disentangled
*
* Copyright (C) 2006-2007 Enea AB
* Copyright (C) 2007 Ulrik Svensson
* Copyright (C) 2007 Joel Rosdahl
*
* This program is made available under the GNU GPL version 2, with a special
* exception for linking with JUnit. See the accompanying file LICENSE.txt for
* details.
*
* 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.
*/
package com.enea.jcarder.agent.instrument;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.CheckClassAdapter;
import com.enea.jcarder.util.logging.Logger;
/**
* This class is responsible for all instrumentations and handles related issues
* with class loaders.
*
* TODO Add basic test for this class.
*/
public class ClassTransformer implements ClassFileTransformer {
private static final String ORIGINAL_CLASSES_DIRNAME =
"jcarder_original_classes";
private static final String INSTRUMENTED_CLASSES_DIRNAME =
"jcarder_instrumented_classes";
private final Logger mLogger;
private final ClassLoader mAgentClassLoader;
private final InstrumentConfig mInstrumentConfig;
private File mOriginalClassesDir;
private File mInstrumentedClassesDir;
public ClassTransformer(Logger logger,
File outputDirectory,
InstrumentConfig config) {
mLogger = logger;
mOriginalClassesDir =
new File(outputDirectory, ORIGINAL_CLASSES_DIRNAME);
mInstrumentedClassesDir =
new File(outputDirectory, INSTRUMENTED_CLASSES_DIRNAME);
mInstrumentConfig = config;
mAgentClassLoader = getClass().getClassLoader();
mLogger.fine("JCarder loaded with "
+ getClassLoaderName(mAgentClassLoader) + ".");
if (mAgentClassLoader == null) {
mLogger.info("Will instrument AWT and Swing classes");
} else {
mLogger.info("Not instrumenting standard library classes "
+ "(AWT, Swing, etc.)");
}
deleteDirRecursively(mInstrumentedClassesDir);
deleteDirRecursively(mOriginalClassesDir);
}
public byte[] transform(final ClassLoader classLoader,
final String jvmInternalClassName,
final Class<?> classBeingRedefined,
final ProtectionDomain protectionDomain,
final byte[] originalClassBuffer)
throws IllegalClassFormatException {
String className = jvmInternalClassName.replace('/', '.');
try {
return instrument(classLoader, originalClassBuffer, className);
} catch (Throwable t) {
mLogger.severe("Failed to transform the class "
+ className + ": " + t.getMessage());
dumpClassToFile(originalClassBuffer,
mOriginalClassesDir,
className);
return null;
}
}
private byte[] instrument(final ClassLoader classLoader,
final byte[] originalClassBuffer,
final String className) {
if (className.startsWith("com.enea.jcarder")
&& !className.startsWith("com.enea.jcarder.testclasses")) {
return null; // Don't instrument ourself.
}
final String reason = isInstrumentable(className);
if (reason != null) {
mLogger.finest(
"Won't instrument class " + className + ": " + reason);
return null;
}
if (!isCompatibleClassLoader(classLoader)) {
mLogger.finest("Can't instrument class " + className
+ " loaded with " + getClassLoaderName(classLoader));
return null;
}
final ClassReader reader = new ClassReader(originalClassBuffer);
final ClassWriter writer = new ClassWriter(true);
ClassVisitor visitor = writer;
if (mInstrumentConfig.getValidateTransfomedClasses()) {
visitor = new CheckClassAdapter(visitor);
}
visitor = new ClassAdapter(mLogger, visitor, className);
reader.accept(visitor, false);
byte[] instrumentedClassfileBuffer = writer.toByteArray();
if (mInstrumentConfig.getDumpClassFiles()) {
dumpClassToFile(originalClassBuffer,
mOriginalClassesDir,
className);
dumpClassToFile(instrumentedClassfileBuffer,
mInstrumentedClassesDir,
className);
}
return instrumentedClassfileBuffer;
}
/**
* Instrumented classes must use the same static members in the
* com.ena.jcarder.agent.StaticEventListener class as the Java agent and
* therefore they must be loaded with the same class loader as the agent was
* loaded with, or with a class loader that has the agent's class loader as
* a parent or ancestor.
*
* Note that the agentLoader may have been loaded with the bootstrap class
* loader (null) and then "null" is a compatible class loader.
*/
private boolean isCompatibleClassLoader(final ClassLoader classLoader) {
ClassLoader c = classLoader;
while (c != mAgentClassLoader) {
if (c == null) {
return false;
}
c = c.getParent();
}
return true;
}
private static String getClassLoaderName(final ClassLoader loader) {
if (loader == null) {
return "the bootstrap class loader";
} else if (loader == ClassLoader.getSystemClassLoader()) {
return "the system class loader";
} else {
return "the class loader \"" + loader + "\"";
}
}
/**
* Check whether we want to instrument a class.
*
* @param className Name of the class, including package.
* @return null if the class should be instrumented, otherwise a
* string containing the reason of why the class shouldn't be
* instrumented.
*/
private static String isInstrumentable(String className) {
// AWK and Swing classes are OK.
if (className.startsWith("java.awt.")
|| className.startsWith("javax.swing.")) {
return null;
}
// Other standard library classes are not.
if (className.startsWith("java.")
|| className.startsWith("javax.")
|| className.startsWith("sun.")) {
return "standard library class";
}
// All other classes should be instrumented.
return null;
}
private static boolean deleteDirRecursively(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success =
deleteDirRecursively(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
return dir.delete();
}
/**
* The dumped file can be decompiled with javap or with gnu.bytecode.dump.
* The latter also prints detailed information about the constant pool,
* something which javap does not.
*/
private void dumpClassToFile(byte[] content,
File baseDir,
String className) {
try {
String separator = System.getProperty("file.separator");
File file = new File(baseDir + separator
+ className.replace(".", separator)
+ ".class");
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content);
fos.close();
} catch (IOException e) {
mLogger.severe("Failed to dump class to file: " + e.getMessage());
}
}
}