1 /* 2 * Copyright (c) 2000, 2020, 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 private int rmiPort; 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. Allows to specify the port number 205 to which the RMI connector is bound. If not specified a random 206 available port is used. */ 207 public synchronized void startServer(int processID, 208 String uniqueID, 209 int rmiPort) { 210 if (debugger != null) { 211 throw new DebuggerException("Already attached"); 212 } 213 pid = processID; 214 startupMode = PROCESS_MODE; 215 isServer = true; 216 serverID = uniqueID; 217 this.rmiPort = rmiPort; 218 go(); 219 } 220 221 /** This attaches to a process running on the local machine and 222 starts a debug server, allowing remote machines to connect and 223 examine this process. Uses specified name to uniquely identify a 224 specific debuggee on the server */ 225 public synchronized void startServer(int processID, String uniqueID) { 226 startServer(processID, uniqueID, 0); 227 } 228 229 /** This attaches to a process running on the local machine and 230 starts a debug server, allowing remote machines to connect and 231 examine this process. */ 232 public synchronized void startServer(int processID) 233 throws DebuggerException { 234 startServer(processID, null); 235 } 236 237 /** This opens a core file on the local machine and starts a debug 238 server, allowing remote machines to connect and examine this 239 core file. Uses supplied uniqueID to uniquely identify a specific 240 debuggee. Allows to specify the port number to which the RMI connector 241 is bound. If not specified a random available port is used. */ 242 public synchronized void startServer(String javaExecutableName, 243 String coreFileName, 244 String uniqueID, 245 int rmiPort) { 246 if (debugger != null) { 247 throw new DebuggerException("Already attached"); 248 } 249 if ((javaExecutableName == null) || (coreFileName == null)) { 250 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 251 } 252 this.javaExecutableName = javaExecutableName; 253 this.coreFileName = coreFileName; 254 startupMode = CORE_FILE_MODE; 255 isServer = true; 256 serverID = uniqueID; 257 this.rmiPort = rmiPort; 258 go(); 259 } 260 261 /** This opens a core file on the local machine and starts a debug 262 server, allowing remote machines to connect and examine this 263 core file. Uses supplied uniqueID to uniquely identify a specific 264 debugee */ 265 public synchronized void startServer(String javaExecutableName, 266 String coreFileName, 267 String uniqueID) { 268 startServer(javaExecutableName, coreFileName, uniqueID, 0); 269 } 270 271 /** This opens a core file on the local machine and starts a debug 272 server, allowing remote machines to connect and examine this 273 core file. */ 274 public synchronized void startServer(String javaExecutableName, String coreFileName) 275 throws DebuggerException { 276 startServer(javaExecutableName, coreFileName, null); 277 } 278 279 /** This may only be called on the server side after startServer() 280 has been called */ 281 public synchronized boolean shutdownServer() throws DebuggerException { 282 if (!isServer) { 283 throw new DebuggerException("Should not call shutdownServer() for client configuration"); 284 } 285 return detachInternal(); 286 } 287 288 289 //-------------------------------------------------------------------------------- 290 // Internals only below this point 291 // 292 293 private boolean detachInternal() { 294 if (debugger == null) { 295 return false; 296 } 297 boolean retval = true; 298 if (!isServer) { 299 VM.shutdown(); 300 } 301 // We must not call detach() if we are a client and are connected 302 // to a remote debugger 303 Debugger dbg = null; 304 DebuggerException ex = null; 305 if (isServer) { 306 try { 307 RMIHelper.unbind(serverID); 308 } 309 catch (DebuggerException de) { 310 ex = de; 311 } 312 dbg = debugger; 313 } else { 314 if (startupMode != REMOTE_MODE) { 315 dbg = debugger; 316 } 317 } 318 if (dbg != null) { 319 retval = dbg.detach(); 320 } 321 322 debugger = null; 323 machDesc = null; 324 db = null; 325 if (ex != null) { 326 throw(ex); 327 } 328 return retval; 329 } 330 331 private void go() { 332 setupDebugger(); 333 setupVM(); 334 } 335 336 private void setupDebugger() { 337 if (startupMode != REMOTE_MODE) { 338 // 339 // Local mode (client attaching to local process or setting up 340 // server, but not client attaching to server) 341 // 342 343 // Handle existing or alternate JVMDebugger: 344 // these will set os, cpu independently of our PlatformInfo implementation. 345 String alternateDebugger = System.getProperty("sa.altDebugger"); 346 if (debugger != null) { 347 setupDebuggerExisting(); 348 349 } else if (alternateDebugger != null) { 350 setupDebuggerAlternate(alternateDebugger); 351 352 } else { 353 // Otherwise, os, cpu are those of our current platform: 354 try { 355 os = PlatformInfo.getOS(); 356 cpu = PlatformInfo.getCPU(); 357 } catch (UnsupportedPlatformException e) { 358 throw new DebuggerException(e); 359 } 360 if (os.equals("solaris")) { 361 setupDebuggerSolaris(); 362 } else if (os.equals("win32")) { 363 setupDebuggerWin32(); 364 } else if (os.equals("linux")) { 365 setupDebuggerLinux(); 366 } else if (os.equals("bsd")) { 367 setupDebuggerBsd(); 368 } else if (os.equals("darwin")) { 369 setupDebuggerDarwin(); 370 } else { 371 // Add support for more operating systems here 372 throw new DebuggerException("Operating system " + os + " not yet supported"); 373 } 374 } 375 376 if (isServer) { 377 RemoteDebuggerServer remote = null; 378 try { 379 remote = new RemoteDebuggerServer(debugger, rmiPort); 380 } 381 catch (RemoteException rem) { 382 throw new DebuggerException(rem); 383 } 384 RMIHelper.rebind(serverID, remote); 385 } 386 } else { 387 // 388 // Remote mode (client attaching to server) 389 // 390 391 // Create and install a security manager 392 393 // FIXME: currently commented out because we were having 394 // security problems since we're "in the sun.* hierarchy" here. 395 // Perhaps a permissive policy file would work around this. In 396 // the long run, will probably have to move into com.sun.*. 397 398 // if (System.getSecurityManager() == null) { 399 // System.setSecurityManager(new RMISecurityManager()); 400 // } 401 402 connectRemoteDebugger(); 403 } 404 } 405 406 private void setupVM() { 407 // We need to instantiate a HotSpotTypeDataBase on both the client 408 // and server machine. On the server it is only currently used to 409 // configure the Java primitive type sizes (which we should 410 // consider making constant). On the client it is used to 411 // configure the VM. 412 413 try { 414 if (os.equals("solaris")) { 415 db = new HotSpotTypeDataBase(machDesc, 416 new HotSpotSolarisVtblAccess(debugger, jvmLibNames), 417 debugger, jvmLibNames); 418 } else if (os.equals("win32")) { 419 db = new HotSpotTypeDataBase(machDesc, 420 new Win32VtblAccess(debugger, jvmLibNames), 421 debugger, jvmLibNames); 422 } else if (os.equals("linux")) { 423 db = new HotSpotTypeDataBase(machDesc, 424 new LinuxVtblAccess(debugger, jvmLibNames), 425 debugger, jvmLibNames); 426 } else if (os.equals("bsd")) { 427 db = new HotSpotTypeDataBase(machDesc, 428 new BsdVtblAccess(debugger, jvmLibNames), 429 debugger, jvmLibNames); 430 } else if (os.equals("darwin")) { 431 db = new HotSpotTypeDataBase(machDesc, 432 new BsdVtblAccess(debugger, jvmLibNames), 433 debugger, jvmLibNames); 434 } else { 435 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 436 } 437 } 438 catch (NoSuchSymbolException e) { 439 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 440 e.getSymbol() + "\" in remote process)"); 441 } 442 443 if (startupMode != REMOTE_MODE) { 444 // Configure the debugger with the primitive type sizes just obtained from the VM 445 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 446 db.getJByteType().getSize(), 447 db.getJCharType().getSize(), 448 db.getJDoubleType().getSize(), 449 db.getJFloatType().getSize(), 450 db.getJIntType().getSize(), 451 db.getJLongType().getSize(), 452 db.getJShortType().getSize()); 453 } 454 455 if (!isServer) { 456 // Do not initialize the VM on the server (unnecessary, since it's 457 // instantiated on the client) 458 try { 459 VM.initialize(db, debugger); 460 } catch (DebuggerException e) { 461 throw (e); 462 } catch (Exception e) { 463 throw new DebuggerException(e); 464 } 465 } 466 } 467 468 //-------------------------------------------------------------------------------- 469 // OS-specific debugger setup/connect routines 470 // 471 472 // Use the existing JVMDebugger, as passed to our constructor. 473 // Retrieve os and cpu from that debugger, not the current platform. 474 private void setupDebuggerExisting() { 475 476 os = debugger.getOS(); 477 cpu = debugger.getCPU(); 478 setupJVMLibNames(os); 479 machDesc = debugger.getMachineDescription(); 480 } 481 482 // Given a classname, load an alternate implementation of JVMDebugger. 483 private void setupDebuggerAlternate(String alternateName) { 484 485 try { 486 Class<?> c = Class.forName(alternateName); 487 Constructor cons = c.getConstructor(); 488 debugger = (JVMDebugger) cons.newInstance(); 489 attachDebugger(); 490 setupDebuggerExisting(); 491 492 } catch (ClassNotFoundException cnfe) { 493 throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'"); 494 } catch (NoSuchMethodException nsme) { 495 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor."); 496 } catch (InstantiationException ie) { 497 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie); 498 } catch (IllegalAccessException iae) { 499 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 500 } catch (InvocationTargetException iae) { 501 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 502 } 503 504 System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName); 505 } 506 507 // 508 // Solaris 509 // 510 511 private void setupDebuggerSolaris() { 512 setupJVMLibNamesSolaris(); 513 ProcDebuggerLocal dbg = new ProcDebuggerLocal(null, true); 514 debugger = dbg; 515 attachDebugger(); 516 517 // Set up CPU-dependent stuff 518 if (cpu.equals("x86")) { 519 machDesc = new MachineDescriptionIntelX86(); 520 } else if (cpu.equals("sparc")) { 521 int addressSize = dbg.getRemoteProcessAddressSize(); 522 if (addressSize == -1) { 523 throw new DebuggerException("Error occurred while trying to determine the remote process's " + 524 "address size"); 525 } 526 527 if (addressSize == 32) { 528 machDesc = new MachineDescriptionSPARC32Bit(); 529 } else if (addressSize == 64) { 530 machDesc = new MachineDescriptionSPARC64Bit(); 531 } else { 532 throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC"); 533 } 534 } else if (cpu.equals("amd64")) { 535 machDesc = new MachineDescriptionAMD64(); 536 } else { 537 throw new DebuggerException("Solaris only supported on sparc/sparcv9/x86/amd64"); 538 } 539 540 dbg.setMachineDescription(machDesc); 541 return; 542 } 543 544 private void connectRemoteDebugger() throws DebuggerException { 545 RemoteDebugger remote = 546 (RemoteDebugger) RMIHelper.lookup(debugServerID); 547 debugger = new RemoteDebuggerClient(remote); 548 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 549 os = debugger.getOS(); 550 setupJVMLibNames(os); 551 cpu = debugger.getCPU(); 552 } 553 554 private void setupJVMLibNames(String os) { 555 if (os.equals("solaris")) { 556 setupJVMLibNamesSolaris(); 557 } else if (os.equals("win32")) { 558 setupJVMLibNamesWin32(); 559 } else if (os.equals("linux")) { 560 setupJVMLibNamesLinux(); 561 } else if (os.equals("bsd")) { 562 setupJVMLibNamesBsd(); 563 } else if (os.equals("darwin")) { 564 setupJVMLibNamesDarwin(); 565 } else { 566 throw new RuntimeException("Unknown OS type"); 567 } 568 } 569 570 private void setupJVMLibNamesSolaris() { 571 jvmLibNames = new String[] { "libjvm.so" }; 572 } 573 574 // 575 // Win32 576 // 577 578 private void setupDebuggerWin32() { 579 setupJVMLibNamesWin32(); 580 581 if (cpu.equals("x86")) { 582 machDesc = new MachineDescriptionIntelX86(); 583 } else if (cpu.equals("amd64")) { 584 machDesc = new MachineDescriptionAMD64(); 585 } else { 586 throw new DebuggerException("Win32 supported under x86 and amd64 only"); 587 } 588 589 // Note we do not use a cache for the local debugger in server 590 // mode; it will be taken care of on the client side (once remote 591 // debugging is implemented). 592 593 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 594 595 attachDebugger(); 596 597 // FIXME: add support for server mode 598 } 599 600 private void setupJVMLibNamesWin32() { 601 jvmLibNames = new String[] { "jvm.dll" }; 602 } 603 604 // 605 // Linux 606 // 607 608 private void setupDebuggerLinux() { 609 setupJVMLibNamesLinux(); 610 611 if (cpu.equals("x86")) { 612 machDesc = new MachineDescriptionIntelX86(); 613 } else if (cpu.equals("amd64")) { 614 machDesc = new MachineDescriptionAMD64(); 615 } else if (cpu.equals("ppc64")) { 616 machDesc = new MachineDescriptionPPC64(); 617 } else if (cpu.equals("aarch64")) { 618 machDesc = new MachineDescriptionAArch64(); 619 } else if (cpu.equals("sparc")) { 620 if (LinuxDebuggerLocal.getAddressSize()==8) { 621 machDesc = new MachineDescriptionSPARC64Bit(); 622 } else { 623 machDesc = new MachineDescriptionSPARC32Bit(); 624 } 625 } else { 626 try { 627 machDesc = (MachineDescription) 628 Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + 629 cpu.toUpperCase()).getDeclaredConstructor().newInstance(); 630 } catch (Exception e) { 631 throw new DebuggerException("Linux not supported on machine type " + cpu); 632 } 633 } 634 635 LinuxDebuggerLocal dbg = 636 new LinuxDebuggerLocal(machDesc, !isServer); 637 debugger = dbg; 638 639 attachDebugger(); 640 } 641 642 private void setupJVMLibNamesLinux() { 643 jvmLibNames = new String[] { "libjvm.so" }; 644 } 645 646 // 647 // BSD 648 // 649 650 private void setupDebuggerBsd() { 651 setupJVMLibNamesBsd(); 652 653 if (cpu.equals("x86")) { 654 machDesc = new MachineDescriptionIntelX86(); 655 } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { 656 machDesc = new MachineDescriptionAMD64(); 657 } else { 658 throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); 659 } 660 661 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 662 debugger = dbg; 663 664 attachDebugger(); 665 } 666 667 private void setupJVMLibNamesBsd() { 668 jvmLibNames = new String[] { "libjvm.so" }; 669 } 670 671 // 672 // Darwin 673 // 674 675 private void setupDebuggerDarwin() { 676 setupJVMLibNamesDarwin(); 677 678 if (cpu.equals("amd64") || cpu.equals("x86_64")) { 679 machDesc = new MachineDescriptionAMD64(); 680 } else { 681 throw new DebuggerException("Darwin only supported on x86_64. Current arch: " + cpu); 682 } 683 684 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 685 debugger = dbg; 686 687 attachDebugger(); 688 } 689 690 private void setupJVMLibNamesDarwin() { 691 jvmLibNames = new String[] { "libjvm.dylib" }; 692 } 693 694 /** Convenience routine which should be called by per-platform 695 debugger setup. Should not be called when startupMode is 696 REMOTE_MODE. */ 697 private void attachDebugger() { 698 if (startupMode == PROCESS_MODE) { 699 debugger.attach(pid); 700 } else if (startupMode == CORE_FILE_MODE) { 701 debugger.attach(javaExecutableName, coreFileName); 702 } else { 703 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 704 } 705 } 706 }