1f437b30f3
Reviewed-by: kvn, cjplummer, dholmes
648 lines
23 KiB
Java
648 lines
23 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|