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