Review Request JDK-8200559: Java agents doing instrumentation need a means to define auxiliary classes
mandy chung
mandy.chung at oracle.com
Wed May 30 04:46:10 UTC 2018
Hi Rafael,
Thanks for the additional use cases of Unsafe::defineClass and looking
through many different agents to identify their migration path.
Summarize the requirements you collected (please correct/add):
1. need a means to define auxiliary classes in the same runtime package
of an instrumented class, e.g. to bridge non-public members
2. need a means to define agent classes in a module that provides
similar functionality as appendToBootstrapClassLoader and
appendToSystemClassLoaderSearch
3. define a class in a different runtime package as the instrumented
class. It's still unclear how common this is (inject a subclass
that depends on a package-private constructor)
The proposed API requires the agent to redefine the class in a
package that the agent intends to inject its class into (e.g.
redefine java.util.List in order to define java.util.AgentDispatcher).
The proposed API requires more work than the existing
Unsafe::defineClass that can define a class in any package but
still plausible, right?
We should explore other alternatives, if any.
Mandy
[1]
http://mail.openjdk.java.net/pipermail/serviceability-dev/2018-April/023535.html
On 5/29/18 2:17 PM, Rafael Winterhalter wrote:
> Hei Mandy,
>
> after further investigation I have found several other use cases of
> sun.misc.Unsafe::defineClass for which the proposal for JDK-8200559
> would not currently offer a solution. I will try to describe a generic
> case of the problem I found:
>
> Java agents sometimes need to communicate state from instrumented
> classes to a dispatcher that comes with the agent. However, a Java agent
> cannot generally expect that any agent classes are visible to an
> application as a Java agent is loaded on the class path whereas
> application classes might not be loaded by the system class loader or
> any of its children. Therefore, instrumented classes cannot typically
> callback into the agent. This is typically solved by adding classes to
> the bootstrap class loader which is universally visible.
>
> Such a callback typically looks like this:
>
> public abstract class Dispatcher {
> public volatile Dispatcher dispatcher;
> public abstract void receiveMessage(Object msg);
> public static void sendMessage(Object msg) {
> Dispatcher d = dispatcher;
> if (d != null) d.receiveMessage(msg);
> }
> }
>
> The agent can now register a subclass of the dispatcher in the field and
> receive messages from any instrumented code throughout an application
> without considering class loaders.
>
> Adding this call would be trivial by using the
> Instrumentation::appendToBootSearchPath method. However, using this
> method has two major downsides:
> 1. The method accepts a file which must be stored on the local file
> system. This can be really tricky in some cases, it is typically easier
> to just define the classes as byte arrays and
> sun.misc.Unsafe::defineClass is a way to avoid this problem.
> 2. Appending to the search path is only possible in the unnamed module
> since Java 9. It is no longer possible to define the above dispatcher to
> be located in java.util for instance as the new standard class loader
> does not consider the new jar for known modules anymore. Defining the
> dispatcher in a well known package has two major advantages:
> a) It is not necessary to alter the module graph to make sure that
> all modules with instrumented classes read the boot loaders unnamed
> module. This requires a lot of additional maintenance which impacts
> performance and memory use. Also, bugs can trigger security risks if
> edges are added excessively. With defining a class in java.util, this
> can be avoided since the java.base module is impliiclty read by any module.
> b) Some class loaders filter packages that are considered to be
> loaded, especially OSGi and JBoss modules class loaders. Those class
> loaders would for example never attempt to find classes in the com.acme
> package on the bootstrap loader which is why it has become a popular
> practice to add classes to java.* packages for such universal dispatch.
> There are dozens of such restricting class loaders and working around
> them is nearly impossible as there are so many.
>
> With the current consideration, it would instead be required to "pseudo
> redefine" a class such as java.util.List only to inject the dispatcher
> into the desired package. (Alternatively one can also open the module of
> java.base and use a method handles lookup but this is a bad idea if the
> Java agent runs on the unnamed system class loader.) Note that all this
> is possible using standard API. It is even easier to do with JVMTI where
> class definition is trivial.
>
> For all the reasons I have mentioned in previous mails and also for this
> additional use case I hope that adding a method for defining a class can
> be added to the Instrumentation interface, it would definitely allow for
> the cleanest, fastest and least error-prone migration while not inviting
> to the mentioned work-arounds which are also currently favored by most
> teams working with Java agents that I know of where many of them just
> open jdk.internal.misc to use the new Unsafe class what is really
> unfortunate as this is a chance to avoid use of internal API
> alltogether. This choice would also reduce additional time for Java 11
> being supported by all major production monitoring tools that could just
> add simple switches to their code to move from Unsafe to Instrumentation.
>
> It would also be trivial to implement with an interface extension like:
>
> interface Instrumentation {
> default Class<?> defineClass(byte[] bytes, ClassLoader loader,
> ProtectionDomain pd) { throw new
> UnsupportedOperationException("defineClass"); }
> }
>
> Thanks for considering my suggestion and your feedback!
> Best regards, Rafael
>
>
>
> 2018-04-15 8:23 GMT+02:00 mandy chung <mandy.chung at oracle.com
> <mailto:mandy.chung at oracle.com>>:
>
> Background:
>
> Java agents support both load time and dynamic instrumentation. At
> load time,
> the agent's ClassFileTransformer is invoked to transform class
> bytes. There is
> no Class objects at this time. Dynamic instrumentation is when
> redefineClasses
> or retransformClasses is used to redefine an existing loaded class. The
> ClassFileTransformer is invoked with class bytes where the Class
> object is present.
>
> Java agent doing instrumentation needs a means to define auxiliary
> classes
> that are visible and accessible to the instrumented class. Existing
> agents
> have been using sun.misc.Unsafe::defineClass to define aux classes
> directly
> or accessing protected ClassLoader::defineClass method with
> setAccessible to
> suppress the language access check (see [1] where this issue was
> brought up).
>
> Instrumentation::appendToBootstrapClassLoaderSearch and
> appendToSystemClassLoaderSearch
> APIs are existing means to supply additional classes. It's too limited
> for example it can't inject a class in the same runtime package as
> the class
> being transformed.
>
> Proposal:
>
> This proposes to add a new ClassFileTransformer.transform method
> taking additional ClassDefiner parameter. A transformer can define
> additional
> classes during the transformation process, i.e.
> when ClassFileTransformer::transform is invoked. Some details:
>
> 1. ClassDefiner::defineClass defines a class in the same runtime package
> as the class being transformed.
> 2. The class is defined in the same thread as the transformers are being
> invoked. ClassDefiner::defineClass returns Class object directly
> before the transformed class is defined.
> 3. No transformation is applied to classes defined by
> ClassDefiner::defineClass.
>
> The first prototype we did is to collect the auxiliary classes and
> define
> them until all transformers are invoked and have these aux classes
> to go
> through the transformation pipeline. Several complicated issues would
> need to be resolved for example timing whether the auxiliary classes
> should
> be defined before the transformed class (otherwise a potential race
> where
> some other thread references the transformed class and cause the code to
> execute that in turn reference the auxiliary classes. The current
> implementation has a native reentrancy check that ensure one class
> is being
> transformed to avoid potential circularity issues. This may need
> JVM TI
> support to be reliable.
>
> This proposal would allow java agents to migrate from internal API
> and ClassDefiner to be enhanced in the future.
>
> Webrev:
> http://cr.openjdk.java.net/~mchung/jdk11/webrevs/8200559/webrev.00/
> <http://cr.openjdk.java.net/~mchung/jdk11/webrevs/8200559/webrev.00/>
>
> Mandy
> [1]
> http://mail.openjdk.java.net/pipermail/jdk-dev/2018-January/000405.html
> <http://mail.openjdk.java.net/pipermail/jdk-dev/2018-January/000405.html>
>
>
More information about the serviceability-dev
mailing list