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