1 /* 2 * Copyright (c) 2018, 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 package lib.jdb; 25 26 import jdk.test.lib.Utils; 27 import jdk.test.lib.process.ProcessTools; 28 29 import java.io.Closeable; 30 import java.io.IOException; 31 import java.util.LinkedList; 32 import java.util.List; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.TimeoutException; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 import java.util.stream.Collectors; 38 39 /** 40 * Helper class to run java debuggee and parse agent listening address. 41 * Usage: 42 * try (Debugee debugee = Debuggee.launcher("MyClass").setTransport("dt_shmem").launch()) { 43 * String addr = debuggee.getAddress(); 44 * } 45 * or 46 * ProcessBuilder pb = Debuggee.launcher("MyClass").setSuspended(false).prepare(); 47 * ProcessTools.executeProcess(pb); 48 */ 49 public class Debuggee implements Closeable { 50 51 public static Launcher launcher(String mainClass) { 52 return new Launcher(mainClass); 53 } 54 55 public static class Launcher { 56 private final String mainClass; 57 private final List<String> options = new LinkedList<>(); 58 private String transport = "dt_socket"; 59 private String address = null; 60 private boolean suspended = true; 61 boolean addTestVmAndJavaOptions = true; 62 63 private Launcher(String mainClass) { 64 this.mainClass = mainClass; 65 } 66 public Launcher addOption(String option) { 67 options.add(option); 68 return this; 69 } 70 public Launcher addOptions(List<String> options) { 71 this.options.addAll(options); 72 return this; 73 } 74 // default is "dt_socket" 75 public Launcher setTransport(String value) { 76 transport = value; 77 return this; 78 } 79 // default is "null" (auto-generate) 80 public Launcher setAddress(String value) { 81 address = value; 82 return this; 83 } 84 // default is "true" 85 public Launcher setSuspended(boolean value) { 86 suspended = value; 87 return this; 88 } 89 // default is "true" 90 public Launcher addTestVmAndJavaOptions(boolean value) { 91 addTestVmAndJavaOptions = value; 92 return this; 93 } 94 95 public ProcessBuilder prepare() { 96 List<String> debuggeeArgs = new LinkedList<>(); 97 debuggeeArgs.add("-agentlib:jdwp=transport=" + transport 98 + (address == null ? "" : ",address=" + address) 99 + ",server=y,suspend=" + (suspended ? "y" : "n")); 100 debuggeeArgs.addAll(options); 101 debuggeeArgs.add(mainClass); 102 return ProcessTools.createJavaProcessBuilder(addTestVmAndJavaOptions, 103 debuggeeArgs.toArray(new String[0])); 104 } 105 106 public Debuggee launch(String name) { 107 return new Debuggee(prepare(), name); 108 } 109 public Debuggee launch() { 110 return launch("debuggee"); 111 } 112 } 113 114 // starts the process, waits for "Listening for transport" output and detects transport/address 115 private Debuggee(ProcessBuilder pb, String name) { 116 // debuggeeListen[0] - transport, debuggeeListen[1] - address 117 String[] debuggeeListen = new String[2]; 118 Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(.+)\\b"); 119 try { 120 p = ProcessTools.startProcess(name, pb, 121 s -> output.add(s), // output consumer 122 s -> { // warm-up predicate 123 Matcher m = listenRegexp.matcher(s); 124 if (!m.matches()) { 125 return false; 126 } 127 debuggeeListen[0] = m.group(1); 128 debuggeeListen[1] = m.group(2); 129 return true; 130 }, 131 30, TimeUnit.SECONDS); 132 transport = debuggeeListen[0]; 133 address = debuggeeListen[1]; 134 } catch (IOException | InterruptedException | TimeoutException ex) { 135 throw new RuntimeException("failed to launch debuggee", ex); 136 } 137 } 138 139 private final Process p; 140 private final List<String> output = new LinkedList<>(); 141 private final String transport; 142 private final String address; 143 144 public void shutdown() { 145 try { 146 close(); 147 } catch (IOException ex) { 148 // ignore 149 } 150 } 151 152 // waits until the process shutdown or crash 153 public boolean waitFor(long timeout, TimeUnit unit) { 154 try { 155 return p.waitFor(Utils.adjustTimeout(timeout), unit); 156 } catch (InterruptedException e) { 157 return false; 158 } 159 } 160 161 Process getProcess() { 162 return p; 163 } 164 165 // returns the whole debuggee output as a string 166 public String getOutput() { 167 return output.stream().collect(Collectors.joining(Utils.NEW_LINE)); 168 } 169 170 String getTransport() { 171 return transport; 172 } 173 174 public String getAddress() { 175 return address; 176 } 177 178 // helper for dt_socket transport 179 public int getPort() { 180 return Integer.parseInt(address); 181 } 182 183 @Override 184 public void close() throws IOException { 185 if (p.isAlive()) { 186 p.destroy(); 187 } 188 } 189 190 }