1 /* 2 * Copyright (c) 2005, 2017, 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.nio.charset.StandardCharsets; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.nio.file.Files; 39 40 /* 41 * Linux implementation of HotSpotVirtualMachine 42 */ 43 public class VirtualMachineImpl extends HotSpotVirtualMachine { 44 // "/tmp" is used as a global well-known location for the files 45 // .java_pid<pid>. and .attach_pid<pid>. It is important that this 46 // location is the same for all processes, otherwise the tools 47 // will not be able to find all Hotspot processes. 48 // Any changes to this needs to be synchronized with HotSpot. 49 private static final String tmpdir = "/tmp"; 50 String socket_name; 51 /** 52 * Attaches to the target VM 53 */ 54 VirtualMachineImpl(AttachProvider provider, String vmid) 55 throws AttachNotSupportedException, IOException 56 { 57 super(provider, vmid); 58 59 // This provider only understands 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 // Try to resolve to the "inner most" pid namespace 68 int ns_pid = getNamespacePid(pid); 69 70 // Find the socket file. If not found then we attempt to start the 71 // attach mechanism in the target VM by sending it a QUIT signal. 72 // Then we attempt to find the socket file again. 73 File path = findSocketFile(pid, ns_pid); 74 socket_name = path.getPath(); 75 if (!path.exists()) { 76 File f = createAttachFile(pid, ns_pid); 77 try { 78 sendQuitTo(pid); 79 80 // give the target VM time to start the attach mechanism 81 final int delay_step = 100; 82 final long timeout = attachTimeout(); 83 long time_spend = 0; 84 long delay = 0; 85 do { 86 // Increase timeout on each attempt to reduce polling 87 delay += delay_step; 88 try { 89 Thread.sleep(delay); 90 } catch (InterruptedException x) { } 91 92 time_spend += delay; 93 if (time_spend > timeout/2 && !path.exists()) { 94 // Send QUIT again to give target VM the last chance to react 95 sendQuitTo(pid); 96 } 97 } while (time_spend <= timeout && !path.exists()); 98 if (!path.exists()) { 99 throw new AttachNotSupportedException( 100 String.format("Unable to open socket file %s: " + 101 "target process %d doesn't respond within %dms " + 102 "or HotSpot VM not loaded", socket_name, pid, 103 time_spend)); 104 } 105 } finally { 106 f.delete(); 107 } 108 } 109 110 // Check that the file owner/permission to avoid attaching to 111 // bogus process 112 checkPermissions(socket_name); 113 114 // Check that we can connect to the process 115 // - this ensures we throw the permission denied error now rather than 116 // later when we attempt to enqueue a command. 117 int s = socket(); 118 try { 119 connect(s, socket_name); 120 } finally { 121 close(s); 122 } 123 } 124 125 /** 126 * Detach from the target VM 127 */ 128 public void detach() throws IOException { 129 } 130 131 // protocol version 132 private final static String PROTOCOL_VERSION = "1"; 133 134 // known errors 135 private final static int ATTACH_ERROR_BADVERSION = 101; 136 137 /** 138 * Execute the given command in the target VM. 139 */ 140 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { 141 assert args.length <= 3; // includes null 142 143 // create UNIX socket 144 int s = socket(); 145 146 // connect to target VM 147 try { 148 connect(s, socket_name); 149 } catch (IOException x) { 150 close(s); 151 throw x; 152 } 153 154 IOException ioe = null; 155 156 // connected - write request 157 // <ver> <cmd> <args...> 158 try { 159 writeString(s, PROTOCOL_VERSION); 160 writeString(s, cmd); 161 162 for (int i=0; i<3; i++) { 163 if (i < args.length && args[i] != null) { 164 writeString(s, (String)args[i]); 165 } else { 166 writeString(s, ""); 167 } 168 } 169 } catch (IOException x) { 170 ioe = x; 171 } 172 173 174 // Create an input stream to read reply 175 SocketInputStream sis = new SocketInputStream(s); 176 177 // Read the command completion status 178 int completionStatus; 179 try { 180 completionStatus = readInt(sis); 181 } catch (IOException x) { 182 sis.close(); 183 if (ioe != null) { 184 throw ioe; 185 } else { 186 throw x; 187 } 188 } 189 190 if (completionStatus != 0) { 191 // read from the stream and use that as the error message 192 String message = readErrorMessage(sis); 193 sis.close(); 194 195 // In the event of a protocol mismatch then the target VM 196 // returns a known error so that we can throw a reasonable 197 // error. 198 if (completionStatus == ATTACH_ERROR_BADVERSION) { 199 throw new IOException("Protocol mismatch with target VM"); 200 } 201 202 // Special-case the "load" command so that the right exception is 203 // thrown. 204 if (cmd.equals("load")) { 205 String msg = "Failed to load agent library"; 206 if (!message.isEmpty()) 207 msg += ": " + message; 208 throw new AgentLoadException(msg); 209 } else { 210 if (message.isEmpty()) 211 message = "Command failed in target VM"; 212 throw new AttachOperationFailedException(message); 213 } 214 } 215 216 // Return the input stream so that the command output can be read 217 return sis; 218 } 219 220 /* 221 * InputStream for the socket connection to get target VM 222 */ 223 private class SocketInputStream extends InputStream { 224 int s; 225 226 public SocketInputStream(int s) { 227 this.s = s; 228 } 229 230 public synchronized int read() throws IOException { 231 byte b[] = new byte[1]; 232 int n = this.read(b, 0, 1); 233 if (n == 1) { 234 return b[0] & 0xff; 235 } else { 236 return -1; 237 } 238 } 239 240 public synchronized int read(byte[] bs, int off, int len) throws IOException { 241 if ((off < 0) || (off > bs.length) || (len < 0) || 242 ((off + len) > bs.length) || ((off + len) < 0)) { 243 throw new IndexOutOfBoundsException(); 244 } else if (len == 0) 245 return 0; 246 247 return VirtualMachineImpl.read(s, bs, off, len); 248 } 249 250 public void close() throws IOException { 251 VirtualMachineImpl.close(s); 252 } 253 } 254 255 // Return the socket file for the given process. 256 private File findSocketFile(int pid, int ns_pid) { 257 // A process may not exist in the same mount namespace as the caller. 258 // Instead, attach relative to the target root filesystem as exposed by 259 // procfs regardless of namespaces. 260 String root = "/proc/" + pid + "/root/" + tmpdir; 261 return new File(root, ".java_pid" + ns_pid); 262 } 263 264 // On Solaris/Linux a simple handshake is used to start the attach mechanism 265 // if not already started. The client creates a .attach_pid<pid> file in the 266 // target VM's working directory (or temp directory), and the SIGQUIT handler 267 // checks for the file. 268 private File createAttachFile(int pid, int ns_pid) throws IOException { 269 String fn = ".attach_pid" + ns_pid; 270 String path = "/proc/" + pid + "/cwd/" + fn; 271 File f = new File(path); 272 try { 273 f.createNewFile(); 274 } catch (IOException x) { 275 String root; 276 if (pid != ns_pid) { 277 // A process may not exist in the same mount namespace as the caller. 278 // Instead, attach relative to the target root filesystem as exposed by 279 // procfs regardless of namespaces. 280 root = "/proc/" + pid + "/root/" + tmpdir; 281 } else { 282 root = tmpdir; 283 } 284 f = new File(root, fn); 285 f.createNewFile(); 286 } 287 return f; 288 } 289 290 /* 291 * Write/sends the given to the target VM. String is transmitted in 292 * UTF-8 encoding. 293 */ 294 private void writeString(int fd, String s) throws IOException { 295 if (s.length() > 0) { 296 byte b[]; 297 try { 298 b = s.getBytes("UTF-8"); 299 } catch (java.io.UnsupportedEncodingException x) { 300 throw new InternalError(x); 301 } 302 VirtualMachineImpl.write(fd, b, 0, b.length); 303 } 304 byte b[] = new byte[1]; 305 b[0] = 0; 306 write(fd, b, 0, 1); 307 } 308 309 310 // Return the inner most namespaced PID if there is one, 311 // otherwise return the original PID. 312 private int getNamespacePid(int pid) throws AttachNotSupportedException, IOException { 313 // Assuming a real procfs sits beneath, reading this doesn't block 314 // nor will it consume a lot of memory. 315 String statusFile = "/proc/" + pid + "/status"; 316 File f = new File(statusFile); 317 if (!f.exists()) { 318 return pid; // Likely a bad pid, but this is properly handled later. 319 } 320 321 Path statusPath = Paths.get(statusFile); 322 323 try { 324 for (String line : Files.readAllLines(statusPath, StandardCharsets.UTF_8)) { 325 String[] parts = line.split(":"); 326 if (parts.length == 2 && parts[0].trim().equals("NSpid")) { 327 parts = parts[1].trim().split("\\s+"); 328 // The last entry represents the PID the JVM "thinks" it is. 329 // Even in non-namespaced pids these entries should be 330 // valid. You could refer to it as the inner most pid. 331 int ns_pid = Integer.parseInt(parts[parts.length - 1]); 332 return ns_pid; 333 } 334 } 335 // Old kernels may not have NSpid field (i.e. 3.10). 336 // Fallback to original pid in the event we cannot deduce. 337 return pid; 338 } catch (NumberFormatException | IOException x) { 339 throw new AttachNotSupportedException("Unable to parse namespace"); 340 } 341 } 342 343 344 //-- native methods 345 346 static native void sendQuitToChildrenOf(int pid) throws IOException; 347 348 static native void sendQuitTo(int pid) throws IOException; 349 350 static native void checkPermissions(String path) throws IOException; 351 352 static native int socket() throws IOException; 353 354 static native void connect(int fd, String path) throws IOException; 355 356 static native void close(int fd) throws IOException; 357 358 static native int read(int fd, byte buf[], int off, int bufLen) throws IOException; 359 360 static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; 361 362 static { 363 System.loadLibrary("attach"); 364 } 365 }