| /* |
| * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package nsk.share.jdi; |
| |
| import nsk.share.*; |
| import nsk.share.jpda.*; |
| |
| import com.sun.jdi.*; |
| import com.sun.jdi.request.*; |
| import com.sun.jdi.event.*; |
| |
| import java.util.*; |
| |
| /** |
| * This class is used to interact with debugee VM using JDI features. |
| * <p> |
| * This class is wrapper for debugee VM constructed by <code>Binder</code> |
| * and it uses <code>com.sun.jdi.VirtualMachine</code> to interact with debugee VM. |
| * <p> |
| * In addition to the general abities to control of debugee VM process, |
| * provided by the base class <code>DebugeeProcess</code>, this class |
| * adds also several service methods over the JDI features to simplify interaction |
| * with debugee VM (such as finding classes, setting breakpoints, |
| * handling events, and so on.). |
| * |
| * @see Binder |
| * @see DebugeeProcess |
| */ |
| abstract public class Debugee extends DebugeeProcess { |
| /** |
| * Mirror of the debugee VM. This must be initialized by every |
| * particular non-abstract class extending Debugee class. |
| */ |
| protected VirtualMachine vm = null; |
| |
| /** Binder that created this debugee. */ |
| protected Binder binder = null; |
| |
| /** Argument handler. */ |
| protected ArgumentHandler argumentHandler = null; |
| |
| /** Create new <code>Debugee</code> object for a given binder. */ |
| protected Debugee (Binder binder) { |
| super(binder); |
| this.binder = binder; |
| this.argumentHandler = (ArgumentHandler)binder.getArgumentHandler(); |
| } |
| |
| /** Setup <code>Debugee</code> object with given VM mirror. */ |
| public void setupVM(VirtualMachine vm) { |
| if (this.vm != null) { |
| throw new TestBug("Setting duplicated VM mirror for Debugee object"); |
| } |
| this.vm = vm; |
| int traceMode = argumentHandler.getTraceMode(); |
| if (traceMode != VirtualMachine.TRACE_NONE) { |
| display("Setting JDI trace mode to: " + argumentHandler.getTraceModeString()); |
| setDebugTraceMode(traceMode); |
| } |
| } |
| |
| /** Return <code>Binder</code> of the debugee object. */ |
| public Binder getBinder() { |
| return binder; |
| } |
| |
| /** Return JDI mirror of the debugee VM. */ |
| public VirtualMachine VM() { |
| return vm; |
| } |
| |
| /** Return <code>EventRequestManager</code> of the debugee object. */ |
| public EventRequestManager getEventRequestManager() { |
| return vm.eventRequestManager(); |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** List of the currently running threads. */ |
| public ThreadReference[] threads () { |
| List list = vm.allThreads(); |
| int size = list.size(); |
| ThreadReference array[] = new ThreadReference[size]; |
| Iterator iterator = list.iterator(); |
| for (int i = 0; i < size; i++) |
| array[i] = (ThreadReference) iterator.next(); |
| if (iterator.hasNext()) |
| throw new Oddity("extra element in a list?"); |
| return array; |
| } |
| |
| /** List of all types loaded by the debugee VM. */ |
| public ReferenceType[] classes() { |
| return classes(null); |
| } |
| |
| /** |
| * List of all classes of the given <code>name</code> loaded by |
| * the debugee VM; or list of all classes, if <code>name</code> |
| * is <code>null</code>. |
| */ |
| private ReferenceType[] classes (String name) { |
| List list = (name==null)? vm.allClasses(): vm.classesByName(name); |
| int size = list.size(); |
| ReferenceType array[] = new ReferenceType [ size ]; |
| Iterator iterator = list.iterator(); |
| for (int i=0; i<size; i++) |
| array[i] = (ReferenceType) iterator.next(); |
| if (iterator.hasNext()) |
| throw new Oddity("extra element in a list?"); |
| return array; |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** |
| * Return mirror for the only class of the given <code>name</code> |
| * loaded by the debugee VM; or throw TestBug exception if there |
| * are more than one such class found. TestFailure exception |
| * will be thrown in case when mirrors for classes with different |
| * names or duplicated mirrors were returned. |
| * Return <code>null</code> if there is no such class loaded. |
| */ |
| public ReferenceType classByName (String name) { |
| ReferenceType classes[] = this.classes(name); |
| |
| // if on first call debuggee doesn't return needed class try get this class one more time after delay to avoid 6446633 |
| if (classes == null || classes.length == 0) { |
| try { |
| Thread.sleep(1000); |
| } |
| catch(InterruptedException e) { |
| throw new TestBug("Unexpected InterruptedException"); |
| } |
| |
| classes = this.classes(name); |
| } |
| |
| if (classes == null || classes.length == 0) |
| return null; |
| |
| // analyze returned mirrors and throw appropriate exception |
| if (classes.length > 1) { |
| boolean duplicatesFound = false; |
| boolean differentNamesFound = false; |
| boolean visited[] = new boolean[classes.length]; |
| complain("Classes that were found for name \"" + name + "\":"); |
| for(ReferenceType klass : classes) { |
| complain("\t" + klass); |
| } |
| for(int c = 0; c < classes.length; c++) { |
| if(visited[c]) { |
| continue; |
| } |
| if(!classes[c].name().equals(name)) { |
| differentNamesFound = true; |
| continue; |
| } |
| for(int i = c + 1; i < classes.length; i++) { |
| if(visited[i]) { |
| continue; |
| } else { |
| visited[i] = true; |
| } |
| if(classes[c].classLoader() == classes[i].classLoader()) { |
| duplicatesFound = true; |
| } |
| } |
| } |
| if(duplicatesFound) { |
| throw new TestFailure("classes with the same name and " + |
| "loaded with the same class loader " + |
| "were found."); |
| } else if(differentNamesFound) { |
| throw new TestFailure("class with name different from '" + name + |
| "' was returned by VirutualMachine.classesByName."); |
| } else { |
| throw new TestBug("found " + classes.length + " such classes: " + name); |
| } |
| } |
| return classes[0]; |
| } |
| |
| /** |
| * Return mirror for the only method of the given <code>refType</code> |
| * class in the debugee VM; or throw TestBug exception if there |
| * are more than one such method found. Return <code>null</code> if |
| * there is no such method found. |
| */ |
| public Method methodByName(ReferenceType refType, String name) { |
| List methods = refType.methodsByName(name); |
| if (methods == null || methods.isEmpty()) return null; |
| if (methods.size() > 1) |
| throw new TestBug( |
| "found " + methods.size() + " such methods: " + name); |
| Method method = (Method)methods.get(0); |
| return method; |
| } |
| |
| /** |
| * Return a currently running thread of the given <code>name</code>; or |
| * throw TestBug exception if there are more than one such thread found. |
| * Return <code>null</code> if there is no such thread. |
| */ |
| public ThreadReference threadByName (String name) { |
| ThreadReference threads[] = this.threads(); |
| int count = 0, index = -1; |
| for (int i = 0; i < threads.length; i++) { |
| if (threads[i].name().compareTo(name)==0) { |
| count++; |
| index = i; |
| } |
| } |
| if (count == 0) |
| return null; |
| if (count > 1) |
| throw new TestBug( |
| "found " + count + " such threads: " + name); |
| return threads[index]; |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** |
| * Returns Location object for given line number in specified method or null |
| * if no location for this line is found. |
| * |
| * @param method method mirror containing given line number |
| * @param line line number to find location |
| */ |
| public Location getLineLocation(Method method, int line) { |
| List locs = null; |
| try { |
| locs = method.allLineLocations(); |
| } catch(AbsentInformationException e) { |
| throw new TestBug("Unable to find location for line " + line + ": " + e); |
| } |
| Iterator iter = locs.iterator(); |
| while (iter.hasNext()) { |
| Location location = (Location)iter.next(); |
| if (location.lineNumber() == line) { |
| return location; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns Location object for given line number in specified reference type or null |
| * if no location for this line is found. |
| * |
| * @param refType reference type mirror containing given line number |
| * @param line line number to find location |
| */ |
| public Location getLineLocation(ReferenceType refType, int line) { |
| List locs = null; |
| try { |
| locs = refType.allLineLocations(); |
| } catch(AbsentInformationException e) { |
| throw new TestBug("Unable to find location for line " + line + ": " + e); |
| } |
| Iterator iter = locs.iterator(); |
| while (iter.hasNext()) { |
| Location location = (Location)iter.next(); |
| if (location.lineNumber() == line) { |
| return location; |
| } |
| } |
| return null; |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** |
| * Make disabled breakpoint to given location and return BreakpointRequest. |
| * |
| * @param location location to set breakpoint |
| * |
| * @see #setBreakpoint(Method, int) |
| * @see #setBreakpoint(ReferenceType, String, int) |
| */ |
| public BreakpointRequest makeBreakpoint(Location location) { |
| EventRequestManager evm = getEventRequestManager(); |
| BreakpointRequest request = evm.createBreakpointRequest(location); |
| display("Breakpoint set:\n\t" + request); |
| return request; |
| } |
| |
| /** |
| * Make disabled breakpoint to given line number in specified method |
| * and return BreakpointRequest. |
| * |
| * @param method method mirror to set breakpoint |
| * @param lineNumber line number inside the method |
| * |
| * @throws Failure if no location found for specified line number |
| * |
| * @see #makeBreakpoint(Location) |
| * @see #makeBreakpoint(ReferenceType, String, int) |
| */ |
| public BreakpointRequest makeBreakpoint(Method method, int lineNumber) { |
| Location location = getLineLocation(method, lineNumber); |
| if (location == null) { |
| throw new Failure("No location found for setting breakpoint to line " + lineNumber); |
| } |
| return makeBreakpoint(location); |
| } |
| |
| /** |
| * Make disabled breakpoint to given line number for specified method name |
| * of the given reference type and return BreakpointRequest. |
| * |
| * @param refType reference type for specified method |
| * @param methodName method name to set breakpoint |
| * @param lineNumber line number inside the method |
| * |
| * @throws Failure if no location found for specified line number |
| * |
| * @see #makeBreakpoint(Method, int) |
| */ |
| public BreakpointRequest makeBreakpoint(ReferenceType refType, |
| String methodName, int lineNumber) { |
| Method method = methodByName(refType, methodName); |
| if (method == null) { |
| throw new Failure("No method found for setting breakpoint: " + methodName); |
| } |
| return makeBreakpoint(method, lineNumber); |
| } |
| |
| /** |
| * Set and enable breakpoint to given line number for specified method |
| * and return BreakpointRequest. |
| * |
| * @param method method mirror to set breakpoint |
| * @param lineNumber line number inside the method |
| * |
| * @throws Failure if no location found for specified line number |
| * |
| * @see #setBreakpoint(ReferenceType, String, int) |
| */ |
| public BreakpointRequest setBreakpoint(Method method, int lineNumber) { |
| BreakpointRequest request = makeBreakpoint(method, lineNumber); |
| request.enable(); |
| return request; |
| } |
| |
| /** |
| * Set and enable breakpoint to given line number for specified method name |
| * of the given reference type and return BreakpointRequest. |
| * |
| * @param refType reference type for specified method |
| * @param methodName method name to set breakpoint |
| * @param lineNumber line number inside the method |
| * |
| * @throws Failure if no location found for specified line number |
| * |
| * @see #setBreakpoint(Method, int) |
| */ |
| public BreakpointRequest setBreakpoint(ReferenceType refType, |
| String methodName, int lineNumber) { |
| BreakpointRequest request = makeBreakpoint(refType, methodName, lineNumber); |
| request.enable(); |
| return request; |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** Suspend the debugee VM. */ |
| public void suspend() { |
| vm.suspend(); |
| } |
| |
| /** Resume the debugee VM. */ |
| public void resume() { |
| vm.resume(); |
| } |
| |
| /** Dispose the debugee VM. */ |
| public void dispose() { |
| vm.dispose(); |
| } |
| |
| /* |
| * Set internal JDI tracing mode. |
| */ |
| public void setDebugTraceMode(int traceMode) { |
| vm.setDebugTraceMode(traceMode); |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** |
| * Wait for the requested event and skip other events. |
| * |
| * @param request non-null value for events generated by this |
| * event request; null value for <code>VMStartEvent</code>. |
| * @param timeout timeout in milliseconds to wait for the requested event. |
| * |
| * @throws InterruptedException if another thread has interrupted this thread |
| */ |
| public Event waitingEvent(EventRequest request, long timeout) |
| throws InterruptedException { |
| |
| if (request == null) { |
| throw new Failure("Null request specified for waiting events: " + request); |
| } |
| |
| long timeToFinish = System.currentTimeMillis() + timeout; |
| long timeLeft = timeout; |
| boolean exit = false; |
| |
| display("Waiting for event by request:\n\t" + request); |
| |
| EventQueue eventQueue = vm.eventQueue(); |
| while (timeLeft > 0 && !exit) { |
| |
| EventSet eventSet = eventQueue.remove(timeLeft); |
| if (eventSet == null) { |
| continue; |
| } |
| |
| EventIterator eventIterator = eventSet.eventIterator(); |
| while (eventIterator.hasNext()) { |
| |
| Event event = eventIterator.nextEvent(); |
| EventRequest eventRequest = event.request(); |
| |
| if (request == eventRequest || request.equals(eventRequest)) { |
| display("Got requested event:\n\t" + event); |
| return event; |
| } else if (event instanceof VMDeathEvent) { |
| display("Ignore unexpected VMDeathEvent"); |
| } else if (event instanceof VMDisconnectEvent) { |
| display("Got unexpected VMDisconnectEvent"); |
| exit = true; |
| break; |
| } else { |
| display("Ignore unexpected event:\n\t" + event); |
| } // if |
| |
| } // while |
| |
| timeLeft = timeToFinish - System.currentTimeMillis(); |
| |
| } // while |
| |
| return null; |
| } |
| |
| /* |
| * Wait for VM to initialize by receiving initial VM_START event for specified timeout. |
| */ |
| public void waitForVMInit(long timeout) { |
| waitForVMInit(vm ,log, timeout); |
| } |
| |
| /* |
| * This static method is also used by nsk.share.jdi.ConnectorTest |
| */ |
| static public void waitForVMInit(VirtualMachine vm, Log log, long timeout) { |
| try { |
| EventSet eventSet = vm.eventQueue().remove(timeout); |
| if (eventSet == null) { |
| throw new Failure("No VMStartEvent received for timeout: " + timeout + " ms"); |
| } |
| EventIterator iterator = eventSet.eventIterator(); |
| while (iterator.hasNext()) { |
| Event event = iterator.nextEvent(); |
| if (event == null) { |
| throw new Failure("Null event received instead of VMStartEvent"); |
| } |
| if (event instanceof VMStartEvent) { |
| log.display("Initial VMStartEvent received: " + event); |
| } else { |
| throw new Failure("Unexpected event received instead of VMStartEvent: " + event); |
| } |
| } |
| int suspendPolicy = eventSet.suspendPolicy(); |
| if (suspendPolicy != EventRequest.SUSPEND_ALL) { |
| throw new Failure("Suspend policy of VMStartEvent is not SUSPEND_ALL: " + suspendPolicy); |
| } |
| } catch (InterruptedException e) { |
| e.printStackTrace(log.getOutStream()); |
| throw new Failure("Thread interrupted while waiting for VMStartEvent:\n\t" + e); |
| } |
| } |
| |
| // --------------------------------------------------- // |
| |
| /** |
| * Bind to debuggee VM using <code>Binder</code> and make initial |
| * synchronization via IOPipe. |
| * |
| * @param argHandler command line arguments handler to make <code>Binder</code> object |
| * @param log <code>Log</code> object to log messages |
| * @param mainClassName main class of debugee |
| * |
| * @throws Failure if there were problems with binding to debuggee VM |
| * |
| * @see Binder#bindToDebugee(String) |
| */ |
| public static Debugee prepareDebugee(ArgumentHandler argHandler, Log log, |
| String mainClassName) { |
| Binder binder = new Binder(argHandler, log); |
| Debugee debugee = binder.bindToDebugee(mainClassName); |
| |
| debugee.createIOPipe(); |
| |
| debugee.redirectStderr(log, DEBUGEE_STDERR_LOG_PREFIX); |
| debugee.resume(); |
| |
| debugee.receiveExpectedSignal("ready"); |
| |
| return debugee; |
| } |
| |
| /** |
| * Send <code>"quit"</code> signal, wait for debugee VM exit and check exit. |
| * |
| * @throws Failure if exit status is not <code>Consts.JCK_STATUS_BASE</code> |
| * |
| * @see #endDebugee() |
| */ |
| public void quit() { |
| sendSignal("quit"); |
| int status = endDebugee(); |
| if ( status != Consts.JCK_STATUS_BASE ) { |
| throw new Failure("Got unexpected debugee VM exit status: " + status |
| + " (not " + Consts.JCK_STATUS_BASE + ")"); |
| } |
| display("Got expected debugee VM exit status: " + status); |
| } |
| |
| /* |
| * Dispose debuggee VM, wait for it to exit, close all resources and return |
| * exit status code. |
| */ |
| public int endDebugee() { |
| if (vm != null) { |
| try { |
| vm.dispose(); |
| } catch (VMDisconnectedException ignore) { |
| } |
| vm = null; |
| } |
| return waitFor(); |
| } |
| |
| /* |
| * Print information about all threads in debuggee VM |
| */ |
| protected void printThreadsInfo(VirtualMachine vm) { |
| try { |
| log.display("------------ Try to print debuggee threads before killing process ------------"); |
| if (vm == null) { |
| log.display("Can't print threads info because 'vm' is null"); |
| return; |
| } |
| List<ThreadReference> threads = vm.allThreads(); |
| log.display("Threads: " + threads); |
| log.display("Total threads: " + threads.size()); |
| for (ThreadReference thread : threads) { |
| log.display("\nThread: " + thread.name()); |
| log.display("Is suspended: " + thread.isSuspended()); |
| log.display("Is at breakpoint: " + thread.isAtBreakpoint()); |
| boolean wasSuspended = false; |
| try { |
| if (!thread.isSuspended()) { |
| log.display("\n suspend thread to get its stack \n"); |
| thread.suspend(); |
| wasSuspended = true; |
| } |
| log.display("Stack frame count: " + thread.frameCount()); |
| if (thread.frameCount() > 0) { |
| log.display("Frames:"); |
| for (StackFrame frame : thread.frames()) { |
| Location location = frame.location(); |
| log.display(location.declaringType().name() + "." + location.method().name() + ", line: " + location.lineNumber()); |
| } |
| } |
| } finally { |
| if (wasSuspended) { |
| log.display("\n resume thread \n"); |
| thread.resume(); |
| } |
| } |
| } |
| log.display("----------------------------------------------------------------------"); |
| } catch (Throwable t) { |
| log.complain(""); |
| t.printStackTrace(log.getOutStream()); |
| } |
| } |
| |
| /** |
| * Force debugge VM to exit using JDI interface if possible. |
| */ |
| protected void killDebugee() { |
| try { |
| // print information about debuggee threads to simplify failure analysis |
| printThreadsInfo(vm); |
| } finally { |
| if (vm != null) { |
| try { |
| display("Killing debuggee by forcing target VM to exit"); |
| vm.exit(97); |
| display("Debugee VM successfully forced to exit"); |
| vm = null; |
| } catch (VMDisconnectedException e) { |
| display("Ignore VMDisconnectedException while forcing debuggee VM to exit:\n\t" |
| + e); |
| } |
| } |
| } |
| } |
| |
| public boolean isJFR_active() { |
| String opts = argumentHandler.getLaunchOptions(); |
| int jfrPos = opts.indexOf("-XX:+FlightRecorder"); |
| |
| if (jfrPos >= 0) |
| return true; |
| else |
| return false; |
| } |
| } |