1 /*
2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package sun.instrument;
28 
29 import java.lang.reflect.Method;
30 import java.lang.reflect.AccessibleObject;
31 
32 import java.lang.instrument.ClassFileTransformer;
33 import java.lang.instrument.ClassDefinition;
34 import java.lang.instrument.Instrumentation;
35 
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.security.ProtectionDomain;
39 
40 import java.util.jar.JarFile;
41 
42 /*
43  * Copyright 2003 Wily Technology, Inc.
44  */
45 
46 /**
47  * The Java side of the JPLIS implementation. Works in concert with a native JVMTI agent
48  * to implement the JPLIS API set. Provides both the Java API implementation of
49  * the Instrumentation interface and utility Java routines to support the native code.
50  * Keeps a pointer to the native data structure in a scalar field to allow native
51  * processing behind native methods.
52  */
53 public class InstrumentationImpl implements Instrumentation {
54     private final     TransformerManager      mTransformerManager;
55     private           TransformerManager      mRetransfomableTransformerManager;
56     // needs to store a native pointer, so use 64 bits
57     private final     long                    mNativeAgent;
58     private final     boolean                 mEnvironmentSupportsRedefineClasses;
59     private volatile  boolean                 mEnvironmentSupportsRetransformClassesKnown;
60     private volatile  boolean                 mEnvironmentSupportsRetransformClasses;
61     private final     boolean                 mEnvironmentSupportsNativeMethodPrefix;
62 
63     private
InstrumentationImpl(long nativeAgent, boolean environmentSupportsRedefineClasses, boolean environmentSupportsNativeMethodPrefix)64     InstrumentationImpl(long    nativeAgent,
65                         boolean environmentSupportsRedefineClasses,
66                         boolean environmentSupportsNativeMethodPrefix) {
67         mTransformerManager                    = new TransformerManager(false);
68         mRetransfomableTransformerManager      = null;
69         mNativeAgent                           = nativeAgent;
70         mEnvironmentSupportsRedefineClasses    = environmentSupportsRedefineClasses;
71         mEnvironmentSupportsRetransformClassesKnown = false; // false = need to ask
72         mEnvironmentSupportsRetransformClasses = false;      // don't know yet
73         mEnvironmentSupportsNativeMethodPrefix = environmentSupportsNativeMethodPrefix;
74     }
75 
76     public void
addTransformer(ClassFileTransformer transformer)77     addTransformer(ClassFileTransformer transformer) {
78         addTransformer(transformer, false);
79     }
80 
81     public synchronized void
addTransformer(ClassFileTransformer transformer, boolean canRetransform)82     addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
83         if (transformer == null) {
84             throw new NullPointerException("null passed as 'transformer' in addTransformer");
85         }
86         if (canRetransform) {
87             if (!isRetransformClassesSupported()) {
88                 throw new UnsupportedOperationException(
89                   "adding retransformable transformers is not supported in this environment");
90             }
91             if (mRetransfomableTransformerManager == null) {
92                 mRetransfomableTransformerManager = new TransformerManager(true);
93             }
94             mRetransfomableTransformerManager.addTransformer(transformer);
95             if (mRetransfomableTransformerManager.getTransformerCount() == 1) {
96                 setHasRetransformableTransformers(mNativeAgent, true);
97             }
98         } else {
99             mTransformerManager.addTransformer(transformer);
100         }
101     }
102 
103     public synchronized boolean
removeTransformer(ClassFileTransformer transformer)104     removeTransformer(ClassFileTransformer transformer) {
105         if (transformer == null) {
106             throw new NullPointerException("null passed as 'transformer' in removeTransformer");
107         }
108         TransformerManager mgr = findTransformerManager(transformer);
109         if (mgr != null) {
110             mgr.removeTransformer(transformer);
111             if (mgr.isRetransformable() && mgr.getTransformerCount() == 0) {
112                 setHasRetransformableTransformers(mNativeAgent, false);
113             }
114             return true;
115         }
116         return false;
117     }
118 
119     public boolean
isModifiableClass(Class<?> theClass)120     isModifiableClass(Class<?> theClass) {
121         if (theClass == null) {
122             throw new NullPointerException(
123                          "null passed as 'theClass' in isModifiableClass");
124         }
125         return isModifiableClass0(mNativeAgent, theClass);
126     }
127 
128     public boolean
isRetransformClassesSupported()129     isRetransformClassesSupported() {
130         // ask lazily since there is some overhead
131         if (!mEnvironmentSupportsRetransformClassesKnown) {
132             mEnvironmentSupportsRetransformClasses = isRetransformClassesSupported0(mNativeAgent);
133             mEnvironmentSupportsRetransformClassesKnown = true;
134         }
135         return mEnvironmentSupportsRetransformClasses;
136     }
137 
138     public void
retransformClasses(Class<?>.... classes)139     retransformClasses(Class<?>... classes) {
140         if (!isRetransformClassesSupported()) {
141             throw new UnsupportedOperationException(
142               "retransformClasses is not supported in this environment");
143         }
144         retransformClasses0(mNativeAgent, classes);
145     }
146 
147     public boolean
isRedefineClassesSupported()148     isRedefineClassesSupported() {
149         return mEnvironmentSupportsRedefineClasses;
150     }
151 
152     public void
redefineClasses(ClassDefinition... definitions)153     redefineClasses(ClassDefinition...  definitions)
154             throws  ClassNotFoundException {
155         if (!isRedefineClassesSupported()) {
156             throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
157         }
158         if (definitions == null) {
159             throw new NullPointerException("null passed as 'definitions' in redefineClasses");
160         }
161         for (int i = 0; i < definitions.length; ++i) {
162             if (definitions[i] == null) {
163                 throw new NullPointerException("element of 'definitions' is null in redefineClasses");
164             }
165         }
166         if (definitions.length == 0) {
167             return; // short-circuit if there are no changes requested
168         }
169 
170         redefineClasses0(mNativeAgent, definitions);
171     }
172 
173     @SuppressWarnings("rawtypes")
174     public Class[]
getAllLoadedClasses()175     getAllLoadedClasses() {
176         return getAllLoadedClasses0(mNativeAgent);
177     }
178 
179     @SuppressWarnings("rawtypes")
180     public Class[]
getInitiatedClasses(ClassLoader loader)181     getInitiatedClasses(ClassLoader loader) {
182         return getInitiatedClasses0(mNativeAgent, loader);
183     }
184 
185     public long
getObjectSize(Object objectToSize)186     getObjectSize(Object objectToSize) {
187         if (objectToSize == null) {
188             throw new NullPointerException("null passed as 'objectToSize' in getObjectSize");
189         }
190         return getObjectSize0(mNativeAgent, objectToSize);
191     }
192 
193     public void
appendToBootstrapClassLoaderSearch(JarFile jarfile)194     appendToBootstrapClassLoaderSearch(JarFile jarfile) {
195         appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), true);
196     }
197 
198     public void
appendToSystemClassLoaderSearch(JarFile jarfile)199     appendToSystemClassLoaderSearch(JarFile jarfile) {
200         appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), false);
201     }
202 
203     public boolean
isNativeMethodPrefixSupported()204     isNativeMethodPrefixSupported() {
205         return mEnvironmentSupportsNativeMethodPrefix;
206     }
207 
208     public synchronized void
setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)209     setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
210         if (!isNativeMethodPrefixSupported()) {
211             throw new UnsupportedOperationException(
212                    "setNativeMethodPrefix is not supported in this environment");
213         }
214         if (transformer == null) {
215             throw new NullPointerException(
216                        "null passed as 'transformer' in setNativeMethodPrefix");
217         }
218         TransformerManager mgr = findTransformerManager(transformer);
219         if (mgr == null) {
220             throw new IllegalArgumentException(
221                        "transformer not registered in setNativeMethodPrefix");
222         }
223         mgr.setNativeMethodPrefix(transformer, prefix);
224         String[] prefixes = mgr.getNativeMethodPrefixes();
225         setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable());
226     }
227 
228     private TransformerManager
findTransformerManager(ClassFileTransformer transformer)229     findTransformerManager(ClassFileTransformer transformer) {
230         if (mTransformerManager.includesTransformer(transformer)) {
231             return mTransformerManager;
232         }
233         if (mRetransfomableTransformerManager != null &&
234                 mRetransfomableTransformerManager.includesTransformer(transformer)) {
235             return mRetransfomableTransformerManager;
236         }
237         return null;
238     }
239 
240 
241     /*
242      *  Natives
243      */
244     private native boolean
isModifiableClass0(long nativeAgent, Class<?> theClass)245     isModifiableClass0(long nativeAgent, Class<?> theClass);
246 
247     private native boolean
isRetransformClassesSupported0(long nativeAgent)248     isRetransformClassesSupported0(long nativeAgent);
249 
250     private native void
setHasRetransformableTransformers(long nativeAgent, boolean has)251     setHasRetransformableTransformers(long nativeAgent, boolean has);
252 
253     private native void
retransformClasses0(long nativeAgent, Class<?>[] classes)254     retransformClasses0(long nativeAgent, Class<?>[] classes);
255 
256     private native void
redefineClasses0(long nativeAgent, ClassDefinition[] definitions)257     redefineClasses0(long nativeAgent, ClassDefinition[]  definitions)
258         throws  ClassNotFoundException;
259 
260     @SuppressWarnings("rawtypes")
261     private native Class[]
getAllLoadedClasses0(long nativeAgent)262     getAllLoadedClasses0(long nativeAgent);
263 
264     @SuppressWarnings("rawtypes")
265     private native Class[]
getInitiatedClasses0(long nativeAgent, ClassLoader loader)266     getInitiatedClasses0(long nativeAgent, ClassLoader loader);
267 
268     private native long
getObjectSize0(long nativeAgent, Object objectToSize)269     getObjectSize0(long nativeAgent, Object objectToSize);
270 
271     private native void
appendToClassLoaderSearch0(long nativeAgent, String jarfile, boolean bootLoader)272     appendToClassLoaderSearch0(long nativeAgent, String jarfile, boolean bootLoader);
273 
274     private native void
setNativeMethodPrefixes(long nativeAgent, String[] prefixes, boolean isRetransformable)275     setNativeMethodPrefixes(long nativeAgent, String[] prefixes, boolean isRetransformable);
276 
277     static {
278         System.loadLibrary("instrument");
279     }
280 
281     /*
282      *  Internals
283      */
284 
285 
286     // Enable or disable Java programming language access checks on a
287     // reflected object (for example, a method)
setAccessible(final AccessibleObject ao, final boolean accessible)288     private static void setAccessible(final AccessibleObject ao, final boolean accessible) {
289         AccessController.doPrivileged(new PrivilegedAction<Object>() {
290                 public Object run() {
291                     ao.setAccessible(accessible);
292                     return null;
293                 }});
294     }
295 
296     // Attempt to load and start an agent
297     private void
loadClassAndStartAgent( String classname, String methodname, String optionsString)298     loadClassAndStartAgent( String  classname,
299                             String  methodname,
300                             String  optionsString)
301             throws Throwable {
302 
303         ClassLoader mainAppLoader   = ClassLoader.getSystemClassLoader();
304         Class<?>    javaAgentClass  = mainAppLoader.loadClass(classname);
305 
306         Method m = null;
307         NoSuchMethodException firstExc = null;
308         boolean twoArgAgent = false;
309 
310         // The agent class must have a premain or agentmain method that
311         // has 1 or 2 arguments. We check in the following order:
312         //
313         // 1) declared with a signature of (String, Instrumentation)
314         // 2) declared with a signature of (String)
315         // 3) inherited with a signature of (String, Instrumentation)
316         // 4) inherited with a signature of (String)
317         //
318         // So the declared version of either 1-arg or 2-arg always takes
319         // primary precedence over an inherited version. After that, the
320         // 2-arg version takes precedence over the 1-arg version.
321         //
322         // If no method is found then we throw the NoSuchMethodException
323         // from the first attempt so that the exception text indicates
324         // the lookup failed for the 2-arg method (same as JDK5.0).
325 
326         try {
327             m = javaAgentClass.getDeclaredMethod( methodname,
328                                  new Class<?>[] {
329                                      String.class,
330                                      java.lang.instrument.Instrumentation.class
331                                  }
332                                );
333             twoArgAgent = true;
334         } catch (NoSuchMethodException x) {
335             // remember the NoSuchMethodException
336             firstExc = x;
337         }
338 
339         if (m == null) {
340             // now try the declared 1-arg method
341             try {
342                 m = javaAgentClass.getDeclaredMethod(methodname,
343                                                  new Class<?>[] { String.class });
344             } catch (NoSuchMethodException x) {
345                 // ignore this exception because we'll try
346                 // two arg inheritance next
347             }
348         }
349 
350         if (m == null) {
351             // now try the inherited 2-arg method
352             try {
353                 m = javaAgentClass.getMethod( methodname,
354                                  new Class<?>[] {
355                                      String.class,
356                                      java.lang.instrument.Instrumentation.class
357                                  }
358                                );
359                 twoArgAgent = true;
360             } catch (NoSuchMethodException x) {
361                 // ignore this exception because we'll try
362                 // one arg inheritance next
363             }
364         }
365 
366         if (m == null) {
367             // finally try the inherited 1-arg method
368             try {
369                 m = javaAgentClass.getMethod(methodname,
370                                              new Class<?>[] { String.class });
371             } catch (NoSuchMethodException x) {
372                 // none of the methods exists so we throw the
373                 // first NoSuchMethodException as per 5.0
374                 throw firstExc;
375             }
376         }
377 
378         // the premain method should not be required to be public,
379         // make it accessible so we can call it
380         // Note: The spec says the following:
381         //     The agent class must implement a public static premain method...
382         setAccessible(m, true);
383 
384         // invoke the 1 or 2-arg method
385         if (twoArgAgent) {
386             m.invoke(null, new Object[] { optionsString, this });
387         } else {
388             m.invoke(null, new Object[] { optionsString });
389         }
390 
391         // don't let others access a non-public premain method
392         setAccessible(m, false);
393     }
394 
395     // WARNING: the native code knows the name & signature of this method
396     private void
loadClassAndCallPremain( String classname, String optionsString)397     loadClassAndCallPremain(    String  classname,
398                                 String  optionsString)
399             throws Throwable {
400 
401         loadClassAndStartAgent( classname, "premain", optionsString );
402     }
403 
404 
405     // WARNING: the native code knows the name & signature of this method
406     private void
loadClassAndCallAgentmain( String classname, String optionsString)407     loadClassAndCallAgentmain(  String  classname,
408                                 String  optionsString)
409             throws Throwable {
410 
411         loadClassAndStartAgent( classname, "agentmain", optionsString );
412     }
413 
414     // WARNING: the native code knows the name & signature of this method
415     private byte[]
transform( ClassLoader loader, String classname, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer, boolean isRetransformer)416     transform(  ClassLoader         loader,
417                 String              classname,
418                 Class<?>            classBeingRedefined,
419                 ProtectionDomain    protectionDomain,
420                 byte[]              classfileBuffer,
421                 boolean             isRetransformer) {
422         TransformerManager mgr = isRetransformer?
423                                         mRetransfomableTransformerManager :
424                                         mTransformerManager;
425         if (mgr == null) {
426             return null; // no manager, no transform
427         } else {
428             return mgr.transform(   loader,
429                                     classname,
430                                     classBeingRedefined,
431                                     protectionDomain,
432                                     classfileBuffer);
433         }
434     }
435 }
436