Instrumenting sun.misc.Unsafe

26 views
Skip to first unread message

paolo.d...@gmail.com

unread,
Aug 19, 2024, 6:17:07 PM8/19/24
to Byte Buddy
I wonder if it's possible to instrument the class `sun.misc.Unsafe` to trace the usage of off-head memory made by a Java app. 

So far I've tried the following: 

```
public class UnsafeTracerAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.err.println("=== Starting UnsafeTracerAgent premain ===");
        new AgentBuilder.Default()
                .ignore(ElementMatchers.none()) // Ensure we intercept Unsafe which is a bootstrap class
                .type(ElementMatchers.named("sun.misc.Unsafe"))  // Target the Unsafe class
                .transform((builder, typeDescription, classLoader, module, domain) -> {
                        System.err.println("*** Transforming: " + typeDescription.getName());
                        return builder.method(ElementMatchers.named("allocateMemory")
                                        .or(ElementMatchers.named("freeMemory"))) // Target specific methods
                                .intercept(Advice.to(UnsafeTracerAdvice.class));
                })
                .installOn(inst);
    }

 ... 

```

When running it, the agent is installed and the premain invoked, however no transformation is applied when accessing to Unsafe methods. 


Any idea what's wrong? 


Thanks! 

Rafael Winterhalter

unread,
Aug 19, 2024, 6:42:28 PM8/19/24
to paolo.d...@gmail.com, Byte Buddy
You should apply the advice as a visitor:

builder.visit(Advice.to(...).on(...))

At the same time, you will need to enable RetransformationStrategy.RETRANSFORM and set disableClassFormatChanges().

To discover errors, you can register an AgentBuilder.Listener which is notified if a class cannot be transformed.

Best regards, Rafael

--
You received this message because you are subscribed to the Google Groups "Byte Buddy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/byte-buddy/4f3a78c8-5881-45a8-ad69-0cd79d025fd2n%40googlegroups.com.

paolo.d...@gmail.com

unread,
Aug 20, 2024, 2:17:07 AM8/20/24
to Byte Buddy
Interesting! made some progress, now I can see the class "sun.misc.Unsafe" being transformed, however the advice is not applied to any method.

```
public class UnsafeTracerAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.err.println("=== Starting UnsafeTracerAgent premain ===");
        new AgentBuilder.Default()
                .ignore(ElementMatchers.none())
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges()
                .type(ElementMatchers.named("sun.misc.Unsafe"))

                .transform((builder, typeDescription, classLoader, module, domain) -> {
                    System.out.println("Transforming: " + typeDescription.getName());
                    return builder
                            .visit(Advice.to(MyAdvice.class)
                                    .on(ElementMatchers.any()));
                })
                .installOn(inst);
    }

    public static class MyAdvice {

        @Advice.OnMethodEnter
        public static void onEnter(@Advice.Origin String method, @Advice.AllArguments Object[] args) {
            System.err.println("Intercepted method: " + method + " with arguments: " + java.util.Arrays.toString(args));
        }

        @Advice.OnMethodExit
        public static void onExit(@Advice.Origin String method, @Advice.Return Object returnValue) {
            System.err.println("Exiting method: " + method + " with return value: " + returnValue);
        }
    }
}
```

Any tip is appreciated . 

Rafael Winterhalter

unread,
Aug 20, 2024, 6:04:09 AM8/20/24
to paolo.d...@gmail.com, Byte Buddy
Are those methods native? If so, they cannot be instrumented. In this case, have  look at MemberSubstitution. This would allow you to instrument the caller rather than the callee.

paolo.d...@gmail.com

unread,
Aug 20, 2024, 6:14:39 AM8/20/24
to Byte Buddy
Not really. I've made a little test app that access `Unsafe.getUnsafe().pageSize()` , `allocateMemory` and `freeMemory` 

Looking at the source code: 

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

    @ForceInline
    public int pageSize() {
        return theInternalUnsafe.pageSize();
    }

    @ForceInline
    public long allocateMemory(long bytes) {
        return theInternalUnsafe.allocateMemory(bytes);
    }

    @ForceInline
    public void freeMemory(long address) {
        theInternalUnsafe.freeMemory(address);
    }



The use of `@ForceInline` is suspect, but at least I should see the invocation for the `getUnsafe` method.

Thanks for looking into this. 

paolo.d...@gmail.com

unread,
Aug 20, 2024, 6:21:45 AM8/20/24
to Byte Buddy

Rafael Winterhalter

unread,
Aug 20, 2024, 6:44:10 AM8/20/24
to paolo.d...@gmail.com, Byte Buddy
If you set a breakpoint while having the agent active, can you see that it is triggered?

You can also set -Dnet.bytebuddy.dump=/some/folder and use javap to compare original and instrumented byte code. If Byte Buddy succeeded, you should see that the method implementation has changed in the target.

paolo.d...@gmail.com

unread,
Aug 20, 2024, 7:22:38 PM8/20/24
to Byte Buddy
Thanks Rafael, an update on this. Finally I've managed to intercept the method `Unsafe.allocateMemory` with this agent configuration: 
 
```

        new AgentBuilder.Default()
                .ignore(ElementMatchers.none())
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges()
                .type(ElementMatchers.named("sun.misc.Unsafe"))
                .transform((builder, typeDescription, classLoader, module, domain) -> {
                    return builder
                            .visit(Advice.to(MyAdvice.class) .on(named("allocateMemory").and(isPublic())))
                            ;
                })
                .installOn(inst);
```

Now, I'd like to also intercept the method `freeMemory` along with `allocateMemory`. 
I've tried using the snippet below, but using that it does *not* catch `freeMemory` nor `allocateMemory` anymore:
 
```
           named("allocateMemory")
             .or(named("freeMemory"))
             .and(isPublic()))

```


What's the correct syntax to intercept more than one method? 

paolo.d...@gmail.com

unread,
Aug 20, 2024, 8:15:59 PM8/20/24
to Byte Buddy
OK, the problem was that the `freeMemory` does return a void, and instead the advice method was declaring an Object as return value

paolo.d...@gmail.com

unread,
Aug 20, 2024, 9:12:06 PM8/20/24
to Byte Buddy
Next challenge is using an around advice instead of using a `OnEnter` and `OnExit` separate advice methods. 

The following code works: 

```
public class UnsafeTracerAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.err.println("=== Starting UnsafeTracerAgent premain ===");

        new AgentBuilder.Default()
                .ignore(ElementMatchers.none())
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges()
                .type(ElementMatchers.named("sun.misc.Unsafe"))
                .transform((builder, typeDescription, classLoader, module, domain) -> builder
                        .method(ElementMatchers.named("allocateMemory")
                                .and(ElementMatchers.isPublic()))
                        .intercept(MethodDelegation.to(AllocMemWrapper.class)))
                .with(new AgentBuilder.Listener.StreamWriting(System.out))
                .installOn(inst);
    }

    static public class AllocMemWrapper {
        @RuntimeType
        public static Long intercept(@Argument(0) long value) throws Exception {
            System.out.println("== Before method with argument: " + value);
            //long result = zuper.call();  // Invoke the original method
            System.out.println("== After method with result: " + 100);
            return 100L;
        }
    }
}
```  

However when adding the `@SuperCall Callable<Long> zuper` argument in the intercept signature, it fails with this error 

```
java.lang.IllegalArgumentException: None of [public static java.lang.Long io.seqera.debug.UnsafeTracerAgent$AllocMemWrapper.intercept(java.util.concurrent.Callable,long) throws java.lang.Exception] allows for delegation from public long sun.misc.Unsafe.allocateMemory(long)
at net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor.bind(MethodDelegationBinder.java:1099)
at net.bytebuddy.implementation.MethodDelegation$Appender.apply(MethodDelegation.java:1349)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:730)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:715)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$WithFullProcessing$RedefinitionClassVisitor$CodePreservingMethodVisitor.visitCode(TypeWriter.java:5502)
at net.bytebuddy.jar.asm.ClassReader.readMethod(ClassReader.java:1511)
```

Not sure to understand the reason. Code here for reference

Rafael Winterhalter

unread,
Aug 21, 2024, 3:03:45 PM8/21/24
to paolo.d...@gmail.com, Byte Buddy

You will have to use advice for this.

You can communicate values from the enter to the exit advice. The concept is needed to create byte code through the compiler that can be inlined.


Paolo Di Tommaso

unread,
Aug 21, 2024, 3:41:36 PM8/21/24
to Rafael Winterhalter, Byte Buddy
 

You can communicate values from the enter to the exit advice. The concept is needed to create byte code through the compiler that can be inlined.


Do you mean by using some Byte buddy manipulation? Is there any example that could be used as a reference?

Thank you 
  

Rafael Winterhalter

unread,
Aug 21, 2024, 3:45:12 PM8/21/24
to Paolo Di Tommaso, Byte Buddy

Look at Enter and LocalValue. The javadoc also has some info.


--
You received this message because you are subscribed to the Google Groups "Byte Buddy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages