Home > Mobile >  How to debug a ByteBuddy instrumentation?
How to debug a ByteBuddy instrumentation?

Time:01-02

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:

  • issue with the type matcher?
  • issue with the method matcher?
  • instrumented class not reloaded correctly?
  • in the wrong ClassLoader?
  • ...?

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.

CodePudding user response:

I assume that FileInputStream is already loaded. You would need to define a RetransformationStrategy to capture types that are already loaded.

For debugging issues like this, it is recommended to add a Listener which will display instrumented types.

CodePudding user response:

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.

  • Related