1 /* 2 * Copyright (c) 2000, 2013, 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 29 import sun.jvm.hotspot.debugger.Debugger; 30 import sun.jvm.hotspot.debugger.DebuggerException; 31 import sun.jvm.hotspot.debugger.JVMDebugger; 32 import sun.jvm.hotspot.debugger.MachineDescription; 33 import sun.jvm.hotspot.debugger.MachineDescriptionAMD64; 34 import sun.jvm.hotspot.debugger.MachineDescriptionIA64; 35 import sun.jvm.hotspot.debugger.MachineDescriptionIntelX86; 36 import sun.jvm.hotspot.debugger.MachineDescriptionSPARC32Bit; 37 import sun.jvm.hotspot.debugger.MachineDescriptionSPARC64Bit; 38 import sun.jvm.hotspot.debugger.NoSuchSymbolException; 39 import sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal; 40 import sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal; 41 import sun.jvm.hotspot.debugger.proc.ProcDebuggerLocal; 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 private String fileSep; 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 private static final int PROCESS_MODE = 0; 77 private static final int CORE_FILE_MODE = 1; 78 private 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 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 attaches to a "debug server" on a remote machine; this 159 remote server has already attached to a process or opened a 160 core file and is waiting for RMI calls on the Debugger object to 161 come in. */ 162 public synchronized void attach(String remoteServerID) 163 throws DebuggerException { 164 if (debugger != null) { 165 throw new DebuggerException("Already attached to a process"); 166 } 167 if (remoteServerID == null) { 168 throw new DebuggerException("Debug server id must be specified"); 169 } 170 171 debugServerID = remoteServerID; 172 startupMode = REMOTE_MODE; 173 isServer = false; 174 go(); 175 } 176 177 /** This should only be called by the user on the client machine, 178 not the server machine */ 179 public synchronized boolean detach() throws DebuggerException { 180 if (isServer) { 181 throw new DebuggerException("Should not call detach() for server configuration"); 182 } 183 return detachInternal(); 184 } 185 186 //-------------------------------------------------------------------------------- 187 // Server-side operations 188 // 189 190 /** This attaches to a process running on the local machine and 191 starts a debug server, allowing remote machines to connect and 192 examine this process. Uses specified name to uniquely identify a 193 specific debuggee on the server */ 194 public synchronized void startServer(int processID, String uniqueID) { 195 if (debugger != null) { 196 throw new DebuggerException("Already attached"); 197 } 198 pid = processID; 199 startupMode = PROCESS_MODE; 200 isServer = true; 201 serverID = uniqueID; 202 go(); 203 } 204 205 /** This attaches to a process running on the local machine and 206 starts a debug server, allowing remote machines to connect and 207 examine this process. */ 208 public synchronized void startServer(int processID) 209 throws DebuggerException { 210 startServer(processID, null); 211 } 212 213 /** This opens a core file on the local machine and starts a debug 214 server, allowing remote machines to connect and examine this 215 core file. Uses supplied uniqueID to uniquely identify a specific 216 debugee */ 217 public synchronized void startServer(String javaExecutableName, 218 String coreFileName, 219 String uniqueID) { 220 if (debugger != null) { 221 throw new DebuggerException("Already attached"); 222 } 223 if ((javaExecutableName == null) || (coreFileName == null)) { 224 throw new DebuggerException("Both the core file name and Java executable name must be specified"); 225 } 226 this.javaExecutableName = javaExecutableName; 227 this.coreFileName = coreFileName; 228 startupMode = CORE_FILE_MODE; 229 isServer = true; 230 serverID = uniqueID; 231 go(); 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. */ 237 public synchronized void startServer(String javaExecutableName, String coreFileName) 238 throws DebuggerException { 239 startServer(javaExecutableName, coreFileName, null); 240 } 241 242 /** This may only be called on the server side after startServer() 243 has been called */ 244 public synchronized boolean shutdownServer() throws DebuggerException { 245 if (!isServer) { 246 throw new DebuggerException("Should not call shutdownServer() for client configuration"); 247 } 248 return detachInternal(); 249 } 250 251 252 //-------------------------------------------------------------------------------- 253 // Internals only below this point 254 // 255 256 private boolean detachInternal() { 257 if (debugger == null) { 258 return false; 259 } 260 boolean retval = true; 261 if (!isServer) { 262 VM.shutdown(); 263 } 264 // We must not call detach() if we are a client and are connected 265 // to a remote debugger 266 Debugger dbg = null; 267 DebuggerException ex = null; 268 if (isServer) { 269 try { 270 RMIHelper.unbind(serverID); 271 } 272 catch (DebuggerException de) { 273 ex = de; 274 } 275 dbg = debugger; 276 } else { 277 if (startupMode != REMOTE_MODE) { 278 dbg = debugger; 279 } 280 } 281 if (dbg != null) { 282 retval = dbg.detach(); 283 } 284 285 debugger = null; 286 machDesc = null; 287 db = null; 288 if (ex != null) { 289 throw(ex); 290 } 291 return retval; 292 } 293 294 private void go() { 295 setupDebugger(); 296 setupVM(); 297 } 298 299 private void setupDebugger() { 300 if (startupMode != REMOTE_MODE) { 301 // 302 // Local mode (client attaching to local process or setting up 303 // server, but not client attaching to server) 304 // 305 306 try { 307 os = PlatformInfo.getOS(); 308 cpu = PlatformInfo.getCPU(); 309 } 310 catch (UnsupportedPlatformException e) { 311 throw new DebuggerException(e); 312 } 313 fileSep = System.getProperty("file.separator"); 314 315 if (os.equals("solaris")) { 316 setupDebuggerSolaris(); 317 } else if (os.equals("win32")) { 318 setupDebuggerWin32(); 319 } else if (os.equals("linux")) { 320 setupDebuggerLinux(); 321 } else if (os.equals("bsd")) { 322 setupDebuggerBsd(); 323 } else if (os.equals("darwin")) { 324 setupDebuggerDarwin(); 325 } else { 326 // Add support for more operating systems here 327 throw new DebuggerException("Operating system " + os + " not yet supported"); 328 } 329 330 if (isServer) { 331 RemoteDebuggerServer remote = null; 332 try { 333 remote = new RemoteDebuggerServer(debugger); 334 } 335 catch (RemoteException rem) { 336 throw new DebuggerException(rem); 337 } 338 RMIHelper.rebind(serverID, remote); 339 } 340 } else { 341 // 342 // Remote mode (client attaching to server) 343 // 344 345 // Create and install a security manager 346 347 // FIXME: currently commented out because we were having 348 // security problems since we're "in the sun.* hierarchy" here. 349 // Perhaps a permissive policy file would work around this. In 350 // the long run, will probably have to move into com.sun.*. 351 352 // if (System.getSecurityManager() == null) { 353 // System.setSecurityManager(new RMISecurityManager()); 354 // } 355 356 connectRemoteDebugger(); 357 } 358 } 359 360 private void setupVM() { 361 // We need to instantiate a HotSpotTypeDataBase on both the client 362 // and server machine. On the server it is only currently used to 363 // configure the Java primitive type sizes (which we should 364 // consider making constant). On the client it is used to 365 // configure the VM. 366 367 try { 368 if (os.equals("solaris")) { 369 db = new HotSpotTypeDataBase(machDesc, 370 new HotSpotSolarisVtblAccess(debugger, jvmLibNames), 371 debugger, jvmLibNames); 372 } else if (os.equals("win32")) { 373 db = new HotSpotTypeDataBase(machDesc, 374 new Win32VtblAccess(debugger, jvmLibNames), 375 debugger, jvmLibNames); 376 } else if (os.equals("linux")) { 377 db = new HotSpotTypeDataBase(machDesc, 378 new LinuxVtblAccess(debugger, jvmLibNames), 379 debugger, jvmLibNames); 380 } else if (os.equals("bsd")) { 381 db = new HotSpotTypeDataBase(machDesc, 382 new BsdVtblAccess(debugger, jvmLibNames), 383 debugger, jvmLibNames); 384 } else if (os.equals("darwin")) { 385 db = new HotSpotTypeDataBase(machDesc, 386 new BsdVtblAccess(debugger, jvmLibNames), 387 debugger, jvmLibNames); 388 } else { 389 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)"); 390 } 391 } 392 catch (NoSuchSymbolException e) { 393 throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" + 394 e.getSymbol() + "\" in remote process)"); 395 } 396 397 if (startupMode != REMOTE_MODE) { 398 // Configure the debugger with the primitive type sizes just obtained from the VM 399 debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(), 400 db.getJByteType().getSize(), 401 db.getJCharType().getSize(), 402 db.getJDoubleType().getSize(), 403 db.getJFloatType().getSize(), 404 db.getJIntType().getSize(), 405 db.getJLongType().getSize(), 406 db.getJShortType().getSize()); 407 } 408 409 if (!isServer) { 410 // Do not initialize the VM on the server (unnecessary, since it's 411 // instantiated on the client) 412 try { 413 VM.initialize(db, debugger); 414 } catch (DebuggerException e) { 415 throw (e); 416 } catch (Exception e) { 417 throw new DebuggerException(e); 418 } 419 } 420 } 421 422 //-------------------------------------------------------------------------------- 423 // OS-specific debugger setup/connect routines 424 // 425 426 // 427 // Solaris 428 // 429 430 private void setupDebuggerSolaris() { 431 setupJVMLibNamesSolaris(); 432 ProcDebuggerLocal dbg = new ProcDebuggerLocal(null, true); 433 debugger = dbg; 434 attachDebugger(); 435 436 // Set up CPU-dependent stuff 437 if (cpu.equals("x86")) { 438 machDesc = new MachineDescriptionIntelX86(); 439 } else if (cpu.equals("sparc")) { 440 int addressSize = dbg.getRemoteProcessAddressSize(); 441 if (addressSize == -1) { 442 throw new DebuggerException("Error occurred while trying to determine the remote process's " + 443 "address size"); 444 } 445 446 if (addressSize == 32) { 447 machDesc = new MachineDescriptionSPARC32Bit(); 448 } else if (addressSize == 64) { 449 machDesc = new MachineDescriptionSPARC64Bit(); 450 } else { 451 throw new DebuggerException("Address size " + addressSize + " is not supported on SPARC"); 452 } 453 } else if (cpu.equals("amd64")) { 454 machDesc = new MachineDescriptionAMD64(); 455 } else { 456 throw new DebuggerException("Solaris only supported on sparc/sparcv9/x86/amd64"); 457 } 458 459 dbg.setMachineDescription(machDesc); 460 return; 461 } 462 463 private void connectRemoteDebugger() throws DebuggerException { 464 RemoteDebugger remote = 465 (RemoteDebugger) RMIHelper.lookup(debugServerID); 466 debugger = new RemoteDebuggerClient(remote); 467 machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription(); 468 os = debugger.getOS(); 469 if (os.equals("solaris")) { 470 setupJVMLibNamesSolaris(); 471 } else if (os.equals("win32")) { 472 setupJVMLibNamesWin32(); 473 } else if (os.equals("linux")) { 474 setupJVMLibNamesLinux(); 475 } else if (os.equals("bsd")) { 476 setupJVMLibNamesBsd(); 477 } else if (os.equals("darwin")) { 478 setupJVMLibNamesDarwin(); 479 } else { 480 throw new RuntimeException("Unknown OS type"); 481 } 482 483 cpu = debugger.getCPU(); 484 } 485 486 private void setupJVMLibNamesSolaris() { 487 jvmLibNames = new String[] { "libjvm.so" }; 488 } 489 490 // 491 // Win32 492 // 493 494 private void setupDebuggerWin32() { 495 setupJVMLibNamesWin32(); 496 497 if (cpu.equals("x86")) { 498 machDesc = new MachineDescriptionIntelX86(); 499 } else if (cpu.equals("amd64")) { 500 machDesc = new MachineDescriptionAMD64(); 501 } else if (cpu.equals("ia64")) { 502 machDesc = new MachineDescriptionIA64(); 503 } else { 504 throw new DebuggerException("Win32 supported under x86, amd64 and ia64 only"); 505 } 506 507 // Note we do not use a cache for the local debugger in server 508 // mode; it will be taken care of on the client side (once remote 509 // debugging is implemented). 510 511 debugger = new WindbgDebuggerLocal(machDesc, !isServer); 512 513 attachDebugger(); 514 515 // FIXME: add support for server mode 516 } 517 518 private void setupJVMLibNamesWin32() { 519 jvmLibNames = new String[] { "jvm.dll" }; 520 } 521 522 // 523 // Linux 524 // 525 526 private void setupDebuggerLinux() { 527 setupJVMLibNamesLinux(); 528 529 if (cpu.equals("x86")) { 530 machDesc = new MachineDescriptionIntelX86(); 531 } else if (cpu.equals("ia64")) { 532 machDesc = new MachineDescriptionIA64(); 533 } else if (cpu.equals("amd64")) { 534 machDesc = new MachineDescriptionAMD64(); 535 } else if (cpu.equals("sparc")) { 536 if (LinuxDebuggerLocal.getAddressSize()==8) { 537 machDesc = new MachineDescriptionSPARC64Bit(); 538 } else { 539 machDesc = new MachineDescriptionSPARC32Bit(); 540 } 541 } else { 542 try { 543 machDesc = (MachineDescription) 544 Class.forName("sun.jvm.hotspot.debugger.MachineDescription" + 545 cpu.toUpperCase()).newInstance(); 546 } catch (Exception e) { 547 throw new DebuggerException("Linux not supported on machine type " + cpu); 548 } 549 } 550 551 LinuxDebuggerLocal dbg = 552 new LinuxDebuggerLocal(machDesc, !isServer); 553 debugger = dbg; 554 555 attachDebugger(); 556 } 557 558 private void setupJVMLibNamesLinux() { 559 jvmLibNames = new String[] { "libjvm.so" }; 560 } 561 562 // 563 // BSD 564 // 565 566 private void setupDebuggerBsd() { 567 setupJVMLibNamesBsd(); 568 569 if (cpu.equals("x86")) { 570 machDesc = new MachineDescriptionIntelX86(); 571 } else if (cpu.equals("amd64") || cpu.equals("x86_64")) { 572 machDesc = new MachineDescriptionAMD64(); 573 } else { 574 throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu); 575 } 576 577 BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer); 578 debugger = dbg; 579 580 attachDebugger(); 581 } 582 583 private void setupJVMLibNamesBsd() { 584 jvmLibNames = new String[] { "libjvm.so" }; 585 } 586 587 // 588 // Darwin 589 // 590 591 private void setupDebuggerDarwin() { 592 setupJVMLibNamesDarwin(); 593 594 if (cpu.equals("amd64") || cpu.equals("x86_64")) { 595 machDesc = new MachineDescriptionAMD64(); 596 } else { 597 throw new DebuggerException("Darwin only supported on 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 setupJVMLibNamesDarwin() { 607 jvmLibNames = new String[] { "libjvm.dylib" }; 608 } 609 610 /** Convenience routine which should be called by per-platform 611 debugger setup. Should not be called when startupMode is 612 REMOTE_MODE. */ 613 private void attachDebugger() { 614 if (startupMode == PROCESS_MODE) { 615 debugger.attach(pid); 616 } else if (startupMode == CORE_FILE_MODE) { 617 debugger.attach(javaExecutableName, coreFileName); 618 } else { 619 throw new DebuggerException("Should not call attach() for startupMode == " + startupMode); 620 } 621 } 622 }