Its is my first time implementing a java agent and im trying to learn something about bytecode instrumentation. After reading several introductions and tutorials i coded a small Application with two classes (Summer and Application). Now i want to run a java agent via premain method to show the execution path using the following code:
public class TestJavaAgent {
public static void premain(String agentArgument,
Instrumentation instrumentation){
instrumentation.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
ClassPool cp = ClassPool.getDefault();
try {
CtClass cc = cp.get("Summer");
CtMethod methods [] = cc.getMethods();
for( CtMethod method : methods){
System.out.println("Entering "+method.getName());
method.addLocalVariable("elapsedTime", CtClass.longType);
method.insertBefore("elapsedTime = System.currentTimeMillis();");
method.insertAfter("{elapsedTime = System.currentTimeMillis() - elapsedTime;"
+ "System.out.println(\"Method Executed in ms: \" + elapsedTime);}");
}
return cc.toBytecode();
} catch (Exception ex) {
return bytes;
}
}
});
}
}
I started the Agent via java -javaagent{Agent JAR} -jar {Application Jar} but it did not print anything of the inserted messages. After debugging the code i realized everything after "ClassPool.getDefault()" will not be reached but i dont know why. Can someone help me?
The transformer is supposed to transform the class being passed as parameter, not some arbitrary class you like. And after registration, it will be called for all classes that are loaded, including the classes you are using yourself (ClassPool at first). So you are creating a circular dependency.
You have to check the class name argument and wait until your method has been called for the class you want to transform. For all other classes, just return null.
public class TestJavaAgent {
public static void premain(String agentArgument, Instrumentation instrumentation) {
instrumentation.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader classLoader,
String className, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
if(!className.equals("Summer")) return null;
ClassPool cp = ClassPool.getDefault();
try {
// use the class bytes your received as parameter
cp.insertClassPath(new ByteArrayClassPath(className, bytes));
CtClass cc = cp.get("Summer");
CtMethod[] methods = cc.getMethods();
for( CtMethod method : methods){
System.out.println("Entering "+method.getName());
method.addLocalVariable("elapsedTime", CtClass.longType);
method.insertBefore("elapsedTime = System.currentTimeMillis();");
method.insertAfter("{elapsedTime = System.currentTimeMillis() - elapsedTime;"
+ "System.out.println(\"Method Executed in ms: \" + elapsedTime);}");
}
return cc.toBytecode();
} catch (Exception ex) {
return null;
}
}
});
}
}
Note that returning null is preferred to returning the original array if you didn’t change anything as then the JVM can immediately recognize that you didn’t change anything, without looking into the array contents.
Related
I am writing a java agent to instrument user annotated methods. Currently, with javassist, I can identify the annotated methods and insert logging information. However, I am wondering how I can instrument the methods to call java agent callbacks. Following is the transform method. I want to call a custom agent method by insertBefore and insertAfter.
#Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
try {
ClassPool classPool = scopedClassPoolFactory.create(loader, rootPool,
ScopedClassPoolRepositoryImpl.getInstance());
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod[] methods = ctClass.getDeclaredMethods();
for (CtMethod method : methods) {
Annotation annotation = getAnnotation(method);
if (annotation != null) {
log.info("Going to Transform the class " + className);
method.insertAfter("System.out.println(\"Logging using Agent\");");
}
}
byteCode = ctClass.toBytecode();
ctClass.detach();
} catch (Throwable ex) {
log.log(Level.SEVERE, "Error in transforming the class: " + className, ex);
}
return byteCode;
}
I wanted to post the answer to my question in case it helps others. I created a singleton TraceTool class with TraceStart and TraceEnd methods.
package com.example.monitoring;
public class TraceTool {
private static TraceTool ins = new TraceTool();
public static TraceTool getInstance() {
return ins;
}
public void traceStart(){
System.out.println("Hello!!!! This is Trace Tool: We intercepted function start!!!!!!");
}
public void traceEnd(){
System.out.println("This is Trace Tool: We intercepted function End! Bye!!!!!");
}
}
Then I inject these methods using javassist.
method.insertAfter("com.example.monitoring.TraceTool.getInstance().traceStart();");
method.insertAfter("com.example.monitoring.TraceTool.getInstance().traceEnd();");
I would like to use Byte Buddy together with OSGi weaving hook.
For instance, it is possible to use Javassist together with OSGi weaving hook like this:
//... other imports
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
#Component (immediate = true)
public class MyWeavingHook implements WeavingHook {
#Activate
public void activate(ComponentContext ctx) {
System.out.print("Activating demo weaving hook...");
}
#Override
public void weave(WovenClass wovenClass) {
System.out.println("Weaving hook called on " + wovenClass.getClassName());
if (wovenClass.getClassName().equals("DecoratedTestServiceImpl")) {
try (InputStream is = new ByteArrayInputStream(wovenClass.getBytes())) {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(is);
ctClass.getDeclaredMethod("ping").setBody("return \"WAIVED\";");
wovenClass.setBytes(ctClass.toBytecode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
How to process the wovenClass with Byte Buddy? I see that I can get the bytecode out like this:
byte[] classBytes = new ByteBuddy()
.subclass(AClass.class)
.name("MyClass")
.method(named("theMethod"))
.intercept(FixedValue.value("Hello World!"))
.make()
.getBytes();
wovenClass.setBytes(classBytes);
But I cannot see how to provide the wovenClass bytecode as an input to Byte Buddy. I would need something like:
new ByteBuddy().rebase(wovenClass.getBytes())...
The rebase method is overloaded and accepts a ClassFileLocator as a second argument. You can provide the class bytes directly by providing an explicit mapping:
ClassFileLocator.Simple.of(wovenClass.getTypeDescription().getName(), wovenClass.getBytes())
I'm writing some Javassist code to intercept method calls and replace them with a proxy. To do that I'm using ExprEditor to replace the call in the following manner:
public static void main(String[] args) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("Test");
CtMethod meth = ctClass.getDeclaredMethod("main");
meth.instrument(new ExprEditor() {
#Override
public void edit(final MethodCall m) throws CannotCompileException {
try {
if (m.getClassName().contains("Functions")) {
/* MAKE NON STATIC
CtClass old = pool.get(m.getClassName());
m.getMethod().setModifiers(Modifier.PUBLIC);
old.toClass();
*/
String s = m.getClassName() + " proxy = (" +
m.getClassName() + ") " + Main.class.getName() + ".create(" + m.getClassName() + ".class);" +
" $_ = proxy." + m.getMethodName() + "($$);";
m.replace(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
Class clazz = ctClass.toClass();
Method method = clazz.getMethod("main", String[].class);
method.invoke(null, new Object[]{new String[]{}});
} catch (InvocationTargetException e) {
e.getCause().printStackTrace();
} catch (NoSuchMethodException | IllegalAccessException | NotFoundException | CannotCompileException e) {
e.printStackTrace();
}
}
This works as desired as long as the method isn't static. Now I'm trying to change static methods to non static through the use of the commented code.
It seems to me that this should work and there are similar usages in the Javassist docs for other modifiers, but when I uncomment and run it I get the following error message:
javassist.CannotCompileException: by java.lang.ClassFormatError: Arguments can't fit into locals in class file Functions/Color
I've also tried to remove the static modifier instead of just setting modifiers to public as such
m.getMethod().setModifiers(m.getMethod().getModifiers() & ~Modifier.STATIC);
But the problem remains.
Is this actually possible?
So you are trying to remove a static modifier from a reserved entry point method name "main" in class Test. I think the compiler won't let you do that, because main is a reserved method name and can only have one predefined signature. And also, static methods are problematic; when called from within the class, if you remove the static modifier, all calls to them would also cause a compilation error, because they were not ment to be instance methods in original code.
I'm not that good in Java but I have my webApplication running on a Wildfly.
I have 3 threads who just call a function that insert logs in in and the function saves the logs to a Database and after that every thread sends a time how long did it takes to do this.
They send the data to another programm I wrote what has 3 threads to call one of the 3 server threads.
So now I try to do some ByteCode Manipulation every thread on the server saves the datetime call the log function waits 1 second and returns then the time they needed.
1 Thread write something in an logfile before or after they waitet 1 second.
But this part where they wait a second and call the log function I want that to inject to every 3 threads with Bytecode manipulation.
public class MyTransformer implements ClassFileTransformer {
#Override
public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
return transformClass(redefiningClass, bytes);
}
private byte[] transformClass(Class classToTransform, byte[] b) {
ClassPool pool = ClassPool.getDefault();
CtClass cl = null;
try {
cl = pool.get("de.soptim.ws.MyApplication");
} catch (javassist.NotFoundException e) {
e.printStackTrace();
}
try {
assert cl != null;
CtMethod[] methods = cl.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].isEmpty() == false) {
changeMethod(methods[i]);
}
}
b = cl.toBytecode();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cl != null) {
cl.detach();
}
}
return b;
}
private void changeMethod(CtMethod method) throws NotFoundException, CannotCompileException {
if (method.hasAnnotation(Loggable.class)) {
method.insertBefore("threadLogger.info(\"ADDED THIS FROM BYTECODE !!!\");");
method.insertAfter("threadLogger.info(\"ADDED THIS FROM BYTECODE !!!\");");
}
}}
Thats my transformer class it should increase the Code my Methods need it checks what Method has an #Loggable annotation an then adds the code into it("at the moment it's just some log statments for checking if it works")
My biggest problem is now that I don't know how to call my agent ... I googled hwo to call an agent at runtime with agentmain() but I think I dind't really understood how it works.
Agent Class
public class LogAgent {
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("Starting the agent");
inst.addTransformer(new MyTransformer());
}}
Hope you understand My problem :) I and if you answere pleas try to stay noob friendly :D.
you don't call your agent explicitly, you should specify additional argument to your JVM :
java -javaagent:jarpath[=options]
where jarpath is a path to jar containing your agent. JVM will invoke premain method before main method of java program.
And transform method will be called before classloading by JVM (you don't call it explicitly).
Last remark : you should implement premain method, not agentmain.
agentmain is used during attaching to running vm, while premain is used when you start JVM with -javaagent method.
And make sure that your jar have valid manifest, as described : https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html
I haven't using javaassit so I cannot say that your code is valid, but instrumenting webapp server like Wildfly is much harder than normal java app (mostly due to classloaders visibility and hierarchy).
See also :
http://www.tomsquest.com/blog/2014/01/intro-java-agent-and-bytecode-manipulation/
Tutorials about javaagents
I've tested on three windows machines, and two linux VPSes, on different versions of Java, both on the OpenJDK & Oracle JDK. It functioned perfectly, and then all of a sudden, it only works in my IDE, though I haven't changed any relevant code, and I can't imagine what can cause this.
Prevalent code in system:
Class<?> cls = (session == null ? secjlcl : session.getJLCL()).loadClass(name);
Logger.log(JavaLoader.class.isAssignableFrom(cls) + " - " + cls + " - " + cls.getSuperclass().getName());
if (JavaLoader.class.isAssignableFrom(cls)) {
And my ClassLoader:
public class JavaLoaderClassLoader extends URLClassLoader {
public JavaLoaderClassLoader(URL[] url, ClassLoader parent) {
super(url);
}
private HashMap<String, Class<?>> javaLoaders = new HashMap<String, Class<?>>();
public String addClass(byte[] data) throws LinkageError {
Class<?> cls = defineClass(null, data, 0, data.length);
javaLoaders.put(cls.getName(), cls);
return cls.getName();
}
public Class<?> loadClass(String name, boolean resolve) {
if (javaLoaders.containsKey(name)) return javaLoaders.get(name);
try {
Class<?> see = super.loadClass(name, resolve);
if (see != null) return see;
}catch (ClassNotFoundException e) {
Logger.logError(e);
}
return null;
}
public void finalize() throws Throwable {
super.finalize();
javaLoaders = null;
}
}
One note, I expect many classloaders to load a different file in the same name/package, so I use separate classloaders to keep them separate, however in testing, that was NOT tested.
Now, this had worked flawlessly in the past, and I have zero clue why it stopped. I would assume I broke something, but the code still works in my IDE?
This appears to be your bug:
public JavaLoaderClassLoader(URL[] url, ClassLoader parent) {
super(url);
}
You aren't installing parent as the parent class loader through the super constructor.