diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 4a8e10d7353..1e24475ea46 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -88,7 +88,7 @@ ifneq ($(call check-jvm-feature, jvmti), true) jvmtiImpl.cpp jvmtiManageCapabilities.cpp jvmtiRawMonitor.cpp jvmtiUtil.cpp jvmtiTrace.cpp \ jvmtiCodeBlobEvents.cpp jvmtiEnv.cpp jvmtiRedefineClasses.cpp jvmtiEnvBase.cpp jvmtiEnvThreadState.cpp \ jvmtiTagMap.cpp jvmtiEventController.cpp evmCompat.cpp jvmtiEnter.xsl jvmtiExport.cpp \ - jvmtiClassFileReconstituter.cpp jvmtiTagMapTable.cpp + jvmtiClassFileReconstituter.cpp jvmtiTagMapTable.cpp jvmtiAgent.cpp jvmtiAgentList.cpp endif ifneq ($(call check-jvm-feature, jvmci), true) diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 349f01793b8..4a06f44fe26 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1172,6 +1172,25 @@ + + + + + + + + + + + + + + + + + diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index f8496ecf177..011dc37be9d 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -52,6 +52,7 @@ #include "memory/heapInspection.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" +#include "prims/jvmtiAgentList.hpp" #include "runtime/arguments.hpp" #include "runtime/flags/jvmFlag.hpp" #include "runtime/globals.hpp" @@ -269,6 +270,43 @@ TRACE_REQUEST_FUNC(SystemProcess) { } } +template +static void send_agent_event(AgentEvent& event, const JvmtiAgent* agent) { + event.set_name(agent->name()); + event.set_options(agent->options()); + event.set_dynamic(agent->is_dynamic()); + event.set_initializationTime(agent->initialization_time()); + event.set_initializationDuration(agent->initialization_duration()); + event.commit(); +} + +TRACE_REQUEST_FUNC(JavaAgent) { + const JvmtiAgentList::Iterator it =JvmtiAgentList::java_agents(); + while (it.has_next()) { + const JvmtiAgent* agent = it.next(); + assert(agent->is_jplis(), "invariant"); + EventJavaAgent event; + send_agent_event(event, agent); + } +} + +static void send_native_agent_events(const JvmtiAgentList::Iterator& it) { + while (it.has_next()) { + const JvmtiAgent* agent = it.next(); + assert(!agent->is_jplis(), "invariant"); + EventNativeAgent event; + event.set_path(agent->os_lib_path()); + send_agent_event(event, agent); + } +} + +TRACE_REQUEST_FUNC(NativeAgent) { + const JvmtiAgentList::Iterator native_agents_it = JvmtiAgentList::native_agents(); + send_native_agent_events(native_agents_it); + const JvmtiAgentList::Iterator xrun_agents_it = JvmtiAgentList::xrun_agents(); + send_native_agent_events(xrun_agents_it); +} + TRACE_REQUEST_FUNC(ThreadContextSwitchRate) { double rate = 0.0; int ret_val = OS_ERR; diff --git a/src/hotspot/share/prims/jvmtiAgent.cpp b/src/hotspot/share/prims/jvmtiAgent.cpp new file mode 100644 index 00000000000..36cbb049732 --- /dev/null +++ b/src/hotspot/share/prims/jvmtiAgent.cpp @@ -0,0 +1,620 @@ +/* + * 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 "precompiled.hpp" +#include "prims/jvmtiAgent.hpp" + +#include "cds/cds_globals.hpp" +#include "jni.h" +#include "jvm_io.h" +#include "jvmtifiles/jvmtiEnv.hpp" +#include "prims/jvmtiEnvBase.hpp" +#include "prims/jvmtiExport.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/os.inline.hpp" +#include "runtime/thread.inline.hpp" + +static inline const char* copy_string(const char* str) { + return str != nullptr ? os::strdup(str, mtServiceability) : nullptr; +} + +// Returns the lhs before '=', parsed_options output param gets the rhs. +static const char* split_options_and_allocate_copy(const char* options, const char** parsed_options) { + assert(options != nullptr, "invariant"); + assert(parsed_options != nullptr, "invariant"); + const char* const equal_sign = strchr(options, '='); + const size_t length = strlen(options); + size_t name_length = length; + if (equal_sign != nullptr) { + name_length = equal_sign - options; + const size_t options_length = length - name_length - 1; + *parsed_options = copy_string(equal_sign + 1); + } else { + *parsed_options = nullptr; + name_length = length; + } + char* const name = AllocateHeap(name_length + 1, mtServiceability); + jio_snprintf(name, name_length + 1, "%s", options); + assert(strncmp(name, options, name_length) == 0, "invariant"); + return name; +} + +JvmtiAgent::JvmtiAgent(const char* name, const char* options, bool is_absolute_path, bool dynamic /* false */) : + _initialization_time(), + _initialization_duration(), + _next(nullptr), + _name(copy_string(name)), + _options(copy_string(options)), + _os_lib(nullptr), + _os_lib_path(nullptr), + _jplis(nullptr), + _loaded(false), + _absolute_path(is_absolute_path), + _static_lib(false), + _instrument_lib(strcmp(name, "instrument") == 0), + _dynamic(dynamic), + _xrun(false) {} + +JvmtiAgent* JvmtiAgent::next() const { + return _next; +} + +void JvmtiAgent::set_next(JvmtiAgent* agent) { + _next = agent; +} + +const char* JvmtiAgent::name() const { + return _name; +} + +const char* JvmtiAgent::options() const { + return _options; +} + +void* JvmtiAgent::os_lib() const { + return _os_lib; +} + +void JvmtiAgent::set_os_lib(void* os_lib) { + _os_lib = os_lib; +} + +void JvmtiAgent::set_os_lib_path(const char* path) { + assert(path != nullptr, "invariant"); + if (_os_lib_path == nullptr) { + _os_lib_path = copy_string(path); + } + assert(strcmp(_os_lib_path, path) == 0, "invariant"); +} + +const char* JvmtiAgent::os_lib_path() const { + return _os_lib_path; +} + +bool JvmtiAgent::is_loaded() const { + return _loaded; +} + +void JvmtiAgent::set_loaded() { + _loaded = true; +} + +bool JvmtiAgent::is_absolute_path() const { + return _absolute_path; +} + +bool JvmtiAgent::is_static_lib() const { + return _static_lib; +} + +void JvmtiAgent::set_static_lib() { + _static_lib = true; +} + +bool JvmtiAgent::is_dynamic() const { + return _dynamic; +} + +bool JvmtiAgent:: is_instrument_lib() const { + return _instrument_lib; +} + +bool JvmtiAgent::is_xrun() const { + return _xrun; +} + +void JvmtiAgent::set_xrun() { + _xrun = true; +} + +bool JvmtiAgent::is_jplis() const { + return _jplis != nullptr; +} + +const Ticks& JvmtiAgent::initialization_time() const { + return _initialization_time; +} + +const Tickspan& JvmtiAgent::initialization_duration() const { + return _initialization_duration; +} + +bool JvmtiAgent::is_initialized() const { + return _initialization_time.value() != 0; +} + +void JvmtiAgent::initialization_begin() { + assert(!is_initialized(), "invariant"); + _initialization_time = Ticks::now(); +} + +void JvmtiAgent::initialization_end() { + assert(is_initialized(), "invariant"); + assert(_initialization_duration.value() == 0, "invariant"); + _initialization_duration = Ticks::now() - initialization_time(); +} + +/* + * The implementation builds a mapping bewteen JvmtiEnvs and JPLIS agents, + * using internal JDK implementation knowledge about the way JPLIS agents + * store data in their JvmtiEnv local storage. + * + * Please see JPLISAgent.h and JPLISAgent.c in module java.instrument. + * + * jvmtierror = (*jvmtienv)->SetEnvironmentLocalStorage( jvmtienv, &(agent->mNormalEnvironment)); + * + * It is the pointer to the field agent->mNormalEnvironment that is stored in the jvmtiEnv local storage. + * It has the following type: + * + * struct _JPLISEnvironment { + * jvmtiEnv* mJVMTIEnv; // the JVMTI environment + * JPLISAgent* mAgent; // corresponding agent + * jboolean mIsRetransformer; // indicates if special environment + * }; + * + * We mirror this struct to get the mAgent field as an identifier. + */ + +struct JPLISEnvironmentMirror { + jvmtiEnv* mJVMTIEnv; // the JVMTI environment + const void* mAgent; // corresponding agent + jboolean mIsRetransformer; // indicates if special environment +}; + +static inline const JPLISEnvironmentMirror* get_env_local_storage(JvmtiEnv* env) { + assert(env != nullptr, "invariant"); + return reinterpret_cast(env->get_env_local_storage()); +} + +bool JvmtiAgent::is_jplis(JvmtiEnv* env) const { + assert(env != nullptr, "invariant"); + assert(is_instrument_lib(), "invariant"); + const JPLISEnvironmentMirror* const jplis_env = get_env_local_storage(env); + return jplis_env != nullptr && _jplis == jplis_env->mAgent; +} + +void JvmtiAgent::set_jplis(const void* jplis) { + assert(jplis != nullptr, "invaiant"); + assert(is_instrument_lib(), "invariant"); + assert(_jplis == nullptr, "invariant"); + if (_options != nullptr) { + // For JPLIS agents, update with the java name and options. + os::free(const_cast(_name)); + const char* options = _options; + _name = split_options_and_allocate_copy(options, &_options); + os::free(const_cast(options)); + } + _jplis = jplis; +} + +static const char* not_found_error_msg = "Could not find agent library "; +static const char* missing_module_error_msg = "\nModule java.instrument may be missing from runtime image."; +static char ebuf[1024]; +static char buffer[JVM_MAXPATHLEN]; + +static void vm_exit(const JvmtiAgent* agent, const char* sub_msg1, const char* sub_msg2) { + assert(agent != nullptr, "invariant"); + assert(sub_msg1 != nullptr, "invariant"); + assert(!agent->is_instrument_lib() || sub_msg2 != nullptr, "invariant"); + const size_t len = strlen(not_found_error_msg) + strlen(agent->name()) + strlen(sub_msg1) + strlen(&ebuf[0]) + 1 + (agent->is_instrument_lib() ? strlen(sub_msg2) : 0); + char* buf = NEW_C_HEAP_ARRAY(char, len, mtServiceability); + if (agent->is_instrument_lib()) { + jio_snprintf(buf, len, "%s%s%s%s%s", not_found_error_msg, agent->name(), sub_msg1, &ebuf[0], sub_msg2); + } else { + jio_snprintf(buf, len, "%s%s%s%s", not_found_error_msg, agent->name(), sub_msg1, &ebuf[0]); + } + vm_exit_during_initialization(buf, nullptr); + FREE_C_HEAP_ARRAY(char, buf); +} + +#ifdef ASSERT +static void assert_preload(const JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + assert(!agent->is_loaded(), "invariant"); +} +#endif + +// 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 +// 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(). +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"); + return os::find_builtin_agent(agent, &on_load_symbols[0], num_symbol_entries); +} + +// Load the library from the absolute path of the agent, if available. +static void* load_agent_from_absolute_path(JvmtiAgent* agent, bool vm_exit_on_error) { + DEBUG_ONLY(assert_preload(agent);) + assert(agent->is_absolute_path(), "invariant"); + assert(!agent->is_instrument_lib(), "invariant"); + void* const library = os::dll_load(agent->name(), &ebuf[0], sizeof ebuf); + if (library == nullptr && vm_exit_on_error) { + vm_exit(agent, " in absolute path, with error: ", nullptr); + } + return library; +} + +// Agents with relative paths are loaded from the standard dll directory. +static void* load_agent_from_relative_path(JvmtiAgent* agent, bool vm_exit_on_error) { + DEBUG_ONLY(assert_preload(agent);) + assert(!agent->is_absolute_path(), "invariant"); + const char* const name = agent->name(); + void* library = nullptr; + // Try to load the agent from the standard dll directory + if (os::dll_locate_lib(&buffer[0], sizeof buffer, Arguments::get_dll_dir(), name)) { + library = os::dll_load(&buffer[0], &ebuf[0], sizeof ebuf); + } + if (library == nullptr && os::dll_build_name(&buffer[0], sizeof buffer, name)) { + // Try the library path directory. + library = os::dll_load(&buffer[0], &ebuf[0], sizeof ebuf); + if (library != nullptr) { + return library; + } + if (vm_exit_on_error) { + vm_exit(agent, " on the library path, with error: ", missing_module_error_msg); + } + } + return library; +} + +// For absolute and relative paths. +static void* load_library(JvmtiAgent* agent, const char* on_symbols[], size_t num_symbol_entries, bool vm_exit_on_error) { + return agent->is_absolute_path() ? load_agent_from_absolute_path(agent, vm_exit_on_error) : + load_agent_from_relative_path(agent, vm_exit_on_error); +} + +// Type for the Agent_OnLoad and JVM_OnLoad entry points. +extern "C" { + typedef jint(JNICALL* OnLoadEntry_t)(JavaVM*, char*, void*); +} + +// Find the OnLoad entry point for -agentlib: -agentpath: -Xrun agents. +// num_symbol_entries must be passed-in since only the caller knows the number of symbols in the array. +static OnLoadEntry_t lookup_On_Load_entry_point(JvmtiAgent* agent, const char* on_load_symbols[], size_t num_symbol_entries) { + assert(agent != nullptr, "invariant"); + if (!agent->is_loaded()) { + if (!load_agent_from_executable(agent, on_load_symbols, num_symbol_entries)) { + void* const library = load_library(agent, on_load_symbols, num_symbol_entries, /* vm exit on error */ true); + assert(library != nullptr, "invariant"); + agent->set_os_lib(library); + agent->set_loaded(); + } + } + assert(agent->is_loaded(), "invariant"); + // Find the OnLoad function. + return CAST_TO_FN_PTR(OnLoadEntry_t, os::find_agent_function(agent, false, on_load_symbols, num_symbol_entries)); +} + +static OnLoadEntry_t lookup_JVM_OnLoad_entry_point(JvmtiAgent* lib) { + const char* on_load_symbols[] = JVM_ONLOAD_SYMBOLS; + return lookup_On_Load_entry_point(lib, on_load_symbols, sizeof(on_load_symbols) / sizeof(char*)); +} + +static OnLoadEntry_t lookup_Agent_OnLoad_entry_point(JvmtiAgent* agent) { + const char* on_load_symbols[] = AGENT_ONLOAD_SYMBOLS; + return lookup_On_Load_entry_point(agent, on_load_symbols, sizeof(on_load_symbols) / sizeof(char*)); +} + +void JvmtiAgent::convert_xrun_agent() { + assert(is_xrun(), "invariant"); + assert(!is_loaded(), "invariant"); + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence"); + OnLoadEntry_t on_load_entry = lookup_JVM_OnLoad_entry_point(this); + // If there is an JVM_OnLoad function it will get called later, + // otherwise see if there is an Agent_OnLoad. + if (on_load_entry == nullptr) { + on_load_entry = lookup_Agent_OnLoad_entry_point(this); + if (on_load_entry == nullptr) { + vm_exit_during_initialization("Could not find JVM_OnLoad or Agent_OnLoad function in the library", name()); + } + _xrun = false; // converted + } +} + +// Called after the VM is initialized for -Xrun agents which have not been converted to JVMTI agents. +static bool invoke_JVM_OnLoad(JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + assert(agent->is_xrun(), "invariant"); + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence"); + OnLoadEntry_t on_load_entry = lookup_JVM_OnLoad_entry_point(agent); + if (on_load_entry == nullptr) { + vm_exit_during_initialization("Could not find JVM_OnLoad function in -Xrun library", agent->name()); + } + // Invoke the JVM_OnLoad function + JavaThread* thread = JavaThread::current(); + ThreadToNativeFromVM ttn(thread); + HandleMark hm(thread); + extern struct JavaVM_ main_vm; + const jint err = (*on_load_entry)(&main_vm, const_cast(agent->options()), NULL); + if (err != JNI_OK) { + vm_exit_during_initialization("-Xrun library failed to init", agent->name()); + } + return true; +} + +// The newest jvmtiEnv is appended to the list, +// hence the JvmtiEnvIterator order is from oldest to newest. +static JvmtiEnv* get_last_jplis_jvmtienv() { + JvmtiEnvIterator it; + JvmtiEnv* env = it.first(); + assert(env != nullptr, "invariant"); + JvmtiEnv* next = it.next(env); + while (next != nullptr) { + assert(env != nullptr, "invariant"); + // get_env_local_storage() lets us find which JVMTI env map to which JPLIS agent. + if (next->get_env_local_storage() == nullptr) { + JvmtiEnv* temp = it.next(next); + if (temp != nullptr) { + next = temp; + continue; + } + break; + } + env = next; + next = it.next(env); + } + assert(env != nullptr, "invariant"); + assert(env->get_env_local_storage() != nullptr, "invariant"); + return env; +} + +// Associate the last, i.e. most recent, JvmtiEnv that is a JPLIS agent with the current agent. +static void convert_to_jplis(JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + assert(agent->is_instrument_lib(), "invariant"); + JvmtiEnv* const env = get_last_jplis_jvmtienv(); + assert(env != nullptr, "invariant"); + const JPLISEnvironmentMirror* const jplis_env = get_env_local_storage(env); + assert(jplis_env != nullptr, "invaiant"); + assert(reinterpret_cast(jplis_env->mJVMTIEnv) == env, "invariant"); + agent->set_jplis(jplis_env->mAgent); +} + +// Use this for JavaThreads and state is _thread_in_vm. +class AgentJavaThreadEventTransition : StackObj { + private: + ResourceMark _rm; + ThreadToNativeFromVM _transition; + HandleMark _hm; + public: + AgentJavaThreadEventTransition(JavaThread* thread) : _rm(), _transition(thread), _hm(thread) {}; +}; + +class AgentEventMark : StackObj { + private: + JavaThread* _thread; + JNIEnv* _jni_env; + JvmtiThreadState::ExceptionState _saved_exception_state; + + public: + AgentEventMark(JavaThread* thread) : _thread(thread), + _jni_env(thread->jni_environment()), + _saved_exception_state(JvmtiThreadState::ES_CLEARED) { + JvmtiThreadState* state = thread->jvmti_thread_state(); + // we are before an event. + // Save current jvmti thread exception state. + if (state != nullptr) { + _saved_exception_state = state->get_exception_state(); + } + thread->push_jni_handle_block(); + assert(thread == JavaThread::current(), "thread must be current!"); + thread->frame_anchor()->make_walkable(); + } + + ~AgentEventMark() { + _thread->pop_jni_handle_block(); + JvmtiThreadState* state = _thread->jvmti_thread_state(); + // we are continuing after an event. + if (state != nullptr) { + // Restore the jvmti thread exception state. + state->restore_exception_state(_saved_exception_state); + } + } +}; + +class AgentThreadEventMark : public AgentEventMark { + private: + jobject _jthread; + public: + AgentThreadEventMark(JavaThread* thread) : AgentEventMark(thread), + _jthread(JNIHandles::make_local(thread, thread->threadObj())) {} + jthread jni_thread() { return (jthread)_jthread; } +}; + +static void unload_library(JvmtiAgent* agent, void* library) { + assert(agent != nullptr, "invariant"); + assert(agent->is_loaded(), "invariant"); + if (!agent->is_static_lib()) { + assert(library != nullptr, "invariant"); + os::dll_unload(library); + } +} + +// type for the Agent_OnAttach entry point +extern "C" { + typedef jint(JNICALL* OnAttachEntry_t)(JavaVM*, char*, void*); +} + +// Loading the agent by invoking Agent_OnAttach. +static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) { + DEBUG_ONLY(assert_preload(agent);) + assert(agent->is_dynamic(), "invariant"); + assert(st != nullptr, "invariant"); + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_LIVE, "not in live phase!"); + 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)) { + 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()); + if (*ebuf != '\0') { + st->print_cr("%s", &ebuf[0]); + } + return false; + } + agent->set_os_lib_path(&buffer[0]); + agent->set_os_lib(library); + agent->set_loaded(); + } + 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, + os::find_agent_function(agent, false, &on_attach_symbols[0], num_symbol_entries)); + + if (on_attach_entry == nullptr) { + st->print_cr("%s is not available in %s", on_attach_symbols[0], agent->name()); + unload_library(agent, library); + return false; + } + + // Invoke the Agent_OnAttach function + JavaThread* thread = JavaThread::current(); + jint result = JNI_ERR; + { + extern struct JavaVM_ main_vm; + AgentThreadEventMark jem(thread); + AgentJavaThreadEventTransition jet(thread); + + agent->initialization_begin(); + + result = (*on_attach_entry)(&main_vm, (char*)agent->options(), nullptr); + + agent->initialization_end(); + + // Agent_OnAttach may have used JNI + if (thread->is_pending_jni_exception_check()) { + thread->clear_pending_jni_exception_check(); + } + } + + // Agent_OnAttach may have used JNI + if (thread->has_pending_exception()) { + thread->clear_pending_exception(); + } + + st->print_cr("return code: %d", result); + + if (result != JNI_OK) { + unload_library(agent, library); + return false; + } + + if (agent->is_instrument_lib()) { + // Convert the instrument lib to the actual JPLIS / javaagent it represents. + convert_to_jplis(agent); + } + return true; +} + +// CDS dumping does not support native JVMTI agent. +// CDS dumping supports Java agent if the AllowArchivingWithJavaAgent diagnostic option is specified. +static void check_cds_dump(JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + assert(Arguments::is_dumping_archive(), "invariant"); + if (!agent->is_instrument_lib()) { + vm_exit_during_cds_dumping("CDS dumping does not support native JVMTI agent, name", agent->name()); + } + if (!AllowArchivingWithJavaAgent) { + vm_exit_during_cds_dumping( + "Must enable AllowArchivingWithJavaAgent in order to run Java agent during CDS dumping"); + } +} + +// Loading the agent by invoking Agent_OnLoad. +static bool invoke_Agent_OnLoad(JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + assert(!agent->is_xrun(), "invariant"); + assert(!agent->is_dynamic(), "invariant"); + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_ONLOAD, "invariant"); + if (Arguments::is_dumping_archive()) { + check_cds_dump(agent); + } + OnLoadEntry_t on_load_entry = lookup_Agent_OnLoad_entry_point(agent); + if (on_load_entry == nullptr) { + vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name()); + } + // Invoke the Agent_OnLoad function + extern struct JavaVM_ main_vm; + if ((*on_load_entry)(&main_vm, const_cast(agent->options()), nullptr) != JNI_OK) { + vm_exit_during_initialization("agent library failed Agent_OnLoad", agent->name()); + } + // Convert the instrument lib to the actual JPLIS / javaagent it represents. + if (agent->is_instrument_lib()) { + convert_to_jplis(agent); + } + return true; +} + +bool JvmtiAgent::load(outputStream* st /* nullptr */) { + if (is_xrun()) { + return invoke_JVM_OnLoad(this); + } + return is_dynamic() ? invoke_Agent_OnAttach(this, st) : invoke_Agent_OnLoad(this); +} + +extern "C" { + typedef void (JNICALL* Agent_OnUnload_t)(JavaVM*); +} + +void JvmtiAgent::unload() { + const char* on_unload_symbols[] = AGENT_ONUNLOAD_SYMBOLS; + // Find the Agent_OnUnload function. + Agent_OnUnload_t unload_entry = CAST_TO_FN_PTR(Agent_OnUnload_t, + os::find_agent_function(this, false, &on_unload_symbols[0], ARRAY_SIZE(on_unload_symbols))); + if (unload_entry != nullptr) { + // Invoke the Agent_OnUnload function + JavaThread* thread = JavaThread::current(); + ThreadToNativeFromVM ttn(thread); + HandleMark hm(thread); + extern struct JavaVM_ main_vm; + (*unload_entry)(&main_vm); + } +} diff --git a/src/hotspot/share/prims/jvmtiAgent.hpp b/src/hotspot/share/prims/jvmtiAgent.hpp new file mode 100644 index 00000000000..e51c45351b0 --- /dev/null +++ b/src/hotspot/share/prims/jvmtiAgent.hpp @@ -0,0 +1,88 @@ +/* + * 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. + * + */ + +#ifndef SHARE_PRIMS_JVMTIAGENT_HPP +#define SHARE_PRIMS_JVMTIAGENT_HPP + +#include "memory/allocation.hpp" +#include "utilities/ticks.hpp" + +class JvmtiEnv; +class outputStream; + +// Represents an agent launched on the command-line by -agentlib, -agentpath or -Xrun. +// Also agents loaded dynamically during runtime, for example using the Attach API. +class JvmtiAgent : public CHeapObj { + friend class JvmtiAgentList; + private: + Ticks _initialization_time; + Tickspan _initialization_duration; + JvmtiAgent* _next; + const char* _name; + const char* _options; + void* _os_lib; + const char* _os_lib_path; + const void* _jplis; + bool _loaded; + bool _absolute_path; + bool _static_lib; + bool _instrument_lib; + bool _dynamic; + bool _xrun; + + JvmtiAgent* next() const; + void set_next(JvmtiAgent* agent); + void convert_xrun_agent(); + void set_xrun(); + + public: + JvmtiAgent(const char* name, const char* options, bool is_absolute_path, bool dynamic = false); + const char* name() const; + const char* options() const; + bool is_absolute_path() const; + void* os_lib() const; + void set_os_lib(void* os_lib); + const char* os_lib_path() const; + void set_os_lib_path(const char* path); + bool is_static_lib() const; + void set_static_lib(); + bool is_dynamic() const; + bool is_xrun() const; + bool is_instrument_lib() const; + bool is_loaded() const; + void set_loaded(); + bool is_jplis() const; + bool is_jplis(JvmtiEnv* env) const; + void set_jplis(const void* jplis); + bool is_initialized() const; + void initialization_begin(); + void initialization_end(); + const Ticks& initialization_time() const; + const Tickspan& initialization_duration() const; + + bool load(outputStream* st = nullptr); + void unload(); +}; + +#endif // SHARE_PRIMS_JVMTIAGENT_HPP diff --git a/src/hotspot/share/prims/jvmtiAgentList.cpp b/src/hotspot/share/prims/jvmtiAgentList.cpp new file mode 100644 index 00000000000..fb46ddea661 --- /dev/null +++ b/src/hotspot/share/prims/jvmtiAgentList.cpp @@ -0,0 +1,265 @@ +/* + * 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 "precompiled.hpp" +#include "prims/jvmtiAgentList.hpp" + +#include "prims/jvmtiEnvBase.hpp" +#include "prims/jvmtiExport.hpp" +#include "runtime/atomic.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/growableArray.hpp" + +JvmtiAgent* JvmtiAgentList::_list = nullptr; + +// Selection as a function of the filter. +JvmtiAgent* JvmtiAgentList::Iterator::select(JvmtiAgent* agent) const { + while (agent != nullptr) { + if (_filter == ALL) { + return agent; + } else if (_filter == NOT_XRUN) { + if (!agent->is_xrun()) { + return agent; + } + } else if (_filter == JAVA) { + if (agent->is_jplis()) { + return agent; + } + } else if (_filter == NATIVE) { + if (!agent->is_jplis() && !agent->is_xrun()) { + return agent; + } + } else { + assert(_filter == XRUN, "invariant"); + if (agent->is_xrun()) { + return agent; + } + } + agent = agent->next(); + } + return nullptr; +} + +static inline JvmtiAgent* head(JvmtiAgent** list) { + assert(list != nullptr, "invariant"); + return Atomic::load_acquire(list); +} + + +// The storage list is a single cas-linked-list, to allow for concurrent iterations. +// Especially during initial loading of agents, there exist an order requirement to iterate oldest -> newest. +// Our concurrent storage linked-list is newest -> oldest. +// The correct order is preserved by the iterator, by storing a filtered set of entries in a stack. +JvmtiAgentList::Iterator::Iterator(JvmtiAgent** list, Filter filter) : + _stack(new GrowableArrayCHeap(16)), _filter(filter) { + JvmtiAgent* next = head(list); + while (next != nullptr) { + next = select(next); + if (next != nullptr) { + _stack->push(next); + next = next->next(); + } + } +} + +JvmtiAgentList::Iterator::~Iterator() { + delete _stack; +} + +bool JvmtiAgentList::Iterator::has_next() const { + assert(_stack != nullptr, "invariant"); + return _stack->is_nonempty(); +} + +const JvmtiAgent* JvmtiAgentList::Iterator::next() const { + assert(has_next(), "invariant"); + return _stack->pop(); +} + +JvmtiAgent* JvmtiAgentList::Iterator::next() { + return const_cast(const_cast(this)->next()); +} + +JvmtiAgentList::Iterator JvmtiAgentList::agents() { + return Iterator(&_list, Iterator::NOT_XRUN); +} + +JvmtiAgentList::Iterator JvmtiAgentList::java_agents() { + return Iterator(&_list, Iterator::JAVA); +} + +JvmtiAgentList::Iterator JvmtiAgentList::native_agents() { + return Iterator(&_list, Iterator::NATIVE); +} + +JvmtiAgentList::Iterator JvmtiAgentList::xrun_agents() { + return Iterator(&_list, Iterator::XRUN); +} + +JvmtiAgentList::Iterator JvmtiAgentList::all() { + return Iterator(&_list, Iterator::ALL); +} + +void JvmtiAgentList::add(JvmtiAgent* agent) { + assert(agent != nullptr, "invariant"); + JvmtiAgent* next; + do { + next = head(&_list); + agent->set_next(next); + } while (Atomic::cmpxchg(&_list, next, agent) != next); +} + +void JvmtiAgentList::add(const char* name, char* options, bool absolute_path) { + add(new JvmtiAgent(name, options, absolute_path)); +} + +void JvmtiAgentList::add_xrun(const char* name, char* options, bool absolute_path) { + JvmtiAgent* agent = new JvmtiAgent(name, options, absolute_path); + agent->set_xrun(); + add(agent); +} + +#ifdef ASSERT +static void assert_initialized(JvmtiAgentList::Iterator& it) { + while (it.has_next()) { + assert(it.next()->is_initialized(), "invariant"); + } +} +#endif + +// In case an agent did not enable the VMInit callback, or if it is an -Xrun agent, +// it gets an initializiation timestamp here. +void JvmtiAgentList::initialize() { + Iterator it = all(); + while (it.has_next()) { + JvmtiAgent* agent = it.next(); + if (!agent->is_initialized()) { + agent->initialization_begin(); + } + } + DEBUG_ONLY(Iterator assert_it = all(); assert_initialized(assert_it);) +} + +void JvmtiAgentList::convert_xrun_agents() { + Iterator it = xrun_agents(); + while (it.has_next()) { + it.next()->convert_xrun_agent(); + } +} + +class JvmtiPhaseTransition : public StackObj { + public: + JvmtiPhaseTransition() { + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence"); + JvmtiExport::enter_onload_phase(); + } + ~JvmtiPhaseTransition() { + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_ONLOAD, "invariant"); + JvmtiExport::enter_primordial_phase(); + } +}; + +static void load_agents(JvmtiAgentList::Iterator& it) { + while (it.has_next()) { + it.next()->load(); + } +} + +// Invokes Agent_OnLoad for -agentlib:.. -agentpath: and converted -Xrun agents. +// Called very early -- before JavaThreads exist +void JvmtiAgentList::load_agents() { + // Convert -Xrun to -agentlib: if there is no JVM_OnLoad + convert_xrun_agents(); + JvmtiPhaseTransition transition; + Iterator it = agents(); + ::load_agents(it); +} + +// Launch -Xrun agents +void JvmtiAgentList::load_xrun_agents() { + assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_PRIMORDIAL, "invalid init sequence"); + Iterator it = xrun_agents(); + ::load_agents(it); +} + +// Invokes Agent_OnAttach for agents loaded dynamically during runtime. +jint JvmtiAgentList::load_agent(const char* agent_name, const char* absParam, + const char* options, outputStream* st) { + // The abs parameter should be "true" or "false" + const bool is_absolute_path = (absParam != nullptr) && (strcmp(absParam, "true") == 0); + JvmtiAgent* const agent = new JvmtiAgent(agent_name, options, is_absolute_path, /* dynamic agent */ true); + if (agent->load(st)) { + add(agent); + } else { + delete agent; + } + // Agent_OnAttach executed so completion status is JNI_OK + return JNI_OK; +} + +// Send any Agent_OnUnload notifications +void JvmtiAgentList::unload_agents() { + Iterator it = agents(); + while (it.has_next()) { + it.next()->unload(); + } +} + +static bool match(JvmtiEnv* env, const JvmtiAgent* agent, const void* os_module_address) { + assert(env != nullptr, "invariant"); + assert(agent != nullptr, "invariant"); + if (agent->is_static_lib()) { + return os::get_default_process_handle() == os_module_address; + } + if (agent->os_lib() != os_module_address) { + return false; + } + return agent->is_instrument_lib() ? agent->is_jplis(env) : true; +} + +// The function pointer is a JVMTI callback function. +// Find the os module (dll) that exports this function. +// Now we can map a JVMTI env to its corresponding agent. +JvmtiAgent* JvmtiAgentList::lookup(JvmtiEnv* env, void* f_ptr) { + assert(env != nullptr, "invariant"); + assert(f_ptr != nullptr, "invariant"); + static char ebuf[1024]; + static char buffer[JVM_MAXPATHLEN]; + int offset; + if (!os::dll_address_to_library_name(reinterpret_cast
(f_ptr), &buffer[0], JVM_MAXPATHLEN, &offset)) { + return nullptr; + } + assert(buffer[0] != '\0', "invariant"); + assert(offset >= 0, "invariant"); + const void* const os_module_address = reinterpret_cast
(f_ptr) - offset; + + JvmtiAgentList::Iterator it = JvmtiAgentList::agents(); + while (it.has_next()) { + JvmtiAgent* const agent = it.next(); + if (match(env, agent, os_module_address)) { + agent->set_os_lib_path(&buffer[0]); + return agent; + } + } + return nullptr; +} diff --git a/src/hotspot/share/prims/jvmtiAgentList.hpp b/src/hotspot/share/prims/jvmtiAgentList.hpp new file mode 100644 index 00000000000..5fc4e140887 --- /dev/null +++ b/src/hotspot/share/prims/jvmtiAgentList.hpp @@ -0,0 +1,87 @@ +/* + * 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. + * + */ + +#ifndef SHARE_PRIMS_JVMTIAGENTLIST_HPP +#define SHARE_PRIMS_JVMTIAGENTLIST_HPP + +#include "memory/allocation.hpp" +#include "prims/jvmtiAgent.hpp" + +template +class GrowableArrayCHeap; +class JvmtiEnv; + +// Maintains a single cas linked-list of JvmtiAgents. +class JvmtiAgentList : AllStatic { + friend class Iterator; + friend class JvmtiExport; + public: + class Iterator { + friend class JvmtiAgentList; + private: + enum Filter { + JAVA, + NATIVE, + XRUN, + NOT_XRUN, + ALL + }; + GrowableArrayCHeap* _stack; + const Filter _filter; + Iterator(JvmtiAgent** list, Filter filter); + JvmtiAgent* select(JvmtiAgent* agent) const; + public: + bool has_next() const; + JvmtiAgent* next(); + const JvmtiAgent* next() const; + ~Iterator(); + }; + + private: + static JvmtiAgent* _list; + + static Iterator all(); + static void initialize(); + static void convert_xrun_agents(); + + public: + static void add(JvmtiAgent* agent); + static void add(const char* name, char* options, bool absolute_path); + static void add_xrun(const char* name, char* options, bool absolute_path); + + static void load_agents(); + static jint load_agent(const char* agent, const char* absParam, + const char* options, outputStream* st); + static void load_xrun_agents(); + static void unload_agents(); + + static JvmtiAgent* lookup(JvmtiEnv* env, void* f_ptr); + + static Iterator agents(); + static Iterator java_agents(); + static Iterator native_agents(); + static Iterator xrun_agents(); +}; + +#endif // SHARE_PRIMS_JVMTIAGENTLIST_HPP diff --git a/src/hotspot/share/prims/jvmtiEnvBase.hpp b/src/hotspot/share/prims/jvmtiEnvBase.hpp index 02a3e2f7968..17848b0ee32 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.hpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.hpp @@ -135,7 +135,6 @@ class JvmtiEnvBase : public CHeapObj { void env_dispose(); void set_env_local_storage(const void* data) { _env_local_storage = data; } - const void* get_env_local_storage() { return _env_local_storage; } void record_class_file_load_hook_enabled(); void record_first_time_class_file_load_hook_enabled(); @@ -170,6 +169,8 @@ class JvmtiEnvBase : public CHeapObj { bool is_retransformable() { return _is_retransformable; } + const void* get_env_local_storage() { return _env_local_storage; } + static ByteSize jvmti_external_offset() { return byte_offset_of(JvmtiEnvBase, _jvmti_external); }; diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 74269375c5e..b760a664e2b 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -43,6 +43,7 @@ #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiCodeBlobEvents.hpp" #include "prims/jvmtiEventController.hpp" #include "prims/jvmtiEventController.inline.hpp" @@ -697,6 +698,13 @@ void JvmtiExport::initialize_oop_storage() { _weak_tag_storage->register_num_dead_callback(&JvmtiTagMap::gc_notification); } +// Lookup an agent from an JvmtiEnv. Return agent only if it is not yet initialized. +// An agent can create multiple JvmtiEnvs, but for agent initialization, we are only interested in the initial one. +static JvmtiAgent* lookup_uninitialized_agent(JvmtiEnv* env, void* callback) { + JvmtiAgent* const agent = JvmtiAgentList::lookup(env, callback); + return agent == nullptr || agent->is_initialized() ? nullptr : agent; +} + void JvmtiExport::post_vm_initialized() { EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("Trg VM init event triggered" )); @@ -713,12 +721,25 @@ void JvmtiExport::post_vm_initialized() { JvmtiJavaThreadEventTransition jet(thread); jvmtiEventVMInit callback = env->callbacks()->VMInit; if (callback != nullptr) { + // We map the JvmtiEnv to its Agent to measure when and for how long + // it took to initialize so that JFR can report this information. + JvmtiAgent* const agent = lookup_uninitialized_agent(env, reinterpret_cast(callback)); + if (agent != nullptr) { + agent->initialization_begin(); + } (*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread()); + if (agent != nullptr) { + agent->initialization_end(); + } } } } -} + // Agents are initialized as part of posting the VMInit event above. + // For -Xrun agents and agents with no VMInit callback, we explicitly ensure they are also initialized. + // JVM_OnLoad and Agent_OnLoad callouts are performed too early for the proper timestamp logic. + JvmtiAgentList::initialize(); +} void JvmtiExport::post_vm_death() { EVT_TRIG_TRACE(JVMTI_EVENT_VM_DEATH, ("Trg VM death event triggered" )); @@ -2929,118 +2950,6 @@ void JvmtiExport::transition_pending_onload_raw_monitors() { JvmtiPendingMonitors::transition_raw_monitors(); } -//////////////////////////////////////////////////////////////////////////////////////////////// -#if INCLUDE_SERVICES -// Attach is disabled if SERVICES is not included - -// type for the Agent_OnAttach entry point -extern "C" { - typedef jint (JNICALL *OnAttachEntry_t)(JavaVM*, char *, void *); -} - -jint JvmtiExport::load_agent_library(const char *agent, const char *absParam, - const char *options, outputStream* st) { - char ebuf[1024] = {0}; - char buffer[JVM_MAXPATHLEN]; - void* library = nullptr; - jint result = JNI_ERR; - const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS; - size_t num_symbol_entries = ARRAY_SIZE(on_attach_symbols); - - // The abs parameter should be "true" or "false" - bool is_absolute_path = (absParam != nullptr) && (strcmp(absParam,"true")==0); - - // Initially marked as invalid. It will be set to valid if we can find the agent - AgentLibrary *agent_lib = new AgentLibrary(agent, options, is_absolute_path, nullptr); - - // Check for statically linked in agent. If not found then if the path is - // absolute we attempt to load the library. Otherwise we try to load it - // from the standard dll directory. - - if (!os::find_builtin_agent(agent_lib, on_attach_symbols, num_symbol_entries)) { - if (is_absolute_path) { - library = os::dll_load(agent, ebuf, sizeof ebuf); - } else { - // Try to load the agent from the standard dll directory - if (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(), - agent)) { - library = os::dll_load(buffer, ebuf, sizeof ebuf); - } - if (library == nullptr) { - // not found - try OS default library path - if (os::dll_build_name(buffer, sizeof(buffer), agent)) { - library = os::dll_load(buffer, ebuf, sizeof ebuf); - } - } - } - if (library != nullptr) { - agent_lib->set_os_lib(library); - agent_lib->set_valid(); - } - } - // If the library was loaded then we attempt to invoke the Agent_OnAttach - // function - if (agent_lib->valid()) { - // Lookup the Agent_OnAttach function - OnAttachEntry_t on_attach_entry = nullptr; - on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t, - os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries)); - if (on_attach_entry == nullptr) { - // Agent_OnAttach missing - unload library - if (!agent_lib->is_static_lib()) { - os::dll_unload(library); - } - st->print_cr("%s is not available in %s", - on_attach_symbols[0], agent_lib->name()); - delete agent_lib; - } else { - // Invoke the Agent_OnAttach function - JavaThread* THREAD = JavaThread::current(); // For exception macros. - { - extern struct JavaVM_ main_vm; - JvmtiThreadEventMark jem(THREAD); - JvmtiJavaThreadEventTransition jet(THREAD); - - result = (*on_attach_entry)(&main_vm, (char*)options, nullptr); - - // Agent_OnAttach may have used JNI - if (THREAD->is_pending_jni_exception_check()) { - THREAD->clear_pending_jni_exception_check(); - } - } - - // Agent_OnAttach may have used JNI - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; - } - - // If OnAttach returns JNI_OK then we add it to the list of - // agent libraries so that we can call Agent_OnUnload later. - if (result == JNI_OK) { - Arguments::add_loaded_agent(agent_lib); - } else { - if (!agent_lib->is_static_lib()) { - os::dll_unload(library); - } - delete agent_lib; - } - - // Agent_OnAttach executed so completion status is JNI_OK - st->print_cr("return code: %d", result); - result = JNI_OK; - } - } else { - st->print_cr("%s was not loaded.", agent); - if (*ebuf != '\0') { - st->print_cr("%s", ebuf); - } - } - return result; -} - -#endif // INCLUDE_SERVICES -//////////////////////////////////////////////////////////////////////////////////////////////// - // Setup current current thread for event collection. void JvmtiEventCollector::setup_jvmti_thread_state() { // set this event collector to be the current one. diff --git a/src/hotspot/share/prims/nativeLookup.cpp b/src/hotspot/share/prims/nativeLookup.cpp index cd25137964b..0f73af197b6 100644 --- a/src/hotspot/share/prims/nativeLookup.cpp +++ b/src/hotspot/share/prims/nativeLookup.cpp @@ -37,6 +37,7 @@ #include "oops/method.hpp" #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvm_misc.hpp" #include "prims/jvmtiExport.hpp" #include "prims/nativeLookup.hpp" @@ -285,9 +286,9 @@ address NativeLookup::lookup_style(const methodHandle& method, char* pure_name, if (entry == nullptr) { // findNative didn't find it, if there are any agent libraries look in them - AgentLibrary* agent; - for (agent = Arguments::agents(); agent != nullptr; agent = agent->next()) { - entry = (address) os::dll_lookup(agent->os_lib(), jni_name); + JvmtiAgentList::Iterator it = JvmtiAgentList::agents(); + while (it.has_next()) { + entry = (address)os::dll_lookup(it.next()->os_lib(), jni_name); if (entry != nullptr) { return entry; } diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 9222d3e3f7b..c87c1cb03f9 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -43,6 +43,7 @@ #include "memory/allocation.inline.hpp" #include "oops/instanceKlass.hpp" #include "oops/oop.inline.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/arguments.hpp" #include "runtime/flags/jvmFlag.hpp" @@ -105,9 +106,6 @@ char* Arguments::SharedDynamicArchivePath = nullptr; LegacyGCLogging Arguments::_legacyGCLogging = { 0, 0 }; -AgentLibraryList Arguments::_libraryList; -AgentLibraryList Arguments::_agentList; - // These are not set by the JDK's built-in launchers, but they can be set by // programs that embed the JVM using JNI_CreateJavaVM. See comments around // JavaVMOption in jni.h. @@ -217,25 +215,6 @@ SystemProperty::SystemProperty(const char* key, const char* value, bool writeabl _writeable = writeable; } -AgentLibrary::AgentLibrary(const char* name, const char* options, - bool is_absolute_path, void* os_lib, - bool instrument_lib) { - _name = AllocateHeap(strlen(name)+1, mtArguments); - strcpy(_name, name); - if (options == nullptr) { - _options = nullptr; - } else { - _options = AllocateHeap(strlen(options)+1, mtArguments); - strcpy(_options, options); - } - _is_absolute_path = is_absolute_path; - _os_lib = os_lib; - _next = nullptr; - _state = agent_invalid; - _is_static_lib = false; - _is_instrument_lib = instrument_lib; -} - // Check if head of 'option' matches 'name', and sets 'tail' to the remaining // part of the option string. static bool match_option(const JavaVMOption *option, const char* name, @@ -326,23 +305,6 @@ bool needs_module_property_warning = false; #define ENABLE_NATIVE_ACCESS "enable.native.access" #define ENABLE_NATIVE_ACCESS_LEN 20 -void Arguments::add_init_library(const char* name, const char* options) { - _libraryList.add(new AgentLibrary(name, options, false, nullptr)); -} - -void Arguments::add_init_agent(const char* name, const char* options, bool absolute_path) { - _agentList.add(new AgentLibrary(name, options, absolute_path, nullptr)); -} - -void Arguments::add_instrument_agent(const char* name, const char* options, bool absolute_path) { - _agentList.add(new AgentLibrary(name, options, absolute_path, nullptr, true)); -} - -// Late-binding agents not started via arguments -void Arguments::add_loaded_agent(AgentLibrary *agentLib) { - _agentList.add(agentLib); -} - // Return TRUE if option matches 'property', or 'property=', or 'property.'. static bool matches_property_suffix(const char* option, const char* property, size_t len) { return ((strncmp(option, property, len) == 0) && @@ -2381,7 +2343,7 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m return JNI_ERR; } #endif // !INCLUDE_JVMTI - add_init_library(name, options); + JvmtiAgentList::add_xrun(name, options, false); FREE_C_HEAP_ARRAY(char, name); FREE_C_HEAP_ARRAY(char, options); } @@ -2453,7 +2415,7 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m return JNI_ERR; } #endif // !INCLUDE_JVMTI - add_init_agent(name, options, is_absolute_path); + JvmtiAgentList::add(name, options, is_absolute_path); os::free(name); os::free(options); } @@ -2468,8 +2430,9 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m size_t length = strlen(tail) + 1; char *options = NEW_C_HEAP_ARRAY(char, length, mtArguments); jio_snprintf(options, length, "%s", tail); - add_instrument_agent("instrument", options, false); + JvmtiAgentList::add("instrument", options, false); FREE_C_HEAP_ARRAY(char, options); + // java agents need module java.instrument if (!create_numbered_module_property("jdk.module.addmods", "java.instrument", addmods_count++)) { return JNI_ENOMEM; diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index eab754589ea..4f7eec12735 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -138,98 +138,6 @@ class SystemProperty : public PathString { SystemProperty(const char* key, const char* value, bool writeable, bool internal = false); }; - -// For use by -agentlib, -agentpath and -Xrun -class AgentLibrary : public CHeapObj { - friend class AgentLibraryList; -public: - // Is this library valid or not. Don't rely on os_lib == nullptr as statically - // linked lib could have handle of RTLD_DEFAULT which == 0 on some platforms - enum AgentState { - agent_invalid = 0, - agent_valid = 1 - }; - - private: - char* _name; - char* _options; - void* _os_lib; - bool _is_absolute_path; - bool _is_static_lib; - bool _is_instrument_lib; - AgentState _state; - AgentLibrary* _next; - - public: - // Accessors - const char* name() const { return _name; } - char* options() const { return _options; } - bool is_absolute_path() const { return _is_absolute_path; } - void* os_lib() const { return _os_lib; } - void set_os_lib(void* os_lib) { _os_lib = os_lib; } - AgentLibrary* next() const { return _next; } - bool is_static_lib() const { return _is_static_lib; } - bool is_instrument_lib() const { return _is_instrument_lib; } - void set_static_lib(bool is_static_lib) { _is_static_lib = is_static_lib; } - bool valid() { return (_state == agent_valid); } - void set_valid() { _state = agent_valid; } - - // Constructor - AgentLibrary(const char* name, const char* options, bool is_absolute_path, - void* os_lib, bool instrument_lib=false); -}; - -// maintain an order of entry list of AgentLibrary -class AgentLibraryList { - private: - AgentLibrary* _first; - AgentLibrary* _last; - public: - bool is_empty() const { return _first == nullptr; } - AgentLibrary* first() const { return _first; } - - // add to the end of the list - void add(AgentLibrary* lib) { - if (is_empty()) { - _first = _last = lib; - } else { - _last->_next = lib; - _last = lib; - } - lib->_next = nullptr; - } - - // search for and remove a library known to be in the list - void remove(AgentLibrary* lib) { - AgentLibrary* curr; - AgentLibrary* prev = nullptr; - for (curr = first(); curr != nullptr; prev = curr, curr = curr->next()) { - if (curr == lib) { - break; - } - } - assert(curr != nullptr, "always should be found"); - - if (curr != nullptr) { - // it was found, by-pass this library - if (prev == nullptr) { - _first = curr->_next; - } else { - prev->_next = curr->_next; - } - if (curr == _last) { - _last = prev; - } - curr->_next = nullptr; - } - } - - AgentLibraryList() { - _first = nullptr; - _last = nullptr; - } -}; - // Helper class for controlling the lifetime of JavaVMInitArgs objects. class ScopedVMInitArgs; @@ -331,18 +239,6 @@ class Arguments : AllStatic { // Value of the conservative maximum heap alignment needed static size_t _conservative_max_heap_alignment; - // -Xrun arguments - static AgentLibraryList _libraryList; - static void add_init_library(const char* name, const char* options); - - // -agentlib and -agentpath arguments - static AgentLibraryList _agentList; - static void add_init_agent(const char* name, const char* options, bool absolute_path); - static void add_instrument_agent(const char* name, const char* options, bool absolute_path); - - // Late-binding agents not started via arguments - static void add_loaded_agent(AgentLibrary *agentLib); - // Operation modi static Mode _mode; static void set_mode_flags(Mode mode); @@ -542,17 +438,6 @@ class Arguments : AllStatic { // -Dsun.java.launcher.is_altjvm static bool sun_java_launcher_is_altjvm(); - // -Xrun - static AgentLibrary* libraries() { return _libraryList.first(); } - static bool init_libraries_at_startup() { return !_libraryList.is_empty(); } - static void convert_library_to_agent(AgentLibrary* lib) - { _libraryList.remove(lib); - _agentList.add(lib); } - - // -agentlib -agentpath - static AgentLibrary* agents() { return _agentList.first(); } - static bool init_agents_at_startup() { return !_agentList.is_empty(); } - // abort, exit, vfprintf hooks static abort_hook_t abort_hook() { return _abort_hook; } static exit_hook_t exit_hook() { return _exit_hook; } diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 3b2cd631f7e..44c82a0a849 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -54,6 +54,7 @@ #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/continuation.hpp" #include "runtime/deoptimization.hpp" @@ -516,7 +517,7 @@ void before_exit(JavaThread* thread, bool halt) { // Always call even when there are not JVMTI environments yet, since environments // may be attached late and JVMTI must track phases of VM execution JvmtiExport::post_vm_death(); - Threads::shutdown_vm_agents(); + JvmtiAgentList::unload_agents(); // Terminate the signal thread // Note: we don't wait until it actually dies. diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index eb21352bd55..e193a411f7a 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -41,6 +41,7 @@ #include "memory/universe.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" +#include "prims/jvmtiAgent.hpp" #include "prims/jvm_misc.hpp" #include "runtime/arguments.hpp" #include "runtime/atomic.hpp" @@ -534,7 +535,7 @@ void* os::native_java_library() { * executable if agent_lib->is_static_lib() == true or in the shared library * referenced by 'handle'. */ -void* os::find_agent_function(AgentLibrary *agent_lib, bool check_lib, +void* os::find_agent_function(JvmtiAgent *agent_lib, bool check_lib, const char *syms[], size_t syms_len) { assert(agent_lib != nullptr, "sanity check"); const char *lib_name; @@ -561,29 +562,29 @@ void* os::find_agent_function(AgentLibrary *agent_lib, bool check_lib, } // See if the passed in agent is statically linked into the VM image. -bool os::find_builtin_agent(AgentLibrary *agent_lib, const char *syms[], +bool os::find_builtin_agent(JvmtiAgent* agent, const char *syms[], size_t syms_len) { void *ret; void *proc_handle; void *save_handle; - assert(agent_lib != nullptr, "sanity check"); - if (agent_lib->name() == nullptr) { + assert(agent != nullptr, "sanity check"); + if (agent->name() == nullptr) { return false; } proc_handle = get_default_process_handle(); // Check for Agent_OnLoad/Attach_lib_name function - save_handle = agent_lib->os_lib(); + save_handle = agent->os_lib(); // We want to look in this process' symbol table. - agent_lib->set_os_lib(proc_handle); - ret = find_agent_function(agent_lib, true, syms, syms_len); + agent->set_os_lib(proc_handle); + ret = find_agent_function(agent, true, syms, syms_len); if (ret != nullptr) { // Found an entry point like Agent_OnLoad_lib_name so we have a static agent - agent_lib->set_valid(); - agent_lib->set_static_lib(true); + agent->set_static_lib(); + agent->set_loaded(); return true; } - agent_lib->set_os_lib(save_handle); + agent->set_os_lib(save_handle); return false; } diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index e07d822a140..c27acbc6d32 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -34,8 +34,8 @@ # include #endif -class AgentLibrary; class frame; +class JvmtiAgent; // Rules for using and implementing methods declared in the "os" class // =================================================================== @@ -732,11 +732,11 @@ class os: AllStatic { static void* get_default_process_handle(); // Check for static linked agent library - static bool find_builtin_agent(AgentLibrary *agent_lib, const char *syms[], + static bool find_builtin_agent(JvmtiAgent *agent_lib, const char *syms[], size_t syms_len); // Find agent entry point - static void *find_agent_function(AgentLibrary *agent_lib, bool check_lib, + static void *find_agent_function(JvmtiAgent *agent_lib, bool check_lib, const char *syms[], size_t syms_len); // Provide C99 compliant versions of these functions, since some versions diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 47816206ed0..384936b905e 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -56,6 +56,7 @@ #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvm_misc.hpp" #include "runtime/arguments.hpp" #include "runtime/fieldDescriptor.inline.hpp" @@ -332,10 +333,6 @@ static void call_initPhase3(TRAPS) { void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { TraceTime timer("Initialize java.lang classes", TRACETIME_LOG(Info, startuptime)); - if (EagerXrunInit && Arguments::init_libraries_at_startup()) { - create_vm_init_libraries(); - } - initialize_class(vmSymbols::java_lang_String(), CHECK); // Inject CompactStrings value after the static initializers for String ran. @@ -499,16 +496,8 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { // Initialize output stream logging ostream_init_log(); - // Convert -Xrun to -agentlib: if there is no JVM_OnLoad - // Must be before create_vm_init_agents() - if (Arguments::init_libraries_at_startup()) { - convert_vm_init_libraries_to_agents(); - } - // Launch -agentlib/-agentpath and converted -Xrun agents - if (Arguments::init_agents_at_startup()) { - create_vm_init_agents(); - } + JvmtiAgentList::load_agents(); // Initialize Threads state _number_of_threads = 0; @@ -623,6 +612,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents. JvmtiExport::post_early_vm_start(); + // Launch -Xrun agents early if EagerXrunInit is set + if (EagerXrunInit) { + JvmtiAgentList::load_xrun_agents(); + } + initialize_java_lang_classes(main_thread, CHECK_JNI_ERR); quicken_jni_functions(); @@ -658,11 +652,9 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { } } - // Launch -Xrun agents - // Must be done in the JVMTI live phase so that for backward compatibility the JDWP - // back-end can launch with -Xdebug -Xrunjdwp. - if (!EagerXrunInit && Arguments::init_libraries_at_startup()) { - create_vm_init_libraries(); + // Launch -Xrun agents if EagerXrunInit is not set. + if (!EagerXrunInit) { + JvmtiAgentList::load_xrun_agents(); } Chunk::start_chunk_pool_cleaner_task(); @@ -804,208 +796,6 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { return JNI_OK; } -// type for the Agent_OnLoad and JVM_OnLoad entry points -extern "C" { - typedef jint (JNICALL *OnLoadEntry_t)(JavaVM *, char *, void *); -} -// Find a command line agent library and return its entry point for -// -agentlib: -agentpath: -Xrun -// num_symbol_entries must be passed-in since only the caller knows the number of symbols in the array. -static OnLoadEntry_t lookup_on_load(AgentLibrary* agent, - const char *on_load_symbols[], - size_t num_symbol_entries) { - OnLoadEntry_t on_load_entry = nullptr; - void *library = nullptr; - - if (!agent->valid()) { - char buffer[JVM_MAXPATHLEN]; - char ebuf[1024] = ""; - const char *name = agent->name(); - const char *msg = "Could not find agent library "; - - // First check to see if agent is statically linked into executable - if (os::find_builtin_agent(agent, on_load_symbols, num_symbol_entries)) { - library = agent->os_lib(); - } else if (agent->is_absolute_path()) { - library = os::dll_load(name, ebuf, sizeof ebuf); - if (library == nullptr) { - const char *sub_msg = " in absolute path, with error: "; - size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) + strlen(ebuf) + 1; - char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread); - jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf); - // If we can't find the agent, exit. - vm_exit_during_initialization(buf, nullptr); - FREE_C_HEAP_ARRAY(char, buf); - } - } else { - // Try to load the agent from the standard dll directory - if (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(), - name)) { - library = os::dll_load(buffer, ebuf, sizeof ebuf); - } - if (library == nullptr) { // Try the library path directory. - if (os::dll_build_name(buffer, sizeof(buffer), name)) { - library = os::dll_load(buffer, ebuf, sizeof ebuf); - } - if (library == nullptr) { - const char *sub_msg = " on the library path, with error: "; - const char *sub_msg2 = "\nModule java.instrument may be missing from runtime image."; - - size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) + - strlen(ebuf) + strlen(sub_msg2) + 1; - char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread); - if (!agent->is_instrument_lib()) { - jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf); - } else { - jio_snprintf(buf, len, "%s%s%s%s%s", msg, name, sub_msg, ebuf, sub_msg2); - } - // If we can't find the agent, exit. - vm_exit_during_initialization(buf, nullptr); - FREE_C_HEAP_ARRAY(char, buf); - } - } - } - agent->set_os_lib(library); - agent->set_valid(); - } - - // Find the OnLoad function. - on_load_entry = - CAST_TO_FN_PTR(OnLoadEntry_t, os::find_agent_function(agent, - false, - on_load_symbols, - num_symbol_entries)); - return on_load_entry; -} - -// Find the JVM_OnLoad entry point -static OnLoadEntry_t lookup_jvm_on_load(AgentLibrary* agent) { - const char *on_load_symbols[] = JVM_ONLOAD_SYMBOLS; - return lookup_on_load(agent, on_load_symbols, sizeof(on_load_symbols) / sizeof(char*)); -} - -// Find the Agent_OnLoad entry point -static OnLoadEntry_t lookup_agent_on_load(AgentLibrary* agent) { - const char *on_load_symbols[] = AGENT_ONLOAD_SYMBOLS; - return lookup_on_load(agent, on_load_symbols, sizeof(on_load_symbols) / sizeof(char*)); -} - -// For backwards compatibility with -Xrun -// Convert libraries with no JVM_OnLoad, but which have Agent_OnLoad to be -// treated like -agentpath: -// Must be called before agent libraries are created -void Threads::convert_vm_init_libraries_to_agents() { - AgentLibrary* agent; - AgentLibrary* next; - - for (agent = Arguments::libraries(); agent != nullptr; agent = next) { - next = agent->next(); // cache the next agent now as this agent may get moved off this list - OnLoadEntry_t on_load_entry = lookup_jvm_on_load(agent); - - // If there is an JVM_OnLoad function it will get called later, - // otherwise see if there is an Agent_OnLoad - if (on_load_entry == nullptr) { - on_load_entry = lookup_agent_on_load(agent); - if (on_load_entry != nullptr) { - // switch it to the agent list -- so that Agent_OnLoad will be called, - // JVM_OnLoad won't be attempted and Agent_OnUnload will - Arguments::convert_library_to_agent(agent); - } else { - vm_exit_during_initialization("Could not find JVM_OnLoad or Agent_OnLoad function in the library", agent->name()); - } - } - } -} - -// Create agents for -agentlib: -agentpath: and converted -Xrun -// Invokes Agent_OnLoad -// Called very early -- before JavaThreads exist -void Threads::create_vm_init_agents() { - extern struct JavaVM_ main_vm; - AgentLibrary* agent; - - JvmtiExport::enter_onload_phase(); - - for (agent = Arguments::agents(); agent != nullptr; agent = agent->next()) { - // CDS dumping does not support native JVMTI agent. - // CDS dumping supports Java agent if the AllowArchivingWithJavaAgent diagnostic option is specified. - if (Arguments::is_dumping_archive()) { - if(!agent->is_instrument_lib()) { - vm_exit_during_cds_dumping("CDS dumping does not support native JVMTI agent, name", agent->name()); - } else if (!AllowArchivingWithJavaAgent) { - vm_exit_during_cds_dumping( - "Must enable AllowArchivingWithJavaAgent in order to run Java agent during CDS dumping"); - } - } - - OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent); - - if (on_load_entry != nullptr) { - // Invoke the Agent_OnLoad function - jint err = (*on_load_entry)(&main_vm, agent->options(), nullptr); - if (err != JNI_OK) { - vm_exit_during_initialization("agent library failed to init", agent->name()); - } - } else { - vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name()); - } - } - - JvmtiExport::enter_primordial_phase(); -} - -extern "C" { - typedef void (JNICALL *Agent_OnUnload_t)(JavaVM *); -} - -void Threads::shutdown_vm_agents() { - // Send any Agent_OnUnload notifications - const char *on_unload_symbols[] = AGENT_ONUNLOAD_SYMBOLS; - size_t num_symbol_entries = ARRAY_SIZE(on_unload_symbols); - extern struct JavaVM_ main_vm; - for (AgentLibrary* agent = Arguments::agents(); agent != nullptr; agent = agent->next()) { - - // Find the Agent_OnUnload function. - Agent_OnUnload_t unload_entry = CAST_TO_FN_PTR(Agent_OnUnload_t, - os::find_agent_function(agent, - false, - on_unload_symbols, - num_symbol_entries)); - - // Invoke the Agent_OnUnload function - if (unload_entry != nullptr) { - JavaThread* thread = JavaThread::current(); - ThreadToNativeFromVM ttn(thread); - HandleMark hm(thread); - (*unload_entry)(&main_vm); - } - } -} - -// Called for after the VM is initialized for -Xrun libraries which have not been converted to agent libraries -// Invokes JVM_OnLoad -void Threads::create_vm_init_libraries() { - extern struct JavaVM_ main_vm; - AgentLibrary* agent; - - for (agent = Arguments::libraries(); agent != nullptr; agent = agent->next()) { - OnLoadEntry_t on_load_entry = lookup_jvm_on_load(agent); - - if (on_load_entry != nullptr) { - // Invoke the JVM_OnLoad function - JavaThread* thread = JavaThread::current(); - ThreadToNativeFromVM ttn(thread); - HandleMark hm(thread); - jint err = (*on_load_entry)(&main_vm, agent->options(), nullptr); - if (err != JNI_OK) { - vm_exit_during_initialization("-Xrun library failed to init", agent->name()); - } - } else { - vm_exit_during_initialization("Could not find JVM_OnLoad function in -Xrun library", agent->name()); - } - } -} - // Threads::destroy_vm() is normally called from jni_DestroyJavaVM() when // the program falls off the end of main(). Another VM exit path is through // vm_exit() when the program calls System.exit() to return a value or when diff --git a/src/hotspot/share/services/attachListener.cpp b/src/hotspot/share/services/attachListener.cpp index a3c55e01dd5..9a5da5e592b 100644 --- a/src/hotspot/share/services/attachListener.cpp +++ b/src/hotspot/share/services/attachListener.cpp @@ -31,6 +31,7 @@ #include "memory/universe.hpp" #include "oops/oop.inline.hpp" #include "oops/typeArrayOop.inline.hpp" +#include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/arguments.hpp" #include "runtime/flags/jvmFlag.hpp" @@ -134,7 +135,7 @@ static jint load_agent(AttachOperation* op, outputStream* out) { } } - return JvmtiExport::load_agent_library(agent, absParam, options, out); + return JvmtiAgentList::load_agent(agent, absParam, options, out); } // Implementation of "properties" command. diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 0d5ae4870a0..e43d7bbec85 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -42,6 +42,7 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/typeArrayOop.inline.hpp" +#include "prims/jvmtiAgentList.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/flags/jvmFlag.hpp" #include "runtime/handles.inline.hpp" @@ -297,8 +298,7 @@ void JVMTIAgentLoadDCmd::execute(DCmdSource source, TRAPS) { if (is_java_agent) { if (_option.value() == nullptr) { - JvmtiExport::load_agent_library("instrument", "false", - _libpath.value(), output()); + JvmtiAgentList::load_agent("instrument", "false", _libpath.value(), output()); } else { size_t opt_len = strlen(_libpath.value()) + strlen(_option.value()) + 2; if (opt_len > 4096) { @@ -315,13 +315,12 @@ void JVMTIAgentLoadDCmd::execute(DCmdSource source, TRAPS) { } jio_snprintf(opt, opt_len, "%s=%s", _libpath.value(), _option.value()); - JvmtiExport::load_agent_library("instrument", "false", opt, output()); + JvmtiAgentList::load_agent("instrument", "false", opt, output()); os::free(opt); } } else { - JvmtiExport::load_agent_library(_libpath.value(), "true", - _option.value(), output()); + JvmtiAgentList::load_agent(_libpath.value(), "true", _option.value(), output()); } } @@ -1031,7 +1030,9 @@ void DebugOnCmdStartDCmd::execute(DCmdSource source, TRAPS) { const char *error = "Could not find jdwp agent."; if (!dvc_start_ptr) { - for (AgentLibrary* agent = Arguments::agents(); agent != nullptr; agent = agent->next()) { + JvmtiAgentList::Iterator it = JvmtiAgentList::agents(); + while (it.has_next()) { + JvmtiAgent* agent = it.next(); if ((strcmp("jdwp", agent->name()) == 0) && (dvc_start_ptr == nullptr)) { char const* func = "debugInit_startDebuggingViaCommand"; dvc_start_ptr = (debugInit_startDebuggingViaCommandPtr) os::find_agent_function(agent, false, &func, 1); diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 05936fe8255..39593b2e3c9 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -858,6 +858,16 @@ endChunk + + true + endChunk + + + + true + endChunk + + diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index d3b5adf1aef..d15126986ef 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -858,7 +858,15 @@ endChunk + + true + endChunk + + + true + endChunk + diff --git a/test/jdk/jdk/jfr/event/runtime/JavaAgent.java b/test/jdk/jdk/jfr/event/runtime/JavaAgent.java new file mode 100644 index 00000000000..3c83dbcb809 --- /dev/null +++ b/test/jdk/jdk/jfr/event/runtime/JavaAgent.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +package jdk.jfr.event.runtime; + +import java.lang.instrument.Instrumentation; + +public class JavaAgent { + + public static void agentmain(String agentArgs, Instrumentation inst) throws Exception { + System.out.println("agentmain: " + agentArgs); + } + + public static void premain(String agentArgs, Instrumentation inst) throws Exception { + System.out.println("premain: " + agentArgs); + } +} diff --git a/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java b/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java new file mode 100644 index 00000000000..a457b417a90 --- /dev/null +++ b/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java @@ -0,0 +1,161 @@ +/* + * 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. + */ + +package jdk.jfr.event.runtime; + +import java.lang.reflect.Method; +import java.security.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import com.sun.tools.attach.VirtualMachine; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedClassLoader; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.Event; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.TestClassLoader; + +/** + * @test + * @key jfr + * @summary Tests Agent Loaded event by starting native and Java agents + * @requires vm.hasJFR + * + * @library /test/lib + * @modules java.instrument + * + * @build jdk.jfr.event.runtime.JavaAgent + * + * @run driver jdk.test.lib.util.JavaAgentBuilder + * jdk.jfr.event.runtime.JavaAgent + * JavaAgent.jar + * + * @run main/othervm -javaagent:JavaAgent.jar=foo=bar + * jdk.jfr.event.runtime.TestAgentEvent + * testJavaStatic + * + * @run main/othervm -Djdk.attach.allowAttachSelf=true + * jdk.jfr.event.runtime.TestAgentEvent + * testJavaDynamic + * + * @run main/othervm -agentlib:jdwp=transport=dt_socket,server=y,address=any,onjcmd=y + * jdk.jfr.event.runtime.TestAgentEvent + * testNativeStatic + */ +public final class TestAgentEvent { + private static final String JAVA_AGENT_JAR = "JavaAgent.jar"; + + public static void main(String[] args) throws Throwable { + String testMethod = args[0]; + Method m = TestAgentEvent.class.getDeclaredMethod(testMethod, new Class[0]); + if (m == null) { + throw new Exception("Unknown test method: " + testMethod); + } + m.invoke(null, new Object[0]); + } + + private static void testJavaStatic() throws Throwable { + try (Recording r = new Recording()) { + r.enable(EventNames.JavaAgent); + r.start(); + r.stop(); + List events = Events.fromRecording(r); + RecordedEvent e = events.get(0); + System.out.println(e); + Events.assertField(e, "name").equal(JAVA_AGENT_JAR); + Events.assertField(e, "options").equal("foo=bar"); + Events.assertField(e, "dynamic").equal(false); + Instant initializationTime = e.getInstant("initializationTime"); + if (initializationTime.isAfter(r.getStartTime())) { + throw new Exception("Expected a static JavaAgent to be initialized before recording start"); + } + Events.assertField(e, "initializationDuration").atLeast(0L); + } + } + + private static void testNativeStatic() throws Throwable { + try (Recording r = new Recording()) { + r.enable(EventNames.NativeAgent); + r.start(); + r.stop(); + List events = Events.fromRecording(r); + RecordedEvent e = events.get(0); + System.out.println(e); + Events.assertField(e, "name").equal("jdwp"); + Events.assertField(e, "options").equal("transport=dt_socket,server=y,address=any,onjcmd=y"); + Events.assertField(e, "dynamic").equal(false); + Instant initializationTime = e.getInstant("initializationTime"); + if (initializationTime.isAfter(r.getStartTime())) { + throw new Exception("Expected a static NativeAgent to be initialized before recording start"); + } + Events.assertField(e, "initializationDuration").atLeast(0L); + } + } + + private static void testJavaDynamic() throws Throwable { + try (Recording r = new Recording()) { + r.enable(EventNames.JavaAgent); + r.start(); + long pid = ProcessHandle.current().pid(); + VirtualMachine vm = VirtualMachine.attach(Long.toString(pid)); + vm.loadAgent(JAVA_AGENT_JAR, "bar=baz"); + vm.detach(); + vm = VirtualMachine.attach(Long.toString(pid)); + vm.loadAgent(JAVA_AGENT_JAR); // options = null + vm.detach(); + vm = VirtualMachine.attach(Long.toString(pid)); + vm.loadAgent(JAVA_AGENT_JAR, ""); + vm.loadAgent(JAVA_AGENT_JAR, "="); + vm.detach(); + r.stop(); + List events = Events.fromRecording(r); + for (RecordedEvent e : events) { + System.out.println(e); + Instant initializationTime = e.getInstant("initializationTime"); + if (initializationTime.isBefore(r.getStartTime())) { + throw new Exception("Expected a dynamic JavaAgent to be initialized after recording start"); + } + if (initializationTime.isAfter(r.getStopTime())) { + throw new Exception("Expected a dynamic JavaAgent to be initialized before recording stop"); + } + Duration initializationDuration = e.getDuration("initializationDuration"); + if (initializationDuration.isNegative()) { + throw new Exception("Expected initalizationDuration to be positive value"); + } + if (initializationDuration.toSeconds() > 3600) { + throw new Exception("Expected initializationDuration to be less than 1 hour"); + } + Events.assertField(e, "name").equal(JAVA_AGENT_JAR); + } + Events.assertField(events.get(0), "options").equal("bar=baz"); + Events.assertField(events.get(1), "options").equal(null); + Events.assertField(events.get(2), "options").equal(""); + Events.assertField(events.get(3), "options").equal("="); + } + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 87e526f3c73..ec2e079208c 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -88,6 +88,8 @@ public class EventNames { public static final String FinalizerStatistics = PREFIX + "FinalizerStatistics"; public static final String NativeMemoryUsage = PREFIX + "NativeMemoryUsage"; public static final String NativeMemoryUsageTotal = PREFIX + "NativeMemoryUsageTotal"; + public static final String JavaAgent = PREFIX + "JavaAgent"; + public static final String NativeAgent = PREFIX + "NativeAgent"; // This event is hard to test public static final String ReservedStackActivation = PREFIX + "ReservedStackActivation";