1 /*
2 * Copyright 2003-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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
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
77 addTransformer(ClassFileTransformer transformer) {
78 addTransformer(transformer, false);
79 }
80
81 public synchronized void
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
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
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
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
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
148 isRedefineClassesSupported() {
149 return mEnvironmentSupportsRedefineClasses;
150 }
151
152 public void
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 public Class[]
174 getAllLoadedClasses() {
175 return getAllLoadedClasses0(mNativeAgent);
176 }
177
178 public Class[]
179 getInitiatedClasses(ClassLoader loader) {
180 return getInitiatedClasses0(mNativeAgent, loader);
181 }
182
183 public long
184 getObjectSize(Object objectToSize) {
185 if (objectToSize == null) {
186 throw new NullPointerException("null passed as 'objectToSize' in getObjectSize");
187 }
188 return getObjectSize0(mNativeAgent, objectToSize);
189 }
190
191 public void
192 appendToBootstrapClassLoaderSearch(JarFile jarfile) {
193 appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), true);
194 }
195
196 public void
197 appendToSystemClassLoaderSearch(JarFile jarfile) {
198 appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), false);
199 }
200
201 public boolean
202 isNativeMethodPrefixSupported() {
203 return mEnvironmentSupportsNativeMethodPrefix;
204 }
205
206 public synchronized void
207 setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
208 if (!isNativeMethodPrefixSupported()) {
209 throw new UnsupportedOperationException(
210 "setNativeMethodPrefix is not supported in this environment");
211 }
212 if (transformer == null) {
213 throw new NullPointerException(
214 "null passed as 'transformer' in setNativeMethodPrefix");
215 }
216 TransformerManager mgr = findTransformerManager(transformer);
217 if (mgr == null) {
218 throw new IllegalArgumentException(
219 "transformer not registered in setNativeMethodPrefix");
220 }
221 mgr.setNativeMethodPrefix(transformer, prefix);
222 String[] prefixes = mgr.getNativeMethodPrefixes();
223 setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable());
224 }
225
226 private TransformerManager
227 findTransformerManager(ClassFileTransformer transformer) {
228 if (mTransformerManager.includesTransformer(transformer)) {
229 return mTransformerManager;
230 }
231 if (mRetransfomableTransformerManager != null &&
232 mRetransfomableTransformerManager.includesTransformer(transformer)) {
233 return mRetransfomableTransformerManager;
234 }
235 return null;
236 }
237
238
239 /*
240 * Natives
241 */
242 private native boolean
243 isModifiableClass0(long nativeAgent, Class<?> theClass);
244
245 private native boolean
246 isRetransformClassesSupported0(long nativeAgent);
247
248 private native void
249 setHasRetransformableTransformers(long nativeAgent, boolean has);
250
251 private native void
252 retransformClasses0(long nativeAgent, Class<?>[] classes);
253
254 private native void
255 redefineClasses0(long nativeAgent, ClassDefinition[] definitions)
256 throws ClassNotFoundException;
257
258 private native Class[]
259 getAllLoadedClasses0(long nativeAgent);
260
261 private native Class[]
262 getInitiatedClasses0(long nativeAgent, ClassLoader loader);
263
264 private native long
265 getObjectSize0(long nativeAgent, Object objectToSize);
266
267 private native void
268 appendToClassLoaderSearch0(long nativeAgent, String jarfile, boolean bootLoader);
269
270 private native void
271 setNativeMethodPrefixes(long nativeAgent, String[] prefixes, boolean isRetransformable);
272
273 static {
274 System.loadLibrary("instrument");
275 }
276
277 /*
278 * Internals
279 */
280
281
282 // Enable or disable Java programming language access checks on a
283 // reflected object (for example, a method)
284 private static void setAccessible(final AccessibleObject ao, final boolean accessible) {
285 AccessController.doPrivileged(new PrivilegedAction<Object>() {
286 public Object run() {
287 ao.setAccessible(accessible);
288 return null;
289 }});
290 }
291
292 // Attempt to load and start an agent
293 private void
294 loadClassAndStartAgent( String classname,
295 String methodname,
296 String optionsString)
297 throws Throwable {
298
299 ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader();
300 Class<?> javaAgentClass = mainAppLoader.loadClass(classname);
301
302 Method m = null;
303 NoSuchMethodException firstExc = null;
304 boolean twoArgAgent = false;
305
306 // The agent class must have a premain or agentmain method that
307 // has 1 or 2 arguments. We check in the following order:
308 //
309 // 1) declared with a signature of (String, Instrumentation)
310 // 2) declared with a signature of (String)
311 // 3) inherited with a signature of (String, Instrumentation)
312 // 4) inherited with a signature of (String)
313 //
314 // So the declared version of either 1-arg or 2-arg always takes
315 // primary precedence over an inherited version. After that, the
316 // 2-arg version takes precedence over the 1-arg version.
317 //
318 // If no method is found then we throw the NoSuchMethodException
319 // from the first attempt so that the exception text indicates
320 // the lookup failed for the 2-arg method (same as JDK5.0).
321
322 try {
323 m = javaAgentClass.getDeclaredMethod( methodname,
324 new Class[] {
325 String.class,
326 java.lang.instrument.Instrumentation.class
327 }
328 );
329 twoArgAgent = true;
330 } catch (NoSuchMethodException x) {
331 // remember the NoSuchMethodException
332 firstExc = x;
333 }
334
335 if (m == null) {
336 // now try the declared 1-arg method
337 try {
338 m = javaAgentClass.getDeclaredMethod(methodname,
339 new Class[] { String.class });
340 } catch (NoSuchMethodException x) {
341 // ignore this exception because we'll try
342 // two arg inheritance next
343 }
344 }
345
346 if (m == null) {
347 // now try the inherited 2-arg method
348 try {
349 m = javaAgentClass.getMethod( methodname,
350 new Class[] {
351 String.class,
352 java.lang.instrument.Instrumentation.class
353 }
354 );
355 twoArgAgent = true;
356 } catch (NoSuchMethodException x) {
357 // ignore this exception because we'll try
358 // one arg inheritance next
359 }
360 }
361
362 if (m == null) {
363 // finally try the inherited 1-arg method
364 try {
365 m = javaAgentClass.getMethod(methodname,
366 new Class[] { String.class });
367 } catch (NoSuchMethodException x) {
368 // none of the methods exists so we throw the
369 // first NoSuchMethodException as per 5.0
370 throw firstExc;
371 }
372 }
373
374 // the premain method should not be required to be public,
375 // make it accessible so we can call it
376 // Note: The spec says the following:
377 // The agent class must implement a public static premain method...
378 setAccessible(m, true);
379
380 // invoke the 1 or 2-arg method
381 if (twoArgAgent) {
382 m.invoke(null, new Object[] { optionsString, this });
383 } else {
384 m.invoke(null, new Object[] { optionsString });
385 }
386
387 // don't let others access a non-public premain method
388 setAccessible(m, false);
389 }
390
391 // WARNING: the native code knows the name & signature of this method
392 private void
393 loadClassAndCallPremain( String classname,
394 String optionsString)
395 throws Throwable {
396
397 loadClassAndStartAgent( classname, "premain", optionsString );
398 }
399
400
401 // WARNING: the native code knows the name & signature of this method
402 private void
403 loadClassAndCallAgentmain( String classname,
404 String optionsString)
405 throws Throwable {
406
407 loadClassAndStartAgent( classname, "agentmain", optionsString );
408 }
409
410 // WARNING: the native code knows the name & signature of this method
411 private byte[]
412 transform( ClassLoader loader,
413 String classname,
414 Class classBeingRedefined,
415 ProtectionDomain protectionDomain,
416 byte[] classfileBuffer,
417 boolean isRetransformer) {
418 TransformerManager mgr = isRetransformer?
419 mRetransfomableTransformerManager :
420 mTransformerManager;
421 if (mgr == null) {
422 return null; // no manager, no transform
423 } else {
424 return mgr.transform( loader,
425 classname,
426 classBeingRedefined,
427 protectionDomain,
428 classfileBuffer);
429 }
430 }
431 }