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.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             connectRemoteDebugger();
395         }
396     }
397 
398     private void setupVM() {
399         // We need to instantiate a HotSpotTypeDataBase on both the client
400         // and server machine. On the server it is only currently used to
401         // configure the Java primitive type sizes (which we should
402         // consider making constant). On the client it is used to
403         // configure the VM.
404 
405         try {
406             if (os.equals("win32")) {
407                 db = new HotSpotTypeDataBase(machDesc,
408                 new Win32VtblAccess(debugger, jvmLibNames),
409                 debugger, jvmLibNames);
410             } else if (os.equals("linux")) {
411                 db = new HotSpotTypeDataBase(machDesc,
412                 new LinuxVtblAccess(debugger, jvmLibNames),
413                 debugger, jvmLibNames);
414             } else if (os.equals("bsd")) {
415                 db = new HotSpotTypeDataBase(machDesc,
416                 new BsdVtblAccess(debugger, jvmLibNames),
417                 debugger, jvmLibNames);
418             } else if (os.equals("darwin")) {
419                 db = new HotSpotTypeDataBase(machDesc,
420                 new BsdVtblAccess(debugger, jvmLibNames),
421                 debugger, jvmLibNames);
422             } else {
423                 throw new DebuggerException("OS \"" + os + "\" not yet supported (no VtblAccess yet)");
424             }
425         }
426         catch (NoSuchSymbolException e) {
427             throw new DebuggerException("Doesn't appear to be a HotSpot VM (could not find symbol \"" +
428             e.getSymbol() + "\" in remote process)");
429         }
430 
431         if (startupMode != REMOTE_MODE) {
432             // Configure the debugger with the primitive type sizes just obtained from the VM
433             debugger.configureJavaPrimitiveTypeSizes(db.getJBooleanType().getSize(),
434             db.getJByteType().getSize(),
435             db.getJCharType().getSize(),
436             db.getJDoubleType().getSize(),
437             db.getJFloatType().getSize(),
438             db.getJIntType().getSize(),
439             db.getJLongType().getSize(),
440             db.getJShortType().getSize());
441         }
442 
443         try {
444             VM.initialize(db, debugger);
445         } catch (DebuggerException e) {
446             throw (e);
447         } catch (Exception e) {
448             throw new DebuggerException(e);
449         }
450     }
451 
452     //--------------------------------------------------------------------------------
453     // OS-specific debugger setup/connect routines
454     //
455 
456     // Use the existing JVMDebugger, as passed to our constructor.
457     // Retrieve os and cpu from that debugger, not the current platform.
458     private void setupDebuggerExisting() {
459 
460         os = debugger.getOS();
461         cpu = debugger.getCPU();
462         setupJVMLibNames(os);
463         machDesc = debugger.getMachineDescription();
464     }
465 
466     // Given a classname, load an alternate implementation of JVMDebugger.
467     private void setupDebuggerAlternate(String alternateName) {
468 
469         try {
470             Class<?> c = Class.forName(alternateName);
471             Constructor cons = c.getConstructor();
472             debugger = (JVMDebugger) cons.newInstance();
473             attachDebugger();
474             setupDebuggerExisting();
475 
476         } catch (ClassNotFoundException cnfe) {
477             throw new DebuggerException("Cannot find alternate SA Debugger: '" + alternateName + "'");
478         } catch (NoSuchMethodException nsme) {
479             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' has missing constructor.");
480         } catch (InstantiationException ie) {
481             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", ie);
482         } catch (IllegalAccessException iae) {
483             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae);
484         } catch (InvocationTargetException iae) {
485             throw new DebuggerException("Alternate SA Debugger: '" + alternateName + "' fails to initialise: ", iae);
486         }
487 
488         System.err.println("Loaded alternate HotSpot SA Debugger: " + alternateName);
489     }
490 
491     private void connectRemoteDebugger() throws DebuggerException {
492         RemoteDebugger remote =
493         (RemoteDebugger) RMIHelper.lookup(debugServerID);
494         debugger = new RemoteDebuggerClient(remote);
495         machDesc = ((RemoteDebuggerClient) debugger).getMachineDescription();
496         os = debugger.getOS();
497         setupJVMLibNames(os);
498         cpu = debugger.getCPU();
499     }
500 
501     private void setupJVMLibNames(String os) {
502         if (os.equals("win32")) {
503             setupJVMLibNamesWin32();
504         } else if (os.equals("linux")) {
505             setupJVMLibNamesLinux();
506         } else if (os.equals("bsd")) {
507             setupJVMLibNamesBsd();
508         } else if (os.equals("darwin")) {
509             setupJVMLibNamesDarwin();
510         } else {
511             throw new RuntimeException("Unknown OS type");
512         }
513     }
514 
515     //
516     // Win32
517     //
518 
519     private void setupDebuggerWin32() {
520         setupJVMLibNamesWin32();
521 
522         if (cpu.equals("x86")) {
523             machDesc = new MachineDescriptionIntelX86();
524         } else if (cpu.equals("amd64")) {
525             machDesc = new MachineDescriptionAMD64();
526         } else if (cpu.equals("aarch64")) {
527             machDesc = new MachineDescriptionAArch64();
528         } else {
529             throw new DebuggerException("Win32 supported under x86, amd64 and aarch64 only");
530         }
531 
532         // Note we do not use a cache for the local debugger in server
533         // mode; it will be taken care of on the client side (once remote
534         // debugging is implemented).
535 
536         debugger = new WindbgDebuggerLocal(machDesc, !isServer);
537 
538         attachDebugger();
539 
540         // FIXME: add support for server mode
541     }
542 
543     private void setupJVMLibNamesWin32() {
544         jvmLibNames = new String[] { "jvm.dll" };
545     }
546 
547     //
548     // Linux
549     //
550 
551     private void setupDebuggerLinux() {
552         setupJVMLibNamesLinux();
553 
554         if (cpu.equals("x86")) {
555             machDesc = new MachineDescriptionIntelX86();
556         } else if (cpu.equals("amd64")) {
557             machDesc = new MachineDescriptionAMD64();
558         } else if (cpu.equals("ppc64")) {
559             machDesc = new MachineDescriptionPPC64();
560         } else if (cpu.equals("aarch64")) {
561             machDesc = new MachineDescriptionAArch64();
562         } else if (cpu.equals("riscv64")) {
563             machDesc = new MachineDescriptionRISCV64();
564         } else {
565           try {
566             machDesc = (MachineDescription)
567               Class.forName("sun.jvm.hotspot.debugger.MachineDescription" +
568                             cpu.toUpperCase()).getDeclaredConstructor().newInstance();
569           } catch (Exception e) {
570             throw new DebuggerException("Linux not supported on machine type " + cpu);
571           }
572         }
573 
574         LinuxDebuggerLocal dbg =
575         new LinuxDebuggerLocal(machDesc, !isServer);
576         debugger = dbg;
577 
578         attachDebugger();
579     }
580 
581     private void setupJVMLibNamesLinux() {
582         jvmLibNames = new String[] { "libjvm.so" };
583     }
584 
585     //
586     // BSD
587     //
588 
589     private void setupDebuggerBsd() {
590         setupJVMLibNamesBsd();
591 
592         if (cpu.equals("x86")) {
593             machDesc = new MachineDescriptionIntelX86();
594         } else if (cpu.equals("amd64") || cpu.equals("x86_64")) {
595             machDesc = new MachineDescriptionAMD64();
596         } else {
597             throw new DebuggerException("BSD only supported on x86/x86_64. Current arch: " + cpu);
598         }
599 
600         BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer);
601         debugger = dbg;
602 
603         attachDebugger();
604     }
605 
606     private void setupJVMLibNamesBsd() {
607         jvmLibNames = new String[] { "libjvm.so" };
608     }
609 
610     //
611     // Darwin
612     //
613 
614     private void setupDebuggerDarwin() {
615         setupJVMLibNamesDarwin();
616 
617         if (cpu.equals("amd64") || cpu.equals("x86_64")) {
618             machDesc = new MachineDescriptionAMD64();
619         } else if (cpu.equals("aarch64")) {
620             machDesc = new MachineDescriptionAArch64();
621         } else {
622             throw new DebuggerException("Darwin only supported on x86_64/aarch64. Current arch: " + cpu);
623         }
624 
625         BsdDebuggerLocal dbg = new BsdDebuggerLocal(machDesc, !isServer);
626         debugger = dbg;
627 
628         attachDebugger();
629     }
630 
631     private void setupJVMLibNamesDarwin() {
632         jvmLibNames = new String[] { "libjvm.dylib" };
633     }
634 
635     /** Convenience routine which should be called by per-platform
636       debugger setup. Should not be called when startupMode is
637       REMOTE_MODE. */
638     private void attachDebugger() {
639         if (startupMode == PROCESS_MODE) {
640             debugger.attach(pid);
641         } else if (startupMode == CORE_FILE_MODE) {
642             debugger.attach(javaExecutableName, coreFileName);
643         } else {
644             throw new DebuggerException("Should not call attach() for startupMode == " + startupMode);
645         }
646     }
647 
648     public int getStartupMode() {
649         return startupMode;
650     }
651 }
--- EOF ---