1 /* 2 * Copyright (c) 2005, 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. 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 */ 25 package sun.tools.attach; 26 27 import com.sun.tools.attach.AttachOperationFailedException; 28 import com.sun.tools.attach.AgentLoadException; 29 import com.sun.tools.attach.AttachNotSupportedException; 30 import com.sun.tools.attach.spi.AttachProvider; 31 32 import java.io.InputStream; 33 import java.io.IOException; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 37 /* 38 * Solaris implementation of HotSpotVirtualMachine. 39 */ 40 public class VirtualMachineImpl extends HotSpotVirtualMachine { 41 // "/tmp" is used as a global well-known location for the files 42 // .java_pid<pid>. and .attach_pid<pid>. It is important that this 43 // location is the same for all processes, otherwise the tools 44 // will not be able to find all Hotspot processes. 45 // Any changes to this needs to be synchronized with HotSpot. 46 private static final String tmpdir = "/tmp"; 47 48 // door descriptor; 49 private int fd = -1; 50 String socket_path; 51 52 /** 53 * Attaches to the target VM 54 */ VirtualMachineImpl(AttachProvider provider, String vmid)55 VirtualMachineImpl(AttachProvider provider, String vmid) 56 throws AttachNotSupportedException, IOException 57 { 58 super(provider, vmid); 59 // This provider only understands process-ids (pids). 60 int pid; 61 try { 62 pid = Integer.parseInt(vmid); 63 } catch (NumberFormatException x) { 64 throw new AttachNotSupportedException("Invalid process identifier"); 65 } 66 67 // Opens the door file to the target VM. If the file is not 68 // found it might mean that the attach mechanism isn't started in the 69 // target VM so we attempt to start it and retry. 70 try { 71 fd = openDoor(pid); 72 } catch (FileNotFoundException fnf1) { 73 File f = createAttachFile(pid); 74 try { 75 sigquit(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 try { 89 fd = openDoor(pid); 90 } catch (FileNotFoundException fnf2) { 91 // pass 92 } 93 94 time_spend += delay; 95 if (time_spend > timeout/2 && fd == -1) { 96 // Send QUIT again to give target VM the last chance to react 97 sigquit(pid); 98 } 99 } while (time_spend <= timeout && fd == -1); 100 if (fd == -1) { 101 throw new AttachNotSupportedException( 102 String.format("Unable to open door %s: " + 103 "target process %d doesn't respond within %dms " + 104 "or HotSpot VM not loaded", socket_path, pid, time_spend)); 105 } 106 } finally { 107 f.delete(); 108 } 109 } 110 assert fd >= 0; 111 } 112 113 /** 114 * Detach from the target VM 115 */ detach()116 public void detach() throws IOException { 117 synchronized (this) { 118 if (fd != -1) { 119 close(fd); 120 fd = -1; 121 } 122 } 123 } 124 125 /** 126 * Execute the given command in the target VM. 127 */ execute(String cmd, Object ... args)128 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { 129 assert args.length <= 3; // includes null 130 131 // first check that we are still attached 132 int door; 133 synchronized (this) { 134 if (fd == -1) { 135 throw new IOException("Detached from target VM"); 136 } 137 door = fd; 138 } 139 140 // enqueue the command via a door call 141 int s = enqueue(door, cmd, args); 142 assert s >= 0; // valid file descriptor 143 144 // The door call returns a file descriptor (one end of a socket pair). 145 // Create an input stream around it. 146 SocketInputStream sis = new SocketInputStream(s); 147 148 // Read the command completion status 149 int completionStatus; 150 try { 151 completionStatus = readInt(sis); 152 } catch (IOException ioe) { 153 sis.close(); 154 throw ioe; 155 } 156 157 // If non-0 it means an error but we need to special-case the 158 // "load" command to ensure that the right exception is thrown. 159 if (completionStatus != 0) { 160 // read from the stream and use that as the error message 161 String message = readErrorMessage(sis); 162 sis.close(); 163 if (cmd.equals("load")) { 164 String msg = "Failed to load agent library"; 165 if (!message.isEmpty()) 166 msg += ": " + message; 167 throw new AgentLoadException(msg); 168 } else { 169 if (message.isEmpty()) 170 message = "Command failed in target VM"; 171 throw new AttachOperationFailedException(message); 172 } 173 } 174 175 // Return the input stream so that the command output can be read 176 return sis; 177 } 178 179 // InputStream over a socket 180 private class SocketInputStream extends InputStream { 181 int s; 182 SocketInputStream(int s)183 public SocketInputStream(int s) { 184 this.s = s; 185 } 186 read()187 public synchronized int read() throws IOException { 188 byte b[] = new byte[1]; 189 int n = this.read(b, 0, 1); 190 if (n == 1) { 191 return b[0] & 0xff; 192 } else { 193 return -1; 194 } 195 } 196 read(byte[] bs, int off, int len)197 public synchronized int read(byte[] bs, int off, int len) throws IOException { 198 if ((off < 0) || (off > bs.length) || (len < 0) || 199 ((off + len) > bs.length) || ((off + len) < 0)) { 200 throw new IndexOutOfBoundsException(); 201 } else if (len == 0) 202 return 0; 203 204 return VirtualMachineImpl.read(s, bs, off, len); 205 } 206 close()207 public void close() throws IOException { 208 VirtualMachineImpl.close(s); 209 } 210 } 211 212 // The door is attached to .java_pid<pid> in the temporary directory. openDoor(int pid)213 private int openDoor(int pid) throws IOException { 214 socket_path = tmpdir + "/.java_pid" + pid; 215 fd = open(socket_path); 216 217 // Check that the file owner/permission to avoid attaching to 218 // bogus process 219 try { 220 checkPermissions(socket_path); 221 } catch (IOException ioe) { 222 close(fd); 223 throw ioe; 224 } 225 return fd; 226 } 227 228 // On Solaris a simple handshake is used to start the attach mechanism 229 // if not already started. The client creates a .attach_pid<pid> file in the 230 // target VM's working directory (or temporary directory), and the SIGQUIT 231 // handler checks for the file. createAttachFile(int pid)232 private File createAttachFile(int pid) throws IOException { 233 String fn = ".attach_pid" + pid; 234 String path = "/proc/" + pid + "/cwd/" + fn; 235 File f = new File(path); 236 try { 237 f.createNewFile(); 238 } catch (IOException x) { 239 f = new File(tmpdir, fn); 240 f.createNewFile(); 241 } 242 return f; 243 } 244 245 //-- native methods 246 open(String path)247 static native int open(String path) throws IOException; 248 close(int fd)249 static native void close(int fd) throws IOException; 250 read(int fd, byte buf[], int off, int buflen)251 static native int read(int fd, byte buf[], int off, int buflen) throws IOException; 252 checkPermissions(String path)253 static native void checkPermissions(String path) throws IOException; 254 sigquit(int pid)255 static native void sigquit(int pid) throws IOException; 256 257 // enqueue a command (and arguments) to the given door enqueue(int fd, String cmd, Object ... args)258 static native int enqueue(int fd, String cmd, Object ... args) 259 throws IOException; 260 261 static { 262 System.loadLibrary("attach"); 263 } 264 } 265