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