javadebugginginstrumentationbyte-buddy

How to debug a ByteBuddy instrumentation?


I have defined a instrumentation of the FileInputStream constructor using ByteBuddy AgentBuilder.Identified.Extendable. It looks like it has no effect. I was expecting the onEnter method to be called, but it is not.

I have several ideas about the possible cause of the issue:

How can I debug this situation and understand the cause of the problem?

Here is the source code:

package com.example.javaagent.instrumentation;

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import static net.bytebuddy.matcher.ElementMatchers.*;


public class FileInputStreamConstructorInstrumentationTests {

    @Test
    public void testByteBuddyfileInputStreamConstructor() {
        ByteBuddyAgent.install();

        AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default()
                .type(named("java.io.FileInputStream"))
                .transform(new AgentBuilder.Transformer.ForAdvice());

        extendableAgentBuilder = extendableAgentBuilder.transform(
                new AgentBuilder.Transformer.ForAdvice()
                        .advice(
                                isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)),
                                this.getClass().getName() + "$FileInputStreamCtorString"));

        extendableAgentBuilder.installOnByteBuddyAgent();

        File file = new File("example.txt");
        createFile(file);
        try {
            // Open a file input stream to read from a file
            FileInputStream inputStream = new FileInputStream(file);

            // Read the contents of the file and print them to the console
            int data = inputStream.read();
            while (data != -1) {
                System.out.print((char) data);
                data = inputStream.read();
            }

            // Close the input stream to release system resources
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        deleteFile(file);
    }


    public static class FileInputStreamCtorString {
        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void onEnter(@Advice.Argument(0) String name) {
            System.out.println("entered " + name);
        }
    }

    private static void deleteFile(File file) {
        // Check if the file exists
        if (file.exists()) {
            // If the file exists, delete it
            if (file.delete()) {
                System.out.println("File deleted successfully");
            } else {
                System.out.println("Failed to delete file");
            }
        } else {
            System.out.println("File does not exist");
        }
    }


    private static void createFile(File file) {
        // Check if the file already exists
        if (!file.exists()) {
            // If the file does not exist, create a new file
            try {
                file.createNewFile();
                System.out.println("File created successfully");
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("File already exists");
        }
    }

}

I tried to make the instrumentation using directly a ByteBuddy instance, and it works as expected. Like in this example:

ByteBuddyAgent.install();
Foo foo = new Foo();
new ByteBuddy()
        .redefine(Bar.class)
        .name(Foo.class.getName())
        .make()
        .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
assertEquals(foo.m(), "bar");

I probably misunderstand how to use correctly the AgentBuilder.


Solution

  • How about this?

    new AgentBuilder.Default()
      .disableClassFormatChanges()
      .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
      .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
      .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
      .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
      .ignore(none())
      .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
      .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
      .type(named("java.io.FileInputStream"))
      .transform((builder, type, classLoader, module) -> builder
        .visit(Advice
          .to(FileInputStreamCtorString.class)
          .on(isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)))
        )
      )
      .installOnByteBuddyAgent();
    

    You get some logging listeners there. Please also note details like .disableClassFormatChanges() and .ignore(none()). Furthermore, I changed the way the advice is applied.

    Please also change your sample code to actually use the FileInputStream(String) constructor you want to intercept, not FileInputStream(File):

    // Open a file input stream to read from a file
    FileInputStream inputStream = new FileInputStream(/*file*/ "example.txt");
    

    For me this works and logs:

    [Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@6f1fba17 on sun.instrument.InstrumentationImpl@185d8b6
    [Byte Buddy] TRANSFORM java.io.FileInputStream [null, null, Thread[main,5,main], loaded=true]
    [Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@6f1fba17 on sun.instrument.InstrumentationImpl@185d8b6
    File already exists
    entered example.txt
    [Example file content]
    File deleted successfully
    

    Not being a Byte Buddy expert, I simply copied code from another example I had used a few years ago. I do not fully understand all options, I just remember that I needed them for some cases. In this example, the transformation also seems to work without this part:

      .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
      .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
    

    Rafael can explain all of this much better.