Many Java applications generate .class files on-the-fly at runtime. Some applications modify loaded Java classes either at class load time or even later using hotswap. Few examples:
It becomes difficult to debug such applications - for example, you may get verifier error because of a bug in the .class generator somewhere. So, sometimes it is better to dump .class files of generated/modified classes for off-line debugging - for example, we may want to view such classes using tools like jclasslib. Instead of modifying each and every component that generates/modifies .class files, we would want to have a generic solution. The solution below uses attach-on-demand facility in JDK 6.
import java.lang.instrument.*;
import java.io.File;
import java.io.FileOutputStream;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
/**
* This is a java.lang.instrument agent to dump .class files
* from a running Java application.
*/
public class ClassDumperAgent implements ClassFileTransformer {
// directory where we would write .class files
private static String dumpDir;
// classes with name matching this pattern
// will be dumped
private static Pattern classes;
public static void premain(String agentArgs, Instrumentation inst) {
agentmain(agentArgs, inst);
}
public static void agentmain(String agentArgs, Instrumentation inst) {
parseArgs(agentArgs);
inst.addTransformer(new ClassDumperAgent(), true);
// by the time we are attached, the classes to be
// dumped may have been loaded already. So, check
// for candidates in the loaded classes.
Class[] classes = inst.getAllLoadedClasses();
List<Class> candidates = new ArrayList<Class>();
for (Class c : classes) {
if (isCandidate(c.getName())) {
candidates.add(c);
}
}
try {
// if we have matching candidates, then
// retransform those classes so that we
// will get callback to transform.
if (! candidates.isEmpty()) {
inst.retransformClasses(candidates.toArray(new Class[0]));
}
} catch (UnmodifiableClassException uce) {
}
}
public byte[] transform(ClassLoader loader, String className,
Class redefinedClass, ProtectionDomain protDomain,
byte[] classBytes) {
// check and dump .class file
if (isCandidate(className)) {
dumpClass(className, classBytes);
}
// we don't mess with .class file, just
// return null
return null;
}
private static boolean isCandidate(String className) {
// ignore array classes
if (className.charAt(0) == '[') {
return false;
}
// convert the class name to external name
className = className.replace('/', '.');
// check for name pattern match
return classes.matcher(className).matches();
}
private static void dumpClass(String className, byte[] classBuf) {
try {
// create package directories if needed
className = className.replace("/", File.separator);
StringBuilder buf = new StringBuilder();
buf.append(dumpDir);
buf.append(File.separatorChar);
int index = className.lastIndexOf(File.separatorChar);
if (index != -1) {
buf.append(className.substring(0, index));
}
String dir = buf.toString();
new File(dir).mkdirs();
// write .class file
String fileName = dumpDir +
File.separator + className + ".class";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(classBuf);
fos.close();
} catch (Exception exp) {
exp.printStackTrace();
}
}
// parse agent args of the form arg1=value1,arg2=value2
private static void parseArgs(String agentArgs) {
if (agentArgs != null) {
String[] args = agentArgs.split(",");
for (String arg: args) {
String[] tmp = arg.split("=");
if (tmp.length == 2) {
String name = tmp[0];
String value = tmp[1];
if (name.equals("dumpDir")) {
dumpDir = value;
} else if (name.equals("classes")) {
classes = Pattern.compile(value);
}
}
}
}
if (dumpDir == null) {
dumpDir = ".";
}
if (classes == null) {
classes = Pattern.compile(".*");
}
}
}
File: manifest.mf
Premain-Class: ClassDumperAgent
Agent-Class: ClassDumperAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
File: Attacher.java
import com.sun.tools.attach.*;
/**
* Simple attach-on-demand client tool that
* loads the given agent into the given Java process.
*/
public class Attacher {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("usage: java Attach <pid> <agent-jar-full-path> [<agent-args>]");
System.exit(1);
}
// JVM is identified by process id (pid).
VirtualMachine vm = VirtualMachine.attach(args[0]);
String agentArgs = (args.length > 2)? args[2] : null;
// load a specified agent onto the JVM
vm.loadAgent(args[1], agentArgs);
}
}
Steps to build class dumper:
The above command will dump all classes matching the given name (regex) pattern into the given directory. The default dump directory is the current working directory (of the target application!). The default pattern is ".*" i.e., match all classes loaded/will be loaded in the target application.
Now, how about a nice GUI that
Posted by Jochen "blackdrag" Theodorou on March 30, 2007 at 07:07 PM IST #
Posted by Moinak Ghosh on April 01, 2007 at 01:34 PM IST #
Posted by A. Sundararajan on April 02, 2007 at 09:13 AM IST #