/******************************************************************************* * Copyright (c) 2014 Willink Transformations and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * R.Dvorak and others - QVTo debugger framework * E.D.Willink - revised API for OCL debugger framework *******************************************************************************/ package org.eclipse.ocl.examples.debug.vm.core; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.IBreakpointManagerListener; import org.eclipse.debug.core.IDebugEventSetListener; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.IStatusHandler; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IMemoryBlock; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IThread; import org.eclipse.debug.core.model.IValue; import org.eclipse.emf.common.util.URI; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.ocl.examples.debug.vm.BreakpointError; import org.eclipse.ocl.examples.debug.vm.IVMVirtualMachineShell; import org.eclipse.ocl.examples.debug.vm.VMEventListener; import org.eclipse.ocl.examples.debug.vm.VMVirtualMachine; import org.eclipse.ocl.examples.debug.vm.data.VMNewBreakpointData; import org.eclipse.ocl.examples.debug.vm.data.VMSuspension; import org.eclipse.ocl.examples.debug.vm.event.VMDisconnectEvent; import org.eclipse.ocl.examples.debug.vm.event.VMEvent; import org.eclipse.ocl.examples.debug.vm.event.VMResumeEvent; import org.eclipse.ocl.examples.debug.vm.event.VMStartEvent; import org.eclipse.ocl.examples.debug.vm.event.VMSuspendEvent; import org.eclipse.ocl.examples.debug.vm.event.VMTerminateEvent; import org.eclipse.ocl.examples.debug.vm.request.VMBreakpointRequest; import org.eclipse.ocl.examples.debug.vm.request.VMRequest; import org.eclipse.ocl.examples.debug.vm.request.VMResumeRequest; import org.eclipse.ocl.examples.debug.vm.request.VMStartRequest; import org.eclipse.ocl.examples.debug.vm.request.VMSuspendRequest; import org.eclipse.ocl.examples.debug.vm.request.VMTerminateRequest; import org.eclipse.ocl.examples.debug.vm.response.VMBreakpointResponse; import org.eclipse.ocl.examples.debug.vm.response.VMResponse; import org.eclipse.ocl.examples.debug.vm.utils.DebugOptions; import org.eclipse.ocl.pivot.utilities.ClassUtil; public abstract class VMDebugTarget extends VMDebugElement implements IVMDebugTarget, IDebugEventSetListener, IBreakpointManagerListener { private final Map<Long, VMLineBreakpoint> fID2Breakpoint = new HashMap<Long, VMLineBreakpoint>(); private final ILaunch fLaunch; private final IProcess fProcess; private VMThread fMainThread; private String fMainModuleName; private boolean fIsStarting; private boolean fIsSuspended = false; private final IVMVirtualMachineShell fVM; private final List<VMEventListener> fEventListener = new LinkedList<VMEventListener>(); private final Object fVMStartMonitor = new Object(); public VMDebugTarget(IProcess process, IVMVirtualMachineShell vm) { super(null); fLaunch = process.getLaunch(); fProcess = process; fVM = vm; fIsStarting = true; fEventListener.add(createVMEventListener()); EventDispatchJob dispatcher = new EventDispatchJob(); Thread eventDispatherThread = new Thread(dispatcher, "OCL Debug"); //$NON-NLS-1$ eventDispatherThread.setDaemon(true); eventDispatherThread.start(); try { // start transformation execution sendRequest(new VMStartRequest(true)); } catch (DebugException e) { getDebugCore().log(e.getStatus()); // FIXME - consult status handler to give UI feedback return; } joinStartOrTerminate(); // Note: VM is still suspended and waiting for resume // => do whatever initialization we need now // install VM breakpoints installVMBreakpoints(); DebugEvent createEvent = new DebugEvent(this, DebugEvent.CREATE); createEvent.setData(new HashMap<Long, VMLineBreakpoint>(fID2Breakpoint)); fMainThread = new VMThread(this); fLaunch.addDebugTarget(this); System.setProperty(getDebugCore().getDebuggerActiveProperty(), Boolean.TRUE.toString()); try { // wake up so far suspended VM unless suspendOnStart if (!fIsSuspended) { fVM.sendRequest(new VMResumeRequest(VMSuspension.UNSPECIFIED)); } } catch (IOException e) { getDebugCore().log(e); } IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager(); breakpointManager.addBreakpointManagerListener(this); breakpointManager.addBreakpointListener(this); DebugPlugin.getDefault().addDebugEventListener(this); fireEvent(createEvent); } protected @NonNull URI computeBreakpointURI(@NonNull URI sourceURI) { return sourceURI; } private void installVMBreakpoints() { HashMap<Long, VMLineBreakpoint> installedBreakpoints = new HashMap<Long, VMLineBreakpoint>(); List<VMNewBreakpointData> allBpData = new ArrayList<VMNewBreakpointData>(); for (VMLineBreakpoint vmBp : getDebugCore().getLineBreakpoints()) { boolean enabled = false; try { enabled = vmBp.isEnabled(); } catch (CoreException e) { getDebugCore().log(e.getStatus()); } if (enabled) { installedBreakpoints.put(new Long(((VMLineBreakpoint) vmBp).getID()), vmBp); try { String unitURI = vmBp.getUnitURI().toString(); @SuppressWarnings("null")@NonNull String targetURI = computeBreakpointURI(ClassUtil.nonNullEMF(URI.createURI(unitURI, true))).toString(); VMNewBreakpointData data = vmBp.createNewBreakpointData(targetURI); allBpData.add(data); } catch (CoreException e) { getDebugCore().log(e.getStatus()); } } } if (!allBpData.isEmpty()) { @SuppressWarnings("null")@NonNull VMNewBreakpointData @NonNull [] bpData = allBpData.toArray(new VMNewBreakpointData[allBpData.size()]); VMBreakpointRequest breakpointRequest = VMBreakpointRequest.createAdd(bpData); try { VMResponse response = fVM.sendRequest(breakpointRequest); // fID2Breakpoint.clear(); if(response instanceof VMBreakpointResponse) { VMBreakpointResponse bpResponse = (VMBreakpointResponse) response; for(long addedID : bpResponse.getAddedBreakpointsIDs()) { Long key = new Long(addedID); VMLineBreakpoint bp = installedBreakpoints.get(key); if(bp != null) { fID2Breakpoint.put(key, bp); } } } } catch (IOException e) { getDebugCore().log(e); } } } public Collection<? extends IBreakpoint> getInstalledBreakpoints() { return Collections.unmodifiableCollection(fID2Breakpoint.values()); } public VMResponse sendRequest(@NonNull VMRequest request) throws DebugException { try { VMResponse response = fVM.sendRequest(request); return response; } catch (IOException e) { throw new DebugException(getDebugCore().createDebugError("Send debug request failed", e)); } } public synchronized boolean isSuspended() { return !isTerminated() && fIsSuspended; } @Override public IDebugTarget getDebugTarget() { return this; } @Override public ILaunch getLaunch() { return fLaunch; } public IVMVirtualMachineShell getVM() { return fVM; } public IProcess getProcess() { IProcess[] processes = getLaunch().getProcesses(); if (processes != null && processes.length > 0) { return processes[0]; } return null; } public boolean hasThreads() throws DebugException { return !isTerminated(); } public IThread[] getThreads() throws DebugException { return (fMainThread != null) ? new IThread[] { fMainThread } : new IThread[0]; } public String getName() throws DebugException { return "OCL Debug target"; } public boolean supportsBreakpoint(IBreakpoint breakpoint) { return breakpoint.getModelIdentifier().equals(getModelIdentifier()); } public boolean canTerminate() { return !isTerminated(); } public boolean isTerminated() { return fVM.isTerminated(); } public void terminate() throws DebugException { sendRequest(new VMTerminateRequest()); } protected void started(String mainModuleName) { setMainModuleName(mainModuleName); setStarting(false); } synchronized protected void setMainModuleName(String mainModuleName) { fMainModuleName = mainModuleName; } synchronized public String getMainModuleName() { return fMainModuleName; } protected void terminated() { getDebugCore().getTrace().trace(DebugOptions.TARGET, "Debug target terminated"); //$NON-NLS-1$ System.setProperty(getDebugCore().getDebuggerActiveProperty(), Boolean.FALSE.toString()); setStarting(false); fMainThread = null; DebugPlugin debugPlugin = DebugPlugin.getDefault(); if (debugPlugin != null) { IBreakpointManager breakpointManager = debugPlugin.getBreakpointManager(); breakpointManager.removeBreakpointListener(this); breakpointManager.removeBreakpointManagerListener(this); debugPlugin.removeDebugEventListener(this); } fID2Breakpoint.clear(); fireTerminateEvent(); if (fProcess instanceof VMVirtualProcess) { VMVirtualProcess vp = (VMVirtualProcess) fProcess; vp.terminated(); } } public boolean canResume() { return !isTerminated() && isSuspended(); } public boolean canSuspend() { return !isTerminated() && !isSuspended(); } public void resume() throws DebugException { sendRequest(new VMResumeRequest(VMSuspension.UNSPECIFIED)); } public void suspend() throws DebugException { sendRequest(new VMSuspendRequest(VMSuspension.UNSPECIFIED)); } public void breakpointAdded(IBreakpoint breakpoint) { if (breakpoint instanceof VMLineBreakpoint == false || !DebugPlugin.getDefault().getBreakpointManager().isEnabled()) { return; } VMLineBreakpoint vmBreakpoint = (VMLineBreakpoint) breakpoint; try { VMNewBreakpointData bpData = vmBreakpoint.createNewBreakpointData(); VMBreakpointRequest addBreakpointRequest = VMBreakpointRequest.createAdd(bpData); VMResponse response = sendRequest(addBreakpointRequest); if(response instanceof VMBreakpointResponse) { VMBreakpointResponse bpResponse = (VMBreakpointResponse) response; long[] addedIDs = bpResponse.getAddedBreakpointsIDs(); if(addedIDs.length > 0) { fID2Breakpoint.put(new Long(addedIDs[0]), vmBreakpoint); } } } catch (CoreException e) { getDebugCore().log(e.getStatus()); } } public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { if (breakpoint instanceof VMLineBreakpoint == false || !DebugPlugin.getDefault().getBreakpointManager().isEnabled()) { return; } boolean nowEnabled = false; try { nowEnabled = breakpoint.isEnabled(); } catch (CoreException e1) { // do nothing } boolean beforeEnabled = delta.getAttribute(IBreakpoint.ENABLED, false); VMBreakpointRequest changeRequest = null; try { VMLineBreakpoint vmBreakpoint = (VMLineBreakpoint) breakpoint; if (nowEnabled && !beforeEnabled) { // just to be added to VM changeRequest = VMBreakpointRequest.createAdd(new @NonNull VMNewBreakpointData[] { vmBreakpoint.createNewBreakpointData() }); } else if (!nowEnabled && beforeEnabled) { // just to be removed from VM changeRequest = VMBreakpointRequest.createRemove(vmBreakpoint.getID()); } else { // modify existing data changeRequest = VMBreakpointRequest.createChange(vmBreakpoint.getID(), vmBreakpoint.createBreakpointData()); } } catch (CoreException e) { getDebugCore().log(e); } if (changeRequest != null) { try { fVM.sendRequest(changeRequest); } catch (IOException e) { getDebugCore().log(e); } } } public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { if (breakpoint instanceof VMLineBreakpoint) { if (delta == null) { IMarker marker = breakpoint.getMarker(); if (marker.exists()) { try { marker.delete(); } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } VMLineBreakpoint vmBreakpoint = (VMLineBreakpoint) breakpoint; fID2Breakpoint.remove(new Long(((VMLineBreakpoint) breakpoint) .getID())); VMBreakpointRequest removeRequest = VMBreakpointRequest .createRemove(vmBreakpoint.getID()); try { fVM.sendRequest(removeRequest); } catch (IOException e) { getDebugCore().log(e); } } } public boolean canDisconnect() { return false; } public void disconnect() throws DebugException { } public boolean isDisconnected() { return false; } public boolean supportsStorageRetrieval() { return false; } public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException { return null; } public void handleDebugEvents(DebugEvent[] events) { for (int i = 0; i < events.length; i++) { DebugEvent event = events[i]; if (event.getKind() == DebugEvent.TERMINATE) { // respond if ((fMainThread != null && event.getSource() == fMainThread) || (event.getSource() == fProcess)) { if(!isTerminated()) { terminated(); } } } } } public void breakpointManagerEnablementChanged(boolean enabled) { for (IBreakpoint breakpoint : getDebugCore().getOCLBreakpoints(IBreakpoint.class)) { if (enabled) { breakpointAdded(breakpoint); } else { breakpointRemoved(breakpoint, null); } } } private void joinStartOrTerminate() { synchronized (fVMStartMonitor) { while(fIsStarting) { try { // wait until we receive VM startup event fVMStartMonitor.wait(); } catch (InterruptedException e) { Thread.interrupted(); } } } } private void setStarting(boolean isStarting) { synchronized (fVMStartMonitor) { fIsStarting = isStarting; fVMStartMonitor.notify(); } } private void handleBreakpointConditionError(VMSuspendEvent suspend) { IStatus breakpointStatus = new BreakpointError(suspend .getBreakpointID(), suspend.getReason(), suspend.getReasonDetail()); IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(breakpointStatus); if(handler != null) { try { handler.handleStatus(breakpointStatus, VMDebugTarget.this); } catch (CoreException e) { getDebugCore().log(e.getStatus()); } } else { // no custom handler found, at least log the status getDebugCore().log(breakpointStatus); } } private VMEventListener createVMEventListener() { return new VMEventListener() { public void handleEvent(VMEvent event) { if (event instanceof VMResumeEvent) { fIsSuspended = false; fireResumeEvent(0); } else if (event instanceof VMSuspendEvent) { fIsSuspended = true; VMSuspendEvent suspend = (VMSuspendEvent) event; fireSuspendEvent(suspend.suspension.getDebugEventDetail()); if (suspend.suspension == VMSuspension.BREAKPOINT_CONDITION_ERR) { handleBreakpointConditionError(suspend); } } else if (event instanceof VMTerminateEvent) { fIsSuspended = false; terminated(); } else if (event instanceof VMDisconnectEvent) { fIsSuspended = false; terminated(); } else if (event instanceof VMStartEvent) { VMStartEvent startEvent = (VMStartEvent) event; started(startEvent.mainModuleName); fIsSuspended = startEvent.suspendOnStartup; if (fIsSuspended) { fireSuspendEvent(VMSuspension.STEP_INTO.getDebugEventDetail()); } } } }; } public void addVMEventListener(@NonNull VMEventListener listener) { synchronized (fEventListener) { fEventListener.add(listener); } } public boolean removeVMEventListener(@NonNull VMEventListener listener) { synchronized (fEventListener) { return fEventListener.remove(listener); } } void handleVMEvent(VMEvent event) { List<VMEventListener> listeners; synchronized (fEventListener) { listeners = new ArrayList<VMEventListener>(fEventListener); } for (VMEventListener vmEventListener : listeners) { try { vmEventListener.handleEvent(event); } catch (Exception e) { getDebugCore().log(e); } } } public IValue evaluate(@NonNull String expressionText, long frameID) throws CoreException { if (getVM() instanceof VMVirtualMachine) { return ((VMVirtualMachine) getVM()).evaluate(expressionText, this, frameID); } return null; } private class EventDispatchJob implements Runnable { EventDispatchJob() { super(); } public void run() { while (!isTerminated()) { VMEvent event; try { event = fVM.readVMEvent(); } catch (IOException e) { break; } if (VMVirtualMachine.VM_EVENT.isActive()) { VMVirtualMachine.VM_EVENT.println(">[" + Thread.currentThread().getName() + "] " + event); } if (event != null) { handleVMEvent(event); } } getDebugCore().getTrace().trace(DebugOptions.TARGET, "Debug target VMEvent dispatcher shutdown"); //$NON-NLS-1$ } } }