« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today
XML

Blog::Navigation

GetJava Download Button
Get the Source
Personal Blog

Blog::Referers

Today's Page Hits: 1128

Powered by Roller Weblogger.
« Script Beans - part... | Main | Retrieving .class... »
20070330 Friday March 30, 2007

Retrieving .class files from a running app

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.


File: ClassDumperAgent.java

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: Steps to run 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

As usual, that is left as an exercise to the reader :-)



( Mar 30 2007, 03:54:52 PM IST ) Permalink Comments [3] del.icio.us | furl | simpy | slashdot | technorati | digg

Comments:

nice idea. Actually this could be helpful in Groovy to react to verify errors. The messages from the VM are not very nice to see the error in the bytecode.

Posted by Jochen "blackdrag" Theodorou on March 30, 2007 at 07:07 PM IST #

Nice! Can be the beginnings of a core dump equivalent for Java (does such a thing already exist ?) apps if we can also dump Java runtime state.

Posted by Moinak Ghosh on April 01, 2007 at 01:34 PM IST #

Hi Moinak Ghosh: There is a high level core dump debugger for JVM. See also: http://blogs.sun.com/sundararajan/entry/hotspot_source_serviceability_agent

Posted by A. Sundararajan on April 02, 2007 at 09:13 AM IST #

Post a Comment:

Comments are closed for this entry.
Copyright (C) 2005, A. Sundararajan's Weblog