The latest Byteman binary release is available from the Byteman project downloads page. Download the binary zip release and unzip it into adirectory on your local machine. Now set environment variable BYTEMAN_HOME to refer to this directory. On Linux you execute a command like this
export BYTEMAN_HOME=${HOME}/Downloads/byteman-1.6.0
or on Windows
set BYTEMAN_HOME=C:\Downloads\byteman-1.6.0
The directory identified by BYTEMAN_HOME should include a file called README and subdirectories sample, lib, docs, contrib and bin.
If you are installing Byteman on Linux then you should add the bin directory to your path so you can use the shell scripts which simplify use of Byteman
export PATH=${PATH}:${BYTEMAN_HOME}/bin
If you are installing Byteman on Windows then you will need to use the java command in place of the shell scripts (note that from release 2.0.1 onwards the bin directory contains equivalent Windows scripts -- provided as .bat files -- which employ the same command line syntax as the Linux scripts).
Anyway, that's it, installation complete.
There are a few different ways to run your Java program with Byteman.The most basic way is on the java command line using the -javaagent option
Here's a simple application class which will be used to show Byteman in action
package org.my;
class AppMain
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
You would normally compile and run this program as follows:
> javac org/my/AppMain.java
> java org.my.AppMain foo bar baz
foo
bar
baz
>
Let's inject to inject some code to trace entry into and exit from method main.
First, we will create a Byteman rule script file. Just open a file in the working directory called appmain.btm and insert the following text
RULE trace main entry
CLASS AppMain
METHOD main
AT ENTRY
IF true
DO traceln("entering main")
ENDRULE
RULE trace main exit
CLASS AppMain
METHOD main
AT EXIT
IF true
DO traceln("exiting main")
ENDRULE
This script contains two rules both of which specify code to be injected into METHOD main of CLASS AppMain. The first rule is injected AT ENTRY i.e.at the start of the method. The code to be injected is the IF DO part. In the first rule it calls builtin operation traceln(String) which prints itsargument to System.out() followed by a newline The second rule is injected AT EXIT i.e. at the point(s) where control returns from method main. Both rules specify condition IF true which means the DO part is always executed. You can supply a different Java expression if you want to compute whether or not the actions following DO are run.
The -javaagent option used to run the progarm with these Byteman rules is supported by all the main desktop and server JVMs. The syntax is
-javaagent : path_to_agent_jar = agent_option1 , agent_option_2 , . . .
The Linux command to run the program with Byteman and this rule set is
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
or on Windows
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
The value after the : tells the JVM where to find the Byteman agent jar, ${BYTEMAN_HOME}/lib/byteman.jar. We only supply one agent option following the = sign, script:appmain.btm, specifying the location of the Byteman rule script. The Byteman agent reads this option then loads and injects the rules from file appmain.btm. If we wanted to load more than one script we could provide multiple script:file agent options, separated by a comma.
When you pass -javaagent on the command line the Byteman agent starts executing during JVM startup before the application is run. So, it will read the rules and inject side effects into AppMain.main() before this method gets called. The output should be as follows
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
entering main
foo
bar
baz
exiting main
>
Well, let's just try tracing some JVM operations. Insert the following rule text into a new script file called thread.btm.
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
This rule is injected into METHOD start() of the JVM runtime CLASS java.lang.Thread. It prints a trace message pasted together using the String + operator. Now start() is an instance method so when it is called there is a specific Thread instance which is the target for the method call. The special variable $0 can be used to refer to this target object. start() has no arguments but in other cases where there are the arguments to the call which triggers the rule they can be referenced using $1, $2 etc.
Byteman knows that $0 references a Thread object so it type checks the method invocation $0.getName() and verifies that the result type is String. The injected code makes a call this method, appends the result to the constant String and passes the result to method traceln() to be written to System.out.
We will inject this rule into a variant of our original class, AppMain2, which creates some threads to do the printing:
package org.my;
class AppMain2
{
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
Thread thread = new Thread(arg) {
public void run() {
System.out.println(arg);
}
};
thread.start();
try {
thread.join()
} catch (Exception e) {
}
}
}
}
To run this program using the new script we modify the script: agent option to reference file thread.btm. But that's not enough if we want to inject code into JVM classes. We need to provide some extra options/arguments. On Linux
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
or on Windows
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
Class Thread is a JVM class which means it gets loaded by the bootstrap classloader. Byteman can only inject code into this class if the Byteman agent classes are also loaded by the bootstrap classloader. The extra agent option boot:${BYTEMAN_HOME}/lib/byteman.jar is appended after the script option (note the comma used as a separator). This makes sure that the byteman agent jar is installed into the bootstrap classpath.
Class Thread also happens to be in package java.lang. Normally Byteman is super-cautious and will not inject code into this package in case it beaks the JVM. If you really want to change methods of classes in this package you need to define the system propertyorg.jboss.byteman.transform.all.
When we run the program now we get this output
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
*** start for thread: foo
foo
*** start for thread: bar
bar
*** start for thread: baz
baz
>
On Linux you can use the command script bmjava.sh to wrap up the -javaagent options for you. It looks very much like the java command but itaccepts Byteman rule scripts on the command line and bundles them up as -javaagent script: options. It also automatically bundles in the byteman jar using the boot: agent option. So, using bmjava.sh the previous command line simplifies to
> bmjava.sh -l thread.btm org.my.AppMain2 foo bar baz
Notice that the flag used there is -l for load.
On Windows if you are using byteman release 2.0.1 or later there is an equivalent script called bmjava.bat which you can execute using commandbjava. So the previous command simplifies to
> bmjava -l thread.btm org.my.AppMain2 foo bar baz
Note that you need to add the installed bin directory ${BYTEMAN_HOME}/bin to your path in orderto be able to execute the script by name (on windows use %BYETMAN_HOME%/bin)
If you have a long running program you may want to load rules after the program has started running or even unload rules and load new ones. You can do this if you tell Byteman to start its agent listener. The listener also allows you to check the status of loaded rules. To show the listener in use here'sanother variant of our program, AppMain3.
package org.my;
import java.io.DataInputStream;
class AppMain3
{
public static void main(String[] args)
{
try {
DataInputStream in = new DataInputStream(System.in);
String next = in.readLine();
while (next != null && next.length() > 0 && !next.contains("end")) {
final String arg = next;
Thread thread = new Thread(arg) {
public void run() {
System.out.println(arg);
}
};
thread.start();
try {
thread.join();
} catch (Exception e){
}
next = in.readLine();
}
} catch (Exception e) {
}
}
}
This version of the program reads and prints input text until it sees a line containing the String "end" or and end of file. If we start it using the Byteman listener we can load and unload rules while the program is sitting in a read wait and also check the status of rules as they are parsed, injected into the program, type-checked and executed.
The agent listener is enabled on the java command line using agent option listener:true. On Linux the command is this
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=listener:true,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
or on Windows
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=listener:true,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
The listener opens a server socket and then waits for incoming commands. Notice that no rules scripts were specified as agent options so initially when we type in input it is just echoed back
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=listener:true,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
foo
foo
bar
bar
On Linux we use command script bmsubmit.sh to talk to the agent listener. Calling it with no arguments lists the state of all installed rules
> bmsubmit.sh
no rules installed
>
Note that you need to run this in a second command shell -- AppMain3 needs to be still running in order for bmsubmit.sh to be able to talk to the agent via the server socket.
On Windows if you are using Byteman release 2.0.1 or later there is an equivalent script bmsubmit.bat which you can execute usingthe simple command bmsubmit.
> bmsubmit
no rules installed
>
If you are using an earlier release you have to use the java command to run the main method of Java class Submit which sends commands to the listener
> java -classpath %BYTEMAN_HOME%\lib\byteman-submit.jar org.jboss.byteman.agent.submit.Submit
no rules installed
>
Now we can load our rule script into the running program using bmsubmit.sh
> bmsubmit.sh -l thread.btm
install rule trace thread start
>
On Windows you do this using the equivalent script bmsubmit.bat. If you are using a release prior to 2.0.1 you need to execute Submit.main()passing the same argumens
> java -classpath %BYTEMAN_HOME%\lib\byteman-submit.jar org.jboss.byteman.agent.submit.Submit -l thread.btm
install rule trace thread start
>
If we check the installed rule state again we see that the rule has been loaded, parsed and and injected into method Thread.start(). If the rule had failed to parse correctly then injection would have been inhibited and this listing would include details of the parse error.
> bmsubmit.sh
# File thread.btm line 4
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
AT ENTRY
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
Transformed in:
loader: sun.misc.Launcher$AppClassLoader@5acac268
trigger method: java.lang.Thread.start() void
>
If we switch back to the running program and type more input we can see the rule being executed
. . .
bar
bar
baz
*** start for thread: baz
baz
mumble
*** start for thread: mumble
mumble
Running bmsubmit.sh again we can see that the rule has been successfully compiled.
> bmsubmit.sh
# File thread.btm line 4
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
AT ENTRY
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
Transformed in:
loader: sun.misc.Launcher$AppClassLoader@5acac268
trigger method: java.lang.Thread.start() void
compiled successfully
>
This time there is an extra line at the end of the output saying compiled successfully. This means the rule has been type-checked and executed. Type checking only happens when the rule is first triggered -- in this case when Thread.start() is called after typing in the line containing the word baz. If type checking had failed then execution of the injected code would be inhibited and the listing would include details of the type error. Injected code is only removed when the rule is unloaded.
We can also use bmsubmit.sh (or class Submit on Windows) to unload rules, restoring the original behaviour of Thread.start()
> bmsubmit.sh -u thread.btm
uninstall RULE trace thread start
> bmsubmit.sh
no rules installed
>
The -u flag is followed by the name of a rule script file. All rules mentioned in the file are unloaded, removing their IF DO code from any methods into which they were injected. If we switch back to the program again it will now revert to just echoing the input text
. . .
mumble
*** start for thread: mumble
mumble
grumble
grumble
bletch
bletch
end
>
n.b. if you want to change the injected code for a rule you don't have to unload and then reload the rule. If you submit a modifed version of a rule script Byteman will remove any existing injected code and reinject the new code.
You will sometimes find that you start your program without loading the Byteman agent option only to realise that you would like to use Byteman to check its behaviour. This commonly occurs with a long-running program like the JBoss Application Server when you notice a log message indicating that something has started misbehaving. You don't have to restart the program in order to be able to use Byteman, at least not if you are running your code on a Hotspot, JRockit or OpenJDK JVM. These JVMs all allow you to install an agent program into a running program.
If you are running on Linux then you can use command script bminstall.sh to install the agent program
> bminstall.sh 13101
>
n.b.not all JVMs allow agents to be loaded dynamically. This is known to work on Linux when using the Oracle JVM, OpenJDK and JRockit JVMs and on OSX when using the Oracle JVM and OpenJDK JVMs. It has not yet been found to work on IBM JVMs or with any JVM on Windows.
On Windows if you are using release 2.0.1 or later you can use the equivalent script bminstall.bat. Otherwise you need to executeInstall.main() passing the relevant arguments.
> java -classpath %BYTEMAN_HOME%\lib\byteman-install.jar org.jboss.byteman.agent.install.Install 13101
>
The numeric argument is the id of the process into which the agent is to be be installed -- obviously you need to supply a value appropriate for the Javaprogram you want to check. Alternatively, you can supply the name of the Java main class of your program (or the jar name if you started it using java -jar myapp.jar). This option is very useful if you want to install the Byteman agent into a running JBoss Application Server instance. Let's run AppMain3again but without the -javaagent option and then load up the agent and some rules after it has started running
> java org.my.AppMain3
foo
foo
bar
bar
Now in another command shell we will install the agent. On Linux
> bminstall -b -Dorg.jboss.byteman.transform.all org.my.AppMain3
>
bminstall.sh does not load any rule scripts. However, it automatically enables the agent listener, allowing you to submit your rules usingbmsubmit.sh. If you try submitting and unsubmitting the rules defined in thread.btm using the commands given in the previous examples you will see them modifying the behaviour of AppMain3.
If you want to install Byteman into your JBoss Application Server at startup you need to use the -javaagent option. But JBoss AS is run by executing the java command from within a command script, run.sh for AS 4, 5 or 6, standalone.sh or domain.sh for AS 7. So, how do you ensure that these scripts pass the necessary -javaagent options to the JVM?
With AS 4, 5 and 6 you can pass the required argument to the scripts by setting the environment variable JAVA_OPTS. On Linux you might use the following command
set JAVA_OPTS="${JAVA_OPTS} -Dorg.jboss.byteman.transform.all -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar,listener:true"
or on Windows
set JAVA_OPTS="%JAVA_OPTS% -Dorg.jboss.byteman.transform.all -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar:listener:true"
Notice that the commands insert the current value of JAVA_OPTS retaining any other java options which might already be set.
With AS 7 you normally insert configuration settings for variables like JAVA_OPTS in the configuration files located in the bin subdirectory of your AS7 installation. On Linux you need to edit the files standalone.conf or domain.conf. On Windows you edit files standalone.conf.bat ordomain.conf.bat. If you edit these files you will see there is an obvious place where you can insert the -javaagent option to JAVA_OPTS. You would use a setting just like the examples given above.
If your JBoss AS instance is already running then you can use bminstall.sh to install the agent into the AS. If you know the process id of the JBoss AS process then you just need to pass this process id as argument to bminstall.sh
> bminstall.sh 13101
>
As mentioned before bminstall.sh allows you to identify the target process using the name of the application main class instead of the process id. So, to install the agent into a running JBoss AS 4, 5 or 6 instance you provide org.jboss.Main as the argument.
> bminstall.sh -b -Dorg.jboss.byteman.transform.all org.jboss.Main
>
Argument -b is needed if you want bminstall.sh to load the agent code into the bootstrap classpath. You can also specify system properties to be set by the agent by providing arguments of the form -Dname or -Dname=value.
AS 7 is slightly different because its is started using a command in the format java -jar jarfile. In this case you need to pass bminstall.sheither the process id or the path to the jar file. The jar files used to provide the entry point for JBoss AS7 is jboss-modules.jar. So, you would use the following command to load the agent into a running JBoss AS7 instance
> bminstall.sh -b -Dorg.jboss.byteman.transform.all $JBOSS_HOME/jboss-modules.jar
>
After loading your rules it is useful to rerun bmsubmit.sh with no arguments to see if they have suffered from parse or type errors. Hwever, it is usually better to identify these problems in advance. Byteman allows you to parse and type check your rules offline before installing them into a Java program. On Linux you run command script bmcheck.sh
> bmcheck.sh thread.btm
checking rule trace thread start
parsed rule "trace thread start" for class java.lang.Thread
type checked rule "trace thread start"
TestScript: no errors
>
On Windows if you are usiing release 2.0.1 or later use the equivalent script bmcheck.bat.
> bmcheck thread.btm
checking rule trace thread start
parsed rule "trace thread start" for class java.lang.Thread
type checked rule "trace thread start"
TestScript: no errors
>
For earlier releases you can use Java class TestScript
> java -classpath %BYTEMAN_HOME%\lib\byteman.jar org.jboss.byteman.check.TestScript thread.btm
checking rule trace thread start
parsed rule "trace thread start" for class java.lang.Thread
type checked rule "trace thread start"
TestScript: no errors
>
If the rule script refers to application classes then you need to tell bmcheck.sh where to find them using the -cp flag. So, we need to use this argument to check our first script appmain.btm. But that's not enough. Here's the first rule again
RULE trace main entry
CLASS AppMain
METHOD main
AT ENTRY
IF true
DO traceln("entering main")
ENDRULE
This rule does not use the fully qualified name of the target class. That's ok because the agent notices when org.my.AppMain gets loaded and realises that the rule applies to it. But when we are checking rules offline the application is not run so Byteman does not know which package to use to lookup the class mentioned in the rule. The -p flag can be used to suggest one or more package prefixes for the type checker to try.
> bmcheck.sh -p org.my -cp . appmain.btm
checking rule trace main entry
parsed rule "trace main entry" for class org.my.AppMain
type checked rule "trace main entry"
checking rule trace main exit
parsed rule "trace main exit" for class org.my.AppMain
type checked rule "trace main exit"
TestScript: no errors
>
On Windows if you are using release 2.0.1 or later the -p and -cp options are also accepted by the script bmcheck.bat.
> bmcheck -p org.my -cp . appmain.btm
For earlier releases you have to explicitly add the current directory to the classpath using the java -classpath flag and then pass the -p flag to the check class's main routine by inserting it after the class name and just before the script name(s).
> java -classpath %BYTEMAN_HOME%\lib\byteman.jar;. org.jboss.byteman.check.TestScript -p org.my thread.btm
Sometimes you may be unsure whether your rules are being injected correctly. Perhaps the target method is never executed. Perhaps the rule condition isalways false. Or maybe a parse or type error is stopping them being executed. If you run Byteman in verbose mode you will see trace output telling you what Byteman is doing. Verbose mode is enabled by setting a system property
> java -Dorg.jboss.byteman.verbose -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
org.jboss.byteman.agent.Transformer : possible trigger for rule trace main entry in class org.my.AppMain
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.AppMain.main(java.lang.String[]) void for rule trace main entry
org.jboss.byteman.agent.Transformer : inserted trigger for trace main entry in class org.my.AppMain
org.jboss.byteman.agent.Transformer : possible trigger for rule trace main exit in class org.my.AppMain
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.AppMain.main(java.lang.String[]) void for rule trace main exit
org.jboss.byteman.agent.Transformer : inserted trigger for trace main exit in class org.my.AppMain
Rule.execute called for trace main entry_0
HelperManager.install for helper classorg.jboss.byteman.rule.helper.Helper
calling activated() for helper classorg.jboss.byteman.rule.helper.Helper
Default helper activated
calling installed(trace main entry) for helper classorg.jboss.byteman.rule.helper.Helper
Installed rule using default helper : trace main entry
trace main entry execute
entering main
foo
bar
baz
Rule.execute called for trace main exit_1
HelperManager.install for helper classorg.jboss.byteman.rule.helper.Helper
calling installed(trace main exit) for helper classorg.jboss.byteman.rule.helper.Helper
Installed rule using default helper : trace main exit
trace main exit execute
exiting main
>
The trace is quite verbose but all the information you need is there if you look for it.
Normally Byteman executes rules by interpreting the parsed rule code. This is fast enough for most uses but if you inject code into a tight loop or into amethod which is called frequently then this may slow down your program. You can speed things up a bit by asking Byteman to translate the rule code to bytecode which the JIT compiler can then optimize. This makes the first execution of the rule take longer but subsequent execution should be a lot faster.
You enable rule compilation by setting system property org.jboss.byteman.compile.to.bytecode when you install the agent. You can do this on the java command line or when you install the agent using bminstall.sh
> bminstall.sh -Dorg.jboss.byteman.compile.to.bytecode -b org.jboss.Main
> bmsubmit.sh hornetq-io-rules.btm
. . .
Here's a http://downloads.jboss.org/byteman/tutorial/tutorial.zipcontaining the sources for the classes and scripts used in this tutorial.
Thanks Andrew Dinn
This is form :https://developer.jboss.org/docs/DOC-17213