8307478: Implementation of Prepare to Restrict The Dynamic Loading of Agents

Reviewed-by: sspitsyn, cjplummer
This commit is contained in:
Alan Bateman 2023-06-02 05:57:01 +00:00
parent 325940b091
commit 5bd2af26e6
21 changed files with 1125 additions and 255 deletions

View File

@ -181,6 +181,7 @@ JVM_NewArray
JVM_NewInstanceFromConstructor
JVM_NewMultiArray
JVM_PhantomReferenceRefersTo
JVM_PrintWarningAtDynamicAgentLoad
JVM_RaiseSignal
JVM_RawMonitorCreate
JVM_RawMonitorDestroy

View File

@ -1164,6 +1164,12 @@ JVM_VirtualThreadHideFrames(JNIEnv* env, jobject vthread, jboolean hide);
JNIEXPORT jint JNICALL
JVM_GetClassFileVersion(JNIEnv *env, jclass current);
/*
* Return JNI_TRUE if warnings are printed when agents are dynamically loaded.
*/
JNIEXPORT jboolean JNICALL
JVM_PrintWarningAtDynamicAgentLoad(void);
/*
* This structure is used by the launcher to get the default thread
* stack size from the VM using JNI_GetDefaultJavaVMInitArgs() with a

View File

@ -4024,3 +4024,10 @@ JVM_END
JVM_ENTRY(void, JVM_EnsureMaterializedForStackWalk_func(JNIEnv* env, jobject vthread, jobject value))
JVM_EnsureMaterializedForStackWalk(env, value);
JVM_END
/*
* Return JNI_TRUE if warnings are printed when agents are dynamically loaded.
*/
JVM_LEAF(jboolean, JVM_PrintWarningAtDynamicAgentLoad(void))
return (EnableDynamicAgentLoading && !FLAG_IS_CMDLINE(EnableDynamicAgentLoading)) ? JNI_TRUE : JNI_FALSE;
JVM_END

View File

@ -640,6 +640,17 @@ Agent_OnLoad_L(JavaVM *vm, char *options, void *reserved)</example>
or implementation specific API, to attach to the running VM, and request it start a given
agent.
<p/>
The VM prints a warning on the standard error stream for each agent that it attempts
to start in the live phase. If an agent was previously started (in the <code>OnLoad</code>
phase or in the live phase), then it is implementation specific as to whether a
warning is printed when attempting to start the same agent a second or subsequent time.
Warnings can be disabled by means of an implementation-specific command line option.
<p/>
<b>Implementation Note:</b> For the HotSpot VM, the VM option
<code>-XX:+EnableDynamicAgentLoading</code> is used to opt-in to allow dynamic loading
of agents in the live phase. This option suppresses the warning to standard error when
starting an agent in the live phase.
<p/>
If an agent is started during the live phase then its agent library
must export a start-up function
with the following prototype:

View File

@ -30,13 +30,16 @@
#include "jvmtifiles/jvmtiEnv.hpp"
#include "prims/jvmtiEnvBase.hpp"
#include "prims/jvmtiExport.hpp"
#include "prims/jvmtiAgentList.hpp"
#include "runtime/arguments.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/java.hpp"
#include "runtime/jniHandles.hpp"
#include "runtime/globals_extension.hpp"
#include "runtime/os.inline.hpp"
#include "runtime/thread.inline.hpp"
#include "utilities/defaultStream.hpp"
static inline const char* copy_string(const char* str) {
return str != nullptr ? os::strdup(str, mtServiceability) : nullptr;
@ -260,9 +263,9 @@ static void assert_preload(const JvmtiAgent* agent) {
// Check for a statically linked-in agent, i.e. in the executable.
// This should be the first function called when loading an agent. It is a bit special:
// For statically linked agents we cant't rely on os_lib == nullptr because
// For statically linked agents we can't rely on os_lib == nullptr because
// statically linked agents could have a handle of RTLD_DEFAULT which == 0 on some platforms.
// If this function returns true, then agent->is_static_lib().&& agent->is_loaded().
// If this function returns true, then agent->is_static_lib() && agent->is_loaded().
static bool load_agent_from_executable(JvmtiAgent* agent, const char* on_load_symbols[], size_t num_symbol_entries) {
DEBUG_ONLY(assert_preload(agent);)
assert(on_load_symbols != nullptr, "invariant");
@ -483,6 +486,7 @@ extern "C" {
}
// Loading the agent by invoking Agent_OnAttach.
// This function is called before the agent is added to JvmtiAgentList.
static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) {
DEBUG_ONLY(assert_preload(agent);)
assert(agent->is_dynamic(), "invariant");
@ -491,7 +495,10 @@ static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) {
const char* on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS;
const size_t num_symbol_entries = ARRAY_SIZE(on_attach_symbols);
void* library = nullptr;
if (!load_agent_from_executable(agent, &on_attach_symbols[0], num_symbol_entries)) {
bool previously_loaded;
if (load_agent_from_executable(agent, &on_attach_symbols[0], num_symbol_entries)) {
previously_loaded = JvmtiAgentList::is_static_lib_loaded(agent->name());
} else {
library = load_library(agent, &on_attach_symbols[0], num_symbol_entries, /* vm_exit_on_error */ false);
if (library == nullptr) {
st->print_cr("%s was not loaded.", agent->name());
@ -503,7 +510,17 @@ static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) {
agent->set_os_lib_path(&buffer[0]);
agent->set_os_lib(library);
agent->set_loaded();
previously_loaded = JvmtiAgentList::is_dynamic_lib_loaded(library);
}
// Print warning if agent was not previously loaded and EnableDynamicAgentLoading not enabled on the command line.
if (!previously_loaded && !FLAG_IS_CMDLINE(EnableDynamicAgentLoading) && !agent->is_instrument_lib()) {
jio_fprintf(defaultStream::error_stream(),
"WARNING: A JVM TI agent has been loaded dynamically (%s)\n"
"WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning\n"
"WARNING: Dynamic loading of agents will be disallowed by default in a future release\n", agent->name());
}
assert(agent->is_loaded(), "invariant");
// The library was loaded so we attempt to lookup and invoke the Agent_OnAttach function.
OnAttachEntry_t on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t,

View File

@ -220,6 +220,30 @@ void JvmtiAgentList::unload_agents() {
}
}
// Return true if a statically linked agent is on the list
bool JvmtiAgentList::is_static_lib_loaded(const char* name) {
JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
while (it.has_next()) {
JvmtiAgent* const agent = it.next();
if (agent->is_static_lib() && strcmp(agent->name(), name) == 0) {
return true;
}
}
return false;
}
// Return true if a agent library on the list
bool JvmtiAgentList::is_dynamic_lib_loaded(void* os_lib) {
JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
while (it.has_next()) {
JvmtiAgent* const agent = it.next();
if (!agent->is_static_lib() && agent->os_lib() == os_lib) {
return true;
}
}
return false;
}
static bool match(JvmtiEnv* env, const JvmtiAgent* agent, const void* os_module_address) {
assert(env != nullptr, "invariant");
assert(agent != nullptr, "invariant");

View File

@ -76,6 +76,9 @@ class JvmtiAgentList : AllStatic {
static void load_xrun_agents() NOT_JVMTI_RETURN;
static void unload_agents() NOT_JVMTI_RETURN;
static bool is_static_lib_loaded(const char* name);
static bool is_dynamic_lib_loaded(void* os_lib);
static JvmtiAgent* lookup(JvmtiEnv* env, void* f_ptr);
static Iterator agents() NOT_JVMTI({ Iterator it; return it; });

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,25 +29,20 @@
/**
* Provides services that allow Java programming language agents to instrument
* programs running on the JVM. The mechanism for instrumentation is modification
* of the byte-codes of methods.
* programs running on the Java Virtual Machine (JVM). The mechanism for
* instrumentation is modification of the bytecodes of methods.
*
* <p> An agent is deployed as a JAR file. An attribute in the JAR file manifest
* specifies the agent class which will be loaded to start the agent. Agents can
* be started in several ways:
* <p> The class files that comprise an agent are packaged into a JAR file, either
* with the application in an executable JAR, or more commonly, as a separate JAR file
* called an <em>agent JAR</em>. An attribute in the main manifest of the JAR file
* identifies one of the class files in the JAR file as the <em>agent class</em>.
* The agent class defines a special method that the JVM invokes to <em>start</em>
* the agent.
*
* <ol>
* <li><p> For implementations that support a command-line interface, an agent
* can be started by specifying an option on the command-line. </p></li>
*
* <li><p> An implementation may support a mechanism to start agents some time
* after the VM has started. For example, an implementation may provide a
* mechanism that allows a tool to <i>attach</i> to a running application, and
* initiate the loading of the tool's agent into the running application. </p></li>
*
* <li><p> An agent may be packaged with an application in an executable JAR
* file.</p></li>
* </ol>
* <p> Agents that are packaged with an application in an executable JAR are started
* at JVM statup time. Agents that are packaged into an agent JAR file may be started
* at JVM startup time via a command line option, or where an implementation supports
* it, started in a running JVM.
*
* <p> Agents can transform classes in arbitrary ways at load time, transform
* modules, or transform the bytecode of methods of already loaded classes.
@ -56,13 +51,47 @@
* running application, are responsible for verifying the trustworthiness of each
* agent including the content and structure of the agent JAR file.
*
* <p> The three ways to start an agent are described below.
* <h2>Starting an agent</h2>
*
* <h2>Starting an Agent from the Command-Line Interface</h2>
* <h3>Starting an agent packaged with an application in an executable JAR file</h3>
*
* <p> Where an implementation provides a means to start agents from the
* command-line interface, an agent is started by adding the following option
* to the command-line:
* <p> The <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a> defines
* manifest attributes for standalone applications that are packaged as <em>executable
* JAR files</em>. If an implementation supports a mechanism to start an application as
* an executable JAR, then the main manifest of the JAR file can include the
* {@code Launcher-Agent-Class} attribute to specify the binary name of the Java agent
* class that is packaged with the application. If the attribute is present then the
* JVM starts the agent by loading the agent class and invoking its {@code agentmain}
* method. The method is invoked before the application {@code main} method is invoked.
* The {@code agentmain} method has one of two possible signatures. The JVM first
* attempts to invoke the following method on the agent class:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs, Instrumentation inst)
* }</blockquote>
*
* <p> If the agent class does not define this method then the JVM will attempt
* to invoke:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs)
* }</blockquote>
*
* <p> The value of the {@code agentArgs} parameter is always the empty string. In
* the first method, the {@code inst} parameter is an {@link Instrumentation} object
* that the agent can use to instrument code.
*
* <p> The {@code agentmain} method should do any necessary initialization
* required to start the agent and return. If the agent cannot be started, for
* example the agent class cannot be loaded, the agent class does not define a
* conformant {@code agentmain} method, or the {@code agentmain} method throws
* an uncaught exception or error, the JVM will abort before the application
* {@code main} method is invoked.
*
* <h3>Starting an agent from the command-line interface</h3>
*
* <p> Where an implementation provides a means to start agents from the command-line
* interface, an agent JAR is specified via the following command line option:
*
* <blockquote>{@code
* -javaagent:<jarpath>[=<options>]
@ -71,40 +100,32 @@
* where <i>{@code <jarpath>}</i> is the path to the agent JAR file and
* <i>{@code <options>}</i> is the agent options.
*
* <p> The manifest of the agent JAR file must contain the attribute {@code
* Premain-Class} in its main manifest. The value of this attribute is the
* name of the <i>agent class</i>. The agent class must implement a public
* static {@code premain} method similar in principle to the {@code main}
* application entry point. After the Java Virtual Machine (JVM) has
* initialized, the {@code premain} method will be called, then the real
* application {@code main} method. The {@code premain} method must return
* in order for the startup to proceed.
*
* <p> The {@code premain} method has one of two possible signatures. The
* JVM first attempts to invoke the following method on the agent class:
* <p> The main manifest of the agent JAR file must contain the attribute {@code
* Premain-Class}. The value of this attribute is the binary name of the agent class
* in the JAR file. The JVM starts the agent by loading the agent class and invoking its
* {@code premain} method. The method is invoked before the application {@code main}
* method is invoked. The {@code premain} method has one of two possible signatures.
* The JVM first attempts to invoke the following method on the agent class:
*
* <blockquote>{@code
* public static void premain(String agentArgs, Instrumentation inst)
* }</blockquote>
*
* <p> If the agent class does not implement this method then the JVM will
* attempt to invoke:
* <p> If the agent class does not define this method then the JVM will attempt to invoke:
* <blockquote>{@code
* public static void premain(String agentArgs)
* }</blockquote>
* <p> The agent class may also have an {@code agentmain} method for use when
* the agent is started after VM startup (see below). When the agent is started
* using a command-line option, the {@code agentmain} method is not invoked.
*
* <p> Each agent is passed its agent options via the {@code agentArgs} parameter.
* <p> The agent is passed its agent options via the {@code agentArgs} parameter.
* The agent options are passed as a single string, any additional parsing
* should be performed by the agent itself.
* should be performed by the agent itself. In the first method, the {@code inst}
* parameter is an {@link Instrumentation} object that the agent can use to instrument
* code.
*
* <p> If the agent cannot be started (for example, because the agent class
* cannot be loaded, or because the agent class does not have an appropriate
* {@code premain} method), the JVM will abort. If a {@code premain} method
* throws an uncaught exception, the JVM will abort.
* <p> If the agent cannot be started, for example the agent class cannot be loaded,
* the agent class does not define a conformant {@code premain} method, or the {@code
* premain} method throws an uncaught exception or error, the JVM will abort before
* the application {@code main} method is invoked.
*
* <p> An implementation is not required to provide a way to start agents
* from the command-line interface. When it does, then it supports the
@ -114,52 +135,58 @@
* agents are specified on the command line. More than one agent may use the
* same <i>{@code <jarpath>}</i>.
*
* <p> There are no modeling restrictions on what the agent {@code premain}
* method may do. Anything application {@code main} can do, including creating
* threads, is legal from {@code premain}.
* <p> The agent class may also have an {@code agentmain} method for use when the agent
* is started after in a running JVM (see below). When the agent is started using a
* command-line option, the {@code agentmain} method is not invoked.
*
* <h3>Starting an agent in a running JVM</h3>
*
* <h2>Starting an Agent After VM Startup</h2>
*
* <p> An implementation may provide a mechanism to start agents sometime after
* the VM has started. The details as to how this is initiated are
* implementation specific but typically the application has already started and
* its {@code main} method has already been invoked. In cases where an
* implementation supports the starting of agents after the VM has started the
* following applies:
*
* <p> An implementation may provide a mechanism to start agents in a running JVM (meaning
* after JVM startup). The details as to how this is initiated are implementation specific
* but typically the application has already started, and its {@code main} method has
* already been invoked. Where an implementation supports starting an agent in a running
* JVM, the following applies:
* <ol>
*
* <li><p> The manifest of the agent JAR must contain the attribute {@code
* Agent-Class} in its main manfiest. The value of this attribute is the name
* of the <i>agent class</i>. </p></li>
* <li><p> The agent class must be packaged into an agent JAR file. The main manifest
* of the agent JAR file must contain the attribute {@code Agent-Class}. The value of
* this attribute is the binary name of the agent class in the JAR file. </p></li>
*
* <li><p> The agent class must implement a public static {@code agentmain}
* method. </p></li>
* <li><p> The agent class must define a public static {@code agentmain} method. </p></li>
*
* <li><p> The JVM prints a warning on the standard error stream for each agent that it
* attempts to start in a running JVM. If an agent was previously started (at JVM
* startup, or started in a running JVM), then it is implementation specific as to whether
* a warning is printed when attempting to start the same agent a second or subsequent
* time. Warnings can be disabled by means of an implementation-specific command line
* option.
* <p><b>Implementation Note:</b> For the HotSpot VM, the JVM option
* {@code -XX:+EnableDynamicAgentLoading} is used to opt-in to allow dynamic loading of
* agents into a running JVM. This option suppresses the warning to standard error when
* starting an agent in a running JVM. </p></li>
*
* </ol>
*
* <p> The {@code agentmain} method has one of two possible signatures. The JVM
* first attempts to invoke the following method on the agent class:
* <p> The JVM starts the agent by loading the agent class and invoking its {@code
* agentmain} method. The {@code agentmain} method has one of two possible signatures.
* The JVM first attempts to invoke the following method on the agent class:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs, Instrumentation inst)
* }</blockquote>
*
* <p> If the agent class does not implement this method then the JVM will
* <p> If the agent class does not define this method then the JVM will
* attempt to invoke:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs)
* }</blockquote>
*
* <p> The agent class may also have a {@code premain} method for use when the
* agent is started using a command-line option. When the agent is started after
* VM startup the {@code premain} method is not invoked.
*
* <p> The agent is passed its agent options via the {@code agentArgs}
* parameter. The agent options are passed as a single string, any additional
* parsing should be performed by the agent itself.
* <p> The agent is passed its agent options via the {@code agentArgs} parameter.
* The agent options are passed as a single string, any additional parsing
* should be performed by the agent itself. In the first method, the {@code inst}
* parameter is an {@link Instrumentation} object that the agent can use to instrument
* code.
*
* <p> The {@code agentmain} method should do any necessary initialization
* required to start the agent. When startup is complete the method should
@ -169,36 +196,9 @@
* method throws an uncaught exception it will be ignored (but may be logged
* by the JVM for troubleshooting purposes).
*
*
* <h2>Including an Agent in an Executable JAR file</h2>
*
* <p> The JAR File Specification defines manifest attributes for standalone
* applications that are packaged as <em>executable JAR files</em>. If an
* implementation supports a mechanism to start an application as an executable
* JAR then the main manifest may include the {@code Launcher-Agent-Class}
* attribute to specify the class name of an agent to start before the application
* {@code main} method is invoked. The Java virtual machine attempts to
* invoke the following method on the agent class:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs, Instrumentation inst)
* }</blockquote>
*
* <p> If the agent class does not implement this method then the JVM will
* attempt to invoke:
*
* <blockquote>{@code
* public static void agentmain(String agentArgs)
* }</blockquote>
*
* <p> The value of the {@code agentArgs} parameter is always the empty string.
*
* <p> The {@code agentmain} method should do any necessary initialization
* required to start the agent and return. If the agent cannot be started, for
* example the agent class cannot be loaded, the agent class does not define a
* conformant {@code agentmain} method, or the {@code agentmain} method throws
* an uncaught exception or error, the JVM will abort.
*
* <p> The agent class may also have a {@code premain} method for use when the agent
* is started using a command-line option. The {@code premain} method is not invoked
* when the agent is started in a running JVM.
*
* <h2> Loading agent classes and the modules/classes available to the agent
* class </h2>
@ -248,31 +248,33 @@
* In other words, a custom system class loader must support the mechanism to
* add an agent JAR file to the system class loader search.
*
* <h2>Manifest Attributes</h2>
* <h2>JAR File Manifest Attributes</h2>
*
* <p> The following manifest attributes are defined for an agent JAR file:
* <p> The following attributes in the main section of the application or agent
* JAR file manifest are defined for Java agents:
*
* <blockquote><dl>
*
* <dt>{@code Launcher-Agent-Class}</dt>
* <dd> If an implementation supports a mechanism to start an application in an
* executable JAR file, then this attribute, if present, specifies the binary name
* of the agent class that is packaged with the application.
* The agent is started by invoking the agent class {@code agentmain} method. It is
* invoked before the application {@code main} method is invoked. </dd>
*
* <dt>{@code Premain-Class}</dt>
* <dd> When an agent is specified at JVM launch time this attribute specifies
* the agent class. That is, the class containing the {@code premain} method.
* When an agent is specified at JVM launch time this attribute is required. If
* the attribute is not present the JVM will abort. Note: this is a class name,
* not a file name or path. </dd>
* <dd> If an agent JAR is specified at JVM launch time, this attribute specifies
* the binary name of the agent class in the JAR file.
* The agent is started by invoking the agent class {@code premain} method. It is
* invoked before the application {@code main} method is invoked.
* If the attribute is not present the JVM will abort. </dd>
*
* <dt>{@code Agent-Class}</dt>
* <dd> If an implementation supports a mechanism to start agents sometime after
* the VM has started then this attribute specifies the agent class. That is,
* the class containing the {@code agentmain} method. This attribute is required
* if it is not present the agent will not be started. Note: this is a class name,
* not a file name or path. </dd>
*
* <dt>{@code Launcher-Agent-Class}</dt>
* <dd> If an implementation supports a mechanism to start an application as an
* executable JAR then the main manifest may include this attribute to specify
* the class name of an agent to start before the application {@code main}
* method is invoked. </dd>
* <dd> If an implementation supports a mechanism to start an agent sometime after
* the JVM has started, then this attribute specifies the binary name of the Java
* agent class in the agent JAR file.
* The agent is started by invoking the agent class {@code agentmain} method.
* This attribute is required; if not present the agent will not be started. </dd>
*
* <dt>{@code Boot-Class-Path}</dt>
* <dd> A list of paths to be searched by the bootstrap class loader. Paths
@ -284,7 +286,7 @@
* URI. The path is absolute if it begins with a slash character ('/'), otherwise
* it is relative. A relative path is resolved against the absolute path of the
* agent JAR file. Malformed and non-existent paths are ignored. When an agent is
* started sometime after the VM has started then paths that do not represent a
* started sometime after the JVM has started then paths that do not represent a
* JAR file are ignored. This attribute is optional. </dd>
*
* <dt>{@code Can-Redefine-Classes}</dt>
@ -310,10 +312,10 @@
* <p> An agent JAR file may have both the {@code Premain-Class} and {@code
* Agent-Class} attributes present in the manifest. When the agent is started
* on the command-line using the {@code -javaagent} option then the {@code
* Premain-Class} attribute specifies the name of the agent class and the {@code
* Premain-Class} attribute specifies the binary name of the agent class and the {@code
* Agent-Class} attribute is ignored. Similarly, if the agent is started sometime
* after the VM has started, then the {@code Agent-Class} attribute specifies
* the name of the agent class (the value of {@code Premain-Class} attribute is
* after the JVM has started, then the {@code Agent-Class} attribute specifies
* the binary name of the agent class (the value of {@code Premain-Class} attribute is
* ignored).
*
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -32,7 +32,12 @@ import java.lang.reflect.AccessibleObject;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.InvalidPathException;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.Collections;
@ -43,7 +48,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import jdk.internal.module.Modules;
import jdk.internal.vm.annotation.IntrinsicCandidate;
@ -59,6 +64,15 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
* processing behind native methods.
*/
public class InstrumentationImpl implements Instrumentation {
private static final String TRACE_USAGE_PROP_NAME = "jdk.instrument.traceUsage";
private static final boolean TRACE_USAGE;
static {
PrivilegedAction<String> pa = () -> System.getProperty(TRACE_USAGE_PROP_NAME);
@SuppressWarnings("removal")
String s = AccessController.doPrivileged(pa);
TRACE_USAGE = (s != null) && (s.isEmpty() || Boolean.parseBoolean(s));
}
private final TransformerManager mTransformerManager;
private TransformerManager mRetransfomableTransformerManager;
// needs to store a native pointer, so use 64 bits
@ -71,7 +85,8 @@ public class InstrumentationImpl implements Instrumentation {
private
InstrumentationImpl(long nativeAgent,
boolean environmentSupportsRedefineClasses,
boolean environmentSupportsNativeMethodPrefix) {
boolean environmentSupportsNativeMethodPrefix,
boolean printWarning) {
mTransformerManager = new TransformerManager(false);
mRetransfomableTransformerManager = null;
mNativeAgent = nativeAgent;
@ -79,60 +94,97 @@ public class InstrumentationImpl implements Instrumentation {
mEnvironmentSupportsRetransformClassesKnown = false; // false = need to ask
mEnvironmentSupportsRetransformClasses = false; // don't know yet
mEnvironmentSupportsNativeMethodPrefix = environmentSupportsNativeMethodPrefix;
if (printWarning) {
String source = jarFile(nativeAgent);
try {
Path path = Path.of(source);
PrivilegedAction<Path> pa = path::toAbsolutePath;
@SuppressWarnings("removal")
Path absolutePath = AccessController.doPrivileged(pa);
source = absolutePath.toString();
} catch (InvalidPathException e) {
// use original path
}
StringBuilder sb = new StringBuilder();
sb.append("WARNING: A Java agent has been loaded dynamically (")
.append(source)
.append(")")
.append(System.lineSeparator());
sb.append("WARNING: If a serviceability tool is in use, please run with"
+ " -XX:+EnableDynamicAgentLoading to hide this warning")
.append(System.lineSeparator());
if (!TRACE_USAGE) {
sb.append("WARNING: If a serviceability tool is not in use, please run with"
+ " -D" + TRACE_USAGE_PROP_NAME + " for more information")
.append(System.lineSeparator());
}
sb.append("WARNING: Dynamic loading of agents will be disallowed by default in a future release");
String warningMessage = sb.toString();
System.err.println(warningMessage);
}
}
public void
addTransformer(ClassFileTransformer transformer) {
@Override
public void addTransformer(ClassFileTransformer transformer) {
addTransformer(transformer, false);
}
public synchronized void
addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
@Override
public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
trace("addTransformer");
if (transformer == null) {
throw new NullPointerException("null passed as 'transformer' in addTransformer");
}
if (canRetransform) {
if (!isRetransformClassesSupported()) {
throw new UnsupportedOperationException(
"adding retransformable transformers is not supported in this environment");
}
if (mRetransfomableTransformerManager == null) {
mRetransfomableTransformerManager = new TransformerManager(true);
}
mRetransfomableTransformerManager.addTransformer(transformer);
if (mRetransfomableTransformerManager.getTransformerCount() == 1) {
setHasRetransformableTransformers(mNativeAgent, true);
}
} else {
mTransformerManager.addTransformer(transformer);
if (mTransformerManager.getTransformerCount() == 1) {
setHasTransformers(mNativeAgent, true);
synchronized (this) {
if (canRetransform) {
if (!isRetransformClassesSupported()) {
throw new UnsupportedOperationException(
"adding retransformable transformers is not supported in this environment");
}
if (mRetransfomableTransformerManager == null) {
mRetransfomableTransformerManager = new TransformerManager(true);
}
mRetransfomableTransformerManager.addTransformer(transformer);
if (mRetransfomableTransformerManager.getTransformerCount() == 1) {
setHasRetransformableTransformers(mNativeAgent, true);
}
} else {
mTransformerManager.addTransformer(transformer);
if (mTransformerManager.getTransformerCount() == 1) {
setHasTransformers(mNativeAgent, true);
}
}
}
}
public synchronized boolean
removeTransformer(ClassFileTransformer transformer) {
@Override
public boolean removeTransformer(ClassFileTransformer transformer) {
trace("removeTransformer");
if (transformer == null) {
throw new NullPointerException("null passed as 'transformer' in removeTransformer");
}
TransformerManager mgr = findTransformerManager(transformer);
if (mgr != null) {
mgr.removeTransformer(transformer);
if (mgr.getTransformerCount() == 0) {
if (mgr.isRetransformable()) {
setHasRetransformableTransformers(mNativeAgent, false);
} else {
setHasTransformers(mNativeAgent, false);
synchronized (this) {
TransformerManager mgr = findTransformerManager(transformer);
if (mgr != null) {
mgr.removeTransformer(transformer);
if (mgr.getTransformerCount() == 0) {
if (mgr.isRetransformable()) {
setHasRetransformableTransformers(mNativeAgent, false);
} else {
setHasTransformers(mNativeAgent, false);
}
}
return true;
}
return true;
return false;
}
return false;
}
public boolean
isModifiableClass(Class<?> theClass) {
@Override
public boolean isModifiableClass(Class<?> theClass) {
trace("isModifiableClass");
if (theClass == null) {
throw new NullPointerException(
"null passed as 'theClass' in isModifiableClass");
@ -140,15 +192,18 @@ public class InstrumentationImpl implements Instrumentation {
return isModifiableClass0(mNativeAgent, theClass);
}
@Override
public boolean isModifiableModule(Module module) {
trace("isModifiableModule");
if (module == null) {
throw new NullPointerException("'module' is null");
}
return true;
}
public boolean
isRetransformClassesSupported() {
@Override
public boolean isRetransformClassesSupported() {
trace("isRetransformClassesSupported");
// ask lazily since there is some overhead
if (!mEnvironmentSupportsRetransformClassesKnown) {
mEnvironmentSupportsRetransformClasses = isRetransformClassesSupported0(mNativeAgent);
@ -157,8 +212,9 @@ public class InstrumentationImpl implements Instrumentation {
return mEnvironmentSupportsRetransformClasses;
}
public void
retransformClasses(Class<?>... classes) {
@Override
public void retransformClasses(Class<?>... classes) {
trace("retransformClasses");
if (!isRetransformClassesSupported()) {
throw new UnsupportedOperationException(
"retransformClasses is not supported in this environment");
@ -169,14 +225,15 @@ public class InstrumentationImpl implements Instrumentation {
retransformClasses0(mNativeAgent, classes);
}
public boolean
isRedefineClassesSupported() {
@Override
public boolean isRedefineClassesSupported() {
trace("isRedefineClassesSupported");
return mEnvironmentSupportsRedefineClasses;
}
public void
redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException {
@Override
public void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException {
trace("retransformClasses");
if (!isRedefineClassesSupported()) {
throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
}
@ -191,47 +248,53 @@ public class InstrumentationImpl implements Instrumentation {
if (definitions.length == 0) {
return; // short-circuit if there are no changes requested
}
redefineClasses0(mNativeAgent, definitions);
}
@Override
@SuppressWarnings("rawtypes")
public Class[]
getAllLoadedClasses() {
public Class[] getAllLoadedClasses() {
trace("getAllLoadedClasses");
return getAllLoadedClasses0(mNativeAgent);
}
@Override
@SuppressWarnings("rawtypes")
public Class[]
getInitiatedClasses(ClassLoader loader) {
public Class[] getInitiatedClasses(ClassLoader loader) {
trace("getInitiatedClasses");
return getInitiatedClasses0(mNativeAgent, loader);
}
public long
getObjectSize(Object objectToSize) {
@Override
public long getObjectSize(Object objectToSize) {
trace("getObjectSize");
if (objectToSize == null) {
throw new NullPointerException("null passed as 'objectToSize' in getObjectSize");
}
return getObjectSize0(mNativeAgent, objectToSize);
}
public void
appendToBootstrapClassLoaderSearch(JarFile jarfile) {
@Override
public void appendToBootstrapClassLoaderSearch(JarFile jarfile) {
trace("appendToBootstrapClassLoaderSearch");
appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), true);
}
public void
appendToSystemClassLoaderSearch(JarFile jarfile) {
@Override
public void appendToSystemClassLoaderSearch(JarFile jarfile) {
trace("appendToSystemClassLoaderSearch");
appendToClassLoaderSearch0(mNativeAgent, jarfile.getName(), false);
}
public boolean
isNativeMethodPrefixSupported() {
@Override
public boolean isNativeMethodPrefixSupported() {
trace("isNativeMethodPrefixSupported");
return mEnvironmentSupportsNativeMethodPrefix;
}
public synchronized void
setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
@Override
public void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
trace("setNativeMethodPrefix");
if (!isNativeMethodPrefixSupported()) {
throw new UnsupportedOperationException(
"setNativeMethodPrefix is not supported in this environment");
@ -240,14 +303,16 @@ public class InstrumentationImpl implements Instrumentation {
throw new NullPointerException(
"null passed as 'transformer' in setNativeMethodPrefix");
}
TransformerManager mgr = findTransformerManager(transformer);
if (mgr == null) {
throw new IllegalArgumentException(
"transformer not registered in setNativeMethodPrefix");
synchronized (this) {
TransformerManager mgr = findTransformerManager(transformer);
if (mgr == null) {
throw new IllegalArgumentException(
"transformer not registered in setNativeMethodPrefix");
}
mgr.setNativeMethodPrefix(transformer, prefix);
String[] prefixes = mgr.getNativeMethodPrefixes();
setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable());
}
mgr.setNativeMethodPrefix(transformer, prefix);
String[] prefixes = mgr.getNativeMethodPrefixes();
setNativeMethodPrefixes(mNativeAgent, prefixes, mgr.isRetransformable());
}
@Override
@ -258,6 +323,8 @@ public class InstrumentationImpl implements Instrumentation {
Set<Class<?>> extraUses,
Map<Class<?>, List<Class<?>>> extraProvides)
{
trace("redefineModule");
if (!module.isNamed())
return;
@ -297,7 +364,6 @@ public class InstrumentationImpl implements Instrumentation {
}
extraProvides = tmpProvides;
// update reads
extraReads.forEach(m -> Modules.addReads(module, m));
@ -351,8 +417,8 @@ public class InstrumentationImpl implements Instrumentation {
}
private TransformerManager
findTransformerManager(ClassFileTransformer transformer) {
private TransformerManager findTransformerManager(ClassFileTransformer transformer) {
assert Thread.holdsLock(this);
if (mTransformerManager.includesTransformer(transformer)) {
return mTransformerManager;
}
@ -367,6 +433,9 @@ public class InstrumentationImpl implements Instrumentation {
/*
* Natives
*/
private native
String jarFile(long nativeAgent);
private native boolean
isModifiableClass0(long nativeAgent, Class<?> theClass);
@ -557,4 +626,61 @@ public class InstrumentationImpl implements Instrumentation {
}
private static native void loadAgent0(String path);
/**
* Prints a trace message and stack trace when tracing is enabled.
*/
private void trace(String methodName) {
if (!TRACE_USAGE) return;
// stack trace without frames in java.instrument module
List<StackWalker.StackFrame> stack = HolderStackWalker.walker.walk(s ->
s.dropWhile(f -> f.getDeclaringClass().getModule() == Instrumentation.class.getModule())
.collect(Collectors.toList())
);
// for tracing purposes, use the direct caller to code in java.instrument as the source
if (stack.size() > 0) {
Class<?> callerClass = stack.get(0).getDeclaringClass();
URL callerUrl = codeSource(callerClass);
String source;
if (callerUrl == null) {
source = callerClass.getName();
} else {
source = callerClass.getName() + " (" + callerUrl + ")";
}
StringBuilder sb = new StringBuilder();
sb.append("java.lang.instrument.Instrumentation.")
.append(methodName)
.append(" has been called by ")
.append(source);
stack.forEach(f -> sb.append(System.lineSeparator()).append("\tat " + f));
String traceMessage = sb.toString();
System.out.println(traceMessage);
}
}
/**
* Returns the possibly-bnull code source of the given class.
*/
private static URL codeSource(Class<?> clazz) {
PrivilegedAction<ProtectionDomain> pa = clazz::getProtectionDomain;
@SuppressWarnings("removal")
CodeSource cs = AccessController.doPrivileged(pa).getCodeSource();
return (cs != null) ? cs.getLocation() : null;
}
/**
* Holder for StackWalker object.
*/
private static class HolderStackWalker {
static final StackWalker walker;
static {
PrivilegedAction<StackWalker> pa = () ->
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
@SuppressWarnings("removal")
StackWalker w = AccessController.doPrivileged(pa);
walker = w;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -54,10 +54,22 @@
*/
DEF_STATIC_JNI_OnLoad
/*
* Class: sun_instrument_InstrumentationImpl
* Method: jarFile
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_sun_instrument_InstrumentationImpl_jarFile
(JNIEnv * jnienv, jobject implThis, jlong agent) {
return jarFile(jnienv, (JPLISAgent*)(intptr_t)agent);
}
/*
* Class: sun_instrument_InstrumentationImpl
* Method: isModifiableClass0
* Signature: (Ljava/lang/Class;)Z
* Signature: (JLjava/lang/Class;)Z
*/
JNIEXPORT jboolean JNICALL
Java_sun_instrument_InstrumentationImpl_isModifiableClass0
@ -68,7 +80,7 @@ Java_sun_instrument_InstrumentationImpl_isModifiableClass0
/*
* Class: sun_instrument_InstrumentationImpl
* Method: isRetransformClassesSupported0
* Signature: ()Z
* Signature: (J)Z
*/
JNIEXPORT jboolean JNICALL
Java_sun_instrument_InstrumentationImpl_isRetransformClassesSupported0
@ -79,7 +91,7 @@ Java_sun_instrument_InstrumentationImpl_isRetransformClassesSupported0
/*
* Class: sun_instrument_InstrumentationImpl
* Method: setHasTransformers
* Signature: (Z)V
* Signature: (JZ)V
*/
JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_setHasTransformers
@ -90,7 +102,7 @@ Java_sun_instrument_InstrumentationImpl_setHasTransformers
/*
* Class: sun_instrument_InstrumentationImpl
* Method: setHasRetransformableTransformers
* Signature: (Z)V
* Signature: (JZ)V
*/
JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_setHasRetransformableTransformers
@ -101,7 +113,7 @@ Java_sun_instrument_InstrumentationImpl_setHasRetransformableTransformers
/*
* Class: sun_instrument_InstrumentationImpl
* Method: retransformClasses0
* Signature: ([Ljava/lang/Class;)V
* Signature: (J[Ljava/lang/Class;)V
*/
JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_retransformClasses0
@ -112,7 +124,7 @@ Java_sun_instrument_InstrumentationImpl_retransformClasses0
/*
* Class: sun_instrument_InstrumentationImpl
* Method: redefineClasses0
* Signature: ([Ljava/lang/instrument/ClassDefinition;)V
* Signature: (J[Ljava/lang/instrument/ClassDefinition;)V
*/
JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_redefineClasses0
(JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classDefinitions) {
@ -122,7 +134,7 @@ JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_redefineClasses0
/*
* Class: sun_instrument_InstrumentationImpl
* Method: getAllLoadedClasses0
* Signature: ()[Ljava/lang/Class;
* Signature: (J)[Ljava/lang/Class;
*/
JNIEXPORT jobjectArray JNICALL Java_sun_instrument_InstrumentationImpl_getAllLoadedClasses0
(JNIEnv * jnienv, jobject implThis, jlong agent) {
@ -132,7 +144,7 @@ JNIEXPORT jobjectArray JNICALL Java_sun_instrument_InstrumentationImpl_getAllLoa
/*
* Class: sun_instrument_InstrumentationImpl
* Method: getInitiatedClasses0
* Signature: (Ljava/lang/ClassLoader;)[Ljava/lang/Class;
* Signature: (JLjava/lang/ClassLoader;)[Ljava/lang/Class;
*/
JNIEXPORT jobjectArray JNICALL Java_sun_instrument_InstrumentationImpl_getInitiatedClasses0
(JNIEnv * jnienv, jobject implThis, jlong agent, jobject classLoader) {
@ -142,7 +154,7 @@ JNIEXPORT jobjectArray JNICALL Java_sun_instrument_InstrumentationImpl_getInitia
/*
* Class: sun_instrument_InstrumentationImpl
* Method: getObjectSize0
* Signature: (Ljava/lang/Object;)J
* Signature: (JLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_sun_instrument_InstrumentationImpl_getObjectSize0
(JNIEnv * jnienv, jobject implThis, jlong agent, jobject objectToSize) {
@ -153,7 +165,7 @@ JNIEXPORT jlong JNICALL Java_sun_instrument_InstrumentationImpl_getObjectSize0
/*
* Class: sun_instrument_InstrumentationImpl
* Method: appendToClassLoaderSearch0
* Signature: (Ljava/lang/String;Z)V
* Signature: (JLjava/lang/String;Z)V
*/
JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_appendToClassLoaderSearch0
(JNIEnv * jnienv, jobject implThis, jlong agent, jstring jarFile, jboolean isBootLoader) {
@ -164,7 +176,7 @@ JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_appendToClassLoad
/*
* Class: sun_instrument_InstrumentationImpl
* Method: setNativeMethodPrefixes
* Signature: ([Ljava/lang/String;Z)V
* Signature: (J[Ljava/lang/String;Z)V
*/
JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_setNativeMethodPrefixes
(JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray prefixArray, jboolean isRetransformable) {

View File

@ -147,24 +147,24 @@ DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jint result = JNI_OK;
JPLISAgent * agent = NULL;
char * jarfile = NULL;
char * options = NULL;
initerror = createNewJPLISAgent(vm, &agent);
/*
* Parse <jarfile>[=options] into jarfile and options
*/
if (parseArgumentTail(tail, &jarfile, &options) != 0) {
fprintf(stderr, "-javaagent: memory allocation failure.\n");
return JNI_ERR;
}
initerror = createNewJPLISAgent(vm, &agent, jarfile, JNI_FALSE);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
int oldLen, newLen;
char * jarfile;
char * options;
jarAttribute* attributes;
char * premainClass;
char * bootClassPath;
/*
* Parse <jarfile>[=options] into jarfile and options
*/
if (parseArgumentTail(tail, &jarfile, &options) != 0) {
fprintf(stderr, "-javaagent: memory allocation failure.\n");
return JNI_ERR;
}
/*
* Agent_OnLoad is specified to provide the agent options
* argument tail in modified UTF8. However for 1.5.0 this is
@ -192,9 +192,6 @@ DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
return JNI_ERR;
}
/* Save the jarfile name */
agent->mJarfile = jarfile;
/*
* The value of the Premain-Class attribute becomes the agent
* class name. The manifest is in UTF8 so need to convert to
@ -254,11 +251,15 @@ DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
/*
* Clean-up
*/
if (options != NULL) free(options);
freeAttributes(attributes);
free(premainClass);
}
if (initerror != JPLIS_INIT_ERROR_NONE) {
free(jarfile);
}
if (options != NULL) free(options);
switch (initerror) {
case JPLIS_INIT_ERROR_NONE:
result = JNI_OK;
@ -307,6 +308,8 @@ DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
jint result = JNI_OK;
JPLISAgent * agent = NULL;
JNIEnv * jni_env = NULL;
char * jarfile = NULL;
char * options = NULL;
/*
* Need JNIEnv - guaranteed to be called from thread that is already
@ -315,23 +318,22 @@ DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
jplis_assert(result==JNI_OK);
initerror = createNewJPLISAgent(vm, &agent);
/*
* Parse <jarfile>[=options] into jarfile and options
*/
if (parseArgumentTail(args, &jarfile, &options) != 0) {
return JNI_ENOMEM;
}
jboolean print_warning = JVM_PrintWarningAtDynamicAgentLoad();
initerror = createNewJPLISAgent(vm, &agent, jarfile, print_warning);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
int oldLen, newLen;
char * jarfile;
char * options;
jarAttribute* attributes;
char * agentClass;
char * bootClassPath;
jboolean success;
/*
* Parse <jarfile>[=options] into jarfile and options
*/
if (parseArgumentTail(args, &jarfile, &options) != 0) {
return JNI_ENOMEM;
}
/*
* Open the JAR file and parse the manifest
*/
@ -450,12 +452,15 @@ DEF_Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
/*
* Clean-up
*/
free(jarfile);
if (options != NULL) free(options);
free(agentClass);
freeAttributes(attributes);
}
if (initerror != JPLIS_INIT_ERROR_NONE || result != JNI_OK) {
free(jarfile);
}
if (options != NULL) free(options);
return result;
}
@ -486,17 +491,18 @@ jint loadAgent(JNIEnv* env, jstring path) {
return JNI_ERR;
}
// create JPLISAgent with JVMTI environment
if (createNewJPLISAgent(vm, &agent) != JPLIS_INIT_ERROR_NONE) {
return JNI_ERR;
}
// get path to JAR file as UTF-8 string
jarfile = (*env)->GetStringUTFChars(env, path, NULL);
if (jarfile == NULL) {
return JNI_ERR;
}
// create JPLISAgent with JVMTI environment
if (createNewJPLISAgent(vm, &agent, jarfile, JNI_FALSE) != JPLIS_INIT_ERROR_NONE) {
(*env)->ReleaseStringUTFChars(env, path, jarfile);
return JNI_ERR;
}
// read the attributes in the main section of JAR manifest
attributes = readAttributes(jarfile);
if (attributes == NULL) {
@ -570,7 +576,7 @@ releaseAndReturn:
if (attributes != NULL) {
freeAttributes(attributes);
}
if (jarfile != NULL) {
if (result != JNI_OK && jarfile != NULL) {
(*env)->ReleaseStringUTFChars(env, path, jarfile);
}
@ -612,8 +618,6 @@ eventHandlerVMInit( jvmtiEnv * jvmtienv,
free((void *)agent->mJarfile);
abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", appending to system class path failed");
}
free((void *)agent->mJarfile);
agent->mJarfile = NULL;
outstandingException = preserveThrowable(jnienv);
success = processJavaStart( environment->mAgent, jnienv);

View File

@ -63,7 +63,9 @@ allocateJPLISAgent(jvmtiEnv * jvmtiEnv);
JPLISInitializationError
initializeJPLISAgent( JPLISAgent * agent,
JavaVM * vm,
jvmtiEnv * jvmtienv);
jvmtiEnv * jvmtienv,
const char * jarfile,
jboolean printWarning);
/* De-allocates a JPLIS agent data structure. Only used in partial-failure cases at startup;
* in normal usage the JPLIS agent lives forever
*/
@ -202,7 +204,7 @@ getJPLISEnvironment(jvmtiEnv * jvmtienv) {
* or NULL if an error has occurred.
*/
JPLISInitializationError
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr, const char * jarfile, jboolean printWarning) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jvmtiEnv * jvmtienv = NULL;
jint jnierror = JNI_OK;
@ -220,7 +222,9 @@ createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {
} else {
initerror = initializeJPLISAgent( agent,
vm,
jvmtienv);
jvmtienv,
jarfile,
printWarning);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
*agent_ptr = agent;
} else {
@ -251,7 +255,9 @@ allocateJPLISAgent(jvmtiEnv * jvmtienv) {
JPLISInitializationError
initializeJPLISAgent( JPLISAgent * agent,
JavaVM * vm,
jvmtiEnv * jvmtienv) {
jvmtiEnv * jvmtienv,
const char * jarfile,
jboolean printWarning) {
jvmtiError jvmtierror = JVMTI_ERROR_NONE;
jvmtiPhase phase;
@ -272,7 +278,8 @@ initializeJPLISAgent( JPLISAgent * agent,
agent->mNativeMethodPrefixAdded = JNI_FALSE;
agent->mAgentClassName = NULL;
agent->mOptionsString = NULL;
agent->mJarfile = NULL;
agent->mJarfile = jarfile;
agent->mPrintWarning = printWarning;
/* make sure we can recover either handle in either direction.
* the agent has a ref to the jvmti; make it mutual
@ -512,7 +519,8 @@ createInstrumentationImpl( JNIEnv * jnienv,
constructorID,
peerReferenceAsScalar,
agent->mRedefineAdded,
agent->mNativeMethodPrefixAdded);
agent->mNativeMethodPrefixAdded,
agent->mPrintWarning);
errorOutstanding = checkForAndClearThrowable(jnienv);
errorOutstanding = errorOutstanding || (localReference == NULL);
jplis_assert_msg(!errorOutstanding, "call constructor on InstrumentationImpl failed");
@ -1605,3 +1613,8 @@ setNativeMethodPrefixes(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray prefix
deallocate(jvmtienv, (void*)originForRelease);
}
}
jstring
jarFile(JNIEnv * jnienv, JPLISAgent * agent) {
return (*jnienv)->NewStringUTF(jnienv, agent->mJarfile);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -59,7 +59,7 @@ typedef struct _JPLISEnvironment JPLISEnvironment;
*/
#define JPLIS_INSTRUMENTIMPL_CLASSNAME "sun/instrument/InstrumentationImpl"
#define JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODNAME "<init>"
#define JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODSIGNATURE "(JZZ)V"
#define JPLIS_INSTRUMENTIMPL_CONSTRUCTOR_METHODSIGNATURE "(JZZZ)V"
#define JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODNAME "loadClassAndCallPremain"
#define JPLIS_INSTRUMENTIMPL_PREMAININVOKER_METHODSIGNATURE "(Ljava/lang/String;Ljava/lang/String;)V"
#define JPLIS_INSTRUMENTIMPL_AGENTMAININVOKER_METHODNAME "loadClassAndCallAgentmain"
@ -108,6 +108,7 @@ struct _JPLISAgent {
char const * mAgentClassName; /* agent class name */
char const * mOptionsString; /* -javaagent options string */
const char * mJarfile; /* agent jar file name */
jboolean mPrintWarning; /* print warning when started */
};
/*
@ -151,7 +152,7 @@ getJPLISEnvironment(jvmtiEnv * jvmtienv);
* or NULL if an error has occurred.
*/
extern JPLISInitializationError
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr);
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr, const char * jarfile, jboolean printWarning);
/* Adds can_redefine_classes capability */
extern void
@ -272,6 +273,9 @@ extern void
setNativeMethodPrefixes(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray prefixArray,
jboolean isRetransformable);
extern jstring
jarFile(JNIEnv * jnienv, JPLISAgent * agent);
#define jvmti(a) a->mNormalEnvironment.mJVMTIEnv
/*

View File

@ -70,6 +70,7 @@ requires.properties= \
vm.hasSA \
vm.hasJFR \
vm.jvmci \
vm.jvmti \
docker.support \
release.implementor \
jdk.containerized \

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.DataOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* The "application" launched by DyamicLoadWarningTest.
*
* The application phones home, sends its pid to the test, waits for a reply, then exits.
*/
public class Application {
public static void main(String[] args) throws Exception {
InetAddress lh = InetAddress.getLoopbackAddress();
int port = Integer.parseInt(args[0]);
try (Socket s = new Socket(lh, port);
DataOutputStream out = new DataOutputStream(s.getOutputStream())) {
// send pid
long pid = ProcessHandle.current().pid();
out.writeLong(pid);
// wait for shutdown
s.getInputStream().read();
}
}
}

View File

@ -0,0 +1,308 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8307478
* @summary Test that a warning is printed when an agent is dynamically loaded
* @requires vm.jvmti
* @modules jdk.attach jdk.jcmd
* @library /test/lib /test/jdk
* @build Application JavaAgent
* @run junit/othervm/native DynamicLoadWarningTest
*/
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.tools.attach.VirtualMachine;
import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.Platform;
import jdk.test.lib.Utils;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.util.JarUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
class DynamicLoadWarningTest {
private static final String JVMTI_AGENT_WARNING = "WARNING: A JVM TI agent has been loaded dynamically";
private static final String JAVA_AGENT_WARNING = "WARNING: A Java agent has been loaded dynamically";
// JVM TI agents
private static final String JVMTI_AGENT1_LIB = "JvmtiAgent1";
private static final String JVMTI_AGENT2_LIB = "JvmtiAgent2";
private static String jvmtiAgentPath1;
private static String jvmtiAgentPath2;
// Java agent
private static final String TEST_CLASSES = System.getProperty("test.classes");
private static String javaAgent;
@BeforeAll
static void setup() throws Exception {
// get absolute path to JVM TI agents
String prefix = Platform.isWindows() ? "" : "lib";
String libname1 = prefix + JVMTI_AGENT1_LIB + "." + Platform.sharedLibraryExt();
String libname2 = prefix + JVMTI_AGENT2_LIB + "." + Platform.sharedLibraryExt();
jvmtiAgentPath1 = Path.of(Utils.TEST_NATIVE_PATH, libname1).toAbsolutePath().toString();
jvmtiAgentPath2 = Path.of(Utils.TEST_NATIVE_PATH, libname2).toAbsolutePath().toString();
// create JAR file with Java agent
Manifest man = new Manifest();
Attributes attrs = man.getMainAttributes();
attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attrs.put(new Attributes.Name("Agent-Class"), "JavaAgent");
Path jarfile = Path.of("javaagent.jar");
Path classes = Path.of(TEST_CLASSES);
JarUtils.createJarFile(jarfile, man, classes, Path.of("JavaAgent.class"));
javaAgent = jarfile.toString();
}
/**
* Actions to load JvmtiAgent1 into a running VM.
*/
private static Stream<OnAttachAction> loadJvmtiAgent1() {
// load agent with the attach API
OnAttachAction loadJvmtiAgent = (pid, vm) -> vm.loadAgentLibrary(JVMTI_AGENT1_LIB);
// jcmd <pid> JVMTI.agent_load <agent>
OnAttachAction jcmdAgentLoad = jcmdAgentLoad(jvmtiAgentPath1);
return Stream.of(loadJvmtiAgent, jcmdAgentLoad);
}
/**
* Test loading JvmtiAgent1 into a running VM.
*/
@ParameterizedTest
@MethodSource("loadJvmtiAgent1")
void testLoadOneJvmtiAgent(OnAttachAction loadJvmtiAgent1) throws Exception {
// dynamically load loadJvmtiAgent1
test().whenRunning(loadJvmtiAgent1)
.stderrShouldContain(JVMTI_AGENT_WARNING);
// dynamically load loadJvmtiAgent1 twice, should be one warning
test().whenRunning(loadJvmtiAgent1)
.whenRunning(loadJvmtiAgent1)
.stderrShouldContain(JVMTI_AGENT_WARNING, 1);
// opt-in via command line option to allow dynamic loading of agents
test().withOpts("-XX:+EnableDynamicAgentLoading")
.whenRunning(loadJvmtiAgent1)
.stderrShouldNotContain(JVMTI_AGENT_WARNING);
// start loadJvmtiAgent1 via the command line, then dynamically load loadJvmtiAgent1
test().withOpts("-agentpath:" + jvmtiAgentPath1)
.whenRunning(loadJvmtiAgent1)
.stderrShouldNotContain(JVMTI_AGENT_WARNING);
}
/**
* Test loading JvmtiAgent1 and JvmtiAgent2 into a running VM.
*/
@ParameterizedTest
@MethodSource("loadJvmtiAgent1")
void testLoadTwoJvmtiAgents(OnAttachAction loadJvmtiAgent1) throws Exception {
OnAttachAction loadJvmtiAgent2 = (pid, vm) -> vm.loadAgentLibrary(JVMTI_AGENT2_LIB);
OnAttachAction jcmdAgentLoad2 = jcmdAgentLoad(jvmtiAgentPath2);
// dynamically load loadJvmtiAgent1, then dynamically load loadJvmtiAgent2 with attach API
test().whenRunning(loadJvmtiAgent1)
.whenRunning(loadJvmtiAgent2)
.stderrShouldContain(JVMTI_AGENT_WARNING, 2);
// dynamically load loadJvmtiAgent1, then dynamically load loadJvmtiAgent2 with jcmd
test().whenRunning(loadJvmtiAgent1)
.whenRunning(jcmdAgentLoad2)
.stderrShouldContain(JVMTI_AGENT_WARNING, 2);
// start loadJvmtiAgent2 via the command line, then dynamically load loadJvmtiAgent1
test().withOpts("-agentpath:" + jvmtiAgentPath2)
.whenRunning(loadJvmtiAgent1)
.stderrShouldContain(JVMTI_AGENT_WARNING);
}
/**
* Test loading Java agent into a running VM.
*/
@Test
void testLoadJavaAgent() throws Exception {
OnAttachAction loadJavaAgent = (pid, vm) -> vm.loadAgent(javaAgent);
// agent dynamically loaded
test().whenRunning(loadJavaAgent)
.stderrShouldContain(JAVA_AGENT_WARNING);
// opt-in via command line option to allow dynamic loading of agents
test().withOpts("-XX:+EnableDynamicAgentLoading")
.whenRunning(loadJavaAgent)
.stderrShouldNotContain(JAVA_AGENT_WARNING);
}
/**
* Represents an operation that accepts a process identifier and a VirtualMachine
* that the current JVM is attached to.
*/
private interface OnAttachAction {
void accept(long pid, VirtualMachine vm) throws Exception;
}
/**
* Returns an operation that invokes "jcmd <pid> JVMTI.agent_load <agentpath>" to
* load the given agent library into the JVM that the current JVM is attached to.
*/
private static OnAttachAction jcmdAgentLoad(String agentPath) {
return (pid, vm) -> {
String[] jcmd = JDKToolLauncher.createUsingTestJDK("jcmd")
.addToolArg(Long.toString(pid))
.addToolArg("JVMTI.agent_load")
.addToolArg(agentPath)
.getCommand();
System.out.println(Arrays.stream(jcmd).collect(Collectors.joining(" ")));
Process p = new ProcessBuilder(jcmd).inheritIO().start();
assertEquals(0, p.waitFor());
};
}
/**
* Returns a new app runner.
*/
private static AppRunner test() {
return new AppRunner();
}
/**
* Runs an application in its own VM. Once started, it attachs to the VM, runs a set
* of actions, then checks that the output contains, or does not contain, a string.
*/
private static class AppRunner {
private String[] vmopts = new String[0];
private List<OnAttachAction> actions = new ArrayList<>();
/**
* Specifies VM options to run the application.
*/
AppRunner withOpts(String... vmopts) {
this.vmopts = vmopts;
return this;
}
/**
* Specifies an action to run when the attached to the running application.
*/
AppRunner whenRunning(OnAttachAction action) {
actions.add(action);
return this;
}
OutputAnalyzer run() throws Exception {
// start a listener socket that the application will connect to
try (ServerSocket listener = new ServerSocket()) {
InetAddress lh = InetAddress.getLoopbackAddress();
listener.bind(new InetSocketAddress(lh, 0));
var done = new AtomicBoolean();
// start a thread to wait for the application to phone home
Thread.ofPlatform().daemon().start(() -> {
try (Socket s = listener.accept();
DataInputStream in = new DataInputStream(s.getInputStream())) {
// read pid
long pid = in.readLong();
// attach and run the actions with the vm object
VirtualMachine vm = VirtualMachine.attach(Long.toString(pid));
try {
for (OnAttachAction action : actions) {
action.accept(pid, vm);
}
} finally {
vm.detach();
}
done.set(true);
// shutdown
s.getOutputStream().write(0);
} catch (Exception e) {
e.printStackTrace();
}
});
// launch application with the given VM options, waiting for it to terminate
Stream<String> s1 = Stream.of(vmopts);
Stream<String> s2 = Stream.of("Application", Integer.toString(listener.getLocalPort()));
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
OutputAnalyzer outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.out)
.errorTo(System.out);
assertEquals(0, outputAnalyzer.getExitValue());
assertTrue(done.get(), "Attach or action failed, see log for details");
return outputAnalyzer;
}
}
/**
* Run the application, checking that standard error contains a string.
*/
void stderrShouldContain(String s) throws Exception {
run().stderrShouldContain(s);
}
/**
* Run the application, checking that standard error contains the given number of
* occurrences of a string.
*/
void stderrShouldContain(String s, int occurrences) throws Exception {
List<String> lines = run().asLines();
int count = (int) lines.stream().filter(line -> line.indexOf(s) >= 0).count();
assertEquals(occurrences, count);
}
/**
* Run the application, checking that standard error does not contain a string.
*/
void stderrShouldNotContain(String s) throws Exception {
run().stderrShouldNotContain(s);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* A no-op Java agent.
*/
public class JavaAgent {
public static void agentmain(String args) {
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jvmti.h"
/**
* A no-op JVM TI agent.
*/
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv* jvmti;
return vm->GetEnv((void**)&jvmti, JVMTI_VERSION);
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv* jvmti;
return vm->GetEnv((void**)&jvmti, JVMTI_VERSION);
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jvmti.h"
/**
* A no-op JVM TI agent.
*/
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv* jvmti;
return vm->GetEnv((void**)&jvmti, JVMTI_VERSION);
}
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv* jvmti;
return vm->GetEnv((void**)&jvmti, JVMTI_VERSION);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.Map;
import java.util.Set;
/**
* Agent used by TraceUsageTest. The premain and agentmain methods invoke Instrumentation
* methods so the usages can be traced by the test.
*/
public class TraceUsageAgent {
public static void premain(String methodNames, Instrumentation inst) throws Exception {
test(methodNames, inst);
}
public static void agentmain(String methodNames, Instrumentation inst) throws Exception {
test(methodNames, inst);
}
private static void test(String methodNames, Instrumentation inst) throws Exception {
for (String methodName : methodNames.split(",")) {
switch (methodName) {
case "addTransformer" -> {
var transformer = new ClassFileTransformer() { };
inst.addTransformer(transformer);
}
case "retransformClasses" -> {
inst.retransformClasses(Object.class);
}
case "redefineModule" -> {
Module base = Object.class.getModule();
inst.redefineModule(base, Set.of(), Map.of(), Map.of(), Set.of(), Map.of());
}
default -> {
throw new RuntimeException("Unknown method name: " + methodName);
}
}
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8307478
* @summary Test Instrumentation tracing is enabled with a system property
* @library /test/lib
* @run shell MakeJAR3.sh TraceUsageAgent 'Agent-Class: TraceUsageAgent' 'Can-Retransform-Classes: true'
* @run junit TraceUsageTest
*/
import com.sun.tools.attach.VirtualMachine;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TraceUsageTest {
private static final String JAVA_AGENT = "TraceUsageAgent.jar";
// Instrumentation methods to test
private static final String[] INSTRUMENTATION_METHODS = {
"addTransformer",
"retransformClasses",
"redefineModule"
};
/**
* If launched with the argument "attach" then it loads the java agent into the
* current VM with the given options.
*/
public static void main(String[] args) throws Exception {
if (args.length > 0 && args[0].equals("attach")) {
String options = args[1];
long pid = ProcessHandle.current().pid();
VirtualMachine vm = VirtualMachine.attach(""+pid);
try {
vm.loadAgent(JAVA_AGENT, options);
} finally {
vm.detach();
}
}
}
/**
* Test agent started on the command line with -javaagent.
*/
@Test
void testPremain() throws Exception {
OutputAnalyzer outputAnalyzer = execute(
"-javaagent:" + JAVA_AGENT + "=" + String.join(",", INSTRUMENTATION_METHODS),
"-Djdk.instrument.traceUsage=true",
"TraceUsageTest"
);
for (String mn : INSTRUMENTATION_METHODS) {
String expected = "Instrumentation." + mn + " has been called by TraceUsageAgent";
outputAnalyzer.shouldContain(expected);
}
outputAnalyzer.shouldContain("at TraceUsageAgent.premain");
}
/**
* Test agent loaded into a running VM with the attach mechanism.
*/
@Test
void testAgentmain() throws Exception {
OutputAnalyzer outputAnalyzer = execute(
"-Djdk.attach.allowAttachSelf=true",
"-Djdk.instrument.traceUsage=true",
"TraceUsageTest",
"attach",
String.join(",", INSTRUMENTATION_METHODS)
);
for (String mn : INSTRUMENTATION_METHODS) {
String expected = "Instrumentation." + mn + " has been called by TraceUsageAgent";
outputAnalyzer.shouldContain(expected);
}
outputAnalyzer.shouldContain("at TraceUsageAgent.agentmain");
}
private OutputAnalyzer execute(String... command) throws Exception {
OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(command)
.outputTo(System.out)
.errorTo(System.out);
assertEquals(0, outputAnalyzer.getExitValue());
return outputAnalyzer;
}
}