| /* |
| * Copyright (c) 2023, 2025, 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 "cds/cdsConfig.hpp" |
| #include "cds/cds_globals.hpp" |
| #include "logging/log.hpp" |
| #include "memory/universe.hpp" |
| #include "prims/jvmtiAgentList.hpp" |
| #include "prims/jvmtiEnvBase.hpp" |
| #include "prims/jvmtiExport.hpp" |
| #include "runtime/atomic.hpp" |
| #include "runtime/os.inline.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<JvmtiAgent*, mtServiceability>(16)), _filter(filter) { |
| JvmtiAgent* next = head(list); |
| while (next != nullptr) { |
| next = select(next); |
| if (next != nullptr) { |
| _stack->push(next); |
| next = next->next(); |
| } |
| } |
| } |
| |
| 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<JvmtiAgent*>(const_cast<const Iterator*>(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, const char* options, bool absolute_path) { |
| add(new JvmtiAgent(name, options, absolute_path)); |
| } |
| |
| void JvmtiAgentList::add_xrun(const char* name, const 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. |
| void JvmtiAgentList::load_agent(const char* agent_name, bool is_absolute_path, |
| const char* options, outputStream* st) { |
| JvmtiAgent* const agent = new JvmtiAgent(agent_name, options, is_absolute_path, /* dynamic agent */ true); |
| if (agent->load(st)) { |
| add(agent); |
| } else { |
| delete agent; |
| } |
| } |
| |
| // Send any Agent_OnUnload notifications |
| void JvmtiAgentList::unload_agents() { |
| Iterator it = agents(); |
| while (it.has_next()) { |
| it.next()->unload(); |
| } |
| } |
| |
| // 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"); |
| 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 buffer[JVM_MAXPATHLEN]; |
| int offset; |
| if (!os::dll_address_to_library_name(reinterpret_cast<address>(f_ptr), &buffer[0], JVM_MAXPATHLEN, &offset)) { |
| return nullptr; |
| } |
| assert(buffer[0] != '\0', "invariant"); |
| const void* const os_module_address = reinterpret_cast<address>(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; |
| } |
| |
| void JvmtiAgentList::disable_agent_list() { |
| #if INCLUDE_CDS |
| assert(CDSConfig::is_dumping_final_static_archive(), "use this only for -XX:AOTMode=create!"); |
| assert(!Universe::is_bootstrapping() && !Universe::is_fully_initialized(), "must do this very early"); |
| log_info(aot)("Disabled all JVMTI agents during -XX:AOTMode=create"); |
| _list = nullptr; // Pretend that no agents have been added. |
| #endif |
| } |