1 /* 2 * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. 9 * 10 * This code is distributed in the hope that it will be useful, but WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 * version 2 for more details (a copy is included in the LICENSE file that 14 * accompanied this code). 15 * 16 * You should have received a copy of the GNU General Public License version 17 * 2 along with this work; if not, write to the Free Software Foundation, 18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 * 20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 21 * or visit www.oracle.com if you need additional information or have any 22 * questions. 23 * 24 */ 25 26 package sun.jvm.hotspot; 27 28 import java.rmi.RemoteException; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.InvocationTargetException; 31 32 import sun.jvm.hotspot.debugger.Debugger; 33 import sun.jvm.hotspot.debugger.DebuggerException; 34 import sun.jvm.hotspot.debugger.JVMDebugger; 35 import sun.jvm.hotspot.debugger.MachineDescription; 36 import sun.jvm.hotspot.debugger.MachineDescriptionAMD64; 37 import sun.jvm.hotspot.debugger.MachineDescriptionPPC64; 38 import sun.jvm.hotspot.debugger.MachineDescriptionAArch64; 39 import sun.jvm.hotspot.debugger.MachineDescriptionIntelX86; 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.remote.RemoteDebugger; 44 import sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient; 45 import sun.jvm.hotspot.debugger.remote.RemoteDebuggerServer; 46 import sun.jvm.hotspot.debugger.windbg.WindbgDebuggerLocal; 47 import sun.jvm.hotspot.runtime.VM; 48 import sun.jvm.hotspot.types.TypeDataBase; 49 import sun.jvm.hotspot.utilities.PlatformInfo; 50 import sun.jvm.hotspot.utilities.UnsupportedPlatformException; 51 52 /** <P> This class wraps much of the basic functionality and is the 53 * highest-level factory for VM data structures. It makes it simple 54 * to start up the debugging system. </P> 55 * 56 * <P> FIXME: especially with the addition of remote debugging, this 57 * has turned into a mess; needs rethinking. </P> 58 */ 59 60 public class HotSpotAgent { 61 private JVMDebugger debugger; 62 private MachineDescription machDesc; 63 private TypeDataBase db; 64 65 private String os; 66 private String cpu; 67 68 // The system can work in several ways: 69 // - Attaching to local process 70 // - Attaching to local core file 71 // - Connecting to remote debug server 72 // - Starting debug server for process 73 // - Starting debug server for core file 74 75 // These are options for the "client" side of things 76 public static final int PROCESS_MODE = 0; 77 public static final int CORE_FILE_MODE = 1; 78 public static final int REMOTE_MODE = 2; 79 private int startupMode; 80 81 // This indicates whether we are really starting a server or not 82 private boolean isServer; 83 84 // All possible required information for connecting 85 private int pid; 86 private String javaExecutableName; 87 private String coreFileName; 88 private String debugServerID; 89 private int rmiPort; 90 91 // All needed information for server side 92 private String serverID; 93 private String serverName; 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 JVMDebugger 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. Allows to specify the port number 204 to which the RMI connector is bound. If not specified a random 205 available port is used. */ 206 public synchronized void startServer(int processID, 207 String serverID, 208 String serverName, 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 this.serverID = serverID; 217 this.serverName = serverName; 218 this.rmiPort = rmiPort; 219 go(); 220 } 221 222 /** This attaches to a process running on the local machine and 223 starts a debug server, allowing remote machines to connect and 224 examine this process. Uses specified name to uniquely identify a 225 specific debuggee on the server */ 226 public synchronized void startServer(int processID, String serverID, String serverName) { 227 startServer(processID, serverID, serverName, 0); 228 } 229 230 /** This attaches to a process running on the local machine and 231 starts a debug server, allowing remote machines to connect and 232 examine this process. */ 233 public synchronized void startServer(int processID) 234 throws DebuggerException { 235 startServer(processID, null, null); 236 } 237 238 /** This opens a core file on the local machine and starts a debug 239 server, allowing remote machines to connect and examine this 240 core file. Uses supplied uniqueID to uniquely identify a specific 241 debuggee. Allows to specify the port number to which the RMI connector 242 is bound. If not specified a random available port is used. */ 243 public synchronized void startServer(String javaExecutableName, 244 String coreFileName, 245 String serverID, 246 String serverName, 247 int rmiPort) { 248 if (debugger != null) { 249 throw new DebuggerException("Already attached"); 250 } 251 if ((javaExecutableName == null) || (coreFileName == null)) { 252 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 253 } 254 this.javaExecutableName = javaExecutableName; 255 this.coreFileName = coreFileName; 256 startupMode = CORE_FILE_MODE; 257 isServer = true; 258 this.serverID = serverID; 259 this.serverName = serverName; 260 this.rmiPort = rmiPort; 261 go(); 262 } 263 264 /** This opens a core file on the local machine and starts a debug 265 server, allowing remote machines to connect and examine this 266 core file. Uses supplied uniqueID to uniquely identify a specific 267 debugee */ 268 public synchronized void startServer(String javaExecutableName, 269 String coreFileName, 270 String serverID, 271 String serverName) { 272 startServer(javaExecutableName, coreFileName, serverID, serverName, 0); 273 } 274 275 /** This opens a core file on the local machine and starts a debug 276 server, allowing remote machines to connect and examine this 277 core file. */ 278 public synchronized void startServer(String javaExecutableName, String coreFileName) 279 throws DebuggerException { 280 startServer(javaExecutableName, coreFileName, null, null); 281 } 282 283 /** This may only be called on the server side after startServer() 284 has been called */ 285 public synchronized boolean shutdownServer() throws DebuggerException { 286 if (!isServer) { 287 throw new DebuggerException("Should not call shutdownServer() for client configuration"); 288 } 289 return detachInternal(); 290 } 291 292 293 //-------------------------------------------------------------------------------- 294 // Internals only below this point 295 // 296 297 private boolean detachInternal() { 298 if (debugger == null) { 299 return false; 300 } 301 boolean retval = true; 302 if (!isServer) { 303 VM.shutdown(); 304 } 305 // We must not call detach() if we are a client and are connected 306 // to a remote debugger 307 Debugger dbg = null; 308 DebuggerException ex = null; 309 if (isServer) { 310 try { 311 RMIHelper.unbind(serverID, serverName); 312 } 313 catch (DebuggerException de) { 314 ex = de; 315 } 316 dbg = debugger; 317 } else { 318 if (startupMode != REMOTE_MODE) { 319 dbg = debugger; 320 } 321 } 322 if (dbg != null) { 323 retval = dbg.detach(); 324 } 325 326 debugger = null; 327 machDesc = null; 328 db = null; 329 if (ex != null) { 330 throw(ex); 331 } 332 return retval; 333 } 334 335 private void go() { 336 setupDebugger(); 337 setupVM(); 338 } 339 340 private void setupDebugger() { 341 if (startupMode != REMOTE_MODE) { 342 // 343 // Local mode (client attaching to local process or setting up 344 // server, but not client attaching to server) 345 // 346 347 // Handle existing or alternate JVMDebugger: 348 // these will set os, cpu independently of our PlatformInfo implementation. 349 String alternateDebugger = System.getProperty("sa.altDebugger"); 350 if (debugger != null) { 351 setupDebuggerExisting(); 352 353 } else if (alternateDebugger != null) { 354 setupDebuggerAlternate(alternateDebugger); 355 356 } else { 357 // Otherwise, os, cpu are those of our current platform: 358 try { 359 os = PlatformInfo.getOS(); 360 cpu = PlatformInfo.getCPU(); 361 } catch (UnsupportedPlatformException e) { 362 throw new DebuggerException(e); 363 } 364 if (os.equals("win32")) { 365 setupDebuggerWin32(); 366 } else if (os.equals("linux")) { 367 setupDebuggerLinux(); 368 } else if (os.equals("bsd")) { 369 setupDebuggerBsd(); 370 } else if (os.equals("darwin")) { 371 setupDebuggerDarwin(); 372 } else { 373 // Add support for more operating systems here 374 throw new DebuggerException("Operating system " + os + " not yet supported"); 375 } 376 } 377 378 if (isServer) { 379 RemoteDebuggerServer remote = null; 380 try { 381 remote = new RemoteDebuggerServer(debugger, rmiPort); 382 } 383 catch (RemoteException rem) { 384 throw new DebuggerException(rem); 385 } 386 RMIHelper.rebind(serverID, serverName, remote); 387 } 388 } else { 389 // 390 // Remote mode (client attaching to server) 391 // 392 393 // Create and install a security manager 394 395 // FIXME: currently commented out because we were having 396 // security problems since we're "in the sun.* hierarchy" here. 397 // Perhaps a permissive policy file would work around this. In 398 // the long run, will probably have to move into com.sun.*. 399 400 // if (System.getSecurityManager() == null) { 401 // System.setSecurityManager(new RMISecurityManager()); 402 // } 403 404 connectRemoteDebugger(); 405 } 406 } 407 408 private void setupVM() { 409 // We need to instantiate a HotSpotTypeDataBase on both the client 410 // and server machine. On the server it is only currently used to 411 // configure the Java primitive type sizes (which we should 412 // consider making constant). On the client it is used to 413 // configure the VM. 414 415 try { 416 if (os.equals("win32")) { 417 db = new HotSpotTypeDataBase(machDesc, 418 new Win32VtblAccess(debugger, jvmLibNames), 419 debugger, jvmLibNames); 420 } else if (os.equals("linux")) { 421 db = new HotSpotTypeDataBase(machDesc, 422 new LinuxVtblAccess(debugger, jvmLibNames), 423 debugger, jvmLibNames); 424 } else if (os.equals("bsd")) { 425 db = new HotSpotTypeDataBase(machDesc, 426 new BsdVtblAccess(debugger, jvmLibNames), 427 debugger, jvmLibNames); 428 } else if (os.equals("darwin")) { 429 db = new HotSpotTypeDataBase(machDesc, 430 new BsdVtblAccess(debugger, jvmLibNames), 431 debugger, jvmLibNames); 432 } else { 433 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 434 } 435 } 436 catch (NoSuchSymbolException e) { 437 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 438 e.getSymbol() + "\" in remote process)"); 439 } 440 441 if (startupMode != REMOTE_MODE) { 442 // Configure the debugger with the primitive type sizes just obtained from the VM 443 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 444 db.getJByteType().getSize(), 445 db.getJCharType().getSize(), 446 db.getJDoubleType().getSize(), 447 db.getJFloatType().getSize(), 448 db.getJIntType().getSize(), 449 db.getJLongType().getSize(), 450 db.getJShortType().getSize()); 451 } 452 453 try { 454 VM.initialize(db, debugger); 455 } catch (DebuggerException e) { 456 throw (e); 457 } catch (Exception e) { 458 throw new DebuggerException(e); 459 } 460 } 461 462 //-------------------------------------------------------------------------------- 463 // OS-specific debugger setup/connect routines 464 // 465 466 // Use the existing JVMDebugger, as passed to our constructor. 467 // Retrieve os and cpu from that debugger, not the current platform. 468 private void setupDebuggerExisting() { 469 470 os = debugger.getOS(); 471 cpu = debugger.getCPU(); 472 setupJVMLibNames(os); 473 machDesc = debugger.getMachineDescription(); 474 } 475 476 // Given a classname, load an alternate implementation of JVMDebugger. 477 private void setupDebuggerAlternate(String alternateName) { 478 479 try { 480 Class<?> c = Class.forName(alternateName); 481 Constructor cons = c.getConstructor(); 482 debugger = (JVMDebugger) cons.newInstance(); 483 attachDebugger(); 484 setupDebuggerExisting(); 485 486 } catch (ClassNotFoundException cnfe) { 487 throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'"); 488 } catch (NoSuchMethodException nsme) { 489 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor."); 490 } catch (InstantiationException ie) { 491 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie); 492 } catch (IllegalAccessException iae) { 493 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 494 } catch (InvocationTargetException iae) { 495 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 496 } 497 498 System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName); 499 } 500 501 private void connectRemoteDebugger() throws DebuggerException { 502 RemoteDebugger remote = 503 (RemoteDebugger) RMIHelper.lookup(debugServerID); 504 debugger = new RemoteDebuggerClient(remote); 505 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 506 os = debugger.getOS(); 507 setupJVMLibNames(os); 508 cpu = debugger.getCPU(); 509 } 510 511 private void setupJVMLibNames(String os) { 512 if (os.equals("win32")) { 513 setupJVMLibNamesWin32(); 514 } else if (os.equals("linux")) { 515 setupJVMLibNamesLinux(); 516 } else if (os.equals("bsd")) { 517 setupJVMLibNamesBsd(); 518 } else if (os.equals("darwin")) { 519 setupJVMLibNamesDarwin(); 520 } else { 521 throw new RuntimeException("Unknown OS type"); 522 } 523 } 524 525 // 526 // Win32 527 // 528 529 private void setupDebuggerWin32() { 530 setupJVMLibNamesWin32(); 531 532 if (cpu.equals("x86")) { 533 machDesc = new MachineDescriptionIntelX86(); 534 } else if (cpu.equals("amd64")) { 535 machDesc = new MachineDescriptionAMD64(); 536 } else if (cpu.equals("aarch64")) { 537 machDesc = new MachineDescriptionAArch64(); 538 } else { 539 throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 only"); 540 } 541 542 // Note we do not use a cache for the local debugger in server 543 // mode; it will be taken care of on the client side (once remote 544 // debugging is implemented). 545 546 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 547 548 attachDebugger(); 549 550 // FIXME: add support for server mode 551 } 552 553 private void setupJVMLibNamesWin32() { 554 jvmLibNames = new String[] { "jvm.dll" }; 555 } 556 557 // 558 // Linux 559 // 560 561 private void setupDebuggerLinux() { 562 setupJVMLibNamesLinux(); 563 564 if (cpu.equals("x86")) { 565 machDesc = new MachineDescriptionIntelX86(); 566 } else if (cpu.equals("amd64")) { 567 machDesc = new MachineDescriptionAMD64(); 568 } else if (cpu.equals("ppc64")) { 569 machDesc = new MachineDescriptionPPC64(); 570 } else if (cpu.equals("aarch64")) { 571 machDesc = new MachineDescriptionAArch64(); 572 } else { 573 try { 574 machDesc = (MachineDescription) 575 Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + 576 cpu.toUpperCase()).getDeclaredConstructor().newInstance(); 577 } catch (Exception e) { 578 throw new DebuggerException("Linux not supported on machine type " + cpu); 579 } 580 } 581 582 LinuxDebuggerLocal dbg = 583 new LinuxDebuggerLocal(machDesc, !isServer); 584 debugger = dbg; 585 586 attachDebugger(); 587 } 588 589 private void setupJVMLibNamesLinux() { 590 jvmLibNames = new String[] { "libjvm.so" }; 591 } 592 593 // 594 // BSD 595 // 596 597 private void setupDebuggerBsd() { 598 setupJVMLibNamesBsd(); 599 600 if (cpu.equals("x86")) { 601 machDesc = new MachineDescriptionIntelX86(); 602 } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { 603 machDesc = new MachineDescriptionAMD64(); 604 } else { 605 throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); 606 } 607 608 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 609 debugger = dbg; 610 611 attachDebugger(); 612 } 613 614 private void setupJVMLibNamesBsd() { 615 jvmLibNames = new String[] { "libjvm.so" }; 616 } 617 618 // 619 // Darwin 620 // 621 622 private void setupDebuggerDarwin() { 623 setupJVMLibNamesDarwin(); 624 625 if (cpu.equals("amd64") || cpu.equals("x86_64")) { 626 machDesc = new MachineDescriptionAMD64(); 627 } else if (cpu.equals("aarch64")) { 628 machDesc = new MachineDescriptionAArch64(); 629 } else { 630 throw new DebuggerException("Darwin only supported on x86_64/aarch64. Current arch: " + cpu); 631 } 632 633 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 634 debugger = dbg; 635 636 attachDebugger(); 637 } 638 639 private void setupJVMLibNamesDarwin() { 640 jvmLibNames = new String[] { "libjvm.dylib" }; 641 } 642 643 /** Convenience routine which should be called by per-platform 644 debugger setup. Should not be called when startupMode is 645 REMOTE_MODE. */ 646 private void attachDebugger() { 647 if (startupMode == PROCESS_MODE) { 648 debugger.attach(pid); 649 } else if (startupMode == CORE_FILE_MODE) { 650 debugger.attach(javaExecutableName, coreFileName); 651 } else { 652 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 653 } 654 } 655 656 public int getStartupMode() { 657 return startupMode; 658 } 659 }