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