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