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