21c2dac159
Reviewed-by: dholmes, mbaesken
308 lines
12 KiB
Java
308 lines
12 KiB
Java
/*
|
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8307478
|
|
* @summary Test that a warning is printed when an agent is dynamically loaded
|
|
* @requires vm.jvmti
|
|
* @modules jdk.attach jdk.jcmd
|
|
* @library /test/lib /test/jdk
|
|
* @build Application JavaAgent
|
|
* @run junit/othervm/native DynamicLoadWarningTest
|
|
*/
|
|
|
|
import java.io.DataInputStream;
|
|
import java.io.IOException;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.nio.file.Path;
|
|
import java.util.Arrays;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.jar.Attributes;
|
|
import java.util.jar.Manifest;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import com.sun.tools.attach.VirtualMachine;
|
|
|
|
import jdk.test.lib.JDKToolLauncher;
|
|
import jdk.test.lib.Platform;
|
|
import jdk.test.lib.Utils;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
import jdk.test.lib.util.JarUtils;
|
|
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.MethodSource;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
class DynamicLoadWarningTest {
|
|
private static final String JVMTI_AGENT_WARNING = "WARNING: A JVM TI agent has been loaded dynamically";
|
|
private static final String JAVA_AGENT_WARNING = "WARNING: A Java agent has been loaded dynamically";
|
|
|
|
// JVM TI agents
|
|
private static final String JVMTI_AGENT1_LIB = "JvmtiAgent1";
|
|
private static final String JVMTI_AGENT2_LIB = "JvmtiAgent2";
|
|
private static String jvmtiAgentPath1;
|
|
private static String jvmtiAgentPath2;
|
|
|
|
// Java agent
|
|
private static final String TEST_CLASSES = System.getProperty("test.classes");
|
|
private static String javaAgent;
|
|
|
|
@BeforeAll
|
|
static void setup() throws Exception {
|
|
// get absolute path to JVM TI agents
|
|
String libname1 = Platform.buildSharedLibraryName(JVMTI_AGENT1_LIB);
|
|
String libname2 = Platform.buildSharedLibraryName(JVMTI_AGENT2_LIB);
|
|
jvmtiAgentPath1 = Path.of(Utils.TEST_NATIVE_PATH, libname1).toAbsolutePath().toString();
|
|
jvmtiAgentPath2 = Path.of(Utils.TEST_NATIVE_PATH, libname2).toAbsolutePath().toString();
|
|
|
|
// create JAR file with Java agent
|
|
Manifest man = new Manifest();
|
|
Attributes attrs = man.getMainAttributes();
|
|
attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
|
attrs.put(new Attributes.Name("Agent-Class"), "JavaAgent");
|
|
Path jarfile = Path.of("javaagent.jar");
|
|
Path classes = Path.of(TEST_CLASSES);
|
|
JarUtils.createJarFile(jarfile, man, classes, Path.of("JavaAgent.class"));
|
|
javaAgent = jarfile.toString();
|
|
}
|
|
|
|
/**
|
|
* Actions to load JvmtiAgent1 into a running VM.
|
|
*/
|
|
private static Stream<OnAttachAction> loadJvmtiAgent1() {
|
|
// load agent with the attach API
|
|
OnAttachAction loadJvmtiAgent = (pid, vm) -> vm.loadAgentLibrary(JVMTI_AGENT1_LIB);
|
|
|
|
// jcmd <pid> JVMTI.agent_load <agent>
|
|
OnAttachAction jcmdAgentLoad = jcmdAgentLoad(jvmtiAgentPath1);
|
|
|
|
return Stream.of(loadJvmtiAgent, jcmdAgentLoad);
|
|
}
|
|
|
|
/**
|
|
* Test loading JvmtiAgent1 into a running VM.
|
|
*/
|
|
@ParameterizedTest
|
|
@MethodSource("loadJvmtiAgent1")
|
|
void testLoadOneJvmtiAgent(OnAttachAction loadJvmtiAgent1) throws Exception {
|
|
// dynamically load loadJvmtiAgent1
|
|
test().whenRunning(loadJvmtiAgent1)
|
|
.stderrShouldContain(JVMTI_AGENT_WARNING);
|
|
|
|
// opt-in via command line option to allow dynamic loading of agents
|
|
test().withOpts("-XX:+EnableDynamicAgentLoading")
|
|
.whenRunning(loadJvmtiAgent1)
|
|
.stderrShouldNotContain(JVMTI_AGENT_WARNING);
|
|
|
|
// start loadJvmtiAgent1 via the command line, then dynamically load loadJvmtiAgent1
|
|
test().withOpts("-agentpath:" + jvmtiAgentPath1)
|
|
.whenRunning(loadJvmtiAgent1)
|
|
.stderrShouldNotContain(JVMTI_AGENT_WARNING);
|
|
|
|
// dynamically load loadJvmtiAgent1 twice, should be one warning
|
|
test().whenRunning(loadJvmtiAgent1)
|
|
.whenRunning(loadJvmtiAgent1)
|
|
.stderrShouldContain(JVMTI_AGENT_WARNING, 1);
|
|
}
|
|
|
|
/**
|
|
* Test loading JvmtiAgent1 and JvmtiAgent2 into a running VM.
|
|
*/
|
|
@ParameterizedTest
|
|
@MethodSource("loadJvmtiAgent1")
|
|
void testLoadTwoJvmtiAgents(OnAttachAction loadJvmtiAgent1) throws Exception {
|
|
OnAttachAction loadJvmtiAgent2 = (pid, vm) -> vm.loadAgentLibrary(JVMTI_AGENT2_LIB);
|
|
OnAttachAction jcmdAgentLoad2 = jcmdAgentLoad(jvmtiAgentPath2);
|
|
|
|
// dynamically load loadJvmtiAgent1, then dynamically load loadJvmtiAgent2 with attach API
|
|
test().whenRunning(loadJvmtiAgent1)
|
|
.whenRunning(loadJvmtiAgent2)
|
|
.stderrShouldContain(JVMTI_AGENT_WARNING, 2);
|
|
|
|
// dynamically load loadJvmtiAgent1, then dynamically load loadJvmtiAgent2 with jcmd
|
|
test().whenRunning(loadJvmtiAgent1)
|
|
.whenRunning(jcmdAgentLoad2)
|
|
.stderrShouldContain(JVMTI_AGENT_WARNING, 2);
|
|
|
|
// start loadJvmtiAgent2 via the command line, then dynamically load loadJvmtiAgent1
|
|
test().withOpts("-agentpath:" + jvmtiAgentPath2)
|
|
.whenRunning(loadJvmtiAgent1)
|
|
.stderrShouldContain(JVMTI_AGENT_WARNING);
|
|
}
|
|
|
|
/**
|
|
* Test loading Java agent into a running VM.
|
|
*/
|
|
@Test
|
|
void testLoadJavaAgent() throws Exception {
|
|
OnAttachAction loadJavaAgent = (pid, vm) -> vm.loadAgent(javaAgent);
|
|
|
|
// agent dynamically loaded
|
|
test().whenRunning(loadJavaAgent)
|
|
.stderrShouldContain(JAVA_AGENT_WARNING);
|
|
|
|
// opt-in via command line option to allow dynamic loading of agents
|
|
test().withOpts("-XX:+EnableDynamicAgentLoading")
|
|
.whenRunning(loadJavaAgent)
|
|
.stderrShouldNotContain(JAVA_AGENT_WARNING);
|
|
}
|
|
|
|
/**
|
|
* Represents an operation that accepts a process identifier and a VirtualMachine
|
|
* that the current JVM is attached to.
|
|
*/
|
|
private interface OnAttachAction {
|
|
void accept(long pid, VirtualMachine vm) throws Exception;
|
|
}
|
|
|
|
/**
|
|
* Returns an operation that invokes "jcmd <pid> JVMTI.agent_load <agentpath>" to
|
|
* load the given agent library into the JVM that the current JVM is attached to.
|
|
*/
|
|
private static OnAttachAction jcmdAgentLoad(String agentPath) {
|
|
return (pid, vm) -> {
|
|
String[] jcmd = JDKToolLauncher.createUsingTestJDK("jcmd")
|
|
.addToolArg(Long.toString(pid))
|
|
.addToolArg("JVMTI.agent_load")
|
|
.addToolArg(agentPath)
|
|
.getCommand();
|
|
System.out.println(Arrays.stream(jcmd).collect(Collectors.joining(" ")));
|
|
Process p = new ProcessBuilder(jcmd).inheritIO().start();
|
|
assertEquals(0, p.waitFor());
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a new app runner.
|
|
*/
|
|
private static AppRunner test() {
|
|
return new AppRunner();
|
|
}
|
|
|
|
/**
|
|
* Runs an application in its own VM. Once started, it attachs to the VM, runs a set
|
|
* of actions, then checks that the output contains, or does not contain, a string.
|
|
*/
|
|
private static class AppRunner {
|
|
private String[] vmopts = new String[0];
|
|
private List<OnAttachAction> actions = new ArrayList<>();
|
|
|
|
/**
|
|
* Specifies VM options to run the application.
|
|
*/
|
|
AppRunner withOpts(String... vmopts) {
|
|
this.vmopts = vmopts;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specifies an action to run when the attached to the running application.
|
|
*/
|
|
AppRunner whenRunning(OnAttachAction action) {
|
|
actions.add(action);
|
|
return this;
|
|
}
|
|
|
|
OutputAnalyzer run() throws Exception {
|
|
// start a listener socket that the application will connect to
|
|
try (ServerSocket listener = new ServerSocket()) {
|
|
InetAddress lh = InetAddress.getLoopbackAddress();
|
|
listener.bind(new InetSocketAddress(lh, 0));
|
|
|
|
var done = new AtomicBoolean();
|
|
|
|
// start a thread to wait for the application to phone home
|
|
Thread.ofPlatform().daemon().start(() -> {
|
|
try (Socket s = listener.accept();
|
|
DataInputStream in = new DataInputStream(s.getInputStream())) {
|
|
|
|
// read pid
|
|
long pid = in.readLong();
|
|
|
|
// attach and run the actions with the vm object
|
|
VirtualMachine vm = VirtualMachine.attach(Long.toString(pid));
|
|
try {
|
|
for (OnAttachAction action : actions) {
|
|
action.accept(pid, vm);
|
|
}
|
|
} finally {
|
|
vm.detach();
|
|
}
|
|
done.set(true);
|
|
|
|
// shutdown
|
|
s.getOutputStream().write(0);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
});
|
|
|
|
// launch application with the given VM options, waiting for it to terminate
|
|
Stream<String> s1 = Stream.of(vmopts);
|
|
Stream<String> s2 = Stream.of("Application", Integer.toString(listener.getLocalPort()));
|
|
String[] opts = Stream.concat(s1, s2).toArray(String[]::new);
|
|
OutputAnalyzer outputAnalyzer = ProcessTools
|
|
.executeTestJava(opts)
|
|
.outputTo(System.out)
|
|
.errorTo(System.out);
|
|
assertEquals(0, outputAnalyzer.getExitValue());
|
|
assertTrue(done.get(), "Attach or action failed, see log for details");
|
|
return outputAnalyzer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the application, checking that standard error contains a string.
|
|
*/
|
|
void stderrShouldContain(String s) throws Exception {
|
|
run().stderrShouldContain(s);
|
|
}
|
|
|
|
/**
|
|
* Run the application, checking that standard error contains the given number of
|
|
* occurrences of a string.
|
|
*/
|
|
void stderrShouldContain(String s, int occurrences) throws Exception {
|
|
List<String> lines = run().asLines();
|
|
int count = (int) lines.stream().filter(line -> line.indexOf(s) >= 0).count();
|
|
assertEquals(occurrences, count);
|
|
}
|
|
|
|
/**
|
|
* Run the application, checking that standard error does not contain a string.
|
|
*/
|
|
void stderrShouldNotContain(String s) throws Exception {
|
|
run().stderrShouldNotContain(s);
|
|
}
|
|
}
|
|
}
|