Javaagent

How to write a Simple Agent

Posted on Updated on

After two long introduction chapters, finally we are writing our simple java agent. Main target of our agent is to add an extra print statement to one of the methods in our main class (SimpleMain). In order to do that, we need to create following three files, which are the content of our agent.

  • SimpleAgent.class
  • SimpleClassFileTransformer.class
  • MANIFEST.MF

We can generate these class files once we compile our .java files. But, before we start writing, check whether you have got maven installed with you, because we are going to need it when we generate the manifest file. Otherwise, generating the manifest file going to be a real pain when you start improving your agent. Therefore, better get use to it from the first lesson itself.

So, assuming you have already created a maven project let’s add dependency of Javassist to our pom.xml file. If you don’t include it, you won’t be able to use the methods of Javassist. (hope those experienced viewers won’t get annoyed with these details, because um trying to provide answers to all the questions i came across when i was writing my simple agent) You can obtain the dependency and jar from here based on your preference.

pom.xml 

<dependency>
 <groupId>org.javassist</groupId>
 <artifactId>javassist</artifactId>
 <version>3.20.0-GA</version>
</dependency>

SimpleClassFileTransformer.java

Next, lets write our SimpleClassFileTransformer class. This is where we include all the operations we need to do during the instrumentation process.

 
package com.testProject.javaagent;

import javassist.*;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class SimpleClassFileTransformer implements 
ClassFileTransformer {
 public byte[] transform(ClassLoader loader, 
 String className, Class classBeingRedefined,
 ProtectionDomain protectionDomain, byte[] classfileBuffer)
 throws IllegalClassFormatException {

   byte[] byteCode = classfileBuffer;
   if(className.equals("com/testMain/SimpleMain")){
      ClassPool classPool = ClassPool.getDefault(); 
      try { 
         CtClass ctClass = classPool.makeClass(
                new ByteArrayInputStream(classfileBuffer)); 
         CtMethod[] methods = ctClass.getDeclaredMethods(); 
         for (CtMethod method : methods) {  
            if (method.getName().equals("print")) {
                System.out.println("Instrumenting method : " + method.getName());
                method.insertAt(1,
                  "System.out.println(\"This is the injected line\");"); 
            } 
         } 
         byteCode = ctClass.toBytecode(); 
         ctClass.detach(); 
       } catch (Throwable e) { 
            e.printStackTrace(); 
       }
   } 
 return byteCode;
 }
}

This class implements the ClassFileTransformer interface and override the transform method which can read and write class files as byte arrays. Parameters available in this method can be used in different situations based on our requirement. ClassfileBuffer contains the content of the currently instrumenting class as a byte array. When this method is called, it will obtain the current byte array and return a byte array including all the modifications done using the byte code instrumentation library. In this example, we try to obtain the class which match the fully qualified class name of our main class (com/testMain/SimpleMain) and instrument the print(). insertAt method of javassist allows us to insert our print statement to the specified line of the method. (Let’s explore other main methods of javassist later) Once the instrumentation is complete, it will recompile and verify whether insertions are according to the standards and load the classes to JVM.

SimpleAgent.java

This is where we create a transformer object and instruct to attach instruction to class (using addTransformer()). As it is mentioned earlier, invocation method of the agent is decided based on the main method used. In our example, we will be using the premain method which pass instrumentation object and other agent parameters and invoke agent before classes are load to VM.

 
package com.testProject.javaagent;

import java.lang.instrument.Instrumentation;

public class SimpleAgent {

 public static void premain(String agentArgs, 
                   Instrumentation instrumentation){

 System.out.println("Starting Agent");
 SimpleClassFileTransformer transformer = 
                  new SimpleClassFileTransformer();
 instrumentation.addTransformer(transformer);
 }
}

MANIFEST.MF

As it is mentioned in previous post, there are few attributes that need to include in the manifest file. But, for our simple agent, we will need a simple manifest file as follows. (I’ll post the code segment needed for the generation of manifest as a separate post). Class path is provided assuming all the jars needed by the agent are included in the ‘lib’ folder

Manifest-Version: 1.0
Premain-Class: org.wso2.javaagent.JDBCAgent
Class-Path: lib/javassist-3.20-GA.jar

These are the main files of our agent. Next step would be bundling of the .class files into our Agent.jar. This can also be automated using small piece of code added to our pom file. You can generate the jar using the command line as well. But i won’t go into detail about it, because it was bit annoying. But if you dig around a bit you would be able to figure it out. But, if you can wait a little, I am sure you’ll be happy with the automated way. Anyhow, assuming we have bundled our files into our Agent.jar let’s write the main class. Because, we need a main class to instrument with our agent.

SimpleMain.java

 
package com.testMain;

public class SimpleMain {
 public static void main(String[] args){
 SimpleMain sm = new SimpleMain();
 System.out.println("Simple Main");
 sm.print();
 }
 
 public void print(){
 System.out.println("Print method of Simple Main class");
 }
}

That’s it, we have everything to test our agent. Let’s run our agent using the command line. First compile the SimpleMain class. Then run the following command after going to the location of the SimpleMain.class.

> java -javaagent:path/to/agent/Agent.jar com.testMain.SimpleMain

There is a special keyword of java called as ‘-javaagent’ which is used to pass our java agent as a parameter to JVM. Value given after jar is the fully qualified name of our main class. You can also provide the class path of the main class using ‘-cp’ attribute of java.

> java -cp path/to/main -javaagent:path/to/agent/Agent.jar com.testMain.SimpleMain

Once we run our agent using above command our output will be as follows,

Starting Agent
Instrumenting method : print
Simple Main
This is the injected line
Print method of Simple Main class

Well, now you know how to write a simple java agent. Adding more features can be done modifying the the transform method. Next post will be on ‘How to generate a MANIFEST using Maven’. Future, ill get back with another simple example which uses more features of javassist.

Hope, you guys enjoy writing your first agent. Have a nice time…!!!

Requirements of a Javaagent

Posted on Updated on

Second post is to introduce main parts of a java agent. Before we jump in an write a java agent, lets see what are the main files needed by an java agent. First of all, you should notice that all the files required by the agent need to be bundled up in a JAR file. There are many ways to prepare our agent.jar. But let’s keep the bundling part aside, till we finish writing a simple agent, and move to files needed.

agent-jar-files
Main content of a Agent.jar

Unlike normal program with just a main class and bunch of other classes, agent require three main files as described below.

SimpleAgent.class

This is the class file which contain our agent. It’ll include a premain method or agentmain method according to the requirement of the writer. There may be either one method type or both in a single class file. But the selection of method used to invoke the agent will be decided based on the time user want to invoke the agent. As it is mentioned in earlier post,

  • premain() – invoke agent before VM start
  • agentmain() – invoke agent after VM is staterd

This class will be used to pass the instrumentation object along with any parameters needed by the agent and add a transformer to classes which we need to instrument.

SimpleClassFileTransformer.class

This is where provide the instructions on all the operations we need to do on our classes. This class is written by implementing the ‘ClassFileTransformer’ interface. The transform method will carryout redefinition / re-transformation of classes as specified by the user. But the layout of the instruction given here would depend based on the bytecode instrumentation framework used. As mentioned earlier, we’ll be using Javassist through out the tutorial.

MANIFEST.MF

Other most important file is the manifest file, which provide all the parameters needed to initialize the agent. This is a special type of file, which provide information on files packaged in a JAR. (For those who has worked with jars this may not be a stranger. But for me, it was totally new thing, because it was the first time i wrote one) When we create a jar, it will add a default manifest file, with set of default values as follows including environment details as follows.

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)

But, in a java agent, we need to include few more attributes as given below. Except for those mentioned as optional, we will not be able to start the agent, because it will throw some kind of an error and abort the agent.

  • Premain-class – Used when agent is invoked at JVM launch time. It need to include the fully qualified name of the agent class. In our case also, we’ll be defining this attribute, because we are working with premain method. This will provide an entry point to our agent by specifying where to look for Agent.class which need to be invoked before the main.class
  • Agent-class – Used when we are invoking the agent after VM has started. This is used when we are using agentmain method in our Agent.class.
  • Can-redefine-class – (optional) Specify as true or false, where default value is false. If it is not specified as true, it won’t allow to carryout re-transformation of classes.
  • Can-redefine-class – (optional) Specify as true or false. Won’t allow to redefine classes during the instrumentation process, if it is not specified as true.
  • Class-Path – We need to add paths of all the jars needed by the agent during run time. (Eg; javassist-xyz.jar) Importing all the relevant jars while working on the IDE will make us think everything is fine. But those files will not be available during run time. Hence it will abort the agent throwing ‘ClassNotFoundException’ and ‘NoClassDefFoundError’.

Therefore, we need to make sure that we add all these parameters and necessary files to our agent before we start our agent. At first i started to manually write this manifest files, which turned out to be a real pain when the number of libraries used in the program increased. Later, figured out the easiest way to generate the manifest file during project build time. (I’ll provide the simple modification on the pom file, needed when building with Maven in a future post)

Apart from above three main files, it may include class files of any other Java classes needed by the agent as well. (For an example, if you want to have a set of methods which you want to call from the instrumented class methods, it would be less complex if you add them in a separate class and call those methods, than adding them in the SimpleClassFileTransformer.)

Well, this is the end of Requirements of a Java Agent. Let’s start writing our simple agent in our next lesson, ‘How to write a simple Agent’. (Don’t forget to install Maven and set up your IDE to make the next step easy…) See you all in next lesson.

Getting to know javaagents

Posted on Updated on

Java agent is a powerful tool introduced with Java 5. It has been highly useful in profiling activities where developers and application users can monitor tasks carried out within servers and applications. The ability to inject code segments by modifying classes and their method bodies during the run time has become useful in many ways. Intercepting parameter values passed between methods, calculation of execution time of methods can be considered as some examples. As long as you know the class name and the structure of class along with their method bodies, injecting a piece of code would take less than a minute.

Since this is going to be a series of instruction for beginners in java agents, I would start with a brief introduction on powerful java agents. Hence, this post will cover basic points as, the concept behind agents and how they work.

Well, the true power of these java agents lie on their ability to do the bytecode instrumentation. Now, you may wonder what is this bytecode. Although we humans can understand the program files written using programming languages known to us (java, python, php), computer processors do not understand them. Processors have their own language, with set of terms known as opcodes. They represent the instruction needed to carryout operations. Collection of these opcodes, instructions are referred to as bytecode, which is included in .class files we obtain after compiling our .java files. Instrumentation is the process of modifying these bytecode.

But, why do we need this java agent feature? If this is the first time you are reading about java agents, you surely may have this question.

Well, the answer is, although we get those .java files when we write our own code, files we receive with jars or any type of application we download or install would contain only .class files. We cannot read them using IDEs we use for development. The only way to access and modify their content is using libraries that work close with bytecode. Given below are three most famous libraries used in bytecode instrumentation.

  • ASM – low level library. Need to have better understanding about bytcode instructions
  • Javassist – middle level library. Operates on top of ASM. Knowledge about bytecode is not a must.
  • AspectJ – high level framework. Much more easy to work with.

Out of these three libraries, Javassist will be the library used in the future examples. But, before we start writing a simple agent, let’s get an overall idea on how an agent works.

Mainly, there are two ways that an agent can be invoked. Type of the agent is decided based on the main method type selected by the developer.

  • premain() – Agent is loaded before main class and other respective classes are loaded onto memory. This premain method, as its name described, will be invoked before the main method.
  • agentmain() – Using this method, we can invoke an agent at an arbitrary point in time, after all the classes are load onto JVM.

Since we are new to the process, let’s start with the simple premain() method. JVM will be informed about our agent by passing the path to our agent jar as a parameter. It can be done by including the -javaagent parameter in the command line.

Instrumentation block diagram
Instrumentation of class files before load onto JVM

The above figure describes the basic concept of this instrumentation process. Once we provide the details of the class we want to modify along with method names and content we need to inject, agent will obtain the bytecode of the respective class before it load onto JVM. It will make a copy of those byte code as a byte array and do requested changes on the instruction set by pushing and popping instruction on the stack. Finally, once the instrumentation is completed, it’ll recompile the new bytecode to verify whether there are any compilation errors. If it cause no compilation errors, it will load the new bytecode to the JVM, which can then be used in the tasks it was assigned to do.

So, above is a brief introduction to java agents. This long description without any example may have make you sleepy. But, don’t worry, next few articles would raise your enthusiasm in agents.

Next topic would be on ‘Requirements of a Java Agent’. Have a great time.