C2B2 logo icon

Using Custom Helpers with Byteman

Byteman comes with a set of built-in operations but what about when we want to extend those operations in order to perform our own custom operations? In this article I'm going to take a look at creating custom helpers for use with Byteman.

What is Byteman?

Byteman is a bytecode manipulation tool which simplifies tracing and testing of Java programs. Byteman allows you to insert extra Java code into your application, either as it is loaded during JVM startup or even after it has already started running. The injected code is allowed to access any of your data and call any application methods, including where they are private. You can inject code almost anywhere you want and there is no need to prepare the original source code in advance nor do you have to recompile, repackage or redeploy your application. In fact you can remove injected code and reinstall different code while the application continues to execute.
For further detail on how Byteman works and to download it head over to the Byteman website
http://www.jboss.org/byteman/

What are custom helpers?

Byteman comes with a set of built-in operations available for use in your rules. However this set of operations is not fixed. By default Byteman uses its own helper class org.jboss.byteman.rule.helper.Helper.

The set of operations that are available for use in rules can be altered by providing an alternative helper class. If you want the helper to still contain the built-in operation simply extend org.jboss.byteman.rule.helper.Helper.

Any non-abstract class can be specified as the helper. Its public instance methods automatically become available as built-in operations in the rule event, condition and action.

So, that's a quick overview of Byteman and custom helpers. Now, let's see what we can do...

I'm going to look at 3 basic examples.

1) Display when a method is entered and when it exits.
2) Display the contents of an array variable within our code (using a custom helper).
3) Create a custom helper that will print the classloading hierarchy of an object (its classloader and all parents).

Pre-requisites:
 

  • Byteman is installed.
  • The environment variable BYTEMAN_HOME is set to point to the byteman install directory

 

Basic Example


First up a very simple example to show how Byteman works. This will use one of the in-built operations traceln to output some text at a given point in the code.

We need 2 things - a Java class that we can modify using Byteman and a rule that states how to modify it.

First up the Java class :

 

 

public class TestApp{    public static void main(String[] args)    {        TestObject testObject = new TestObject();        String[] testArray = {"One", "Two", "Three"};        System.out.println("Doing stuff in main method....");    }}


As you can see we have a very simple class that creates a TestObject (code for this below), creates an array of strings and then outputs some text so we can see that the code is running when we test it.

 

 

 

 

public class TestObject{    public TestObject()    {        System.out.println("Constructed a new TestObject");    }}


Now, we need a rule that will be triggered when we enter the main method.

RULE Trace - Main Entry
CLASS TestApp
METHOD main
AT ENTRY
IF true
DO traceln("======ENTERING MAIN======")
ENDRULE

So, as you can see the rule itself is very simple.

We give it a name : Trace - Main Entry
We state the class, in this case the one we just created - TestApp. 
We state the method we are injecting into - main 
We state where to do the insert - AT ENTRY (to the method).

Following this is the rule itself, in this case a very simple one just for illustrative purposes. If true (so, always), call the traceln method (in the Helper class) and output some text indicating that we've entered the main method.

The exit rule is identical except it outputs on exit of the the method.

RULE Trace - Main Exit
CLASS TestApp
METHOD main
AT EXIT
IF true
DO traceln("======EXITING MAIN======")
ENDRULE

Save these two rules into a file called traceRules.btm in the same place as the code.

Now, in order to run this firstly we need to build our code :

javac TestApp.java

Then we need to run it with our rules :

java -Dorg.jboss.byteman.verbose -javaagent:%BYTEMAN_HOME%libbyteman.jar=script:traceRules.btm TestApp

Couple of things to note here :

-Dorg.jboss.byteman.verbose - this is useful for debugging purposes and to see what Byteman is doing under the covers.

-javaagent:%BYTEMAN_HOME%libbyteman.jar=script:traceRules.btm - this tells the JVM to use the Byteman agent code and that the script we want to use is the one we just wrote containing our two rules - traceRules.btm

On running this you should see a lot of trace from running in verbose mode stating what is happening followed by the running of our code with the new code inserted. If we run without verbose mode (simply take out the -Dorg.jboss.byteman.verbose argument) you should see :

======ENTERING MAIN======
Constructed a new test object
Doing stuff in main method....
======EXITING MAIN======

So far, so simple. With no changes to the original code we have managed to insert our own code, albeit just outputting some debug info at the start and end of our method.

 

 

 

 

 

Custom Helper - displaying internal data


Next up we will look at building a custom helper that will allow us to access a variable within the TestAppcode, the testArray.

No changes are needed to TestApp.

Firstly we need to create a Helper class that will output the contents of the array. This is where I first ran into difficulties.

Initially I intended to use a generic function to display the contents of any array, as follows:

 

 

 

 

 

public <E> void displayArray(E[] inputArray){             for (E element : inputArray)    {               System.out.printf("%s ", element);    }    System.out.println();}


However Byteman's type checker does not understand generics. Therefore if we want to handle different array types we would need a different method for each type. In our case we are only dealing with a String[] therefore to keep things simple we will only add this method to our helper class.

The full class is below :

 

 

 

 

public DisplayArrayHelper{    protected DisplayArrayHelper()    {    }    public void displayArray(String[] strings)    {        System.out.print("Array contents : ");        for (String s : strings)        {            System.out.print(s + " ");} }}


Next we need a rule :

RULE Display Array Rule
CLASS TestApp
METHOD main
HELPER DisplayArrayHelper
AT EXIT
IF true
DO displayArray($testArray)
ENDRULE

Save this as displayArrayRule.btm

Note - here we are stating that rather than use the default Helper we are using our own custom helper - DisplayArrayHelper. We are also accessing a variable within our code - $testArray.

In order to run our example we need to build our Helper class (adding the Byteman jar to the classpath) :

javac -cp ".;%BYTEMAN_HOME%libbyteman.jar" DisplayArrayHelper.java

We also need to recompile the TestApp class using the -g flag to generate debugging info. It is necessary to compile with -g if you want to refer to a local variable in the location (AT) clause. it's exactly the same if you want to refer to it in the BIND, IF or DO clauses.

And to run it :

java -javaagent:%BYTEMAN_HOME%libbyteman.jar=script:displayArrayRule.btm TestApp

Here we get the following output :

Constructed a new test object
Doing stuff in main method....
Array contents : One Two Three

Again, a very simple example but shows how easy it is to create a custom helper and to retrieve data from within the code.

 

 

 

 

Custom Helper - displaying classloader information

 

 

 

Lastly we will look at another helper class, this time one that displays the classloading hierarchy of an object.  Again, no changes need to be made to the TestApp class, aside from building it with the -g flag. Our helper class is as follows:

 

 

public class ClassLoaderHelper{    protected ClassLoaderHelper()    {    }    public void getClassLoaderHierarchy(Object obj)    {        System.out.println(ClassLoaderUtils.showClassLoaderHierarchy(obj, "Testing"));    }}


Again, we override the Helper class then we create one method which takes in an Object and displays its class load hierarchy. To do so we use a utility class called ClassLoaderUtils. This is as follows :

 

 

 

 

/* * Copyright 2002-2005 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *//** * Utility class for diagnostic purposes, to analyze the * ClassLoader hierarchy for any given object or class loader. * * @author Rod Johnson * @author Juergen Hoeller * @since 02 April 2001 * @see java.lang.ClassLoader */public abstract class ClassLoaderUtils {  /**   * Show the class loader hierarchy for this class.   * Uses default line break and tab text characters.   * @param obj object to analyze loader hierarchy for   * @param role a description of the role of this class in the application   * (e.g., "servlet" or "EJB reference")   * @return a String showing the class loader hierarchy for this class   */  public static String showClassLoaderHierarchy(Object obj, String role) {    return showClassLoaderHierarchy(obj, role, "n", "t");  }  /**   * Show the class loader hierarchy for this class.   * @param obj object to analyze loader hierarchy for   * @param role a description of the role of this class in the application   * (e.g., "servlet" or "EJB reference")   * @param lineBreak line break   * @param tabText text to use to set tabs   * @return a String showing the class loader hierarchy for this class   */  public static String showClassLoaderHierarchy(Object obj, String role, String lineBreak, String tabText) {    String s = "object of " + obj.getClass() + ": role is " + role + lineBreak;    return s + showClassLoaderHierarchy(obj.getClass().getClassLoader(), lineBreak, tabText, 0);  }  /**   * Show the class loader hierarchy for the given class loader.   * Uses default line break and tab text characters.   * @param cl class loader to analyze hierarchy for   * @return a String showing the class loader hierarchy for this class   */  public static String showClassLoaderHierarchy(ClassLoader cl) {    return showClassLoaderHierarchy(cl, "n", "t");  }  /**   * Show the class loader hierarchy for the given class loader.   * @param cl class loader to analyze hierarchy for   * @param lineBreak line break   * @param tabText text to use to set tabs   * @return a String showing the class loader hierarchy for this class   */  public static String showClassLoaderHierarchy(ClassLoader cl, String lineBreak, String tabText) {    return showClassLoaderHierarchy(cl, lineBreak, tabText, 0);  }  /**   * Show the class loader hierarchy for the given class loader.   * @param cl class loader to analyze hierarchy for   * @param lineBreak line break   * @param tabText text to use to set tabs   * @param indent nesting level (from 0) of this loader; used in pretty printing   * @return a String showing the class loader hierarchy for this class   */  private static String showClassLoaderHierarchy(ClassLoader cl, String lineBreak, String tabText, int indent) {    if (cl == null) {      ClassLoader ccl = Thread.currentThread().getContextClassLoader();      return "context class loader=[" + ccl + "] hashCode=" + ccl.hashCode();    }    StringBuffer buf = new StringBuffer();    for (int i = 0; i < indent; i++) {      buf.append(tabText);    }    buf.append("[").append(cl).append("] hashCode=").append(cl.hashCode()).append(lineBreak);    ClassLoader parent = cl.getParent();    return buf.toString() + showClassLoaderHierarchy(parent, lineBreak, tabText, indent + 1);  }}

 


We then create the rule that utilises our helper class : 

 

 

 

 

 

RULE Class Loader Rule 

 

 

CLASS TestApp 
METHOD main 
HELPER ClassLoaderHelper 
AT EXIT 
IF true 
DO getClassLoaderHierarchy($testObject); 
ENDRULE 

 

Save this as classLoaderRule.btm. 

 

 

Now we need to build the 2 new classes : 
 

 

javac ClassLoaderUtils.java 
javac -cp ".;%BYTEMAN_HOME%libbyteman.jar" ClassLoaderHelper.java 

 

and then run the rule against our TestApp code : 

 

 

java -javaagent:%BYTEMAN_HOME%libbyteman.jar=script:classLoaderRule.btm TestApp 

 

 

Here we see something similar to this : 

 

 

Constructed a new test object Doing stuff in main method.... object of class TestObject: role is Testing [sun.misc.Launcher$AppClassLoader@553f5d07] hashCode=1430215943         [sun.misc.Launcher$ExtClassLoader@32f4a24a] hashCode=854893130 context class loader=[sun.misc.Launcher$AppClassLoader@553f5d07] hashCode=1430215943 

 

 

 

Conclusion

Byteman is a powerful tool allowing the user to inject code into previously built code. It's particularly useful in  multi-threaded environments where normal debugging is really difficult. Whilst the Helper class contains many useful methods through the use of custom helpers it is possible to extend this to do whatever is required.

 

 

There are a couple of points that were noted whilst working with Byteman : 

 

 

  • Although the Byteman site claims "Since it only needs access to bytecode this means it can modify library code whose source is either unavailable or unable to be recompiled." this is not strictly true.Whilst it can modify it you will not be able to access local variables without recompiling with the -g flag to generate debugging info.
  • You cannot use generics in your helper class method signatures as the type checker does not understand generics.