Tutorials About Javaagents

Tutorials about javaagents

The second case talks about Java Instrumentation API - this link points to a Javadoc which is rather descriptive.

And here, is the full instruction and an example of how to create java instrumentation agent.

The main concept is to:

  1. Implement a static premain (as an analogy to main) method, like this:

    import java.lang.instrument.Instrumentation;

    class Example {
    public static void premain(String args, Instrumentation inst) {
    ...
    }
    }
  2. Create a manifest file (say, manifest.txt) marking this class for pre-main execution. Its contents are:

    Premain-Class: Example
  3. Compile the class and package this class into a JAR archive:

    javac Example.java
    jar cmf manifest.txt yourAwesomeAgent.jar *.class
  4. Execute your JVM with -javaagent parameter, like this:

    java -javaagent:yourAwesomeAgent.jar -jar yourApp.jar

How to set HTTP header with javaagent

The solution I came up with: using bytebuddy to intercept the 'doExecute' Method of the Apache InternalHttpClient that is used by the 3rd party library. So I was able to add the required content-type header.

public class AgentMain {

public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(named("org.apache.http.impl.client.InternalHttpClient"))
.transform((builder, type, classLoader, module) ->
builder.method(named("doExecute"))
.intercept(Advice.to(HttpClientAdvice.class))
).installOn(inst);
}

public static void agentmain(String agentArgs, Instrumentation inst) {
// Not used
}

public static class HttpClientAdvice {
@Advice.OnMethodEnter
public static void doExecute(@Advice.AllArguments Object[] args) {
final HttpRequest request = (HttpRequest) args[1];
request.addHeader("Content-Type", "text/xml");
}
}
}

transforming class has no effect

Thanks for raising this question to let me have chance to take a look of Java Instrumentation.

After spending some time to cross check your sample codes and the provided tutorial. The problem is not from the programming codes, but the way how to launch your program.

If you add some loggers to the transform() method in Transformer.java, you will find that the code path is broken after running:

ClassPool cp = ClassPool.getDefault();

And, after replacing the exception catching code in the same method from:

} catch (Exception e) {

to:

} catch (NotFoundException | CannotCompileException | IOException e) {

It would give your more hints as below:

Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
at doeke.static_agent.Static_Agent.transform(Static_Agent.java:60)
at doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:40)
at doeke.static_agent.Static_Agent.premain(Static_Agent.java:28)
... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 9 more
FATAL ERROR in native method: processing of -javaagent failed

Up to this point, the root cause is more apparent. It is because while launching the program, those javassist relevant classes (e.g. ClassPool, CtClass, CtMethod, etc.) cannot refer to its corresponding libraries during the runtime.

So, the solution is:

  1. assuming you have exported the static_agent.jar in the same "build" folder as of application.jar

  2. all other folder structure remain the same as shown in your provided github

  3. let's "cd" to the build folder in the command console

  4. revising the original program launching script as below

Windows OS:

java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar;application.jar doeke.application.TestApplication

Unix/Linux OS:

java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar:application.jar doeke.application.TestApplication

You would finally get your expected result:

[Agent] In premain method.
>> doeke.application.TestApplication
>> test
[Agent] Transforming class
--- start ---
0
[Application] Withdrawal operation completed in:0 seconds!
1
[Application] Withdrawal operation completed in:0 seconds!

EDIT

In addition, let me paste some codes regarding how to insert codes in the middle of a method through javassist.

In case the test() method in TestApplication.java is changed as:

line 30    public static void test() {
line 31 System.out.println(count++);
line 32
line 33 System.out.println("Last line of test() method");
line 34 }

Assume that we want to add a line between the count and the =========, let's say "This is line separator", which the result would look like:

1 
-- This is line separator --
Last line of test() method

Then, in the transform(...) method of Transformer.java, you could add a code line as of below:

m.insertAt(32,"System.out.println(\"-- This is line separator --\");");

which makes it becomes:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;

String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
if (!className.equals(finalTargetClassName)) {
return byteCode;
}

if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
System.out.println("[Agent] Transforming class TestApplication");
try {
// Step 1 Preparation
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod(targetMethodName);

// Step 2 Declare variables
m.addLocalVariable("startTime", CtClass.longType);
m.addLocalVariable("endTime", CtClass.longType);
m.addLocalVariable("opTime", CtClass.longType);

// Step 3 Insertion of extra logics/implementation
m.insertBefore("startTime = System.currentTimeMillis();");

m.insertAt(32,"System.out.println(\"-- This is line separator --\");");

StringBuilder endBlock = new StringBuilder();

endBlock.append("endTime = System.currentTimeMillis();");
endBlock.append("opTime = (endTime-startTime)/1000;");
endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");

m.insertAfter(endBlock.toString());

// Step 4 Detach from ClassPool and clean up stuff
byteCode = cc.toBytecode();
cc.detach();
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}

}
return byteCode;
}

Finally, would get result like below of printing the code in the middle of a method:

[Agent] In premain method.
className=doeke.application.TestApplication
methodName=test
>> doeke.application.TestApplication
>> test
[Agent] Transforming class TestApplication
--- start ---
0
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
1
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
2
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!

How installing Java Agent?

Presuming that you actually mean agents as in instrumentation, check this tutorial:

http://www.javabeat.net/introduction-to-java-agents/

it will give you a quick start in writing your first agents and set up everything you need to get started.

Just on a side note: if you want specific, detailed answers, ask specific, detailed questions.



Related Topics



Leave a reply



Submit