Skip to content

Instantly share code, notes, and snippets.

@raphw
Last active January 11, 2020 21:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save raphw/be0994259e75652f057c9e1d3ee5f567 to your computer and use it in GitHub Desktop.
Save raphw/be0994259e75652f057c9e1d3ee5f567 to your computer and use it in GitHub Desktop.
Example of a shadingproof JNI binding.
package com.shadeproof;
import net.bytebuddy.ByteBuddy;
public abstract class NativeBinding {
public static final NativeBinding INSTANCE;
static {
System.load("mylibrary");
String expected = "!com!shadeproof!NativeBinding!";
expected = expected.substring(1, expected.length() - 1).replace('!', '.');
if (NativeBinding.class.getName().equals(expected)) {
INSTANCE = new DirectNativeBinding();
} else {
try {
INSTANCE = new ByteBuddy()
.redefine(DirectNativeBinding.class)
.name(expected)
.make()
.load(NativeBinding.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getConstructor()
.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
public abstract void method();
private static class DirectNativeBinding extends NativeBinding {
public native void method();
}
}
@felixbarny
Copy link

Seems like a pretty neat trick! However, I can't make it to work. I've tried it with the async-profiler Java API but had no luck. This is a minimal example:

package shaded.one.profiler;

import net.bytebuddy.ByteBuddy;

public abstract class AsyncProfiler {

    private static AsyncProfiler instance;
    private final String version;

    public AsyncProfiler() {
        this.version = version0();
    }

    // 1st arg should be the path to libasyncProfiler.so which you can find in the release binaries
    // https://github.com/jvm-profiling-tools/async-profiler#download
    public static void main(String[] args) throws Exception {
        AsyncProfiler asyncProfiler = AsyncProfiler.getInstance(args[0]);
        System.out.println(asyncProfiler.getVersion());
    }

    public static synchronized AsyncProfiler getInstance(String libPath) {
        if (instance == null) {
            System.load(libPath);
            instance = newInstance();
        }
        return instance;
    }

    private static AsyncProfiler newInstance() {
        try {
            return new ByteBuddy()
                .redefine(DirectNativeBinding.class)
                .name("one.profiler.AsyncProfiler")
                .make()
                .load(AsyncProfiler.class.getClassLoader())
                .getLoaded()
                .getConstructor()
                .newInstance();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    public String getVersion() {
        return version;
    }

    public abstract String version0();

    public static class DirectNativeBinding extends AsyncProfiler {
        public native String version0();
    }
}

But unfortunately, I still get an UnsatisfiedLinkError

Exception in thread "main" java.lang.IllegalStateException: java.lang.reflect.InvocationTargetException
	at shaded.one.profiler.AsyncProfiler.newInstance(AsyncProfiler.java:44)
	at shaded.one.profiler.AsyncProfiler.getInstance(AsyncProfiler.java:28)
	at shaded.one.profiler.AsyncProfiler.main(AsyncProfiler.java:21)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at shaded.one.profiler.AsyncProfiler.newInstance(AsyncProfiler.java:42)
	... 2 more
Caused by: java.lang.UnsatisfiedLinkError: one.profiler.AsyncProfiler.version0()Ljava/lang/String;
	at one.profiler.AsyncProfiler.version0(Native Method)
	at shaded.one.profiler.AsyncProfiler.<init>(AsyncProfiler.java:17)
	at one.profiler.AsyncProfiler.<init>(AsyncProfiler.java:54)
	... 7 more

Do you have any clue what I could have done wrong? When not shading it and instantiating normally it works as expected. I've tried with JDK 11.0.2.

@raphw
Copy link
Author

raphw commented Dec 7, 2019

Lacks ClassLoadingStrategy.Default.INJECTION, loading native code only works for the same class loader, the hierarchy is not applied for those.

@felixbarny
Copy link

ClassLoadingStrategy.Default.INJECTION does not work if the target class loader contains a non-shaded version as well as a shaded version, therefore ClassLoadingStrategy.Default.CHILD_FIRST seems more appropriate. To make sure the native library is loaded from the child class loader, it should be loaded form the static initializer of the DirectNativeBinding class. See also https://gist.github.com/felixbarny/3f519ac49520018aa8ecc82cae352670

@raphw
Copy link
Author

raphw commented Jan 11, 2020

Indeed, have not considered that case. Thanks for the hint, I'll keep it in the back in my head if I run into that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment