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