1 /* 2 * Copyright (c) 2003, 2018, 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 package sun.instrument; 27 28 import java.lang.instrument.UnmodifiableModuleException; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.AccessibleObject; 31 import java.lang.instrument.ClassFileTransformer; 32 import java.lang.instrument.ClassDefinition; 33 import java.lang.instrument.Instrumentation; 34 import java.security.AccessController; 35 import java.security.PrivilegedAction; 36 import java.security.ProtectionDomain; 37 import java.util.Collections; 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.jar.JarFile; 45 46 import jdk.internal.module.Modules; 47 48 /* 49 * Copyright 2003 Wily Technology, Inc. 50 */ 51 52 /** 53 * The Java side of the JPLIS implementation. Works in concert with a native JVMTI agent 54 * to implement the JPLIS API set. Provides both the Java API implementation of 55 * the Instrumentation interface and utility Java routines to support the native code. 56 * Keeps a pointer to the native data structure in a scalar field to allow native 57 * processing behind native methods. 58 */ 59 public class InstrumentationImpl implements Instrumentation { 60 private final TransformerManager mTransformerManager; 61 private TransformerManager mRetransfomableTransformerManager; 62 // needs to store a native pointer, so use 64 bits 63 private final long mNativeAgent; 64 private final boolean mEnvironmentSupportsRedefineClasses; 65 private volatile boolean mEnvironmentSupportsRetransformClassesKnown; 66 private volatile boolean mEnvironmentSupportsRetransformClasses; 67 private final boolean mEnvironmentSupportsNativeMethodPrefix; 68 69 private 70 InstrumentationImpl(long nativeAgent, 71 boolean environmentSupportsRedefineClasses, 72 boolean environmentSupportsNativeMethodPrefix) { 73 mTransformerManager = new TransformerManager(false); 74 mRetransfomableTransformerManager = null; 75 mNativeAgent = nativeAgent; 76 mEnvironmentSupportsRedefineClasses = environmentSupportsRedefineClasses; 77 mEnvironmentSupportsRetransformClassesKnown = false; // false = need to ask 78 mEnvironmentSupportsRetransformClasses = false; // don't know yet 79 mEnvironmentSupportsNativeMethodPrefix = environmentSupportsNativeMethodPrefix; 80 } 81 82 public void 83 addTransformer(ClassFileTransformer transformer) { 84 addTransformer(transformer, false); 85 } 86 87 public synchronized void 88 addTransformer(ClassFileTransformer transformer, boolean canRetransform) { 89 if (transformer == null) { 90 throw new NullPointerException("null passed as 'transformer' in addTransformer"); 91 } 92 if (canRetransform) { 93 if (!isRetransformClassesSupported()) { 94 throw new UnsupportedOperationException( 95 "adding retransformable transformers is not supported in this environment"); 96 } 97 if (mRetransfomableTransformerManager == null) { 98 mRetransfomableTransformerManager = new TransformerManager(true); 99 } 100 mRetransfomableTransformerManager.addTransformer(transformer); 101 if (mRetransfomableTransformerManager.getTransformerCount() == 1) { 102 setHasRetransformableTransformers(mNativeAgent, true); 103 } 104 } else { 105 mTransformerManager.addTransformer(transformer); 106 if (mTransformerManager.getTransformerCount() == 1) { 107 setHasTransformers(mNativeAgent, true); 108 } 109 } 110 } 111 112 public synchronized boolean 113 removeTransformer(ClassFileTransformer transformer) { 114 if (transformer == null) { 115 throw new NullPointerException("null passed as 'transformer' in removeTransformer"); 116 } 117 TransformerManager mgr = findTransformerManager(transformer); 118 if (mgr != null) { 119 mgr.removeTransformer(transformer); 120 if (mgr.getTransformerCount() == 0) { 121 if (mgr.isRetransformable()) { 122 setHasRetransformableTransformers(mNativeAgent, false); 123 } else { 124 setHasTransformers(mNativeAgent, false); 125 } 126 } 127 return true; 128 } 129 return false; 130 } 131 132 public boolean 133 isModifiableClass(Class<?> theClass) { 134 if (theClass == null) { 135 throw new NullPointerException( 136 "null passed as 'theClass' in isModifiableClass"); 137 } 138 return isModifiableClass0(mNativeAgent, theClass); 139 } 140 141 public boolean isModifiableModule(Module module) { 142 if (module == null) { 143 throw new NullPointerException("'module' is null"); 144 } 145 return true; 146 } 147 148 public boolean 149 isRetransformClassesSupported() { 150 // ask lazily since there is some overhead 151 if (!mEnvironmentSupportsRetransformClassesKnown) { 152 mEnvironmentSupportsRetransformClasses = isRetransformClassesSupported0(mNativeAgent); 153 mEnvironmentSupportsRetransformClassesKnown = true; 154 } 155 return mEnvironmentSupportsRetransformClasses; 156 } 157 158 public void 159 retransformClasses(Class<?>... classes) { 160 if (!isRetransformClassesSupported()) { 161 throw new UnsupportedOperationException( 162 "retransformClasses is not supported in this environment"); 163 } 164 if (classes.length == 0) { 165 return; // no-op 166 } 167 retransformClasses0(mNativeAgent, classes); 168 } 169 170 public boolean 171 isRedefineClassesSupported() { 172 return mEnvironmentSupportsRedefineClasses; 173 } 174 175 public void 176 redefineClasses(ClassDefinition... definitions) 177 throws ClassNotFoundException { 178 if (!isRedefineClassesSupported()) { 179 throw new UnsupportedOperationException("redefineClasses is not supported in this environment"); 180 } 181 if (definitions == null) { 182 throw new NullPointerException("null passed as 'definitions' in redefineClasses"); 183 } 184 for (int i = 0; i < definitions.length; ++i) { 185 if (definitions[i] == null) { 186 throw new NullPointerException("element of 'definitions' is null in redefineClasses"); 187 } 188 } 189 if (definitions.length == 0) { 190 return; // short-circuit if there are no changes requested 191 } 192 193 redefineClasses0(mNativeAgent, definitions); 194 } 195 196 @SuppressWarnings("rawtypes") 197 public Class[] 198 getAllLoadedClasses() { 199 return getAllLoadedClasses0(mNativeAgent); 200 } 201 202 @SuppressWarnings("rawtypes") 203 public Class[] 204 getInitiatedClasses(ClassLoader loader) { 205 return getInitiatedClasses0(mNativeAgent, loader); 206 } 207 208 public long 209 getObjectSize(Object objectToSize) { 210 if (objectToSize == null) { 211 throw new NullPointerException("null passed as 'objectToSize' in getObjectSize"); 212 } 213 return getObjectSize0(mNativeAgent, objectToSize); 214 } 215 216 public void 217 appendToBootstrapClassLoaderSearch(JarFile jarfile) { 218 appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), true); 219 } 220 221 public void 222 appendToSystemClassLoaderSearch(JarFile jarfile) { 223 appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), false); 224 } 225 226 public boolean 227 isNativeMethodPrefixSupported() { 228 return mEnvironmentSupportsNativeMethodPrefix; 229 } 230 231 public synchronized void 232 setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) { 233 if (!isNativeMethodPrefixSupported()) { 234 throw new UnsupportedOperationException( 235 "setNativeMethodPrefix is not supported in this environment"); 236 } 237 if (transformer == null) { 238 throw new NullPointerException( 239 "null passed as 'transformer' in setNativeMethodPrefix"); 240 } 241 TransformerManager mgr = findTransformerManager(transformer); 242 if (mgr == null) { 243 throw new IllegalArgumentException( 244 "transformer not registered in setNativeMethodPrefix"); 245 } 246 mgr.setNativeMethodPrefix(transformer, prefix); 247 String[] prefixes = mgr.getNativeMethodPrefixes(); 248 setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable()); 249 } 250 251 @Override 252 public void redefineModule(Module module, 253 Set<Module> extraReads, 254 Map<String, Set<Module>> extraExports, 255 Map<String, Set<Module>> extraOpens, 256 Set<Class<?>> extraUses, 257 Map<Class<?>, List<Class<?>>> extraProvides) 258 { 259 if (!module.isNamed()) 260 return; 261 262 if (!isModifiableModule(module)) 263 throw new UnmodifiableModuleException(module.getName()); 264 265 // copy and check reads 266 extraReads = new HashSet<>(extraReads); 267 if (extraReads.contains(null)) 268 throw new NullPointerException("'extraReads' contains null"); 269 270 // copy and check exports and opens 271 extraExports = cloneAndCheckMap(module, extraExports); 272 extraOpens = cloneAndCheckMap(module, extraOpens); 273 274 // copy and check uses 275 extraUses = new HashSet<>(extraUses); 276 if (extraUses.contains(null)) 277 throw new NullPointerException("'extraUses' contains null"); 278 279 // copy and check provides 280 Map<Class<?>, List<Class<?>>> tmpProvides = new HashMap<>(); 281 for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) { 282 Class<?> service = e.getKey(); 283 if (service == null) 284 throw new NullPointerException("'extraProvides' contains null"); 285 List<Class<?>> providers = new ArrayList<>(e.getValue()); 286 if (providers.isEmpty()) 287 throw new IllegalArgumentException("list of providers is empty"); 288 providers.forEach(p -> { 289 if (p.getModule() != module) 290 throw new IllegalArgumentException(p + " not in " + module); 291 if (!service.isAssignableFrom(p)) 292 throw new IllegalArgumentException(p + " is not a " + service); 293 }); 294 tmpProvides.put(service, providers); 295 } 296 extraProvides = tmpProvides; 297 298 299 // update reads 300 extraReads.forEach(m -> Modules.addReads(module, m)); 301 302 // update exports 303 for (Map.Entry<String, Set<Module>> e : extraExports.entrySet()) { 304 String pkg = e.getKey(); 305 Set<Module> targets = e.getValue(); 306 targets.forEach(m -> Modules.addExports(module, pkg, m)); 307 } 308 309 // update opens 310 for (Map.Entry<String, Set<Module>> e : extraOpens.entrySet()) { 311 String pkg = e.getKey(); 312 Set<Module> targets = e.getValue(); 313 targets.forEach(m -> Modules.addOpens(module, pkg, m)); 314 } 315 316 // update uses 317 extraUses.forEach(service -> Modules.addUses(module, service)); 318 319 // update provides 320 for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) { 321 Class<?> service = e.getKey(); 322 List<Class<?>> providers = e.getValue(); 323 providers.forEach(p -> Modules.addProvides(module, service, p)); 324 } 325 } 326 327 private Map<String, Set<Module>> 328 cloneAndCheckMap(Module module, Map<String, Set<Module>> map) 329 { 330 if (map.isEmpty()) 331 return Collections.emptyMap(); 332 333 Map<String, Set<Module>> result = new HashMap<>(); 334 Set<String> packages = module.getPackages(); 335 for (Map.Entry<String, Set<Module>> e : map.entrySet()) { 336 String pkg = e.getKey(); 337 if (pkg == null) 338 throw new NullPointerException("package cannot be null"); 339 if (!packages.contains(pkg)) 340 throw new IllegalArgumentException(pkg + " not in module"); 341 Set<Module> targets = new HashSet<>(e.getValue()); 342 if (targets.isEmpty()) 343 throw new IllegalArgumentException("set of targets is empty"); 344 if (targets.contains(null)) 345 throw new NullPointerException("set of targets cannot include null"); 346 result.put(pkg, targets); 347 } 348 return result; 349 } 350 351 352 private TransformerManager 353 findTransformerManager(ClassFileTransformer transformer) { 354 if (mTransformerManager.includesTransformer(transformer)) { 355 return mTransformerManager; 356 } 357 if (mRetransfomableTransformerManager != null && 358 mRetransfomableTransformerManager.includesTransformer(transformer)) { 359 return mRetransfomableTransformerManager; 360 } 361 return null; 362 } 363 364 365 /* 366 * Natives 367 */ 368 private native boolean 369 isModifiableClass0(long nativeAgent, Class<?> theClass); 370 371 private native boolean 372 isRetransformClassesSupported0(long nativeAgent); 373 374 private native void 375 setHasTransformers(long nativeAgent, boolean has); 376 377 private native void 378 setHasRetransformableTransformers(long nativeAgent, boolean has); 379 380 private native void 381 retransformClasses0(long nativeAgent, Class<?>[] classes); 382 383 private native void 384 redefineClasses0(long nativeAgent, ClassDefinition[] definitions) 385 throws ClassNotFoundException; 386 387 @SuppressWarnings("rawtypes") 388 private native Class[] 389 getAllLoadedClasses0(long nativeAgent); 390 391 @SuppressWarnings("rawtypes") 392 private native Class[] 393 getInitiatedClasses0(long nativeAgent, ClassLoader loader); 394 395 private native long 396 getObjectSize0(long nativeAgent, Object objectToSize); 397 398 private native void 399 appendToClassLoaderSearch0(long nativeAgent, String jarfile, boolean bootLoader); 400 401 private native void 402 setNativeMethodPrefixes(long nativeAgent, String[] prefixes, boolean isRetransformable); 403 404 static { 405 System.loadLibrary("instrument"); 406 } 407 408 /* 409 * Internals 410 */ 411 412 413 // Enable or disable Java programming language access checks on a 414 // reflected object (for example, a method) 415 private static void setAccessible(final AccessibleObject ao, final boolean accessible) { 416 AccessController.doPrivileged(new PrivilegedAction<Object>() { 417 public Object run() { 418 ao.setAccessible(accessible); 419 return null; 420 }}); 421 } 422 423 // Attempt to load and start an agent 424 private void 425 loadClassAndStartAgent( String classname, 426 String methodname, 427 String optionsString) 428 throws Throwable { 429 430 ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader(); 431 Class<?> javaAgentClass = mainAppLoader.loadClass(classname); 432 433 Method m = null; 434 NoSuchMethodException firstExc = null; 435 boolean twoArgAgent = false; 436 437 // The agent class must have a premain or agentmain method that 438 // has 1 or 2 arguments. We check in the following order: 439 // 440 // 1) declared with a signature of (String, Instrumentation) 441 // 2) declared with a signature of (String) 442 // 3) inherited with a signature of (String, Instrumentation) 443 // 4) inherited with a signature of (String) 444 // 445 // So the declared version of either 1-arg or 2-arg always takes 446 // primary precedence over an inherited version. After that, the 447 // 2-arg version takes precedence over the 1-arg version. 448 // 449 // If no method is found then we throw the NoSuchMethodException 450 // from the first attempt so that the exception text indicates 451 // the lookup failed for the 2-arg method (same as JDK5.0). 452 453 try { 454 m = javaAgentClass.getDeclaredMethod( methodname, 455 new Class<?>[] { 456 String.class, 457 java.lang.instrument.Instrumentation.class 458 } 459 ); 460 twoArgAgent = true; 461 } catch (NoSuchMethodException x) { 462 // remember the NoSuchMethodException 463 firstExc = x; 464 } 465 466 if (m == null) { 467 // now try the declared 1-arg method 468 try { 469 m = javaAgentClass.getDeclaredMethod(methodname, 470 new Class<?>[] { String.class }); 471 } catch (NoSuchMethodException x) { 472 // ignore this exception because we'll try 473 // two arg inheritance next 474 } 475 } 476 477 if (m == null) { 478 // now try the inherited 2-arg method 479 try { 480 m = javaAgentClass.getMethod( methodname, 481 new Class<?>[] { 482 String.class, 483 java.lang.instrument.Instrumentation.class 484 } 485 ); 486 twoArgAgent = true; 487 } catch (NoSuchMethodException x) { 488 // ignore this exception because we'll try 489 // one arg inheritance next 490 } 491 } 492 493 if (m == null) { 494 // finally try the inherited 1-arg method 495 try { 496 m = javaAgentClass.getMethod(methodname, 497 new Class<?>[] { String.class }); 498 } catch (NoSuchMethodException x) { 499 // none of the methods exists so we throw the 500 // first NoSuchMethodException as per 5.0 501 throw firstExc; 502 } 503 } 504 505 // the premain method should not be required to be public, 506 // make it accessible so we can call it 507 // Note: The spec says the following: 508 // The agent class must implement a public static premain method... 509 setAccessible(m, true); 510 511 // invoke the 1 or 2-arg method 512 if (twoArgAgent) { 513 m.invoke(null, new Object[] { optionsString, this }); 514 } else { 515 m.invoke(null, new Object[] { optionsString }); 516 } 517 } 518 519 // WARNING: the native code knows the name & signature of this method 520 private void 521 loadClassAndCallPremain( String classname, 522 String optionsString) 523 throws Throwable { 524 525 loadClassAndStartAgent( classname, "premain", optionsString ); 526 } 527 528 529 // WARNING: the native code knows the name & signature of this method 530 private void 531 loadClassAndCallAgentmain( String classname, 532 String optionsString) 533 throws Throwable { 534 535 loadClassAndStartAgent( classname, "agentmain", optionsString ); 536 } 537 538 // WARNING: the native code knows the name & signature of this method 539 private byte[] 540 transform( Module module, 541 ClassLoader loader, 542 String classname, 543 Class<?> classBeingRedefined, 544 ProtectionDomain protectionDomain, 545 byte[] classfileBuffer, 546 boolean isRetransformer) { 547 TransformerManager mgr = isRetransformer? 548 mRetransfomableTransformerManager : 549 mTransformerManager; 550 // module is null when not a class load or when loading a class in an 551 // unnamed module and this is the first type to be loaded in the package. 552 if (module == null) { 553 if (classBeingRedefined != null) { 554 module = classBeingRedefined.getModule(); 555 } else { 556 module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule() 557 : loader.getUnnamedModule(); 558 } 559 } 560 if (mgr == null) { 561 return null; // no manager, no transform 562 } else { 563 return mgr.transform( module, 564 loader, 565 classname, 566 classBeingRedefined, 567 protectionDomain, 568 classfileBuffer); 569 } 570 } 571 572 573 /** 574 * Invoked by the java launcher to load a java agent that is packaged with 575 * the main application in an executable JAR file. 576 */ 577 public static void loadAgent(String path) { 578 loadAgent0(path); 579 } 580 581 private static native void loadAgent0(String path); 582 }