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