/******************************************************************************* * Copyright (c) 2008, 2012 Wind River Systems 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: * Wind River Systems - initial API and implementation * Mikhail Khodjaiants (Mentor), Marc Khouzam (Ericsson) * - Optionally use aggressive breakpoint filtering (Bug 360735) * Marc Khouzam (Ericsson) - Add support to display proper breakpoints when dealing * with a multi-selection in the debug view (Bug 360735) *******************************************************************************/ package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.breakpoints; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.RejectedExecutionException; import org.eclipse.cdt.debug.core.CDebugCorePlugin; import org.eclipse.cdt.debug.core.model.ICBreakpoint; import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DsfRunnable; import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; import org.eclipse.cdt.dsf.concurrent.ImmediateCountingRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.debug.service.IBreakpoints; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMData; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpointsExtension; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.ui.viewmodel.breakpoints.BreakpointVMProvider; import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants; import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin; import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; import org.eclipse.cdt.dsf.mi.service.MIBreakpointDMData; import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMAdapter; import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode; import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointUIConstants; import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; /** * @since 3.0 */ public class GdbBreakpointVMProvider extends BreakpointVMProvider { final private DsfSession fSession; final private DsfServicesTracker fServicesTracker; /** Indicator that we should use aggressive breakpoint filtering */ private boolean fUseAggressiveBpFilter = false; /** PropertyChangeListener to keep track of the PREF_HIDE_RUNNING_THREADS preference */ private IPropertyChangeListener fPropertyChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { if (IGdbDebugPreferenceConstants.PREF_AGGRESSIVE_BP_FILTER.equals(event.getProperty())) { fUseAggressiveBpFilter = (Boolean)event.getNewValue(); // Set the property in the presentation context so it can be seen by the vmnode which // will refresh the view getPresentationContext().setProperty(IGdbDebugPreferenceConstants.PREF_AGGRESSIVE_BP_FILTER, fUseAggressiveBpFilter); } } }; public GdbBreakpointVMProvider(AbstractVMAdapter adapter, IPresentationContext presentationContext, DsfSession session) { super(adapter, presentationContext); fSession = session; fServicesTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), fSession.getId()); IPreferenceStore store = GdbUIPlugin.getDefault().getPreferenceStore(); store.addPropertyChangeListener(fPropertyChangeListener); fUseAggressiveBpFilter = store.getBoolean(IGdbDebugPreferenceConstants.PREF_AGGRESSIVE_BP_FILTER); } @Override public void dispose() { GdbUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(fPropertyChangeListener); fServicesTracker.dispose(); super.dispose(); } @Override protected void calcFileteredBreakpoints( final DataRequestMonitor<IBreakpoint[]> rm ) { if ( Boolean.TRUE.equals( getPresentationContext().getProperty( IBreakpointUIConstants.PROP_BREAKPOINTS_FILTER_SELECTION ) ) ) { if ( fUseAggressiveBpFilter ) { // Aggressive filtering of breakpoints. Only return bps that are installed on the target. ISelection debugContext = getDebugContext(); if ( debugContext instanceof IStructuredSelection ) { // Use a set to avoid duplicates final Set<IBreakpoint> bps = new HashSet<IBreakpoint>(); int count = 0; final ImmediateCountingRequestMonitor crm = new ImmediateCountingRequestMonitor( rm ) { @Override protected void handleSuccess() { rm.done( bps.toArray( new IBreakpoint[bps.size()] ) ); } }; for ( Object element : ( (IStructuredSelection)debugContext ).toList() ) { IBreakpointsTargetDMContext bpContext = null; IExecutionDMContext execContext = null; if ( element instanceof IDMVMContext ) { bpContext = DMContexts.getAncestorOfType( ((IDMVMContext)element).getDMContext(), IBreakpointsTargetDMContext.class ); execContext = DMContexts.getAncestorOfType( ((IDMVMContext)element).getDMContext(), IExecutionDMContext.class ); if ( bpContext != null && fSession.getId().equals( bpContext.getSessionId() ) ) { count++; getInstalledBreakpoints( bpContext, execContext, new DataRequestMonitor<Collection<IBreakpoint>>( getExecutor(), crm ) { @Override protected void handleCompleted() { if ( isSuccess() ) { bps.addAll( getData() ); } crm.done(); } } ); } } } crm.setDoneCount(count); } else { rm.done( new Status( IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Invalid debug selection", //$NON-NLS-1$ null ) ); } } else { // Original behavior of bp filtering. Return all bp of type ICBreakpoint IBreakpoint[] allBreakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(); List<IBreakpoint> filteredBPs = new ArrayList<IBreakpoint>( allBreakpoints.length ); for (IBreakpoint bp : allBreakpoints) { if ( bp instanceof ICBreakpoint && bp.getModelIdentifier().equals( CDebugCorePlugin.PLUGIN_ID ) ) { filteredBPs.add( bp ); } } rm.done( filteredBPs.toArray( new IBreakpoint[filteredBPs.size()]) ); } } else { super.calcFileteredBreakpoints( rm ); } } @Override protected IVMNode createBreakpointVMNode() { return new GdbBreakpointVMNode(this); } @Override public void getBreakpointsForDebugContext(ISelection debugContext, final DataRequestMonitor<IBreakpoint[]> rm) { IExecutionDMContext _execCtx = null; if (debugContext instanceof IStructuredSelection) { Object element = ((IStructuredSelection)debugContext).getFirstElement(); if (element instanceof IDMVMContext) { _execCtx = DMContexts.getAncestorOfType( ((IDMVMContext)element).getDMContext(), IExecutionDMContext.class); } } if (_execCtx == null || !fSession.getId().equals(_execCtx.getSessionId())) { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Debug context doesn't contain a thread", null)); //$NON-NLS-1$ rm.done(); return; } final IExecutionDMContext execCtx = _execCtx; try { fSession.getExecutor().execute(new DsfRunnable() { @Override public void run() { IBreakpointsExtension bpService = fServicesTracker.getService(IBreakpointsExtension.class); if (bpService == null) { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Breakpoints service not available", null)); //$NON-NLS-1$ rm.done(); return; } bpService.getExecutionContextBreakpoints( execCtx, new DataRequestMonitor<IBreakpoints.IBreakpointDMContext[]>(fSession.getExecutor(), rm) { @Override protected void handleSuccess() { MIBreakpointsManager bpManager = fServicesTracker.getService(MIBreakpointsManager.class); if (bpManager == null) { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Breakpoints service not available", null)); //$NON-NLS-1$ rm.done(); return; } IBreakpoint bp = null; if (getData().length > 0) { bp = bpManager.findPlatformBreakpoint(getData()[0]); } if (bp != null) { rm.setData(new IBreakpoint[] { bp }); } else { rm.setData(new IBreakpoint[0]); } rm.done(); } }); } }); } catch (RejectedExecutionException e) { rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", e)); //$NON-NLS-1$ //$NON-NLS-2$); rm.done(); } } private ISelection getDebugContext() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if ( window != null ) { return DebugUITools.getDebugContextManager().getContextService( window ).getActiveContext(); } return StructuredSelection.EMPTY; } private void getInstalledBreakpoints( final IBreakpointsTargetDMContext targetContext, final IExecutionDMContext execContext, final DataRequestMonitor<Collection<IBreakpoint>> rm ) { try { fSession.getExecutor().execute( new DsfRunnable() { @Override public void run() { final IBreakpointsExtension bpService = fServicesTracker.getService( IBreakpointsExtension.class ); if ( bpService == null ) { rm.setStatus( new Status( IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Breakpoints service not available", //$NON-NLS-1$ null ) ); rm.done(); return; } bpService.getBreakpoints( targetContext, new DataRequestMonitor<IBreakpointDMContext[]>( fSession.getExecutor(), rm ) { @Override protected void handleSuccess() { final MIBreakpointsManager bpManager = fServicesTracker.getService( MIBreakpointsManager.class ); if ( bpManager == null ) { rm.setStatus( new Status( IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Breakpoint manager service not available", //$NON-NLS-1$ null ) ); rm.done(); return; } if ( getData().length > 0 ) { final Set<IBreakpoint> bps = new HashSet<IBreakpoint>( getData().length ); final CountingRequestMonitor crm = new CountingRequestMonitor( ImmediateExecutor.getInstance(), rm ) { @Override protected void handleSuccess() { rm.setData( bps ); rm.done(); } }; crm.setDoneCount( getData().length ); for ( final IBreakpointDMContext bpCtx : getData() ) { bpService.getBreakpointDMData( bpCtx, new DataRequestMonitor<IBreakpointDMData>( ImmediateExecutor.getInstance(), crm ) { /* (non-Javadoc) * @see org.eclipse.cdt.dsf.concurrent.RequestMonitor#handleSuccess() */ @Override protected void handleSuccess() { if ( getData() instanceof MIBreakpointDMData ) { MIBreakpointDMData data = (MIBreakpointDMData)getData(); if ( !data.isPending() ) { bpBelongsToContext( execContext, data, new ImmediateDataRequestMonitor<Boolean>(crm) { @Override protected void handleSuccess() { if (getData()) { IBreakpoint bp = bpManager.findPlatformBreakpoint( bpCtx ); if ( bp != null ) bps.add( bp ); } crm.done(); } }); return; } } crm.done(); } } ); } } else { rm.setData( new HashSet<IBreakpoint>() ); rm.done(); } } } ); } } ); } catch( RejectedExecutionException e ) { rm.setStatus( new Status( IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", e ) ); //$NON-NLS-1$ //$NON-NLS-2$); rm.done(); } } private void bpBelongsToContext( IExecutionDMContext execContext, final MIBreakpointDMData data, final DataRequestMonitor<Boolean> rm ) { if ( execContext == null ) { // No execution context so accept all breakpoints rm.done( true ); return; } // First check if a thread is selected as the context. In that case, we only want to show breakpoints that // are applicable to that thread. IMIExecutionDMContext threadContext = DMContexts.getAncestorOfType( execContext, IMIExecutionDMContext.class ); if ( threadContext != null ) { // A thread is selected. Now make sure this breakpoint is assigned to this thread. if (data.getThreadId() != null && data.getThreadId().length() > 0) { String bpThreadId = data.getThreadId().trim(); // A threadId of 0 means all threads of this process. We therefore will have to check // if this breakpoint applies to the process that is selected. But if the threadId is not 0 // we simply make sure we have the right thread selected. if (!bpThreadId.equals("0")) { //$NON-NLS-1$ String ctxThreadId = threadContext.getThreadId(); rm.done(ctxThreadId.equals(bpThreadId)); return; } } } // If we get here it is that the breakpoint is not assigned to a single thread. // We therefore make sure the breakpoint is applicable to the selected process. final IMIContainerDMContext containerContext = DMContexts.getAncestorOfType( execContext, IMIContainerDMContext.class ); if ( containerContext != null ) { if ( data.getGroupIds() != null ) { List<String> groupIds = Arrays.asList( data.getGroupIds() ); rm.done( groupIds.contains( containerContext.getGroupId() ) ); } else { // This happens if we are debugging a single process, so all breakpoints apply. rm.done( true ); } } else { // Accept breakpoint rm.done( true ); } } }