1 /* 2 * Copyright (c) 2000, 2011, 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 25 package sun.jvm.hotspot; 26 27 import java.io.PrintStream; 28 import java.net.*; 29 import java.rmi.*; 30 import sun.jvm.hotspot.debugger.*; 31 import sun.jvm.hotspot.debugger.proc.*; 32 import sun.jvm.hotspot.debugger.remote.*; 33 import sun.jvm.hotspot.debugger.windbg.*; 34 import sun.jvm.hotspot.debugger.linux.*; 35 import sun.jvm.hotspot.memory.*; 36 import sun.jvm.hotspot.oops.*; 37 import sun.jvm.hotspot.runtime.*; 38 import sun.jvm.hotspot.types.*; 39 import sun.jvm.hotspot.utilities.*; 40 41 /** <P> This class wraps much of the basic functionality and is the 42 * highest-level factory for VM data structures. It makes it simple 43 * to start up the debugging system. </P> 44 * 45 * <P> FIXME: need to add a way to configure the paths to dbx and the 46 * DSO from the outside. However, this should work for now for 47 * internal use. </P> 48 * 49 * <P> FIXME: especially with the addition of remote debugging, this 50 * has turned into a mess; needs rethinking. </P> 51 */ 52 53 public class HotSpotAgent { 54 private JVMDebugger debugger; 55 private MachineDescription machDesc; 56 private TypeDataBase db; 57 58 private String os; 59 private String cpu; 60 private String fileSep; 61 62 // The system can work in several ways: 63 // - Attaching to local process 64 // - Attaching to local core file 65 // - Connecting to remote debug server 66 // - Starting debug server for process 67 // - Starting debug server for core file 68 69 // These are options for the "client" side of things 70 private static final int PROCESS_MODE = 0; 71 private static final int CORE_FILE_MODE = 1; 72 private static final int REMOTE_MODE = 2; 73 private int startupMode; 74 75 // This indicates whether we are really starting a server or not 76 private boolean isServer; 77 78 // All possible required information for connecting 79 private int pid; 80 private String javaExecutableName; 81 private String coreFileName; 82 private String debugServerID; 83 84 // All needed information for server side 85 private String serverID; 86 87 private String[] jvmLibNames; 88 89 // FIXME: make these configurable, i.e., via a dotfile; also 90 // consider searching within the JDK from which this Java executable 91 // comes to find them 92 private static final String defaultDbxPathPrefix = "/net/jano.sfbay/export/disk05/hotspot/sa"; 93 private static final String defaultDbxSvcAgentDSOPathPrefix = "/net/jano.sfbay/export/disk05/hotspot/sa"; 94 95 static void showUsage() { 96 System.out.println(" You can also pass these -D options to java to specify where to find dbx and the \n" + 97 " Serviceability Agent plugin for dbx:"); 98 System.out.println(" -DdbxPathName=<path-to-dbx-executable>\n" + 99 " Default is derived from dbxPathPrefix"); 100 System.out.println(" or"); 101 System.out.println(" -DdbxPathPrefix=<xxx>\n" + 102 " where xxx is the path name of a dir structure that contains:\n" + 103 " <os>/<arch>/bin/dbx\n" + 104 " The default is " + defaultDbxPathPrefix); 105 System.out.println(" and"); 106 System.out.println(" -DdbxSvcAgentDSOPathName=<path-to-dbx-serviceability-agent-module>\n" + 107 " Default is determined from dbxSvcAgentDSOPathPrefix"); 108 System.out.println(" or"); 109 System.out.println(" -DdbxSvcAgentDSOPathPrefix=<xxx>\n" + 110 " where xxx is the pathname of a dir structure that contains:\n" + 111 " <os>/<arch>/bin/lib/libsvc_agent_dbx.so\n" + 112 " The default is " + defaultDbxSvcAgentDSOPathPrefix); 113 } 114 115 public HotSpotAgent() { 116 // for non-server add shutdown hook to clean-up debugger in case 117 // of forced exit. For remote server, shutdown hook is added by 118 // DebugServer. 119 Runtime.getRuntime().addShutdownHook(new java.lang.Thread( 120 new Runnable() { 121 public void run() { 122 synchronized (HotSpotAgent.this) { 123 if (!isServer) { 124 detach(); 125 } 126 } 127 } 128 })); 129 } 130 131 //-------------------------------------------------------------------------------- 132 // Accessors (once the system is set up) 133 // 134 135 public synchronized Debugger getDebugger() { 136 return debugger; 137 } 138 139 public synchronized TypeDataBase getTypeDataBase() { 140 return db; 141 } 142 143 //-------------------------------------------------------------------------------- 144 // Client-side operations 145 // 146 147 /** This attaches to a process running on the local machine. */ 148 public synchronized void attach(int processID) 149 throws DebuggerException { 150 if (debugger != null) { 151 throw new DebuggerException("Already attached"); 152 } 153 pid = processID; 154 startupMode = PROCESS_MODE; 155 isServer = false; 156 go(); 157 } 158 159 /** This opens a core file on the local machine */ 160 public synchronized void attach(String javaExecutableName, String coreFileName) 161 throws DebuggerException { 162 if (debugger != null) { 163 throw new DebuggerException("Already attached"); 164 } 165 if ((javaExecutableName == null) || (coreFileName == null)) { 166 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 167 } 168 this.javaExecutableName = javaExecutableName; 169 this.coreFileName = coreFileName; 170 startupMode = CORE_FILE_MODE; 171 isServer = false; 172 go(); 173 } 174 175 /** This attaches to a "debug server" on a remote machine; this 176 remote server has already attached to a process or opened a 177 core file and is waiting for RMI calls on the Debugger object to 178 come in. */ 179 public synchronized void attach(String remoteServerID) 180 throws DebuggerException { 181 if (debugger != null) { 182 throw new DebuggerException("Already attached to a process"); 183 } 184 if (remoteServerID == null) { 185 throw new DebuggerException("Debug server id must be specified"); 186 } 187 188 debugServerID = remoteServerID; 189 startupMode = REMOTE_MODE; 190 isServer = false; 191 go(); 192 } 193 194 /** This should only be called by the user on the client machine, 195 not the server machine */ 196 public synchronized boolean detach() throws DebuggerException { 197 if (isServer) { 198 throw new DebuggerException("Should not call detach() for server configuration"); 199 } 200 return detachInternal(); 201 } 202 203 //-------------------------------------------------------------------------------- 204 // Server-side operations 205 // 206 207 /** This attaches to a process running on the local machine and 208 starts a debug server, allowing remote machines to connect and 209 examine this process. Uses specified name to uniquely identify a 210 specific debuggee on the server */ 211 public synchronized void startServer(int processID, String uniqueID) { 212 if (debugger != null) { 213 throw new DebuggerException("Already attached"); 214 } 215 pid = processID; 216 startupMode = PROCESS_MODE; 217 isServer = true; 218 serverID = uniqueID; 219 go(); 220 } 221 222 /** This attaches to a process running on the local machine and 223 starts a debug server, allowing remote machines to connect and 224 examine this process. */ 225 public synchronized void startServer(int processID) 226 throws DebuggerException { 227 startServer(processID, null); 228 } 229 230 /** This opens a core file on the local machine and starts a debug 231 server, allowing remote machines to connect and examine this 232 core file. Uses supplied uniqueID to uniquely identify a specific 233 debugee */ 234 public synchronized void startServer(String javaExecutableName, 235 String coreFileName, 236 String uniqueID) { 237 if (debugger != null) { 238 throw new DebuggerException("Already attached"); 239 } 240 if ((javaExecutableName == null) || (coreFileName == null)) { 241 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 242 } 243 this.javaExecutableName = javaExecutableName; 244 this.coreFileName = coreFileName; 245 startupMode = CORE_FILE_MODE; 246 isServer = true; 247 serverID = uniqueID; 248 go(); 249 } 250 251 /** This opens a core file on the local machine and starts a debug 252 server, allowing remote machines to connect and examine this 253 core file. */ 254 public synchronized void startServer(String javaExecutableName, String coreFileName) 255 throws DebuggerException { 256 startServer(javaExecutableName, coreFileName, null); 257 } 258 259 /** This may only be called on the server side after startServer() 260 has been called */ 261 public synchronized boolean shutdownServer() throws DebuggerException { 262 if (!isServer) { 263 throw new DebuggerException("Should not call shutdownServer() for client configuration"); 264 } 265 return detachInternal(); 266 } 267 268 269 //-------------------------------------------------------------------------------- 270 // Internals only below this point 271 // 272 273 private boolean detachInternal() { 274 if (debugger == null) { 275 return false; 276 } 277 boolean retval = true; 278 if (!isServer) { 279 VM.shutdown(); 280 } 281 // We must not call detach() if we are a client and are connected 282 // to a remote debugger 283 Debugger dbg = null; 284 DebuggerException ex = null; 285 if (isServer) { 286 try { 287 RMIHelper.unbind(serverID); 288 } 289 catch (DebuggerException de) { 290 ex = de; 291 } 292 dbg = debugger; 293 } else { 294 if (startupMode != REMOTE_MODE) { 295 dbg = debugger; 296 } 297 } 298 if (dbg != null) { 299 retval = dbg.detach(); 300 } 301 302 debugger = null; 303 machDesc = null; 304 db = null; 305 if (ex != null) { 306 throw(ex); 307 } 308 return retval; 309 } 310 311 private void go() { 312 setupDebugger(); 313 setupVM(); 314 } 315 316 private void setupDebugger() { 317 if (startupMode != REMOTE_MODE) { 318 // 319 // Local mode (client attaching to local process or setting up 320 // server, but not client attaching to server) 321 // 322 323 try { 324 os = PlatformInfo.getOS(); 325 cpu = PlatformInfo.getCPU(); 326 } 327 catch (UnsupportedPlatformException e) { 328 throw new DebuggerException(e); 329 } 330 fileSep = System.getProperty("file.separator"); 331 332 if (os.equals("solaris")) { 333 setupDebuggerSolaris(); 334 } else if (os.equals("win32")) { 335 setupDebuggerWin32(); 336 } else if (os.equals("linux")) { 337 setupDebuggerLinux(); 338 } else { 339 // Add support for more operating systems here 340 throw new DebuggerException("Operating system " + os + " not yet supported"); 341 } 342 343 if (isServer) { 344 RemoteDebuggerServer remote = null; 345 try { 346 remote = new RemoteDebuggerServer(debugger); 347 } 348 catch (RemoteException rem) { 349 throw new DebuggerException(rem); 350 } 351 RMIHelper.rebind(serverID, remote); 352 } 353 } else { 354 // 355 // Remote mode (client attaching to server) 356 // 357 358 // Create and install a security manager 359 360 // FIXME: currently commented out because we were having 361 // security problems since we're "in the sun.* hierarchy" here. 362 // Perhaps a permissive policy file would work around this. In 363 // the long run, will probably have to move into com.sun.*. 364 365 // if (System.getSecurityManager() == null) { 366 // System.setSecurityManager(new RMISecurityManager()); 367 // } 368 369 connectRemoteDebugger(); 370 } 371 } 372 373 private void setupVM() { 374 // We need to instantiate a HotSpotTypeDataBase on both the client 375 // and server machine. On the server it is only currently used to 376 // configure the Java primitive type sizes (which we should 377 // consider making constant). On the client it is used to 378 // configure the VM. 379 380 try { 381 if (os.equals("solaris")) { 382 db = new HotSpotTypeDataBase(machDesc, 383 new HotSpotSolarisVtblAccess(debugger, jvmLibNames), 384 debugger, jvmLibNames); 385 } else if (os.equals("win32")) { 386 db = new HotSpotTypeDataBase(machDesc, 387 new Win32VtblAccess(debugger, jvmLibNames), 388 debugger, jvmLibNames); 389 } else if (os.equals("linux")) { 390 db = new HotSpotTypeDataBase(machDesc, 391 new LinuxVtblAccess(debugger, jvmLibNames), 392 debugger, jvmLibNames); 393 } else { 394 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 395 } 396 } 397 catch (NoSuchSymbolException e) { 398 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 399 e.getSymbol() + "\" in remote process)"); 400 } 401 402 if (startupMode != REMOTE_MODE) { 403 // Configure the debugger with the primitive type sizes just obtained from the VM 404 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 405 db.getJByteType().getSize(), 406 db.getJCharType().getSize(), 407 db.getJDoubleType().getSize(), 408 db.getJFloatType().getSize(), 409 db.getJIntType().getSize(), 410 db.getJLongType().getSize(), 411 db.getJShortType().getSize()); 412 } 413 414 if (!isServer) { 415 // Do not initialize the VM on the server (unnecessary, since it's 416 // instantiated on the client) 417 try { 418 VM.initialize(db, debugger); 419 } catch (DebuggerException e) { 420 throw (e); 421 } catch (Exception e) { 422 throw new DebuggerException(e); 423 } 424 } 425 } 426 427 //-------------------------------------------------------------------------------- 428 // OS-specific debugger setup/connect routines 429 // 430 431 // 432 // Solaris 433 // 434 435 private void setupDebuggerSolaris() { 436 setupJVMLibNamesSolaris(); 437 ProcDebuggerLocal dbg = new ProcDebuggerLocal(null, true); 438 debugger = dbg; 439 attachDebugger(); 440 441 // Set up CPU-dependent stuff 442 if (cpu.equals("x86")) { 443 machDesc = new MachineDescriptionIntelX86(); 444 } else if (cpu.equals("sparc")) { 445 int addressSize = dbg.getRemoteProcessAddressSize(); 446 if (addressSize == -1) { 447 throw new DebuggerException("Error occurred while trying to determine the remote process's " + 448 "address size"); 449 } 450 451 if (addressSize == 32) { 452 machDesc = new MachineDescriptionSPARC32Bit(); 453 } else if (addressSize == 64) { 454 machDesc = new MachineDescriptionSPARC64Bit(); 455 } else { 456 throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC"); 457 } 458 } else if (cpu.equals("amd64")) { 459 machDesc = new MachineDescriptionAMD64(); 460 } else { 461 throw new DebuggerException("Solaris only supported on sparc/sparcv9/x86/amd64"); 462 } 463 464 dbg.setMachineDescription(machDesc); 465 return; 466 } 467 468 private void connectRemoteDebugger() throws DebuggerException { 469 RemoteDebugger remote = 470 (RemoteDebugger) RMIHelper.lookup(debugServerID); 471 debugger = new RemoteDebuggerClient(remote); 472 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 473 os = debugger.getOS(); 474 if (os.equals("solaris")) { 475 setupJVMLibNamesSolaris(); 476 } else if (os.equals("win32")) { 477 setupJVMLibNamesWin32(); 478 } else if (os.equals("linux")) { 479 setupJVMLibNamesLinux(); 480 } else { 481 throw new RuntimeException("Unknown OS type"); 482 } 483 484 cpu = debugger.getCPU(); 485 } 486 487 private void setupJVMLibNamesSolaris() { 488 jvmLibNames = new String[] { "libjvm.so", "libjvm_g.so", "gamma_g" }; 489 } 490 491 // 492 // Win32 493 // 494 495 private void setupDebuggerWin32() { 496 setupJVMLibNamesWin32(); 497 498 if (cpu.equals("x86")) { 499 machDesc = new MachineDescriptionIntelX86(); 500 } else if (cpu.equals("amd64")) { 501 machDesc = new MachineDescriptionAMD64(); 502 } else if (cpu.equals("ia64")) { 503 machDesc = new MachineDescriptionIA64(); 504 } else { 505 throw new DebuggerException("Win32 supported under x86, amd64 and ia64 only"); 506 } 507 508 // Note we do not use a cache for the local debugger in server 509 // mode; it will be taken care of on the client side (once remote 510 // debugging is implemented). 511 512 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 513 514 attachDebugger(); 515 516 // FIXME: add support for server mode 517 } 518 519 private void setupJVMLibNamesWin32() { 520 jvmLibNames = new String[] { "jvm.dll", "jvm_g.dll" }; 521 } 522 523 // 524 // Linux 525 // 526 527 private void setupDebuggerLinux() { 528 setupJVMLibNamesLinux(); 529 530 if (cpu.equals("x86")) { 531 machDesc = new MachineDescriptionIntelX86(); 532 } else if (cpu.equals("ia64")) { 533 machDesc = new MachineDescriptionIA64(); 534 } else if (cpu.equals("amd64")) { 535 machDesc = new MachineDescriptionAMD64(); 536 } else if (cpu.equals("sparc")) { 537 if (LinuxDebuggerLocal.getAddressSize()==8) { 538 machDesc = new MachineDescriptionSPARC64Bit(); 539 } else { 540 machDesc = new MachineDescriptionSPARC32Bit(); 541 } 542 } else { 543 throw new DebuggerException("Linux only supported on x86/ia64/amd64/sparc/sparc64"); 544 } 545 546 LinuxDebuggerLocal dbg = 547 new LinuxDebuggerLocal(machDesc, !isServer); 548 debugger = dbg; 549 550 attachDebugger(); 551 } 552 553 private void setupJVMLibNamesLinux() { 554 jvmLibNames = new String[] { "libjvm.so", "libjvm_g.so" }; 555 } 556 557 /** Convenience routine which should be called by per-platform 558 debugger setup. Should not be called when startupMode is 559 REMOTE_MODE. */ 560 private void attachDebugger() { 561 if (startupMode == PROCESS_MODE) { 562 debugger.attach(pid); 563 } else if (startupMode == CORE_FILE_MODE) { 564 debugger.attach(javaExecutableName, coreFileName); 565 } else { 566 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 567 } 568 } 569 }