blob: 9d87ef363922f90e5cd8911deb546432a49d1178 [file] [log] [blame]
/*
* 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;
}
}