1/* 2 * Copyright (c) 2005, 2014, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package sun.tools.attach; 26 27import com.sun.tools.attach.AttachOperationFailedException; 28import com.sun.tools.attach.AgentLoadException; 29import com.sun.tools.attach.AttachNotSupportedException; 30import com.sun.tools.attach.spi.AttachProvider; 31 32import java.io.InputStream; 33import java.io.IOException; 34import java.io.File; 35 36/* 37 * Bsd implementation of HotSpotVirtualMachine 38 */ 39public class VirtualMachineImpl extends HotSpotVirtualMachine { 40 // "tmpdir" is used as a global well-known location for the files 41 // .java_pid<pid>. and .attach_pid<pid>. It is important that this 42 // location is the same for all processes, otherwise the tools 43 // will not be able to find all Hotspot processes. 44 // This is intentionally not the same as java.io.tmpdir, since 45 // the latter can be changed by the user. 46 // Any changes to this needs to be synchronized with HotSpot. 47 private static final String tmpdir; 48 49 // The patch to the socket file created by the target VM 50 String path; 51 52 /** 53 * Attaches to the target VM 54 */ 55 VirtualMachineImpl(AttachProvider provider, String vmid) 56 throws AttachNotSupportedException, IOException 57 { 58 super(provider, vmid); 59 60 // This provider only understands pids 61 int pid; 62 try { 63 pid = Integer.parseInt(vmid); 64 } catch (NumberFormatException x) { 65 throw new AttachNotSupportedException("Invalid process identifier"); 66 } 67 68 // Find the socket file. If not found then we attempt to start the 69 // attach mechanism in the target VM by sending it a QUIT signal. 70 // Then we attempt to find the socket file again. 71 path = findSocketFile(pid); 72 if (path == null) { 73 File f = createAttachFile(pid); 74 try { 75 sendQuitTo(pid); 76 77 // give the target VM time to start the attach mechanism 78 final int delay_step = 100; 79 final long timeout = attachTimeout(); 80 long time_spend = 0; 81 long delay = 0; 82 do { 83 // Increase timeout on each attempt to reduce polling 84 delay += delay_step; 85 try { 86 Thread.sleep(delay); 87 } catch (InterruptedException x) { } 88 path = findSocketFile(pid); 89 90 time_spend += delay; 91 if (time_spend > timeout/2 && path == null) { 92 // Send QUIT again to give target VM the last chance to react 93 sendQuitTo(pid); 94 } 95 } while (time_spend <= timeout && path == null); 96 if (path == null) { 97 throw new AttachNotSupportedException( 98 String.format("Unable to open socket file %s: " + 99 "target process %d doesn't respond within %dms " + 100 "or HotSpot VM not loaded", f.getPath(), pid, time_spend)); 101 } 102 } finally { 103 f.delete(); 104 } 105 } 106 107 // Check that the file owner/permission to avoid attaching to 108 // bogus process 109 checkPermissions(path); 110 111 // Check that we can connect to the process 112 // - this ensures we throw the permission denied error now rather than 113 // later when we attempt to enqueue a command. 114 int s = socket(); 115 try { 116 connect(s, path); 117 } finally { 118 close(s); 119 } 120 } 121 122 /** 123 * Detach from the target VM 124 */ 125 public void detach() throws IOException { 126 synchronized (this) { 127 if (this.path != null) { 128 this.path = null; 129 } 130 } 131 } 132 133 // protocol version 134 private final static String PROTOCOL_VERSION = "1"; 135 136 // known errors 137 private final static int ATTACH_ERROR_BADVERSION = 101; 138 139 /** 140 * Execute the given command in the target VM. 141 */ 142 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { 143 assert args.length <= 3; // includes null 144 145 // did we detach? 146 String p; 147 synchronized (this) { 148 if (this.path == null) { 149 throw new IOException("Detached from target VM"); 150 } 151 p = this.path; 152 } 153 154 // create UNIX socket 155 int s = socket(); 156 157 // connect to target VM 158 try { 159 connect(s, p); 160 } catch (IOException x) { 161 close(s); 162 throw x; 163 } 164 165 IOException ioe = null; 166 167 // connected - write request 168 // <ver> <cmd> <args...> 169 try { 170 writeString(s, PROTOCOL_VERSION); 171 writeString(s, cmd); 172 173 for (int i=0; i<3; i++) { 174 if (i < args.length && args[i] != null) { 175 writeString(s, (String)args[i]); 176 } else { 177 writeString(s, ""); 178 } 179 } 180 } catch (IOException x) { 181 ioe = x; 182 } 183 184 185 // Create an input stream to read reply 186 SocketInputStream sis = new SocketInputStream(s); 187 188 // Read the command completion status 189 int completionStatus; 190 try { 191 completionStatus = readInt(sis); 192 } catch (IOException x) { 193 sis.close(); 194 if (ioe != null) { 195 throw ioe; 196 } else { 197 throw x; 198 } 199 } 200 201 if (completionStatus != 0) { 202 // read from the stream and use that as the error message 203 String message = readErrorMessage(sis); 204 sis.close(); 205 206 // In the event of a protocol mismatch then the target VM 207 // returns a known error so that we can throw a reasonable 208 // error. 209 if (completionStatus == ATTACH_ERROR_BADVERSION) { 210 throw new IOException("Protocol mismatch with target VM"); 211 } 212 213 // Special-case the "load" command so that the right exception is 214 // thrown. 215 if (cmd.equals("load")) { 216 String msg = "Failed to load agent library"; 217 if (!message.isEmpty()) 218 msg += ": " + message; 219 throw new AgentLoadException(msg); 220 } else { 221 if (message.isEmpty()) 222 message = "Command failed in target VM"; 223 throw new AttachOperationFailedException(message); 224 } 225 } 226 227 // Return the input stream so that the command output can be read 228 return sis; 229 } 230 231 /* 232 * InputStream for the socket connection to get target VM 233 */ 234 private class SocketInputStream extends InputStream { 235 int s; 236 237 public SocketInputStream(int s) { 238 this.s = s; 239 } 240 241 public synchronized int read() throws IOException { 242 byte b[] = new byte[1]; 243 int n = this.read(b, 0, 1); 244 if (n == 1) { 245 return b[0] & 0xff; 246 } else { 247 return -1; 248 } 249 } 250 251 public synchronized int read(byte[] bs, int off, int len) throws IOException { 252 if ((off < 0) || (off > bs.length) || (len < 0) || 253 ((off + len) > bs.length) || ((off + len) < 0)) { 254 throw new IndexOutOfBoundsException(); 255 } else if (len == 0) { 256 return 0; 257 } 258 259 return VirtualMachineImpl.read(s, bs, off, len); 260 } 261 262 public void close() throws IOException { 263 VirtualMachineImpl.close(s); 264 } 265 } 266 267 // Return the socket file for the given process. 268 // Checks temp directory for .java_pid<pid>. 269 private String findSocketFile(int pid) { 270 String fn = ".java_pid" + pid; 271 File f = new File(tmpdir, fn); 272 return f.exists() ? f.getPath() : null; 273 } 274 275 /* 276 * Write/sends the given to the target VM. String is transmitted in 277 * UTF-8 encoding. 278 */ 279 private void writeString(int fd, String s) throws IOException { 280 if (s.length() > 0) { 281 byte b[]; 282 try { 283 b = s.getBytes("UTF-8"); 284 } catch (java.io.UnsupportedEncodingException x) { 285 throw new InternalError(); 286 } 287 VirtualMachineImpl.write(fd, b, 0, b.length); 288 } 289 byte b[] = new byte[1]; 290 b[0] = 0; 291 write(fd, b, 0, 1); 292 } 293 294 private File createAttachFile(int pid) throws IOException { 295 String fn = ".attach_pid" + pid; 296 File f = new File(tmpdir, fn); 297 createAttachFile0(f.getPath()); 298 return f; 299 } 300 301 //-- native methods 302 303 static native void sendQuitTo(int pid) throws IOException; 304 305 static native void checkPermissions(String path) throws IOException; 306 307 static native int socket() throws IOException; 308 309 static native void connect(int fd, String path) throws IOException; 310 311 static native void close(int fd) throws IOException; 312 313 static native int read(int fd, byte buf[], int off, int bufLen) throws IOException; 314 315 static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; 316 317 static native void createAttachFile0(String path); 318 319 static native String getTempDir(); 320 321 static { 322 System.loadLibrary("attach"); 323 tmpdir = getTempDir(); 324 } 325} 326