Byteman教程

A Byteman Tutorial


        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 samplelibdocscontrib 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 a.bat files -- which employ the same command line syntaas the Linux scripts).

 

Anyway, that's it, installation complete.


How Do I Run A Program Using Byteman?

 

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 prograas 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 Bytemaand 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 Bytemaagent 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 Bytemaagent 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 Bytemaagent 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

>


How Do I Inject Code Into JVM Classes?

 

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 tha$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 Bytemaagent 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 aa separator). This makes sure that the bytemaagent 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

>

 

Is There  a  Simpler W a y To Run Bytem a n?

 

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 a-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)


How Do I Lo a d Rules Into  A  Running Progr a m?

 

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 &amp;&amp; next.length() > 0 &amp;&amp; !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 loaand 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 aagent 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.jaorg.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 prograand type more input we can see the rule being executed

 

. . .

bar

bar

baz

*** start for thread: baz

baz

mumble

*** start for thread: mumble

mumble

 

How Do I See Which Rules  A re Lo a ded  A nd Compiled?

 

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.

 

How Do I Unlo a d Rules?


We caalso 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 prograagain 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 unloaand 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.


How Do I Inst a ll  The  A gent Into  A  Running Progr a m?

 

You will sometimes find that you start your program without loading the Bytemaagent 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 aagent 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 Bytemaagent  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 loaany 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

 

 

How Do I Run JBoss  A S With Bytem a n?

 

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 aargument 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 caalso 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 formajava -jajarfile. 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

>

 

How Do I Know My Rules Are Correct?

 

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. Bytemaallows 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 flaand 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


How Do I Tell If My Rules  A re Being Run?

 

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.

 

  • When class AppMain is loaded the trigger locations for each rule is identified and the rule code is injected
  • Next the main method gets called and you can see an execute message for the entry rule (you can ignore the messages about helper activation and rule installation just now or read the Ptrogrammer's Guide if you want them explained)
  • The execute message is followed  by the the enter rule's trace message.
  • The main method prints all its its ouptut
  • Just before it returns there is another execute message for the exit rule (again,  just ignore the messages about  rule installation)
  • The second execute message is followed  by the exit rule's trace message .

 

How C a n I M a ke My Rules Run F a st?

 

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

. . .


Where C a n I Downlo a d The Tutori a l Sources?

 

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




你可能感兴趣的:(java)