Javassist is a Java library to generate, modify and inspect bytecode (i.e. the machine language of Java).
Let's assume that you want to know which call paths lead to a given method (e.g. foo()) in a given class (e.g. org.soft.micro.Narf).
// get the class pool ClassPool pool = ClassPool.getDefault(); // access the class without loading it just yet CtClass clazz = pool.get("org.soft.micro.Narf"); // get the method; you can find the signature (2nd parameter) like this: // fiji --javap -s org.soft.micro.Narf | grep -A1 foo CtMethod method = clazz.getMethod("foo", "()V"); // now let's output a whole lot of stuff whenever this method is entered method.insertBefore("new Exception(\"Here I am!\").printStackTrace();");
Of course, you are not limited to outputting a stack trace. You could also generate Strings of those traces and put them into a Map, possibly counting the occurrences.
Other useful actions include inspecting the parameters passed to the method which you can access by the special names $1, $2, ...
Further reading: Javassist's online tutorial.
Javassist has very useful tools for disassembly and introspection. For example, you could inspect the contents of a class using some code like this:
// get the class pool ClassPool pool = ClassPool.getDefault(); // access the class without loading it just yet CtClass clazz = pool.get("org.soft.micro.Narf"); // output the contents ClassFilePrinter.print(clazz.getClassFile());
Or you could disassemble a method like this:
// get the class pool ClassPool pool = ClassPool.getDefault(); // access the class without loading it just yet CtClass clazz = pool.get("org.soft.micro.Narf"); // get the method CtMethod method = clazz.getMethod("foo", "()V"); // disassemble the method InstructionPrinter.print(method, System.err);
If you absolutely do not want the output to go to System.err, you could substitute it by new PrintStream(new IJLogOutputStream()) (the class IJLogOutputStream is defined in Fiji Updater's fiji.updater.util class for you to reuse).
For security reasons, HotSpot does not allow any malformed bytecode to execute. To verify that the bytecode is reasonably sane, the code is verified before being executed.
Unfortunately, the error messages of said verifier are not exactly stellar.
If you want to avoid being misled by the error message (the method mentioned in the VerifyError is most likely to be not the offending one), and moreover want to know where in the bytecode the error happened, probably the easiest way to go forward is to use BCEL, Apache's Byte Code Engineering Library. For that, you have to write out .class files first:
CtClass clazz = ...; ...; File directory = ...; File file = new File(directory, clazz.getName().replace('.', '/') + ".class"); file.getParentFile().makeDirectories(); DataOutputStream out = new DataOutputStream(new FileOutputStream(file)); clazz.getClassFile().write(out); out.close();
Then you can use the verifier:
fiji --classpath=/path/to/bcel.jar:directory/ \ --main-class=org.apache.bcel.verifier.Verifier \ my.class.Name
(Side note: Apache calls the verifier JustIce, but the name is not reflected in the program name, only in its output.)
More on this issue can be found here.
Side note: the ASM component (which is included in JRuby, which in turn is included in Fiji) also has a verifier. You can start it with some command-line invocation similar to this:
fiji --classpath /path/to/classes \ --main-class jruby.objectweb.asm.util.CheckClassAdapter \ my.class.Name
If you are using Fiji's JavassistHelper class, you can use the verify() method which does nothing else than to hand off to the ASM component's verifier. Example:
import fiji.JavassistHelper; import javassist.ClassPool; ... ClassPool.getDefault().addClassPath("/path/to/my.jar"); JavassistHelper helper = new JavassistHelper(); // hack away helper.verify(hacker.get("the.class.in.question"), System.err); ...