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 }