1 /*
  2  * Copyright (c) 2000, 2022, 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             connectRemoteDebugger();
394         }
395     }
396 
397     private void setupVM() {
398         // We need to instantiate a HotSpotTypeDataBase on both the client
399         // and server machine. On the server it is only currently used to
400         // configure the Java primitive type sizes (which we should
401         // consider making constant). On the client it is used to
402         // configure the VM.
403 
404         try {
405             if (os.equals("win32")) {
406                 db = new HotSpotTypeDataBase(machDesc,
407                 new Win32VtblAccess(debugger, jvmLibNames),
408                 debugger, jvmLibNames);
409             } else if (os.equals("linux")) {
410                 db = new HotSpotTypeDataBase(machDesc,
411                 new LinuxVtblAccess(debugger, jvmLibNames),
412                 debugger, jvmLibNames);
413             } else if (os.equals("bsd")) {
414                 db = new HotSpotTypeDataBase(machDesc,
415                 new BsdVtblAccess(debugger, jvmLibNames),
416                 debugger, jvmLibNames);
417             } else if (os.equals("darwin")) {
418                 db = new HotSpotTypeDataBase(machDesc,
419                 new BsdVtblAccess(debugger, jvmLibNames),
420                 debugger, jvmLibNames);
421             } else {
422                 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)");
423             }
424         }
425         catch (NoSuchSymbolException e) {
426             throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" +
427             e.getSymbol() + "\" in remote process)");
428         }
429 
430         if (startupMode != REMOTE_MODE) {
431             // Configure the debugger with the primitive type sizes just obtained from the VM
432             debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(),
433             db.getJByteType().getSize(),
434             db.getJCharType().getSize(),
435             db.getJDoubleType().getSize(),
436             db.getJFloatType().getSize(),
437             db.getJIntType().getSize(),
438             db.getJLongType().getSize(),
439             db.getJShortType().getSize());
440         }
441 
442         try {
443             VM.initialize(db, debugger);
444         } catch (DebuggerException e) {
445             throw (e);
446         } catch (Exception e) {
447             throw new DebuggerException(e);
448         }
449     }
450 
451     //--------------------------------------------------------------------------------
452     // OS-specific debugger setup/connect routines
453     //
454 
455     // Use the existing JVMDebugger, as passed to our constructor.
456     // Retrieve os and cpu from that debugger, not the current platform.
457     private void setupDebuggerExisting() {
458 
459         os = debugger.getOS();
460         cpu = debugger.getCPU();
461         setupJVMLibNames(os);
462         machDesc = debugger.getMachineDescription();
463     }
464 
465     // Given a classname, load an alternate implementation of JVMDebugger.
466     private void setupDebuggerAlternate(String alternateName) {
467 
468         try {
469             Class<?> c = Class.forName(alternateName);
470             Constructor cons = c.getConstructor();
471             debugger = (JVMDebugger) cons.newInstance();
472             attachDebugger();
473             setupDebuggerExisting();
474 
475         } catch (ClassNotFoundException cnfe) {
476             throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'");
477         } catch (NoSuchMethodException nsme) {
478             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor.");
479         } catch (InstantiationException ie) {
480             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie);
481         } catch (IllegalAccessException iae) {
482             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae);
483         } catch (InvocationTargetException iae) {
484             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae);
485         }
486 
487         System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName);
488     }
489 
490     private void connectRemoteDebugger() throws DebuggerException {
491         RemoteDebugger remote =
492         (RemoteDebugger) RMIHelper.lookup(debugServerID);
493         debugger = new RemoteDebuggerClient(remote);
494         machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription();
495         os = debugger.getOS();
496         setupJVMLibNames(os);
497         cpu = debugger.getCPU();
498     }
499 
500     private void setupJVMLibNames(String os) {
501         if (os.equals("win32")) {
502             setupJVMLibNamesWin32();
503         } else if (os.equals("linux")) {
504             setupJVMLibNamesLinux();
505         } else if (os.equals("bsd")) {
506             setupJVMLibNamesBsd();
507         } else if (os.equals("darwin")) {
508             setupJVMLibNamesDarwin();
509         } else {
510             throw new RuntimeException("Unknown OS type");
511         }
512     }
513 
514     //
515     // Win32
516     //
517 
518     private void setupDebuggerWin32() {
519         setupJVMLibNamesWin32();
520 
521         if (cpu.equals("x86")) {
522             machDesc = new MachineDescriptionIntelX86();
523         } else if (cpu.equals("amd64")) {
524             machDesc = new MachineDescriptionAMD64();
525         } else if (cpu.equals("aarch64")) {
526             machDesc = new MachineDescriptionAArch64();
527         } else {
528             throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 only");
529         }
530 
531         // Note we do not use a cache for the local debugger in server
532         // mode; it will be taken care of on the client side (once remote
533         // debugging is implemented).
534 
535         debugger = new WindbgDebuggerLocal(machDesc, !isServer);
536 
537         attachDebugger();
538 
539         // FIXME: add support for server mode
540     }
541 
542     private void setupJVMLibNamesWin32() {
543         jvmLibNames = new String[] { "jvm.dll" };
544     }
545 
546     //
547     // Linux
548     //
549 
550     private void setupDebuggerLinux() {
551         setupJVMLibNamesLinux();
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("ppc64")) {
558             machDesc = new MachineDescriptionPPC64();
559         } else if (cpu.equals("aarch64")) {
560             machDesc = new MachineDescriptionAArch64();


561         } else {
562           try {
563             machDesc = (MachineDescription)
564               Class.forName("sun.jvm.hotspot.debugger.MachineDescription" +
565                             cpu.toUpperCase()).getDeclaredConstructor().newInstance();
566           } catch (Exception e) {
567             throw new DebuggerException("Linux not supported on machine type " + cpu);
568           }
569         }
570 
571         LinuxDebuggerLocal dbg =
572         new LinuxDebuggerLocal(machDesc, !isServer);
573         debugger = dbg;
574 
575         attachDebugger();
576     }
577 
578     private void setupJVMLibNamesLinux() {
579         jvmLibNames = new String[] { "libjvm.so" };
580     }
581 
582     //
583     // BSD
584     //
585 
586     private void setupDebuggerBsd() {
587         setupJVMLibNamesBsd();
588 
589         if (cpu.equals("x86")) {
590             machDesc = new MachineDescriptionIntelX86();
591         } else if (cpu.equals("amd64") || cpu.equals("x86_64")) {
592             machDesc = new MachineDescriptionAMD64();
593         } else {
594             throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu);
595         }
596 
597         BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer);
598         debugger = dbg;
599 
600         attachDebugger();
601     }
602 
603     private void setupJVMLibNamesBsd() {
604         jvmLibNames = new String[] { "libjvm.so" };
605     }
606 
607     //
608     // Darwin
609     //
610 
611     private void setupDebuggerDarwin() {
612         setupJVMLibNamesDarwin();
613 
614         if (cpu.equals("amd64") || cpu.equals("x86_64")) {
615             machDesc = new MachineDescriptionAMD64();
616         } else if (cpu.equals("aarch64")) {
617             machDesc = new MachineDescriptionAArch64();
618         } else {
619             throw new DebuggerException("Darwin only supported on x86_64/aarch64. Current arch: " + cpu);
620         }
621 
622         BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer);
623         debugger = dbg;
624 
625         attachDebugger();
626     }
627 
628     private void setupJVMLibNamesDarwin() {
629         jvmLibNames = new String[] { "libjvm.dylib" };
630     }
631 
632     /** Convenience routine which should be called by per-platform
633       debugger setup. Should not be called when startupMode is
634       REMOTE_MODE. */
635     private void attachDebugger() {
636         if (startupMode == PROCESS_MODE) {
637             debugger.attach(pid);
638         } else if (startupMode == CORE_FILE_MODE) {
639             debugger.attach(javaExecutableName, coreFileName);
640         } else {
641             throw new DebuggerException("Should not call attach() for startupMode == " + startupMode);
642         }
643     }
644 
645     public int getStartupMode() {
646         return startupMode;
647     }
648 }
--- EOF ---