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.NoSuchSymbolException; 40 import sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal; 41 import sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal; 42 import sun.jvm.hotspot.debugger.remote.RemoteDebugger; 43 import sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient; 44 import sun.jvm.hotspot.debugger.remote.RemoteDebuggerServer; 45 import sun.jvm.hotspot.debugger.windbg.WindbgDebuggerLocal; 46 import sun.jvm.hotspot.runtime.VM; 47 import sun.jvm.hotspot.types.TypeDataBase; 48 import sun.jvm.hotspot.utilities.PlatformInfo; 49 import sun.jvm.hotspot.utilities.UnsupportedPlatformException; 50 51 /** <P> This class wraps much of the basic functionality and is the 52 * highest-level factory for VM data structures. It makes it simple 53 * to start up the debugging system. </P> 54 * 55 * <P> FIXME: especially with the addition of remote debugging, this 56 * has turned into a mess; needs rethinking. </P> 57 */ 58 59 public class HotSpotAgent { 60 private JVMDebugger debugger; 61 private MachineDescription machDesc; 62 private TypeDataBase db; 63 64 private String os; 65 private String cpu; 66 67 // The system can work in several ways: 68 // - Attaching to local process 69 // - Attaching to local core file 70 // - Connecting to remote debug server 71 // - Starting debug server for process 72 // - Starting debug server for core file 73 74 // These are options for the "client" side of things 75 private static final int PROCESS_MODE = 0; 76 private static final int CORE_FILE_MODE = 1; 77 private static final int REMOTE_MODE = 2; 78 private int startupMode; 79 80 // This indicates whether we are really starting a server or not 81 private boolean isServer; 82 83 // All possible required information for connecting 84 private int pid; 85 private String javaExecutableName; 86 private String coreFileName; 87 private String debugServerID; 88 private int rmiPort; 89 90 // All needed information for server side 91 private String serverID; 92 93 private String[] jvmLibNames; 94 95 static void showUsage() { 96 } 97 98 public HotSpotAgent() { 99 // for non-server add shutdown hook to clean-up debugger in case 100 // of forced exit. For remote server, shutdown hook is added by 101 // DebugServer. 102 Runtime.getRuntime().addShutdownHook(new java.lang.Thread( 103 new Runnable() { 104 public void run() { 105 synchronized (HotSpotAgent.this) { 106 if (!isServer) { 107 detach(); 108 } 109 } 110 } 111 })); 112 } 113 114 //-------------------------------------------------------------------------------- 115 // Accessors (once the system is set up) 116 // 117 118 public synchronized Debugger getDebugger() { 119 return debugger; 120 } 121 122 public synchronized TypeDataBase getTypeDataBase() { 123 return db; 124 } 125 126 //-------------------------------------------------------------------------------- 127 // Client-side operations 128 // 129 130 /** This attaches to a process running on the local machine. */ 131 public synchronized void attach(int processID) 132 throws DebuggerException { 133 if (debugger != null) { 134 throw new DebuggerException("Already attached"); 135 } 136 pid = processID; 137 startupMode = PROCESS_MODE; 138 isServer = false; 139 go(); 140 } 141 142 /** This opens a core file on the local machine */ 143 public synchronized void attach(String javaExecutableName, String coreFileName) 144 throws DebuggerException { 145 if (debugger != null) { 146 throw new DebuggerException("Already attached"); 147 } 148 if ((javaExecutableName == null) || (coreFileName == null)) { 149 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 150 } 151 this.javaExecutableName = javaExecutableName; 152 this.coreFileName = coreFileName; 153 startupMode = CORE_FILE_MODE; 154 isServer = false; 155 go(); 156 } 157 158 /** This uses a JVMDebugger that is already attached to the core or process */ 159 public synchronized void attach(JVMDebugger d) 160 throws DebuggerException { 161 debugger = d; 162 isServer = false; 163 go(); 164 } 165 166 /** This attaches to a "debug server" on a remote machine; this 167 remote server has already attached to a process or opened a 168 core file and is waiting for RMI calls on the Debugger object to 169 come in. */ 170 public synchronized void attach(String remoteServerID) 171 throws DebuggerException { 172 if (debugger != null) { 173 throw new DebuggerException("Already attached to a process"); 174 } 175 if (remoteServerID == null) { 176 throw new DebuggerException("Debug server id must be specified"); 177 } 178 179 debugServerID = remoteServerID; 180 startupMode = REMOTE_MODE; 181 isServer = false; 182 go(); 183 } 184 185 /** This should only be called by the user on the client machine, 186 not the server machine */ 187 public synchronized boolean detach() throws DebuggerException { 188 if (isServer) { 189 throw new DebuggerException("Should not call detach() for server configuration"); 190 } 191 return detachInternal(); 192 } 193 194 //-------------------------------------------------------------------------------- 195 // Server-side operations 196 // 197 198 /** This attaches to a process running on the local machine and 199 starts a debug server, allowing remote machines to connect and 200 examine this process. Uses specified name to uniquely identify a 201 specific debuggee on the server. Allows to specify the port number 202 to which the RMI connector is bound. If not specified a random 203 available port is used. */ 204 public synchronized void startServer(int processID, 205 String uniqueID, 206 int rmiPort) { 207 if (debugger != null) { 208 throw new DebuggerException("Already attached"); 209 } 210 pid = processID; 211 startupMode = PROCESS_MODE; 212 isServer = true; 213 serverID = uniqueID; 214 this.rmiPort = rmiPort; 215 go(); 216 } 217 218 /** This attaches to a process running on the local machine and 219 starts a debug server, allowing remote machines to connect and 220 examine this process. Uses specified name to uniquely identify a 221 specific debuggee on the server */ 222 public synchronized void startServer(int processID, String uniqueID) { 223 startServer(processID, uniqueID, 0); 224 } 225 226 /** This attaches to a process running on the local machine and 227 starts a debug server, allowing remote machines to connect and 228 examine this process. */ 229 public synchronized void startServer(int processID) 230 throws DebuggerException { 231 startServer(processID, null); 232 } 233 234 /** This opens a core file on the local machine and starts a debug 235 server, allowing remote machines to connect and examine this 236 core file. Uses supplied uniqueID to uniquely identify a specific 237 debuggee. Allows to specify the port number to which the RMI connector 238 is bound. If not specified a random available port is used. */ 239 public synchronized void startServer(String javaExecutableName, 240 String coreFileName, 241 String uniqueID, 242 int rmiPort) { 243 if (debugger != null) { 244 throw new DebuggerException("Already attached"); 245 } 246 if ((javaExecutableName == null) || (coreFileName == null)) { 247 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 248 } 249 this.javaExecutableName = javaExecutableName; 250 this.coreFileName = coreFileName; 251 startupMode = CORE_FILE_MODE; 252 isServer = true; 253 serverID = uniqueID; 254 this.rmiPort = rmiPort; 255 go(); 256 } 257 258 /** This opens a core file on the local machine and starts a debug 259 server, allowing remote machines to connect and examine this 260 core file. Uses supplied uniqueID to uniquely identify a specific 261 debugee */ 262 public synchronized void startServer(String javaExecutableName, 263 String coreFileName, 264 String uniqueID) { 265 startServer(javaExecutableName, coreFileName, uniqueID, 0); 266 } 267 268 /** This opens a core file on the local machine and starts a debug 269 server, allowing remote machines to connect and examine this 270 core file. */ 271 public synchronized void startServer(String javaExecutableName, String coreFileName) 272 throws DebuggerException { 273 startServer(javaExecutableName, coreFileName, null); 274 } 275 276 /** This may only be called on the server side after startServer() 277 has been called */ 278 public synchronized boolean shutdownServer() throws DebuggerException { 279 if (!isServer) { 280 throw new DebuggerException("Should not call shutdownServer() for client configuration"); 281 } 282 return detachInternal(); 283 } 284 285 286 //-------------------------------------------------------------------------------- 287 // Internals only below this point 288 // 289 290 private boolean detachInternal() { 291 if (debugger == null) { 292 return false; 293 } 294 boolean retval = true; 295 if (!isServer) { 296 VM.shutdown(); 297 } 298 // We must not call detach() if we are a client and are connected 299 // to a remote debugger 300 Debugger dbg = null; 301 DebuggerException ex = null; 302 if (isServer) { 303 try { 304 RMIHelper.unbind(serverID); 305 } 306 catch (DebuggerException de) { 307 ex = de; 308 } 309 dbg = debugger; 310 } else { 311 if (startupMode != REMOTE_MODE) { 312 dbg = debugger; 313 } 314 } 315 if (dbg != null) { 316 retval = dbg.detach(); 317 } 318 319 debugger = null; 320 machDesc = null; 321 db = null; 322 if (ex != null) { 323 throw(ex); 324 } 325 return retval; 326 } 327 328 private void go() { 329 setupDebugger(); 330 setupVM(); 331 } 332 333 private void setupDebugger() { 334 if (startupMode != REMOTE_MODE) { 335 // 336 // Local mode (client attaching to local process or setting up 337 // server, but not client attaching to server) 338 // 339 340 // Handle existing or alternate JVMDebugger: 341 // these will set os, cpu independently of our PlatformInfo implementation. 342 String alternateDebugger = System.getProperty("sa.altDebugger"); 343 if (debugger != null) { 344 setupDebuggerExisting(); 345 346 } else if (alternateDebugger != null) { 347 setupDebuggerAlternate(alternateDebugger); 348 349 } else { 350 // Otherwise, os, cpu are those of our current platform: 351 try { 352 os = PlatformInfo.getOS(); 353 cpu = PlatformInfo.getCPU(); 354 } catch (UnsupportedPlatformException e) { 355 throw new DebuggerException(e); 356 } 357 if (os.equals("win32")) { 358 setupDebuggerWin32(); 359 } else if (os.equals("linux")) { 360 setupDebuggerLinux(); 361 } else if (os.equals("bsd")) { 362 setupDebuggerBsd(); 363 } else if (os.equals("darwin")) { 364 setupDebuggerDarwin(); 365 } else { 366 // Add support for more operating systems here 367 throw new DebuggerException("Operating system " + os + " not yet supported"); 368 } 369 } 370 371 if (isServer) { 372 RemoteDebuggerServer remote = null; 373 try { 374 remote = new RemoteDebuggerServer(debugger, rmiPort); 375 } 376 catch (RemoteException rem) { 377 throw new DebuggerException(rem); 378 } 379 RMIHelper.rebind(serverID, remote); 380 } 381 } else { 382 // 383 // Remote mode (client attaching to server) 384 // 385 386 // Create and install a security manager 387 388 // FIXME: currently commented out because we were having 389 // security problems since we're "in the sun.* hierarchy" here. 390 // Perhaps a permissive policy file would work around this. In 391 // the long run, will probably have to move into com.sun.*. 392 393 // if (System.getSecurityManager() == null) { 394 // System.setSecurityManager(new RMISecurityManager()); 395 // } 396 397 connectRemoteDebugger(); 398 } 399 } 400 401 private void setupVM() { 402 // We need to instantiate a HotSpotTypeDataBase on both the client 403 // and server machine. On the server it is only currently used to 404 // configure the Java primitive type sizes (which we should 405 // consider making constant). On the client it is used to 406 // configure the VM. 407 408 try { 409 if (os.equals("win32")) { 410 db = new HotSpotTypeDataBase(machDesc, 411 new Win32VtblAccess(debugger, jvmLibNames), 412 debugger, jvmLibNames); 413 } else if (os.equals("linux")) { 414 db = new HotSpotTypeDataBase(machDesc, 415 new LinuxVtblAccess(debugger, jvmLibNames), 416 debugger, jvmLibNames); 417 } else if (os.equals("bsd")) { 418 db = new HotSpotTypeDataBase(machDesc, 419 new BsdVtblAccess(debugger, jvmLibNames), 420 debugger, jvmLibNames); 421 } else if (os.equals("darwin")) { 422 db = new HotSpotTypeDataBase(machDesc, 423 new BsdVtblAccess(debugger, jvmLibNames), 424 debugger, jvmLibNames); 425 } else { 426 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 427 } 428 } 429 catch (NoSuchSymbolException e) { 430 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 431 e.getSymbol() + "\" in remote process)"); 432 } 433 434 if (startupMode != REMOTE_MODE) { 435 // Configure the debugger with the primitive type sizes just obtained from the VM 436 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 437 db.getJByteType().getSize(), 438 db.getJCharType().getSize(), 439 db.getJDoubleType().getSize(), 440 db.getJFloatType().getSize(), 441 db.getJIntType().getSize(), 442 db.getJLongType().getSize(), 443 db.getJShortType().getSize()); 444 } 445 446 if (!isServer) { 447 // Do not initialize the VM on the server (unnecessary, since it's 448 // instantiated on the client) 449 try { 450 VM.initialize(db, debugger); 451 } catch (DebuggerException e) { 452 throw (e); 453 } catch (Exception e) { 454 throw new DebuggerException(e); 455 } 456 } 457 } 458 459 //-------------------------------------------------------------------------------- 460 // OS-specific debugger setup/connect routines 461 // 462 463 // Use the existing JVMDebugger, as passed to our constructor. 464 // Retrieve os and cpu from that debugger, not the current platform. 465 private void setupDebuggerExisting() { 466 467 os = debugger.getOS(); 468 cpu = debugger.getCPU(); 469 setupJVMLibNames(os); 470 machDesc = debugger.getMachineDescription(); 471 } 472 473 // Given a classname, load an alternate implementation of JVMDebugger. 474 private void setupDebuggerAlternate(String alternateName) { 475 476 try { 477 Class<?> c = Class.forName(alternateName); 478 Constructor cons = c.getConstructor(); 479 debugger = (JVMDebugger) cons.newInstance(); 480 attachDebugger(); 481 setupDebuggerExisting(); 482 483 } catch (ClassNotFoundException cnfe) { 484 throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'"); 485 } catch (NoSuchMethodException nsme) { 486 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor."); 487 } catch (InstantiationException ie) { 488 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie); 489 } catch (IllegalAccessException iae) { 490 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 491 } catch (InvocationTargetException iae) { 492 throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae); 493 } 494 495 System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName); 496 } 497 498 private void connectRemoteDebugger() throws DebuggerException { 499 RemoteDebugger remote = 500 (RemoteDebugger) RMIHelper.lookup(debugServerID); 501 debugger = new RemoteDebuggerClient(remote); 502 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 503 os = debugger.getOS(); 504 setupJVMLibNames(os); 505 cpu = debugger.getCPU(); 506 } 507 508 private void setupJVMLibNames(String os) { 509 if (os.equals("win32")) { 510 setupJVMLibNamesWin32(); 511 } else if (os.equals("linux")) { 512 setupJVMLibNamesLinux(); 513 } else if (os.equals("bsd")) { 514 setupJVMLibNamesBsd(); 515 } else if (os.equals("darwin")) { 516 setupJVMLibNamesDarwin(); 517 } else { 518 throw new RuntimeException("Unknown OS type"); 519 } 520 } 521 522 // 523 // Win32 524 // 525 526 private void setupDebuggerWin32() { 527 setupJVMLibNamesWin32(); 528 529 if (cpu.equals("x86")) { 530 machDesc = new MachineDescriptionIntelX86(); 531 } else if (cpu.equals("amd64")) { 532 machDesc = new MachineDescriptionAMD64(); 533 } else if (cpu.equals("aarch64")) { 534 machDesc = new MachineDescriptionAArch64(); 535 } else { 536 throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 only"); 537 } 538 539 // Note we do not use a cache for the local debugger in server 540 // mode; it will be taken care of on the client side (once remote 541 // debugging is implemented). 542 543 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 544 545 attachDebugger(); 546 547 // FIXME: add support for server mode 548 } 549 550 private void setupJVMLibNamesWin32() { 551 jvmLibNames = new String[] { "jvm.dll" }; 552 } 553 554 // 555 // Linux 556 // 557 558 private void setupDebuggerLinux() { 559 setupJVMLibNamesLinux(); 560 561 if (cpu.equals("x86")) { 562 machDesc = new MachineDescriptionIntelX86(); 563 } else if (cpu.equals("amd64")) { 564 machDesc = new MachineDescriptionAMD64(); 565 } else if (cpu.equals("ppc64")) { 566 machDesc = new MachineDescriptionPPC64(); 567 } else if (cpu.equals("aarch64")) { 568 machDesc = new MachineDescriptionAArch64(); 569 } else { 570 try { 571 machDesc = (MachineDescription) 572 Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + 573 cpu.toUpperCase()).getDeclaredConstructor().newInstance(); 574 } catch (Exception e) { 575 throw new DebuggerException("Linux not supported on machine type " + cpu); 576 } 577 } 578 579 LinuxDebuggerLocal dbg = 580 new LinuxDebuggerLocal(machDesc, !isServer); 581 debugger = dbg; 582 583 attachDebugger(); 584 } 585 586 private void setupJVMLibNamesLinux() { 587 jvmLibNames = new String[] { "libjvm.so" }; 588 } 589 590 // 591 // BSD 592 // 593 594 private void setupDebuggerBsd() { 595 setupJVMLibNamesBsd(); 596 597 if (cpu.equals("x86")) { 598 machDesc = new MachineDescriptionIntelX86(); 599 } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { 600 machDesc = new MachineDescriptionAMD64(); 601 } else { 602 throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); 603 } 604 605 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 606 debugger = dbg; 607 608 attachDebugger(); 609 } 610 611 private void setupJVMLibNamesBsd() { 612 jvmLibNames = new String[] { "libjvm.so" }; 613 } 614 615 // 616 // Darwin 617 // 618 619 private void setupDebuggerDarwin() { 620 setupJVMLibNamesDarwin(); 621 622 if (cpu.equals("amd64") || cpu.equals("x86_64")) { 623 machDesc = new MachineDescriptionAMD64(); 624 } else { 625 throw new DebuggerException("Darwin only supported on x86_64. Current arch: " + cpu); 626 } 627 628 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 629 debugger = dbg; 630 631 attachDebugger(); 632 } 633 634 private void setupJVMLibNamesDarwin() { 635 jvmLibNames = new String[] { "libjvm.dylib" }; 636 } 637 638 /** Convenience routine which should be called by per-platform 639 debugger setup. Should not be called when startupMode is 640 REMOTE_MODE. */ 641 private void attachDebugger() { 642 if (startupMode == PROCESS_MODE) { 643 debugger.attach(pid); 644 } else if (startupMode == CORE_FILE_MODE) { 645 debugger.attach(javaExecutableName, coreFileName); 646 } else { 647 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 648 } 649 } 650 }