Byteman advanced tutorial

Welcome to the advanced tutorial on Byteman, a Java bytecode manipulation tool!  Byteman is a powerful tool that allows you to inject code into your Java application at runtime. This can be useful for a wide range of tasks, including debugging, testing, and performance monitoring.

In this tutorial, we will cover some of the more advanced features of Byteman, such as how to intercept a JMS message received by an MDB so that we can perform some extra actions on it, without changing the actual MDB code.

A Byteman Advanced Rule

Here’s a basic rule named “JMS Message Rule” which is bound to the javax.jms.MessageListener interface ( by the way you can bind rules both to Classes and Interfaces, do you remember?).

The rule is bound on the onMessage Method and uses an Helper class (com.sample.Utility) passing two parameters (the MessageListener and the Message)

RULE JMS Message Rule

INTERFACE javax.jms.MessageListener

METHOD void onMessage(javax.jms.Message)
HELPER com.sample.Utility
BIND

          messageListener:MessageListener = $0;
          message:Message = $1

IF true

DO readMessage ($0,$1)

ENDRULE

Notice the usage of the positional parameters ($0, $1) which we use to bind the actual Objects (MessageListener and Message) and use them in the DO readMessage call.

Our Rule requires an Utility class to execute any kind of processing on the JMS message, for example check the content and send a warning message to the system.

public class Utility   {

public void readMessage(MessageListener listener, Message message) throws Throwable {
       
       TextMessage txt = (TextMessage)message;
         try {

            String strMessage = txt.getText();

            // Execute actions
            System.out.println("Message received "+strMessage);
        } catch (JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   }
}

One way to improve helper class is extending org.jboss.byteman.rule.helper.Helper class so that we can use also all other available utility methods in the base class. For example:

public class Utility extends  org.jboss.byteman.rule.helper.Helper {  
   protected Utility(Rule rule) {
        super(rule);
 
    }

public void readMessage(MessageListener listener, Message message) throws Throwable {
       setTriggering(false);
       
       TextMessage txt = (TextMessage)message;
         try {
            System.out.println(txt.getText());
        } catch (JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   }

}

Notice that we have to provide a constructor for our helper which calls the constructor for the default helper. This is because, whenever a rule is triggered a helper instance is created to handle the rule trigger and, if necessary, to fill field calls with builtin methods. In other words, the helper provides a context for each firing.

Once that we are extending Byteman rules, we can create more complex constructs like this one, which checks that the MDB that is firing the rule belongs to the com.sample.MyMDB class:

RULE JMS Message Rule

INTERFACE javax.jms.MessageListener

METHOD void onMessage(javax.jms.Message)
HELPER com.sample.Utility
BIND

          messageListener:MessageListener = $0;

          message:Message = $1

IF callerCheck("com.sample.MyMDB.*.onMessage", true, true, true, 0, 1)

DO readMessage ($0,$1)

ENDRULE

In this example, we are using the callerCheck method of the Helper class which can be used to check whether the name of any of the selected methods in the stack which called the trigger method matches the supplied regular expression:

public boolean   callerMatches(String regExp, boolean includeClass, boolean includePackage, int startFrame, int frameCount)

Here is a brief description about the parameters:

  • regExp: matches an expression which will be matched against the name of the method which called the trigger method
  • includeClass: true if the match should be against the class qualified method name
  • includePackage: true if the match should be against the package and class qualified method name.
  • startFrame: identifies the first frame which frame which should be considered.
  • frameCount: counts the frames which should be checked starting from the first caller.

How to manipulate variables with Byteman

In the second part of this tutorial we will go more in deep to learn how to vary the application flow by changing the value of a method variable.

So here’s a simple Java example class:

public class Test   
{  
  public void action(Pojo test) throws Exception  
  {  
    if (!test.isOk()) {  
      throw new Exception("Error in Test " + test);  
    }  
  }  
}

In this simple class we’re checking for the isSuccess method in the action method. In case the isSuccess returns false a new Exception is raised. Hacking the value of the function called in the method is quite simple. Here is a Byteman rule which will prevent the exception to get thrown:

RULE hack rule
CLASS Test  
METHOD action  
AT INVOKE isOk  
IF TRUE  
$test.ok = true  
ENDRULE  

What is worth mentioning is that the rule is injected into the Java code before method isOk is called. If you want to be more precise you could make it clearer by writing

AT INVOKE Test.isOk()

The assignment in the rule action ($test.ok = true ) uses $test to refer to the first parameter of method action by name. This way you can set the value of the Pojo’s ok class member to true. By the way, yuo can also refer to it by index using the syntax $1.

The list of location (AT) clauses is restricted to positions in the source code which clearly identify locations in the corresponding bytecode. For example

AT ENTRY
AT EXIT
AT INVOKE calledMethod
AT WRITE fieldName =
AT READ $localVarName
AT LINE nnn

Here’s one more example which detects a change in local variable using AFTER WRITE:

RULE Check Variable Write
CLASS Test
METHOD main
AFTER WRITE $y
IF $y==2
DO traceln("y is now set to 2")
ENDRULE

Finally, it’s worth mentioning that you can insert more than one expression in a DO clause to make your Rules more complex. The only twist is that you need to separate them using a ‘;’ — for example:

RULE  
...    
DO traceln("y is now set to 2");
traceStack()  
ENDRULE
Found the article helpful? if so please follow us on Socials