1 /* 2 * Copyright (c) 2000, 2022, 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 connectRemoteDebugger(); 394 } 395 } 396 397 private void setupVM() { 398 // We need to instantiate a HotSpotTypeDataBase on both the client 399 // and server machine. On the server it is only currently used to 400 // configure the Java primitive type sizes (which we should 401 // consider making constant). On the client it is used to 402 // configure the VM. 403 404 try { 405 if (os.equals("win32")) { 406 db = new HotSpotTypeDataBase(machDesc, 407 new Win32VtblAccess(debugger, jvmLibNames), 408 debugger, jvmLibNames); 409 } else if (os.equals("linux")) { 410 db = new HotSpotTypeDataBase(machDesc, 411 new LinuxVtblAccess(debugger, jvmLibNames), 412 debugger, jvmLibNames); 413 } else if (os.equals("bsd")) { 414 db = new HotSpotTypeDataBase(machDesc, 415 new BsdVtblAccess(debugger, jvmLibNames), 416 debugger, jvmLibNames); 417 } else if (os.equals("darwin")) { 418 db = new HotSpotTypeDataBase(machDesc, 419 new BsdVtblAccess(debugger, jvmLibNames), 420 debugger, jvmLibNames); 421 } else { 422 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 423 } 424 } 425 catch (NoSuchSymbolException e) { 426 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 427 e.getSymbol() + "\" in remote process)"); 428 } 429 430 if (startupMode != REMOTE_MODE) { 431 // Configure the debugger with the primitive type sizes just obtained from the VM 432 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 433 db.getJByteType().getSize(), 434 db.getJCharType().getSize(), 435 db.getJDoubleType().getSize(), 436 db.getJFloatType().getSize(), 437 db.getJIntType().getSize(), 438 db.getJLongType().getSize(), 439 db.getJShortType().getSize()); 440 } 441 442 try { 443 VM.initialize(db, debugger); 444 } catch (DebuggerException e) { 445 throw (e); 446 } catch (Exception e) { 447 throw new DebuggerException(e); 448 } 449 } 450 451 //-------------------------------------------------------------------------------- 452 // OS-specific debugger setup/connect routines 453 // 454 455 // Use the existing JVMDebugger, as passed to our constructor. 456 // Retrieve os and cpu from that debugger, not the current platform. 457 private void setupDebuggerExisting() { 458 459 os = debugger.getOS(); 460 cpu = debugger.getCPU(); 461 setupJVMLibNames(os); 462 machDesc = debugger.getMachineDescription(); 463 } 464 465 // Given a classname, load an alternate implementation of JVMDebugger. 466 private void setupDebuggerAlternate(String alternateName) { 467 468 try { 469 Class<?> c = Class.forName(alternateName); 470 Constructor cons = c.getConstructor(); 471 debugger = (JVMDebugger) cons.newInstance(); 472 attachDebugger(); 473 setupDebuggerExisting(); 474 475 } catch (ClassNotFoundException cnfe) { 476 throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'"); 477 } catch (NoSuchMethodException nsme) { 478 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor."); 479 } catch (InstantiationException ie) { 480 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie); 481 } catch (IllegalAccessException iae) { 482 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 483 } catch (InvocationTargetException iae) { 484 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 485 } 486 487 System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName); 488 } 489 490 private void connectRemoteDebugger() throws DebuggerException { 491 RemoteDebugger remote = 492 (RemoteDebugger) RMIHelper.lookup(debugServerID); 493 debugger = new RemoteDebuggerClient(remote); 494 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 495 os = debugger.getOS(); 496 setupJVMLibNames(os); 497 cpu = debugger.getCPU(); 498 } 499 500 private void setupJVMLibNames(String os) { 501 if (os.equals("win32")) { 502 setupJVMLibNamesWin32(); 503 } else if (os.equals("linux")) { 504 setupJVMLibNamesLinux(); 505 } else if (os.equals("bsd")) { 506 setupJVMLibNamesBsd(); 507 } else if (os.equals("darwin")) { 508 setupJVMLibNamesDarwin(); 509 } else { 510 throw new RuntimeException("Unknown OS type"); 511 } 512 } 513 514 // 515 // Win32 516 // 517 518 private void setupDebuggerWin32() { 519 setupJVMLibNamesWin32(); 520 521 if (cpu.equals("x86")) { 522 machDesc = new MachineDescriptionIntelX86(); 523 } else if (cpu.equals("amd64")) { 524 machDesc = new MachineDescriptionAMD64(); 525 } else if (cpu.equals("aarch64")) { 526 machDesc = new MachineDescriptionAArch64(); 527 } else { 528 throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 only"); 529 } 530 531 // Note we do not use a cache for the local debugger in server 532 // mode; it will be taken care of on the client side (once remote 533 // debugging is implemented). 534 535 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 536 537 attachDebugger(); 538 539 // FIXME: add support for server mode 540 } 541 542 private void setupJVMLibNamesWin32() { 543 jvmLibNames = new String[] { "jvm.dll" }; 544 } 545 546 // 547 // Linux 548 // 549 550 private void setupDebuggerLinux() { 551 setupJVMLibNamesLinux(); 552 553 if (cpu.equals("x86")) { 554 machDesc = new MachineDescriptionIntelX86(); 555 } else if (cpu.equals("amd64")) { 556 machDesc = new MachineDescriptionAMD64(); 557 } else if (cpu.equals("ppc64")) { 558 machDesc = new MachineDescriptionPPC64(); 559 } else if (cpu.equals("aarch64")) { 560 machDesc = new MachineDescriptionAArch64(); 561 } else { 562 try { 563 machDesc = (MachineDescription) 564 Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + 565 cpu.toUpperCase()).getDeclaredConstructor().newInstance(); 566 } catch (Exception e) { 567 throw new DebuggerException("Linux not supported on machine type " + cpu); 568 } 569 } 570 571 LinuxDebuggerLocal dbg = 572 new LinuxDebuggerLocal(machDesc, !isServer); 573 debugger = dbg; 574 575 attachDebugger(); 576 } 577 578 private void setupJVMLibNamesLinux() { 579 jvmLibNames = new String[] { "libjvm.so" }; 580 } 581 582 // 583 // BSD 584 // 585 586 private void setupDebuggerBsd() { 587 setupJVMLibNamesBsd(); 588 589 if (cpu.equals("x86")) { 590 machDesc = new MachineDescriptionIntelX86(); 591 } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { 592 machDesc = new MachineDescriptionAMD64(); 593 } else { 594 throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); 595 } 596 597 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 598 debugger = dbg; 599 600 attachDebugger(); 601 } 602 603 private void setupJVMLibNamesBsd() { 604 jvmLibNames = new String[] { "libjvm.so" }; 605 } 606 607 // 608 // Darwin 609 // 610 611 private void setupDebuggerDarwin() { 612 setupJVMLibNamesDarwin(); 613 614 if (cpu.equals("amd64") || cpu.equals("x86_64")) { 615 machDesc = new MachineDescriptionAMD64(); 616 } else if (cpu.equals("aarch64")) { 617 machDesc = new MachineDescriptionAArch64(); 618 } else { 619 throw new DebuggerException("Darwin only supported on x86_64/aarch64. Current arch: " + cpu); 620 } 621 622 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 623 debugger = dbg; 624 625 attachDebugger(); 626 } 627 628 private void setupJVMLibNamesDarwin() { 629 jvmLibNames = new String[] { "libjvm.dylib" }; 630 } 631 632 /** Convenience routine which should be called by per-platform 633 debugger setup. Should not be called when startupMode is 634 REMOTE_MODE. */ 635 private void attachDebugger() { 636 if (startupMode == PROCESS_MODE) { 637 debugger.attach(pid); 638 } else if (startupMode == CORE_FILE_MODE) { 639 debugger.attach(javaExecutableName, coreFileName); 640 } else { 641 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 642 } 643 } 644 645 public int getStartupMode() { 646 return startupMode; 647 } 648 }