diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index fb7644b5303..5cf6662cba8 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -181,6 +181,7 @@ JVM_NewArray JVM_NewInstanceFromConstructor JVM_NewMultiArray JVM_PhantomReferenceRefersTo +JVM_PrintWarningAtDynamicAgentLoad JVM_RaiseSignal JVM_RawMonitorCreate JVM_RawMonitorDestroy diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 01c8c317231..f1e2c35891a 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -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 diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 7ab33cd47cc..2d3b66c8fd9 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -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 diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index 7d5361f17a8..92795094a86 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -640,6 +640,17 @@ Agent_OnLoad_L(JavaVM *vm, char *options, void *reserved) or implementation specific API, to attach to the running VM, and request it start a given agent.

+ 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 OnLoad + 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. +

+ Implementation Note: For the HotSpot VM, the VM option + -XX:+EnableDynamicAgentLoading 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. +

If an agent is started during the live phase then its agent library must export a start-up function with the following prototype: diff --git a/src/hotspot/share/prims/jvmtiAgent.cpp b/src/hotspot/share/prims/jvmtiAgent.cpp index 21dff2ca192..feb765aa5f6 100644 --- a/src/hotspot/share/prims/jvmtiAgent.cpp +++ b/src/hotspot/share/prims/jvmtiAgent.cpp @@ -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, diff --git a/src/hotspot/share/prims/jvmtiAgentList.cpp b/src/hotspot/share/prims/jvmtiAgentList.cpp index 22d74e930cf..a32eeb7076c 100644 --- a/src/hotspot/share/prims/jvmtiAgentList.cpp +++ b/src/hotspot/share/prims/jvmtiAgentList.cpp @@ -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"); diff --git a/src/hotspot/share/prims/jvmtiAgentList.hpp b/src/hotspot/share/prims/jvmtiAgentList.hpp index cdb8b7722b0..cf698c69c01 100644 --- a/src/hotspot/share/prims/jvmtiAgentList.hpp +++ b/src/hotspot/share/prims/jvmtiAgentList.hpp @@ -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; }); diff --git a/src/java.instrument/share/classes/java/lang/instrument/package-info.java b/src/java.instrument/share/classes/java/lang/instrument/package-info.java index 36bb02a161d..4219d51f05f 100644 --- a/src/java.instrument/share/classes/java/lang/instrument/package-info.java +++ b/src/java.instrument/share/classes/java/lang/instrument/package-info.java @@ -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. * - *

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: + *

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 agent JAR. An attribute in the main manifest of the JAR file + * identifies one of the class files in the JAR file as the agent class. + * The agent class defines a special method that the JVM invokes to start + * the agent. * - *

    - *
  1. For implementations that support a command-line interface, an agent - * can be started by specifying an option on the command-line.

  2. - * - *
  3. 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 attach to a running application, and - * initiate the loading of the tool's agent into the running application.

  4. - * - *
  5. An agent may be packaged with an application in an executable JAR - * file.

  6. - *
+ *

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. * *

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. * - *

The three ways to start an agent are described below. + *

Starting an agent

* - *

Starting an Agent from the Command-Line Interface

+ *

Starting an agent packaged with an application in an executable JAR file

* - *

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: + *

The JAR File Specification defines + * manifest attributes for standalone applications that are packaged as executable + * JAR files. 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: + * + *

{@code + * public static void agentmain(String agentArgs, Instrumentation inst) + * }
+ * + *

If the agent class does not define this method then the JVM will attempt + * to invoke: + * + *

{@code + * public static void agentmain(String agentArgs) + * }
+ * + *

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. + * + *

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. + * + *

Starting an agent from the command-line interface

+ * + *

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: * *

{@code * -javaagent:[=] @@ -71,40 +100,32 @@ * where {@code } is the path to the agent JAR file and * {@code } is the agent options. * - *

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 agent class. 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. - * - *

The {@code premain} method has one of two possible signatures. The - * JVM first attempts to invoke the following method on the agent class: + *

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: * *

{@code * public static void premain(String agentArgs, Instrumentation inst) * }
* - *

If the agent class does not implement this method then the JVM will - * attempt to invoke: + *

If the agent class does not define this method then the JVM will attempt to invoke: *

{@code * public static void premain(String agentArgs) * }
- - *

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. * - *

Each agent is passed its agent options via the {@code agentArgs} parameter. + *

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. * - *

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. + *

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. * *

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 {@code }. * - *

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}. + *

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. * + *

Starting an agent in a running JVM

* - *

Starting an Agent After VM Startup

- * - *

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: - * + *

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: *

    * - *
  1. 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 agent class.

  2. + *
  3. 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.

  4. * - *
  5. The agent class must implement a public static {@code agentmain} - * method.

  6. + *
  7. The agent class must define a public static {@code agentmain} method.

  8. + * + *
  9. 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. + *

    Implementation Note: 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.

  10. * *
* - *

The {@code agentmain} method has one of two possible signatures. The JVM - * first attempts to invoke the following method on the agent class: + *

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: * *

{@code * public static void agentmain(String agentArgs, Instrumentation inst) * }
* - *

If the agent class does not implement this method then the JVM will + *

If the agent class does not define this method then the JVM will * attempt to invoke: * *

{@code * public static void agentmain(String agentArgs) * }
* - *

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. - * - *

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. + *

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. * *

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). * - * - *

Including an Agent in an Executable JAR file

- * - *

The JAR File Specification defines manifest attributes for standalone - * applications that are packaged as executable JAR files. 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: - * - *

{@code - * public static void agentmain(String agentArgs, Instrumentation inst) - * }
- * - *

If the agent class does not implement this method then the JVM will - * attempt to invoke: - * - *

{@code - * public static void agentmain(String agentArgs) - * }
- * - *

The value of the {@code agentArgs} parameter is always the empty string. - * - *

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. - * + *

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. * *

Loading agent classes and the modules/classes available to the agent * class

@@ -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. * - *

Manifest Attributes

+ *

JAR File Manifest Attributes

* - *

The following manifest attributes are defined for an agent JAR file: + *

The following attributes in the main section of the application or agent + * JAR file manifest are defined for Java agents: * *

* + *
{@code Launcher-Agent-Class}
+ *
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.
+ * *
{@code Premain-Class}
- *
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.
+ *
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.
* *
{@code Agent-Class}
- *
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.
- * - *
{@code Launcher-Agent-Class}
- *
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.
+ *
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.
* *
{@code Boot-Class-Path}
*
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.
* *
{@code Can-Redefine-Classes}
@@ -310,10 +312,10 @@ *

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). * * diff --git a/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java b/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java index 642c7dd7aab..bd610c36480 100644 --- a/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java +++ b/src/java.instrument/share/classes/sun/instrument/InstrumentationImpl.java @@ -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 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 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> extraUses, Map, List>> 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 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 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 pa = () -> + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + @SuppressWarnings("removal") + StackWalker w = AccessController.doPrivileged(pa); + walker = w; + } + } } diff --git a/src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c b/src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c index 7d8937dc508..adb0d448508 100644 --- a/src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c +++ b/src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c @@ -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) { diff --git a/src/java.instrument/share/native/libinstrument/InvocationAdapter.c b/src/java.instrument/share/native/libinstrument/InvocationAdapter.c index 9fbbbe7e341..41ab02f6e6e 100644 --- a/src/java.instrument/share/native/libinstrument/InvocationAdapter.c +++ b/src/java.instrument/share/native/libinstrument/InvocationAdapter.c @@ -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 [=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 [=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 [=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 [=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); diff --git a/src/java.instrument/share/native/libinstrument/JPLISAgent.c b/src/java.instrument/share/native/libinstrument/JPLISAgent.c index 10b93aa2c98..c65bfb9f2f9 100644 --- a/src/java.instrument/share/native/libinstrument/JPLISAgent.c +++ b/src/java.instrument/share/native/libinstrument/JPLISAgent.c @@ -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); +} diff --git a/src/java.instrument/share/native/libinstrument/JPLISAgent.h b/src/java.instrument/share/native/libinstrument/JPLISAgent.h index 1ee046d69a0..3e9642f3de4 100644 --- a/src/java.instrument/share/native/libinstrument/JPLISAgent.h +++ b/src/java.instrument/share/native/libinstrument/JPLISAgent.h @@ -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 "" -#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 /* diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 0fc339ef23f..3c523465c46 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -70,6 +70,7 @@ requires.properties= \ vm.hasSA \ vm.hasJFR \ vm.jvmci \ + vm.jvmti \ docker.support \ release.implementor \ jdk.containerized \ diff --git a/test/jdk/com/sun/tools/attach/warnings/Application.java b/test/jdk/com/sun/tools/attach/warnings/Application.java new file mode 100644 index 00000000000..40ac41644d7 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/warnings/Application.java @@ -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(); + } + } +} diff --git a/test/jdk/com/sun/tools/attach/warnings/DynamicLoadWarningTest.java b/test/jdk/com/sun/tools/attach/warnings/DynamicLoadWarningTest.java new file mode 100644 index 00000000000..4997ac84b7d --- /dev/null +++ b/test/jdk/com/sun/tools/attach/warnings/DynamicLoadWarningTest.java @@ -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 loadJvmtiAgent1() { + // load agent with the attach API + OnAttachAction loadJvmtiAgent = (pid, vm) -> vm.loadAgentLibrary(JVMTI_AGENT1_LIB); + + // jcmd JVMTI.agent_load + 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 JVMTI.agent_load " 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 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 s1 = Stream.of(vmopts); + Stream 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 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); + } + } +} diff --git a/test/jdk/com/sun/tools/attach/warnings/JavaAgent.java b/test/jdk/com/sun/tools/attach/warnings/JavaAgent.java new file mode 100644 index 00000000000..c4732a15230 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/warnings/JavaAgent.java @@ -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) { + } +} diff --git a/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent1.cpp b/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent1.cpp new file mode 100644 index 00000000000..65e0c59af86 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent1.cpp @@ -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); +} diff --git a/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent2.cpp b/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent2.cpp new file mode 100644 index 00000000000..65e0c59af86 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/warnings/libJvmtiAgent2.cpp @@ -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); +} diff --git a/test/jdk/java/lang/instrument/TraceUsageAgent.java b/test/jdk/java/lang/instrument/TraceUsageAgent.java new file mode 100644 index 00000000000..7e5c0c5613c --- /dev/null +++ b/test/jdk/java/lang/instrument/TraceUsageAgent.java @@ -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); + } + } + } + } +} diff --git a/test/jdk/java/lang/instrument/TraceUsageTest.java b/test/jdk/java/lang/instrument/TraceUsageTest.java new file mode 100644 index 00000000000..ee3b83fb775 --- /dev/null +++ b/test/jdk/java/lang/instrument/TraceUsageTest.java @@ -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; + } +}