1 /* |
|
2 * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 * |
|
23 */ |
|
24 |
|
25 /* |
|
26 * @test |
|
27 * @summary Exercise the java.lang.instrument.Instrumentation APIs on classes archived |
|
28 * using CDS/AppCDSv1/AppCDSv2. |
|
29 * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes |
|
30 * @requires vm.cds |
|
31 * @requires vm.flavor != "minimal" |
|
32 * @build sun.hotspot.WhiteBox |
|
33 * InstrumentationApp |
|
34 * InstrumentationClassFileTransformer |
|
35 * InstrumentationRegisterClassFileTransformer |
|
36 * @run main/othervm InstrumentationTest |
|
37 */ |
|
38 |
|
39 // Note: TestCommon is from /test/hotspot/jtreg/runtime/appcds/TestCommon.java |
|
40 // Note: Util is from /test/hotspot/jtreg/runtime/appcds/test-classes/TestCommon.java |
|
41 |
|
42 import com.sun.tools.attach.VirtualMachine; |
|
43 import java.io.File; |
|
44 import java.io.FileInputStream; |
|
45 import java.util.Scanner; |
|
46 import jdk.test.lib.Asserts; |
|
47 import jdk.test.lib.cds.CDSOptions; |
|
48 import jdk.test.lib.process.OutputAnalyzer; |
|
49 import jdk.test.lib.process.ProcessTools; |
|
50 |
|
51 public class InstrumentationTest { |
|
52 public static String bootClasses[] = { |
|
53 "InstrumentationApp$Intf", |
|
54 "InstrumentationApp$Bar", |
|
55 "sun.hotspot.WhiteBox", |
|
56 }; |
|
57 public static String appClasses[] = { |
|
58 "InstrumentationApp", |
|
59 "InstrumentationApp$Foo", |
|
60 "InstrumentationApp$MyLoader", |
|
61 }; |
|
62 public static String custClasses[] = { |
|
63 "InstrumentationApp$Coo", |
|
64 }; |
|
65 public static String sharedClasses[] = TestCommon.concat(bootClasses, appClasses); |
|
66 |
|
67 public static String agentClasses[] = { |
|
68 "InstrumentationClassFileTransformer", |
|
69 "InstrumentationRegisterClassFileTransformer", |
|
70 "Util", |
|
71 }; |
|
72 |
|
73 public static void main(String[] args) throws Throwable { |
|
74 runTest(false); |
|
75 runTest(true); |
|
76 } |
|
77 |
|
78 public static void runTest(boolean attachAgent) throws Throwable { |
|
79 String bootJar = |
|
80 ClassFileInstaller.writeJar("InstrumentationBoot.jar", bootClasses); |
|
81 String appJar = |
|
82 ClassFileInstaller.writeJar("InstrumentationApp.jar", |
|
83 TestCommon.concat(appClasses, |
|
84 "InstrumentationApp$ArchivedIfAppCDSv2Enabled")); |
|
85 String custJar = |
|
86 ClassFileInstaller.writeJar("InstrumentationCust.jar", custClasses); |
|
87 String agentJar = |
|
88 ClassFileInstaller.writeJar("InstrumentationAgent.jar", |
|
89 ClassFileInstaller.Manifest.fromSourceFile("InstrumentationAgent.mf"), |
|
90 agentClasses); |
|
91 |
|
92 String bootCP = "-Xbootclasspath/a:" + bootJar; |
|
93 |
|
94 System.out.println(""); |
|
95 System.out.println("============================================================"); |
|
96 System.out.println("CDS: NO, attachAgent: " + (attachAgent ? "YES" : "NO")); |
|
97 System.out.println("============================================================"); |
|
98 System.out.println(""); |
|
99 |
|
100 String agentCmdArg, flagFile; |
|
101 if (attachAgent) { |
|
102 // we will attach the agent, so don't specify -javaagent in the command line. We'll use |
|
103 // something harmless like -showversion to make it easier to construct the command line |
|
104 agentCmdArg = "-showversion"; |
|
105 } else { |
|
106 agentCmdArg = "-javaagent:" + agentJar; |
|
107 } |
|
108 |
|
109 // First, run the test class directly, w/o sharing, as a baseline reference |
|
110 flagFile = getFlagFile(attachAgent); |
|
111 AgentAttachThread t = doAttach(attachAgent, flagFile, agentJar); |
|
112 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( |
|
113 bootCP, |
|
114 "-cp", appJar, |
|
115 "-XX:+UnlockDiagnosticVMOptions", |
|
116 "-XX:+WhiteBoxAPI", |
|
117 "-Xshare:off", |
|
118 agentCmdArg, |
|
119 "InstrumentationApp", flagFile, bootJar, appJar, custJar); |
|
120 TestCommon.executeAndLog(pb, "no-sharing").shouldHaveExitValue(0); |
|
121 checkAttach(t); |
|
122 |
|
123 // Dump the AppCDS archive. On some platforms AppCDSv2 may not be enabled, so we |
|
124 // first try the v2 classlist, and if that fails, revert to the v1 classlist. |
|
125 // Note that the InstrumentationApp$ArchivedIfAppCDSv2Enabled class is archived |
|
126 // only if V2 is enabled. This is tested by InstrumentationApp.isAppCDSV2Enabled(). |
|
127 String[] v2Classes = { |
|
128 "InstrumentationApp$ArchivedIfAppCDSv2Enabled", |
|
129 "java/lang/Object id: 0", |
|
130 "InstrumentationApp$Intf id: 1", |
|
131 "InstrumentationApp$Coo id: 2 super: 0 interfaces: 1 source: " + custJar, |
|
132 }; |
|
133 String[] sharedClassesWithV2 = TestCommon.concat(v2Classes, sharedClasses); |
|
134 OutputAnalyzer out = TestCommon.dump(appJar, sharedClassesWithV2, bootCP); |
|
135 if (out.getExitValue() != 0) { |
|
136 System.out.println("Redumping with AppCDSv2 disabled"); |
|
137 TestCommon.testDump(appJar, sharedClasses, bootCP); |
|
138 } |
|
139 |
|
140 // Run with AppCDS. |
|
141 System.out.println(""); |
|
142 System.out.println("============================================================"); |
|
143 System.out.println("CDS: YES, attachAgent: " + (attachAgent ? "YES" : "NO")); |
|
144 System.out.println("============================================================"); |
|
145 System.out.println(""); |
|
146 |
|
147 flagFile = getFlagFile(attachAgent); |
|
148 t = doAttach(attachAgent, flagFile, agentJar); |
|
149 out = TestCommon.execAuto("-cp", appJar, |
|
150 bootCP, |
|
151 "-XX:+UnlockDiagnosticVMOptions", |
|
152 "-XX:+WhiteBoxAPI", |
|
153 agentCmdArg, |
|
154 "InstrumentationApp", flagFile, bootJar, appJar, custJar); |
|
155 |
|
156 CDSOptions opts = (new CDSOptions()).setXShareMode("auto"); |
|
157 TestCommon.checkExec(out, opts); |
|
158 checkAttach(t); |
|
159 } |
|
160 |
|
161 static int flagFileSerial = 1; |
|
162 static private String getFlagFile(boolean attachAgent) { |
|
163 if (attachAgent) { |
|
164 // Do not reuse the same file name as Windows may fail to |
|
165 // delete the file. |
|
166 return "attach.flag." + ProcessHandle.current().pid() + |
|
167 "." + (flagFileSerial++) + "." + System.currentTimeMillis(); |
|
168 } else { |
|
169 return "noattach"; |
|
170 } |
|
171 } |
|
172 |
|
173 static AgentAttachThread doAttach(boolean attachAgent, String flagFile, String agentJar) throws Throwable { |
|
174 if (!attachAgent) { |
|
175 return null; |
|
176 } |
|
177 |
|
178 // Hand-shake protocol with the child process |
|
179 // [1] Parent process (this process) launches child process (InstrumentationApp) |
|
180 // and then waits until child process writes its pid into the flagFile. |
|
181 // [2] Child process process starts up, writes its pid into the flagFile, |
|
182 // and waits for the flagFile to be deleted. |
|
183 // [3] When parent process gets the pid, it attaches to the child process |
|
184 // (if we attempt to attach to a process too early, the SIGQUIT |
|
185 // may cause the child to die) and deletes the flagFile. |
|
186 // [4] Child process resumes execution. |
|
187 |
|
188 File f = new File(flagFile); |
|
189 f.delete(); |
|
190 if (f.exists()) { |
|
191 throw new RuntimeException("Flag file should not exist: " + f); |
|
192 } |
|
193 |
|
194 // At this point, the child process is not yet launched. Note that |
|
195 // TestCommon.exec() and OutputAnalyzer.OutputAnalyzer() both block |
|
196 // until the child process has finished. |
|
197 // |
|
198 // So, we will launch a AgentAttachThread which will poll the flagFile |
|
199 // until the child process is launched. |
|
200 AgentAttachThread t = new AgentAttachThread(flagFile, agentJar); |
|
201 t.start(); |
|
202 return t; |
|
203 } |
|
204 |
|
205 static void checkAttach(AgentAttachThread thread) throws Throwable { |
|
206 if (thread != null) { |
|
207 thread.check(); |
|
208 } |
|
209 } |
|
210 |
|
211 static class AgentAttachThread extends Thread { |
|
212 String flagFile; |
|
213 String agentJar; |
|
214 volatile boolean succeeded; |
|
215 |
|
216 AgentAttachThread(String flagFile, String agentJar) { |
|
217 this.flagFile = flagFile; |
|
218 this.agentJar = agentJar; |
|
219 this.succeeded = false; |
|
220 } |
|
221 |
|
222 static String getPid(String flagFile) throws Throwable { |
|
223 while (true) { |
|
224 // Keep polling until the child process has been launched. If for some |
|
225 // reason the child process fails to launch, this test will be terminated |
|
226 // by JTREG's time-out mechanism. |
|
227 Thread.sleep(100); |
|
228 File f = new File(flagFile); |
|
229 if (f.exists() && f.length() > 100) { |
|
230 try (FileInputStream in = new FileInputStream(f)) { |
|
231 Scanner scanner = new Scanner(in); |
|
232 return Long.toString(scanner.nextLong()); |
|
233 } catch (Throwable t) { |
|
234 // This may happen on Windows if the child process has not |
|
235 // fully closed the output stream to the flagFile |
|
236 System.out.println("Ignored: " + t); |
|
237 t.printStackTrace(System.out); |
|
238 continue; |
|
239 } |
|
240 } |
|
241 } |
|
242 } |
|
243 |
|
244 public void run() { |
|
245 try { |
|
246 String pid = getPid(flagFile); |
|
247 System.out.println("child pid = " + pid); |
|
248 VirtualMachine vm = VirtualMachine.attach(pid); |
|
249 System.out.println(agentJar); |
|
250 vm.loadAgent(agentJar); |
|
251 } catch (Throwable t) { |
|
252 t.printStackTrace(); |
|
253 throw new RuntimeException(t); |
|
254 } |
|
255 |
|
256 // Delete the flagFile to indicate to the child process that we |
|
257 // have attached to it, so it should proceed. |
|
258 File f = new File(flagFile); |
|
259 for (int i=0; i<5; i++) { |
|
260 // The detele may fail on Windows if the child JVM is checking |
|
261 // f.exists() at exactly the same time?? Let's do a little |
|
262 // dance. |
|
263 f.delete(); |
|
264 try { |
|
265 Thread.sleep(10); |
|
266 } catch (Throwable t) {;} |
|
267 } |
|
268 if (f.exists()) { |
|
269 throw new RuntimeException("Failed to delete " + f); |
|
270 } |
|
271 System.out.println("Attach succeeded (parent)"); |
|
272 succeeded = true; |
|
273 } |
|
274 |
|
275 void check() throws Throwable { |
|
276 super.join(); |
|
277 if (!succeeded) { |
|
278 throw new RuntimeException("Attaching agent to child VM failed"); |
|
279 } |
|
280 } |
|
281 } |
|
282 } |
|
283 |
|