/* * Copyright (c) 2011, 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 vm.mlvm.share.jdi; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import nsk.share.jdi.Binder; import nsk.share.jdi.Debugee; import vm.mlvm.share.Env; import vm.mlvm.share.MlvmTest; import vm.mlvm.share.jpda.StratumUtils; import vm.share.options.Option; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.Method; import com.sun.jdi.ReferenceType; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventIterator; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.StepEvent; import com.sun.jdi.event.VMDisconnectEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.ClassPrepareRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.StepRequest; /** * Option value syntax: * *
 * breakpoints        := breakpoint breakpoints?
 * breakpoint         := implicitOpt? methodName options? stratum? subBreakpoints?
 *
 * implicitOpt        := "~"
 *
 * methodName         := STRING
 * methodName         := className "." STRING
 * className          := STRING
 *
 * options            :=
 * options            := ":" option options
 * option             := lineOption | requiredHitsOption | stepsToTraceOption
 * lineOption         := "L" INTEGER                                                     // Line number
 * requiredHitsOption := "H" INTEGER | "H*"                                              // Required number of hits
 * stepsToTraceOption  := "S" INTEGER                                                    // Steps to trace when this breakpoint is hit
 *
 * stratum            := "/" stratumName "=" stratumSourceName ":" stratumSourceLine     // Also check stratum information when this breakpoint is hit
 * stratumName        := STRING
 * stratumSourceName  := STRING
 * stratumSourceLine  := INTEGER
 *
 * subBreakpoints := "=>(" breakpoints ")"                                               // subBreakpoints are only set when its main breakpoint is hit.
 * 
*/ public abstract class JDIBreakpointTest extends MlvmTest { @Option(name="debugger.debuggeeClass", default_value="", description="Debuggee class name") public String _debuggeeClass = "DEBUGGEE-CLASS-NOT-DEFINED"; @Option(name="debugger.terminateWhenAllBPHit", default_value="", description="Hang up in specified point") public boolean _terminateWhenAllBreakpointsHit; protected static int _jdiEventWaitTimeout = 3000; private static final int MAX_EVENT_COUNT = 50000; private static final int SHORT_STACK_TRACE_FRAMES_NUM = 2; protected VirtualMachine _vm; protected EventQueue _eventQueue; private abstract static class BreakpointListIterator { List _biList; public BreakpointListIterator(List biList) { _biList = biList; } public Object go() throws Throwable { return iterate(_biList); } public Object iterate(List biList) throws Throwable { for ( BreakpointInfo bi : biList ) { Object result = apply(bi); if ( result != null ) return result; if ( bi.subBreakpoints != null ) { result = iterate(bi.subBreakpoints); if ( result != null ) return result; } } return null; } protected abstract Object apply(BreakpointInfo bi) throws Throwable; } protected String getDebuggeeClassName() throws Throwable { String debuggeeClass = _debuggeeClass.trim(); if ( debuggeeClass == null || debuggeeClass.isEmpty() ) throw new Exception("Please specify debuggee class name"); return debuggeeClass; } protected abstract List getBreakpoints(String debuggeeClassName); protected boolean getTerminateWhenAllBPHit() { return _terminateWhenAllBreakpointsHit; } protected void breakpointEventHook(BreakpointEvent bpe) {} protected void stepEventHook(StepEvent se) {} protected void classPrepareEventHook(ClassPrepareEvent cpe) {} protected void eventHook(Event e) {} @Override public boolean run() throws Throwable { JDIBreakpointTest._jdiEventWaitTimeout = getArgumentParser().getWaitTime() * 60000; boolean terminateWhenAllBPHit = getTerminateWhenAllBPHit(); final String debuggeeClass = getDebuggeeClassName(); Binder binder = new Binder((ArgumentHandler) getArgumentParser(), getLog()); Debugee debuggee = binder.bindToDebugee(debuggeeClass); if (debuggee == null) throw new Exception("Can't launch debuggee"); debuggee.redirectOutput(getLog()); _vm = debuggee.VM(); _eventQueue = _vm.eventQueue(); EventRequestManager erm = _vm.eventRequestManager(); // Breakpoints final List breakpoints = getBreakpoints(debuggeeClass); final HashMap bpClassNames = new HashMap(); new BreakpointListIterator(breakpoints) { @Override protected Object apply(BreakpointInfo bi) { if ( bi.className.isEmpty() ) bi.className = debuggeeClass; bpClassNames.put(bi.className, null); return null; } }.go(); for (String className : bpClassNames.keySet()) { Env.traceNormal("Requesting ClassPrepareEvent for [" + className + "]"); ClassPrepareRequest cpReq = erm.createClassPrepareRequest(); cpReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); cpReq.addClassFilter(className); cpReq.enable(); bpClassNames.put(className, cpReq); } _vm.resume(); StepRequest currentStepReq = null; int stepsToTrace = 0; int stepCount = 0; boolean stop = false; EVENT_LOOP: while (!stop) { EventIterator ei = getNextEvent(); Map currentLocations = new HashMap(); while (ei.hasNext()) { Event e = ei.next(); Env.traceVerbose("Got JDI event: " + e); eventHook(e); if (e instanceof VMDisconnectEvent) break EVENT_LOOP; ThreadReference thread = null; Location location = null; boolean fullStackTrace = false; if (e instanceof ClassPrepareEvent) { ClassPrepareEvent cpe = (ClassPrepareEvent) e; classPrepareEventHook(cpe); ReferenceType classRef = cpe.referenceType(); setBreakpoints(erm, breakpoints, classRef); } else if (e instanceof BreakpointEvent) { BreakpointEvent bpe = (BreakpointEvent) e; breakpointEventHook(bpe); thread = bpe.thread(); location = bpe.location(); fullStackTrace = true; } else if (e instanceof StepEvent) { StepEvent se = (StepEvent) e; stepEventHook(se); thread = se.thread(); location = se.location(); } if (thread != null) { try { Env.traceDebug("Event thread suspends: " + thread.suspendCount()); if (thread.suspendCount() > 0) Env.traceDebug("Stack trace:" + getStackTraceStr(thread.frames(), fullStackTrace)); currentLocations.put(location, thread); } catch (IncompatibleThreadStateException ex) { Env.traceNormal("Exception: ", ex); } } if (++stepCount > MAX_EVENT_COUNT) { Env.display("Maximum number of events reached (" + MAX_EVENT_COUNT + ") for this test. Exiting."); stop = true; } } for (Map.Entry e : currentLocations.entrySet()) { final Location location = e.getKey(); final ThreadReference thread = e.getValue(); BreakpointInfo bpInfo = (BreakpointInfo) new BreakpointListIterator(breakpoints) { @Override protected Object apply(BreakpointInfo bi) throws Throwable { if ( location.method().name().equals(bi.methodName) && location.codeIndex() == bi.bci ) return bi; else return null; } }.go(); int s = 0; if (bpInfo != null) { Env.traceNormal("Execution hit our breakpoint: [" + bpInfo.methodName + ":" + bpInfo.methodLine + "] on step " + stepCount); bpInfo.hits++; s = bpInfo.stepsToTrace; if (bpInfo.stratumInfo != null) { if ( ! StratumUtils.checkStratum(location, bpInfo.stratumInfo) ) markTestFailed("Stratum " + bpInfo.stratumInfo + " mismatch"); } if ( bpInfo.subBreakpoints != null ) { Env.traceNormal("Enabling sub-breakpoints"); for ( BreakpointInfo subBP : bpInfo.subBreakpoints ) { if ( subBP.type == BreakpointInfo.Type.IMPLICIT ) continue; if ( subBP.bpReq == null ) { Env.complain("Breakpoint " + subBP + " was not set. Skipping."); continue; } if ( subBP.bpReq.isEnabled() ) { Env.traceVerbose("Breakpoint " + subBP + " is already enabled. Skipping."); continue; } subBP.bpReq.enable(); } } if ( terminateWhenAllBPHit && areAllBreakpointsHit(breakpoints) ) { Env.traceNormal("All breakpoints are hit. Terminating."); stop = true; s = 0; } } if (s > 0) { Env.traceVerbose("Stepping " + s + " step or breakpoint events from here"); stepsToTrace = s; if (currentStepReq == null) { currentStepReq = erm.createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO); currentStepReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); currentStepReq.enable(); } } else { if (currentStepReq != null && --stepsToTrace <= 0) { Env.traceVerbose("Continue without stepping"); erm.deleteEventRequest(currentStepReq); currentStepReq = null; } } } Env.traceDebug("Resuming execution"); _vm.resume(); } new BreakpointListIterator(breakpoints) { @Override protected Object apply(BreakpointInfo bi) { if (!bi.isHit()) { markTestFailed("Breakpoint for method " + bi.methodName + ": required hits " + (bi.requiredHits == null ? "> 0" : (" = " + bi.requiredHits)) + "; actual hits = " + bi.hits); } else { Env.display("Breakpoint for method " + bi.methodName + " was hit " + bi.hits + " times. OK."); } return null; } }.go(); if (!debuggee.terminated()) debuggee.endDebugee(); debuggee.waitFor(); return true; } private void setBreakpoints( final EventRequestManager erm, final List breakpoints, final ReferenceType classRef) throws Throwable { Env.traceNormal("Setting breakpoints for class [" + classRef + "]"); new BreakpointListIterator(breakpoints) { @Override protected Object apply(BreakpointInfo bpInfo) throws Throwable { if ( bpInfo.className.equals(classRef.name()) ) { List methods = classRef.methodsByName(bpInfo.methodName); if (methods.size() == 0) throw new Exception("No method named [" + bpInfo.methodName + "]"); Method method = (Method) methods.get(0); List allLineLocations = method.allLineLocations(); Env.traceVerbose("Method [" + method.name() + "] locations: " + Arrays.toString(allLineLocations.toArray())); if (bpInfo.methodLine > allLineLocations.size()) throw new Exception("TEST BUG: no breakpoint line " + bpInfo.methodLine); Location lineLocation = (Location) allLineLocations.get(bpInfo.methodLine); bpInfo.bci = lineLocation.codeIndex(); bpInfo.hits = 0; if ( bpInfo.type == BreakpointInfo.Type.EXPLICIT ) { BreakpointRequest bpReq = erm.createBreakpointRequest(lineLocation); // bpReq.addThreadFilter(mainThread); bpReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); if ( ! bpInfo.isConditional ) bpReq.enable(); bpInfo.bpReq = bpReq; Env.traceNormal("Breakpoint request for [" + method.name() + "]: " + bpReq); } else { Env.traceNormal("Implicit breakpoint " + "[" + bpInfo.methodName + ":" + bpInfo.methodLine + "]"); } } return null; } }.go(); } private static boolean areAllBreakpointsHit(List breakpoints) throws Throwable { return null == new BreakpointListIterator(breakpoints) { @Override protected Object apply(BreakpointInfo bi) throws Throwable { return bi.isHit() ? null : bi; } }.go(); } public static String getStackTraceStr(List frames, boolean full) throws AbsentInformationException { StringBuffer buf = new StringBuffer(); int frameNum = 0; for (StackFrame f : frames) { Location l = f.location(); buf.append(String.format("#%-4d", frameNum)) .append(l.method()) .append("\n source: ") .append(l.sourcePath()) .append(":") .append(l.lineNumber()) .append("; bci=") .append(l.codeIndex()) .append("\n class: ") .append(l.declaringType()) .append("\n strata: ") .append(StratumUtils.getStrataStr(f)) .append("\n locals: "); try { for (Map.Entry m : f.getValues(f.visibleVariables()).entrySet()) { LocalVariable lv = m.getKey(); buf.append("\n "); if (lv.isArgument()) { buf.append("[arg] "); } buf.append(lv.name()) .append(" (") .append(lv.typeName()) .append(") = [") .append(m.getValue()) .append("]; "); } } catch (AbsentInformationException e) { buf.append("NO INFORMATION") .append("\n arguments: "); List argumentValues = f.getArgumentValues(); if (argumentValues == null || argumentValues.size() == 0) { buf.append("none"); } else { int n = 0; for (Value v : argumentValues) { buf.append("\n arg"); if (v == null) { buf.append(n) .append(" [null]"); } else { buf.append(n) .append(" (") .append(v.type()) .append(") = [") .append(v) .append("]; "); } n++; } } } buf.append("\n\n"); ++frameNum; if (!full && frameNum >= SHORT_STACK_TRACE_FRAMES_NUM) { buf.append("...\n"); break; } } return buf.toString(); } protected EventIterator getNextEvent() throws Throwable { EventSet eventSet = _eventQueue.remove(_jdiEventWaitTimeout); if (eventSet == null) throw new Exception("Timed out while waiting for an event"); return eventSet.eventIterator(); } }