From 52bacaa2ee5f3845925f80e415a7863c18daf7cd Mon Sep 17 00:00:00 2001 From: duke Date: Thu, 27 Nov 2025 13:31:44 +0000 Subject: [PATCH] Backport 2a8cbd944ba4d8896e48181e396c65f70e5aa215 --- src/hotspot/share/prims/jvmtiAgentList.cpp | 5 + .../EarlyDynamicLoad/EarlyDynamicLoad.java | 103 ++++++++++++++++++ .../EarlyDynamicLoad/libEarlyDynamicLoad.cpp | 59 ++++++++++ 3 files changed, 167 insertions(+) create mode 100644 test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/EarlyDynamicLoad.java create mode 100644 test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/libEarlyDynamicLoad.cpp diff --git a/src/hotspot/share/prims/jvmtiAgentList.cpp b/src/hotspot/share/prims/jvmtiAgentList.cpp index 2a0f7172888..2857927ac31 100644 --- a/src/hotspot/share/prims/jvmtiAgentList.cpp +++ b/src/hotspot/share/prims/jvmtiAgentList.cpp @@ -200,6 +200,11 @@ void JvmtiAgentList::load_xrun_agents() { // 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) { + if (JvmtiEnvBase::get_phase() != JVMTI_PHASE_LIVE) { + st->print_cr("Dynamic agent loading is only permitted in the live phase"); + return; + } + JvmtiAgent* const agent = new JvmtiAgent(agent_name, options, is_absolute_path, /* dynamic agent */ true); if (agent->load(st)) { add(agent); diff --git a/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/EarlyDynamicLoad.java b/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/EarlyDynamicLoad.java new file mode 100644 index 00000000000..cb1596da08c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/EarlyDynamicLoad.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 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. + */ + +import com.sun.tools.attach.VirtualMachine; +import com.sun.tools.attach.AgentLoadException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterAll; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.TimeUnit; +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Utils; + +/* + * @test EarlyDynamicLoad + * @summary Test that dynamic attach fails gracefully when the JVM is not in live phase. + * @requires vm.jvmti + * @library /test/lib + * @run junit EarlyDynamicLoad + */ +public class EarlyDynamicLoad { + private static final String EXPECTED_MESSAGE = "Dynamic agent loading is only permitted in the live phase"; + + private static Process child; + + @BeforeAll + static void startAndWaitChild() throws Exception { + child = ProcessTools.createTestJavaProcessBuilder( + "-XX:+StartAttachListener", + "-agentpath:" + Utils.TEST_NATIVE_PATH + File.separator + System.mapLibraryName("EarlyDynamicLoad"), + "--version").start(); + + // Wait until the process enters VMStartCallback + try (InputStream is = child.getInputStream()) { + is.read(); + } + } + + @AfterAll + static void stopChild() throws Exception { + try (OutputStream os = child.getOutputStream()) { + os.write(0); + } + + if (!child.waitFor(5, TimeUnit.SECONDS)) { + child.destroyForcibly(); + throw new AssertionError("Timed out while waiting child process to complete"); + } + + OutputAnalyzer analyzer = new OutputAnalyzer(child); + analyzer.shouldHaveExitValue(0); + analyzer.stderrShouldBeEmpty(); + } + + @Test + public void virtualMachine() throws Exception { + try { + VirtualMachine vm = VirtualMachine.attach(String.valueOf(child.pid())); + vm.loadAgent("some.jar"); + vm.detach(); + throw new AssertionError("Should have failed with AgentLoadException"); + } catch(AgentLoadException exception) { + if (!exception.getMessage().contains(EXPECTED_MESSAGE)) { + throw new AssertionError("Unexpected error message", exception); + } + } + } + + @Test + public void jcmd() throws Exception { + PidJcmdExecutor executor = new PidJcmdExecutor(String.valueOf(child.pid())); + OutputAnalyzer out = executor.execute("JVMTI.agent_load some.jar"); + + out.shouldHaveExitValue(0); + out.stdoutShouldContain(EXPECTED_MESSAGE); + } +} diff --git a/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/libEarlyDynamicLoad.cpp b/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/libEarlyDynamicLoad.cpp new file mode 100644 index 00000000000..3991926306e --- /dev/null +++ b/test/hotspot/jtreg/serviceability/attach/EarlyDynamicLoad/libEarlyDynamicLoad.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 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 +#include +#include + +extern "C" { + +static void JNICALL VMStartCallback(jvmtiEnv* jvmti, JNIEnv* env) { + putchar('1'); + fflush(stdout); + getchar(); +} + +JNIEXPORT int Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + jvmtiEnv* jvmti; + if (vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0) != JVMTI_ERROR_NONE) { + fprintf(stderr, "JVMTI error occurred during GetEnv\n"); + return JNI_ERR; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.VMStart = VMStartCallback; + + if (jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)) != JVMTI_ERROR_NONE) { + fprintf(stderr, "JVMTI error occurred during SetEventCallbacks\n"); + return JNI_ERR; + } + if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_START, nullptr) != JVMTI_ERROR_NONE) { + fprintf(stderr, "JVMTI error occurred during SetEventNotificationMode\n"); + return JNI_ERR; + } + + return JNI_OK; +} + +}