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