简体   繁体   中英

OutputStream Instrumentation: Read byte Array from write Method

For a monitoring project I try to instrument the OutputStream class with Javassist. I would like to make the OutputStream write() method print whatever byte[] is given to it (encoding is a different problem and does not matter here). My problem is that replacing the OutputStream class during runtime with an agent crashes the JVM. Using Javassist to just manipulate the Bytecode fails as somehow Javassist ClassPool.get("java.io.OutputStream") also crashes the JVM.

Is that class so untouchable? The same approach worked fine for the URI class.

Is there another way of doing what I would like or is my only option to replace the rt.jar in the JRE?

@Override
public byte[] transform(ClassLoader loader, String className,
        Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
        byte[] classfileBuffer) {
        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass cc = pool.get("java.io.OutputStream");
            CtMethod method =
                    Arrays.stream(cc.getDeclaredMethods()).filter(ctMethod -> ctMethod.getLongName().equals("java.io.OutputStream.write(byte[],int,int)"))
                            .findFirst().get();
            method.insertBefore("System.out.println(new String($1, java.nio.charset.StandardCharsets.UTF_8));");
            return cc.toBytecode();
        } catch (NotFoundException | CannotCompileException | IOException e) {
            e.printStackTrace();
        }
    return null;
}

produces the output:

Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication (wrong name: java/io/OutputStream)

because I overwrite the very first loaded class with my "new" OutputStream already. The OutputStream class however is never called into the transform method...

The transform method is a callback function that may get invoked for every loaded class. You receive the class name in the className parameter and the original definition in the classfileBuffer .

So the first thing you have to do, is to check whether current invocation really corresponds to a class you want to transform. If not, just return null . You don't do this, but return a transformed version of java.io.OutputStream regardless of which class you were requested to transform. That's why you get the error. You were requested to transform the class org/springframework/boot/SpringApplication but return a class named java/io/OutputStream . Note the “wrong name” in the message. Of course, this mismatching class would cause even more problems without that name verification.

After checking that the current invocation is responsible for java.io.OutputStream , you should use the provided classfileBuffer for reading the original class version, instead of using ClassPool.getDefault().get(…) , as the ClassPool does not know the version of the class you received in the parameter and might find a wrong version of it or be unable to access the class al all.

But note that instrumenting the method OutputStream.write(byte[],int,int) alone is unlikely to be sufficient. As its documentation says:

Subclasses are encouraged to override this method and provide a more efficient implementation.

This happens in practice in all relevant implementation classes. To record the invocations of the overriding methods you have to instrument them as well. Also mind that subclasses may override write(byte[]) with an implementation that does not delegate to write(byte[],int,int) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM