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