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