#ReflectiveAccessByInstrumentationAgents

Peter Levart peter.levart at gmail.com
Thu Apr 21 11:09:52 UTC 2016


Hi Andrew,

On 04/21/2016 09:31 AM, Andrew Dinn wrote:
>
> On 20/04/16 12:40, Alan Bateman wrote:
>> On 19/04/2016 18:00, Andrew Dinn wrote:
>>> :
>>> I have been building and testing on JDK9 Byteman using the EA program
>>> releases and have not seen anything break so far. However, that may just
>>> be because i) Byteman and the Byteman tests are only using runtime
>>> classes from the java base module and ii) app code used in tests is not
>>> modularized -- well, at least not using Jigsaw (see below).
>> It's good to hear that you are testing with the EA builds.
>>
>> I think the main thing for Byteman, and this will be true at least some
>> other agents too, is that there will likely need to be updated to
>> support instrumentation with modules. We have a reasonable compatibility
>> story for existing JVM TI and java agents but as soon as you get into
>> instrumenting code to statically or reflectively access code in other
>> modules then it may require the agent to arrange for this access to be
>> allowed.
> Having to explicitly manage module dependencies to retain compatibiity
> with previous behaviour is at the very least a /nuisance/ because it now
> means I -- and, no doubt, others in the agent business -- will have to
> implement a dual-source agent, one for JDK9(+) and another to continue
> to operate in JDK8/7/6 (yes, people can and do still use the latest
> Byteman release in all those JDK releases). A solution internal to the
> JVM which preserved existing behaviour and so did not require the
> insertion of JDK-specific jumps through JDK-specific hoops would be much
> preferred. Still, I suppose the releavant implementors are a relatively
> small crowd and ought to know what we are doing and how to fix it.
>
> That minor rant aside (sorry, I feel better for that but I don't suppose
> anyone else on this list does :-) I will happily investigate the
> available dynamic module-management APIs and see what options exist to
> remedy the changes to the status quo and retain as much backwards
> compatibility as I can. At that point I will come back and comment on
> the very useful advice you have provided below. Thanks very much for
> your help so far.
>
> regards,
>
>
> Andrew Dinn


The situation is not so complicated, I think. If instrumented code calls 
some code in your Byteman module(s) then you should 1st make this code 
public and reside in exported packages. All you have to do then is add 
read edges from modules of instrumented classes to modules of Byteman 
classes that are called by instrumented code as they are being 
instrumented. For example, having the following utility class compiled 
with JDK 8 (not using any JDK 9 API):

public class ClassFileTransformerWrapper {
     public ClassFileTransformer wrap(Instrumentation instrumentation,
                                      ClassFileTransformer transformerImpl,
                                      Class<?>... readableClasses) {
         try {
             Class.forName("java.lang.reflect.Module");
         } catch (ClassNotFoundException e) {
             // don't need to wrap if on JDK 8 or less
             return transformerImpl;
         }
         // wrap it
         return new ReadEdgeAddingClassFileTransformer(instrumentation,
transformerImpl,
readableClasses);
     }
}

...which you call with your ClassFileTransformer implementation and the 
Class(es) where your Byteman code resides that contains methods called 
from instrumented code. The following delegating ClassFileTransformer, 
compiled with -target 9, will add read edges then:

public class ReadEdgeAddingClassFileTransformer implements 
ClassFileTransformer {

     private final Instrumentation instrumentation;
     private final ClassFileTransformer transformerImpl;
     private final Set<Module> readableModules;

     ReadEdgeAddingClassFileTransformer(Instrumentation instrumentation,
                                        ClassFileTransformer 
transformerImpl,
                                        Class<?>[] readableClasses) {
         this.instrumentation = instrumentation;
         this.transformerImpl = transformerImpl;
         this.readableModules = new HashSet<>();
         for (Class<?> c : readableClasses) {
             this.readableModules.add(c.getModule());
         }
     }

     @Override
     public byte[] transform(ClassLoader loader,
                             String className,
                             Class<?> classBeingRedefined,
                             ProtectionDomain protectionDomain,
                             byte[] classfileBuffer) throws 
IllegalClassFormatException {
         return transformerImpl.transform(loader,
                                          className,
                                          classBeingRedefined,
                                          protectionDomain,
                                          classfileBuffer);
     }

     @Override
     public byte[] transform(Module module,
                             String className,
                             Class<?> classBeingRedefined,
                             ProtectionDomain protectionDomain,
                             byte[] classfileBuffer) throws 
IllegalClassFormatException {
         byte[] transformedClassFile = transformerImpl.transform(module,
className,
classBeingRedefined,
protectionDomain,
classfileBuffer);
         // only need to add read edges if there was a transformation 
performed
         if (transformedClassFile != null) {
             for (Module target : readableModules) {
                 if (!module.canRead(target)) {
                     instrumentation.addModuleReads(module, target);
                 }
             }
         }
         return transformedClassFile;
     }
}


This class is the only class that needs to be compiled with JDK 9 actually.


Regards, Peter



More information about the jigsaw-dev mailing list