6

With Javassist, is there any way to inject code into a native method? In this case, I'm trying to make the OpenGL calls in my game print out their names and values when called, but all my attempts have hit errors when I assume the openGL dll code is added.

The method would look something like:

public static native void glEnable(int paramInt);

Since the methods initially have no body, the only way I've found to actually add the code is with something like:

CtBehavior method = cl.getDeclaredBehaviors()[0];
method.setBody("System.out.println(\"Called.\");");

The injection itself works, but then the system fails once the library is loaded saying that the method already has code.

I'd rather not use any premade tools for the call tracking, because of the way I need to format and print out the list for the user. Is there any way to handle this? If not, is there some way to find all calls to an OpenGL method within another class and append an additional call to a tracker class?

Felix
  • 113
  • 1
  • 6

3 Answers3

4

I know it is a little offtopic (2 years later), but if somebody was interested, I think it can be done with setNativeMethodPrefix() method and adequate bytecode transformation.

zerocool
  • 103
  • 1
  • 7
3

I know this thread is old and has accepted answer and hint to my answer as well but I suppose what I can save someone's time with more details and code. I wish someone did it to me some time ago =)

I tried to wrap java.lang.Thread#sleep function. I found examples with bytebuddy but with method replacement and no examples for javassist. Finally I did it. First step is to register transformer and setup prefix:

instrumentation.addTransformer(transformer, true);
instrumentation.setNativeMethodPrefix(transformer, "MYPREFIX_");

Then inside transformer modify Thread class:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    if (!"java/lang/Thread".equals(className)) {
        return null;
    }
    try {
        // prepare class pool with classfileBuffer as main source
        ClassPool cp = new ClassPool(true);
        cp.childFirstLookup = true;
        cp.insertClassPath(new ByteArrayClassPath(Thread.class.getName(), classfileBuffer));
        CtClass cc = cp.get(Thread.class.getName());

        // add native method with prefix
        CtMethod nativeSleep = CtMethod.make("void MYPREFIX_sleep(long millis) throws InterruptedException;", cc);
        nativeSleep.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.NATIVE);
        cc.addMethod(nativeSleep);

        // replace old native method
        CtMethod m = cc.getDeclaredMethod("sleep", new CtClass[]{CtClass.longType});
        m.setModifiers(Modifier.PUBLIC + Modifier.STATIC); // remove native flag
        m.setBody("{" +
                "System.out.println(\"Start sleep\");" +
                "MYPREFIX_sleep($1);" +
                "}");
        byte[] byteCode = cc.toBytecode();
        cc.detach();
        return byteCode;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

Then start transformation:

instrumentation.retransformClasses(Thread.class);

One of problems with classes like java.lang.Thread is that in general You cannot invoke your code from modified method unless You push something in bootstrap classloader.

See also https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#setNativeMethodPrefix-java.lang.instrument.ClassFileTransformer-java.lang.String- for details how jvm resolves method.

ainlolcat
  • 544
  • 4
  • 15
  • ainlolcat, how did you get the java/lang/Thread class to show up? Since this class is loaded by the sytem boot-class loader. Are you doing some trickery with the book-class-path? I would love to see your instrumentation agent setup code. – ril3y Apr 16 '21 at 21:29
  • 1
    @ril3y I dont see any missing essential parts in my setup. there is one important note - since java 13 this code does not work without option `-XX:+AllowRedefinitionToAddDeleteMethods`. This option is marked as deprecated and can be removed but still available in J17. – ainlolcat Jul 19 '22 at 06:01
  • 1
    @ril3y may be You mentioned problems with access from injected code to agent? It cannot access agent static method because it cannot access class from system class loader, yes. To solve this I inject simple class with static runnable/callable field in bootstrap classloader via Unsafe.defineClass with classloader null and then call it from new body. Then I inject from system CL runnable implementation into that simple class. It can access classes from system CL. Make sure You use shared ClassPool or make new class visible during Thread compilation. – ainlolcat Jul 20 '22 at 09:21
1
With Javassist, is there any way to inject code into a native method?

Never tried it, but I am not surprised it does not work. Native code is - native. It's a bunch of platform specific bits that bears no relation to Java byte code. And Javassist is all about Java byte code.

Have you consider using proxy based AOP? Check out http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

I'm not recommending you actually use Spring in your program but it might give you some ideas on how to approach the problem. The reason I think proxy-based AOP might work for you is that you leave your OpenGL based class alone and it just uses the normal native methods. You generate a proxy class which is pure Java but has the same methods as your original class. You call methods on the proxy class which contain the desired call tracking code, plus the invocation of the corresponding method on the "plain object" with it's native methods.

The documentation in Spring says they use JDK dynamic proxies or CGLIB under the covers. So ... I'm thinking that you could use one of these technologies directly as a replacement for your javassist solution.

Hope this helps.

[update]

In the text above I thought you were talking about a class written by you which had primarily instance methods. If you are talking about wrapping the entire OpenGL API, which is primarily static methods, then the AOP proxy method is less appealing. How bad do you want to do this? You could:

  • create a custom class - a singleton class with a factory method. Your singleton class wraps the entire OpenGL API. No logging/tracking code. Just naked calls to the API.
  • modify every single call in your entire app to use your wrapper, instead of calling OpenGL directly

At this point you have an application that works exactly like what you have now.

Now enhance the factory method of your singleton class to return either the bare-bones instance which does nothing except OpenGL calls, or it can return a CGLIB generated proxy which logs every method. Now your app can run in either production mode (fast) or tracking mode depending on some config setting.

And I totally get it if you want to give this up and move on :)

Guido Simone
  • 7,912
  • 2
  • 19
  • 21
  • Sounds interesting. Is there any good way to dynamically generate a proxy class, or would this require editing all my code to reference a premade proxy? – Felix Sep 30 '12 at 21:40
  • Well, I just found a cglib tutorial at http://markbramnik.blogspot.com/2010/04/cglib-introduction.html which will give you an idea of what you would be in for. Based on the tutorial, I think the answer is "yes" it does dynamically generate the proxy class. – Guido Simone Sep 30 '12 at 21:57
  • The generation of the proxy is one thing, but this looks like it'd require that I rewrite every line of code in my game to generate the proxy and call that instead of the OpenGL class. Perhaps I need a different approach. Is there some way to find all calls to an OpenGL method and append an additional call to a tracker class? – Felix Sep 30 '12 at 22:49