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