8307478: Implementation of Prepare to Restrict The Dynamic Loading of Agents
Reviewed-by: sspitsyn, cjplummer
This commit is contained in:
parent
325940b091
commit
5bd2af26e6
@ -181,6 +181,7 @@ JVM_NewArray
|
||||
JVM_NewInstanceFromConstructor
|
||||
JVM_NewMultiArray
|
||||
JVM_PhantomReferenceRefersTo
|
||||
JVM_PrintWarningAtDynamicAgentLoad
|
||||
JVM_RaiseSignal
|
||||
JVM_RawMonitorCreate
|
||||
JVM_RawMonitorDestroy
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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; });
|
||||
|
@ -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).
|
||||
*
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
@ -70,6 +70,7 @@ requires.properties= \
|
||||
vm.hasSA \
|
||||
vm.hasJFR \
|
||||
vm.jvmci \
|
||||
vm.jvmti \
|
||||
docker.support \
|
||||
release.implementor \
|
||||
jdk.containerized \
|
||||
|
48
test/jdk/com/sun/tools/attach/warnings/Application.java
Normal file
48
test/jdk/com/sun/tools/attach/warnings/Application.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
30
test/jdk/com/sun/tools/attach/warnings/JavaAgent.java
Normal file
30
test/jdk/com/sun/tools/attach/warnings/JavaAgent.java
Normal 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) {
|
||||
}
|
||||
}
|
40
test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent1.cpp
Normal file
40
test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent1.cpp
Normal 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);
|
||||
}
|
40
test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent2.cpp
Normal file
40
test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent2.cpp
Normal 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);
|
||||
}
|
62
test/jdk/java/lang/instrument/TraceUsageAgent.java
Normal file
62
test/jdk/java/lang/instrument/TraceUsageAgent.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
111
test/jdk/java/lang/instrument/TraceUsageTest.java
Normal file
111
test/jdk/java/lang/instrument/TraceUsageTest.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user