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