blob: 581f27a90025777efb3c4b8a6ef28489f8d94e13 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2018 Mentor Graphics and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Mentor Graphics - Initial API and implementation
* Salvatore Culcasi (ST) - Bug 407163 - GDB Console: breakpoint not added with MinGW and gdb
* Marc Khouzam (Ericsson) - Update breakpoint handling for GDB >= 7.4 (Bug 389945)
* Marc Khouzam (Ericsson) - Support for dynamic printf (Bug 400628)
* Jonah Graham (Kichwa Coders) - Bug 317173 - cleanup warnings
* Jonah Graham (Kichwa Coders) - Bug 530377 - Corruption of state due to fast events from GDB
* Umair Sair (Siemens) - Bug 571161 - MIBreakpointsSynchronizer is broken in certain scenarios
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.debug.core.CDIDebugModel;
import org.eclipse.cdt.debug.core.breakpointactions.BreakpointActionManager;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICTracepoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
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.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IDsfBreakpointExtension;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.tracepointactions.CollectAction;
import org.eclipse.cdt.dsf.gdb.internal.tracepointactions.EvaluateAction;
import org.eclipse.cdt.dsf.gdb.internal.tracepointactions.ITracepointAction;
import org.eclipse.cdt.dsf.gdb.internal.tracepointactions.TracepointActionManager;
import org.eclipse.cdt.dsf.gdb.internal.tracepointactions.WhileSteppingAction;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext;
import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager.IMIBreakpointsTrackingListener;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakListInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.utils.Addr64;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
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.core.sourcelookup.containers.LocalFileStorage;
import org.osgi.framework.BundleContext;
/**
* Provides synchronization between breakpoints set from outside of the Eclipse
* breakpoint framework (GDB console, trace files, etc.) and the Breakpoints
* view.
* <p>
* Bug 530377: Prior to fixing 530377, events that arrived from GDB faster than
* DSF/Eclipse fully processed them could cause the state within the
* synchronizer and manager to become corrupt. This would happen because it
* takes multiple DSF stages to complete handling 1 event, so the handling of
* the next event would become intermingled. That violated many assumptions in
* the code that the code run in the respective RequestMonitor would be on the
* same state. This is an unsuprising assumption based on the general idea of
* DSF as not requiring the normal synchronization primitives as everything is
* single-threaded. To resolve this problem, there is some code
* {@link #queueEvent(BreakpointEvent)} that ensures each event is fully
* processed before the next event starts processing.
*
* @since 4.2
*/
public class MIBreakpointsSynchronizer extends AbstractDsfService
implements IMIBreakpointsTrackingListener, ICachingService {
// Catchpoint expressions
private static final String CE_EXCEPTION_CATCH = "exception catch"; //$NON-NLS-1$
private static final String CE_EXCEPTION_THROW = "exception throw"; //$NON-NLS-1$
// GDB tracepoint commands
private static final String TC_COLLECT = "collect "; //$NON-NLS-1$
private static final String TC_TEVAL = "teval "; //$NON-NLS-1$
private static final String TC_WHILE_STEPPING = "while-stepping "; //$NON-NLS-1$
private static final String TC_END = "end"; //$NON-NLS-1$
private IMICommandControl fConnection;
private MIBreakpoints fBreakpointsService;
private MIBreakpointsManager fBreakpointsManager;
/**
* Collection of the target contexts that are being tracked.
*/
private Set<IBreakpointsTargetDMContext> fTrackedTargets;
/**
* Collection of breakpoints created from the GDB console or outside of Eclipse.
*
* Map of breakpoint contexts to Map of breakpoint number (String) to MIBreakpoint
*/
private Map<IBreakpointsTargetDMContext, Map<String, MIBreakpoint>> fCreatedTargetBreakpoints;
/**
* Collection of breakpoints deleted from the GDB console or outside of Eclipse
*/
private Map<IBreakpointsTargetDMContext, Set<String>> fDeletedTargetBreakpoints;
/**
* Collection of pending breakpoint modifications
*/
private Map<IBreakpointsTargetDMContext, Map<String, MIBreakpoint>> fPendingModifications;
/**
* Class to store an event that needs to be performed by the synchronizer
*
* @see MIBreakpointsSynchronizer class documentation for design comments
*/
private static class BreakpointEvent {
MIBreakpoint created;
MIBreakpoint modified;
String deleted;
static class BreakpointEventSynchronize {
IBreakpointsTargetDMContext dmc;
MIBreakListInfo list;
}
BreakpointEventSynchronize synchronize;
}
/**
* List of events that are queued, waiting to be processed.
*
* @see MIBreakpointsSynchronizer class documentation for design comments
*/
private Deque<BreakpointEvent> fBreakpointEvents = new LinkedList<>();
/**
* True if the delayed events processing task is idle. If idle, a new event
* should trigger restarting the processing.
*
* @see MIBreakpointsSynchronizer class documentation for design comments
*/
private boolean fEventsIdle = true;
public MIBreakpointsSynchronizer(DsfSession session) {
super(session);
fTrackedTargets = new HashSet<>();
fCreatedTargetBreakpoints = new HashMap<>();
fDeletedTargetBreakpoints = new HashMap<>();
fPendingModifications = new HashMap<>();
}
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
@Override
public void initialize(final RequestMonitor rm) {
super.initialize(new ImmediateRequestMonitor(rm) {
@Override
protected void handleSuccess() {
doInitialize(rm);
}
});
}
private void doInitialize(final RequestMonitor rm) {
fConnection = getServicesTracker().getService(IMICommandControl.class);
fBreakpointsService = getServicesTracker().getService(MIBreakpoints.class);
fBreakpointsManager = getServicesTracker().getService(MIBreakpointsManager.class);
if (fConnection == null || fBreakpointsService == null && fBreakpointsManager == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Service is not available")); //$NON-NLS-1$
rm.done();
return;
}
fBreakpointsManager.addBreakpointsTrackingListener(this);
getSession().addServiceEventListener(this, null);
// Register this service
register(new String[] { MIBreakpointsSynchronizer.class.getName() }, new Hashtable<String, String>());
rm.done();
}
@Override
public void shutdown(RequestMonitor rm) {
fTrackedTargets.clear();
fCreatedTargetBreakpoints.clear();
fDeletedTargetBreakpoints.clear();
fPendingModifications.clear();
fBreakpointEvents.clear();
getSession().removeServiceEventListener(this);
MIBreakpointsManager bm = getBreakpointsManager();
if (bm != null) {
bm.removeBreakpointsTrackingListener(this);
}
unregister();
super.shutdown(rm);
}
/**
* Obtain the collection of MI breakpoints created
*
* @return collection of target breakpoints
* @since 5.3
*/
protected Collection<MIBreakpoint> getCreatedTargetBreakpoints(IBreakpointsTargetDMContext context) {
Map<String, MIBreakpoint> map = fCreatedTargetBreakpoints.get(context);
if (map != null) {
return map.values();
}
return null;
}
@Override
public void breakpointTrackingStarted(IBreakpointsTargetDMContext bpTargetDMC) {
fTrackedTargets.add(bpTargetDMC);
}
@Override
public void breakpointTrackingStopped(IBreakpointsTargetDMContext bpTargetDMC) {
fTrackedTargets.remove(bpTargetDMC);
}
private IMICommandControl getCommandControl() {
return fConnection;
}
private MIBreakpoints getBreakpointsService() {
return fBreakpointsService;
}
private MIBreakpointsManager getBreakpointsManager() {
return fBreakpointsManager;
}
/**
* Queue (and potentially start processing) breakpoint events from GDB.
*
* @param event
* from GDB that needs to be processed once the synchronizer is idle
* and has completed the previous event.
*/
private void queueEvent(BreakpointEvent event) {
fBreakpointEvents.add(event);
if (fEventsIdle) {
runNextEvent();
}
}
private void runNextEvent() {
fEventsIdle = false;
BreakpointEvent event = fBreakpointEvents.poll();
if (event == null) {
fEventsIdle = true;
return;
}
RequestMonitor rm = new RequestMonitor(getExecutor(), null) {
@Override
protected void handleCompleted() {
runNextEvent();
super.handleCompleted();
}
};
if (event.created != null) {
doTargetBreakpointCreated(event.created, rm);
} else if (event.deleted != null) {
doTargetBreakpointDeleted(event.deleted, rm);
} else if (event.modified != null) {
doTargetBreakpointModified(event.modified, rm);
} else if (event.synchronize != null) {
doTargetBreakpointsSynchronized(event.synchronize.dmc, event.synchronize.list, rm);
} else {
rm.done();
}
}
/**
* The effect of flushing the cache of the synchronizer is to recollect all
* breakpoint info from GDB and update the IBreakpoints and MIBreakpointManager
* services too.
*
* Note that an optimization in the number of calls to synchronize can be done, see
* synchronize's removeBpsForAllDmcs parameter.
*/
@Override
public void flushCache(IDMContext context) {
Collection<IBreakpointsTargetDMContext> contexts;
IBreakpointsTargetDMContext breakpointsTargetDMContext = DMContexts.getAncestorOfType(context,
IBreakpointsTargetDMContext.class);
if (breakpointsTargetDMContext != null) {
contexts = Arrays.asList(breakpointsTargetDMContext);
} else {
contexts = getBreakpointsManager().getTrackedBreakpointTargetContexts();
}
for (IBreakpointsTargetDMContext bpContext : contexts) {
synchronize(bpContext, false);
}
}
/**
* Synchronize the breakpoint state with the back end. This is done by issuing a
* -break-list to the backend and adding that result to the list of queue'd
* events from the backend. When this entry in the queue is processed, it
* converts itself to a series of new events that represent the difference
* between the state in the breakpoint manager and GDB.
*
* @param bpContext
* context to issue MI Break List on
* @param removeBpsForAllDmcs
* If the break list command returns breakpoints for all contexts,
* pass true. If false, the synchronizer assumes only bps not listed
* for bpContext will be removed. This provides an optimization to
* prevent issuing createMIBreakList multiple times that will return
* the same value. (Note that using a CommandCache will not achieve
* the optimization because each call to createMIBreakList is a
* different context.)
* @since 5.5
*/
protected void synchronize(IBreakpointsTargetDMContext bpContext, boolean removeBpsForAllDmcs) {
fConnection.queueCommand(fConnection.getCommandFactory().createMIBreakList(bpContext),
new DataRequestMonitor<MIBreakListInfo>(getExecutor(), null) {
@Override
protected void handleSuccess() {
BreakpointEvent event = new BreakpointEvent();
event.synchronize = new BreakpointEvent.BreakpointEventSynchronize();
event.synchronize.dmc = removeBpsForAllDmcs ? null : bpContext;
event.synchronize.list = getData();
queueEvent(event);
}
});
}
private void doTargetBreakpointsSynchronized(IBreakpointsTargetDMContext breakpointsContext, MIBreakListInfo data,
RequestMonitor rm) {
final MIBreakpointsManager bm = getBreakpointsManager();
Map<IBreakpointsTargetDMContext, Map<IBreakpointDMContext, ICBreakpoint>> bpToPlatformMaps = bm
.getBPToPlatformMaps();
Stream<IBreakpointDMContext> breakpointsKnownToManager = bpToPlatformMaps.entrySet().stream()
.flatMap(m -> m.getValue().keySet().stream());
Collector<MIBreakpointDMContext, ?, Map<IBreakpointsTargetDMContext, Set<String>>> collector = Collectors.toMap(
(MIBreakpointDMContext dmc) -> DMContexts.getAncestorOfType(dmc, IBreakpointsTargetDMContext.class),
(MIBreakpointDMContext dmc) -> new HashSet<>(Collections.singleton(dmc.getReference())),
(a, b) -> Stream.concat(a.stream(), b.stream()).collect(Collectors.toCollection(HashSet::new)));
Map<IBreakpointsTargetDMContext, Set<String>> numbersKnownToManager = breakpointsKnownToManager
.filter(MIBreakpointDMContext.class::isInstance).map(MIBreakpointDMContext.class::cast)
.collect(collector);
for (MIBreakpoint miBpt : data.getMIBreakpoints()) {
String number = miBpt.getNumber();
boolean found = false;
for (Set<String> bpNumbers : numbersKnownToManager.values())
if (bpNumbers.remove(number))
found = true;
BreakpointEvent event = new BreakpointEvent();
if (found)
event.modified = miBpt;
else
event.created = miBpt;
fBreakpointEvents.addFirst(event);
}
for (Entry<IBreakpointsTargetDMContext, Set<String>> entry : numbersKnownToManager.entrySet()) {
IBreakpointsTargetDMContext dmc = entry.getKey();
for (String number : entry.getValue())
if (number != null && !number.isEmpty()
&& (breakpointsContext == null || breakpointsContext.equals(dmc))) {
BreakpointEvent event = new BreakpointEvent();
event.deleted = number;
fBreakpointEvents.addFirst(event);
}
}
rm.done();
}
public void targetBreakpointCreated(final MIBreakpoint miBpt) {
BreakpointEvent event = new BreakpointEvent();
event.created = miBpt;
queueEvent(event);
}
private void doTargetBreakpointCreated(final MIBreakpoint miBpt, RequestMonitor rm) {
if (isCatchpoint(miBpt)) {
rm.done();
return;
}
MIBreakpoints breakpointsService = getBreakpointsService();
final MIBreakpointsManager bm = getBreakpointsManager();
if (breakpointsService == null || bm == null) {
rm.done();
return;
}
final IBreakpointsTargetDMContext bpTargetDMC = getBreakpointsTargetContext(miBpt);
if (bpTargetDMC == null) {
rm.done();
return;
}
// Store the target breakpoint data
Map<String, MIBreakpointDMData> contextBreakpoints = breakpointsService.getBreakpointMap(bpTargetDMC);
if (contextBreakpoints == null) {
contextBreakpoints = breakpointsService.createNewBreakpointMap(bpTargetDMC);
}
contextBreakpoints.put(miBpt.getNumber(), fBreakpointsService.createMIBreakpointDMData(miBpt));
// Store the created target breakpoint to prevent setting it again on the target
// when addBreakpoint() is called.
Map<String, MIBreakpoint> targetMap = fCreatedTargetBreakpoints.get(bpTargetDMC);
if (targetMap == null) {
targetMap = new HashMap<>();
fCreatedTargetBreakpoints.put(bpTargetDMC, targetMap);
}
targetMap.put(miBpt.getNumber(), miBpt);
// Convert the debug info file path into the file path in the local file system
String debuggerPath = getFileName(miBpt);
getSource(bpTargetDMC, debuggerPath, new DataRequestMonitor<String>(getExecutor(), rm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleSuccess() {
String fileName = getData();
if (fileName == null) {
fileName = getFileName(miBpt);
}
// Try to find matching platform breakpoint
ICBreakpoint plBpt = getPlatformBreakpoint(miBpt, fileName);
String threadId = miBpt.getThreadId();
boolean isThreadSpecific = threadId != null && !threadId.isEmpty() && !"0".equals(threadId); //$NON-NLS-1$
try {
if (plBpt == null) {
// If matching platform breakpoint doesn't exist create a new one
plBpt = createPlatformBreakpoint(fileName, miBpt);
// If the target breakpoint is thread specific, update thread filters
if (isThreadSpecific) {
setThreadSpecificBreakpoint(plBpt, miBpt);
}
doTargetBreakpointCreatedSync(miBpt, bpTargetDMC, plBpt);
delayDone(100, rm);
return;
} else {
// The corresponding platform breakpoint already exists.
// If the breakpoint tracking has already started we need
// to notify MIBreakpointsManager which will increment its
// install count.
// Otherwise the breakpoint will be processed as an initial
// breakpoint when the breakpoint tracking starts.
if (isBreakpointTargetTracked(bpTargetDMC)) {
// If the target breakpoint is thread specific, update thread filters
if (isThreadSpecific) {
setThreadSpecificBreakpoint(plBpt, miBpt);
}
ICBreakpoint plBpt2 = plBpt;
bm.breakpointAdded(plBpt2, miBpt, new RequestMonitor(getExecutor(), rm) {
@Override
protected void handleCompleted() {
doTargetBreakpointCreatedSync(miBpt, bpTargetDMC, plBpt2);
rm.done();
}
});
return;
} else {
doTargetBreakpointCreatedSync(miBpt, bpTargetDMC, plBpt);
rm.done();
return;
}
}
} catch (CoreException e) {
GdbPlugin.log(getStatus());
}
rm.done();
return;
}
});
}
private void doTargetBreakpointCreatedSync(final MIBreakpoint miBpt, final IBreakpointsTargetDMContext bpTargetDMC,
ICBreakpoint plBpt) {
// Make sure the platform breakpoint's parameters are synchronized
// with the target breakpoint.
Map<String, MIBreakpoint> map = fPendingModifications.get(bpTargetDMC);
if (map != null) {
MIBreakpoint mod = map.remove(miBpt.getNumber());
if (mod != null) {
targetBreakpointModified(bpTargetDMC, plBpt, mod);
}
} else {
targetBreakpointModified(bpTargetDMC, plBpt, miBpt);
}
}
/**
* Some operations that are passed to platform require a number or delays before
* they complete. The reason is that the platform code will retrigger DSF code
* and continue updating state. Ideally there would be completion monitors for
* the platform operations, but that is not available. Use this method to delay
* calling .done() until at least delayExecutorCycles cycles of the executor
* have run.
*
* @param delayExecutorCycles
* @param rm
*/
private void delayDone(int delayExecutorCycles, RequestMonitor rm) {
getExecutor().execute(() -> {
int remaining = delayExecutorCycles - 1;
if (remaining < 0) {
rm.done();
} else {
delayDone(remaining, rm);
}
});
}
/**
* @since 5.0
*/
public void targetBreakpointDeleted(final String id) {
BreakpointEvent event = new BreakpointEvent();
event.deleted = id;
queueEvent(event);
}
private void doTargetBreakpointDeleted(final String id, RequestMonitor rm) {
MIBreakpoints breakpointsService = getBreakpointsService();
final MIBreakpointsManager bm = getBreakpointsManager();
if (breakpointsService == null || bm == null) {
rm.done();
return;
}
final IBreakpointsTargetDMContext bpTargetDMC = breakpointsService.getBreakpointTargetContext(id);
if (bpTargetDMC != null) {
final MIBreakpointDMContext bpDMC = new MIBreakpointDMContext(breakpointsService,
new IDMContext[] { bpTargetDMC }, id);
breakpointsService.getBreakpointDMData(bpDMC, new DataRequestMonitor<IBreakpointDMData>(getExecutor(), rm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleSuccess() {
if (!(getData() instanceof MIBreakpointDMData)) {
rm.done();
return;
}
MIBreakpointDMData data = (MIBreakpointDMData) getData();
if (MIBreakpoints.CATCHPOINT.equals(data.getBreakpointType())) {
rm.done();
return;
}
IBreakpoint plBpt = bm.findPlatformBreakpoint(bpDMC);
if (plBpt instanceof ICBreakpoint) {
Set<String> set = fDeletedTargetBreakpoints.get(bpTargetDMC);
if (set == null) {
set = new HashSet<>();
fDeletedTargetBreakpoints.put(bpTargetDMC, set);
}
set.add(id);
try {
String threadId = data.getThreadId();
if (!threadId.equals("0")) { //$NON-NLS-1$
IDsfBreakpointExtension bpExtension = fBreakpointsManager
.getFilterExtension((ICBreakpoint) plBpt);
IMIProcesses processes = getServicesTracker().getService(IMIProcesses.class);
if (processes == null) {
rm.done();
return;
}
IContainerDMContext contDMC = processes.createContainerContextFromThreadId(
getCommandControl().getContext(), data.getThreadId());
if (contDMC == null) {
rm.done();
return;
}
IExecutionDMContext[] execDMCs = bpExtension.getThreadFilters(contDMC);
List<IExecutionDMContext> list = new ArrayList<>(execDMCs.length);
for (IExecutionDMContext c : execDMCs) {
if (c instanceof IMIExecutionDMContext
&& !((IMIExecutionDMContext) c).getThreadId().equals(threadId)) {
list.add(c);
}
}
if (!list.isEmpty()) {
bpExtension.setThreadFilters(list.toArray(new IExecutionDMContext[list.size()]));
rm.done();
return;
} else {
bm.uninstallBreakpoint(bpTargetDMC, (ICBreakpoint) plBpt,
new RequestMonitor(getExecutor(), rm));
return;
}
} else {
bm.uninstallBreakpoint(bpTargetDMC, (ICBreakpoint) plBpt,
new RequestMonitor(getExecutor(), rm));
return;
}
} catch (CoreException e) {
GdbPlugin.log(e.getStatus());
}
}
rm.done();
}
});
} else {
rm.done();
}
}
public void targetBreakpointModified(final MIBreakpoint miBpt) {
BreakpointEvent event = new BreakpointEvent();
event.modified = miBpt;
queueEvent(event);
}
/**
* Find the platform breakpoint, returning it, if it exists via the DRM. If the
* drm's data is null, it has not been found.
*/
private void findPlatformBreakpoint(final MIBreakpoint miBpt, DataRequestMonitor<IBreakpoint> drm) {
MIBreakpoints breakpointsService = getBreakpointsService();
final MIBreakpointsManager bm = getBreakpointsManager();
if (breakpointsService != null && bm != null) {
final IBreakpointsTargetDMContext bpTargetDMC = getBreakpointsTargetContext(miBpt);
if (bpTargetDMC == null) {
drm.done((IBreakpoint) null);
return;
}
final Map<String, MIBreakpointDMData> contextBreakpoints = breakpointsService.getBreakpointMap(bpTargetDMC);
if (contextBreakpoints == null) {
drm.done((IBreakpoint) null);
return;
}
IBreakpoint b = bm.findPlatformBreakpoint(
new MIBreakpointDMContext(breakpointsService, new IDMContext[] { bpTargetDMC }, miBpt.getNumber()));
if (b != null) {
drm.done(b);
} else {
// Convert the debug info file path into the file path in the local file system
String debuggerPath = getFileName(miBpt);
getSource(bpTargetDMC, debuggerPath, new DataRequestMonitor<String>(getExecutor(), drm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleSuccess() {
String fileName = getData();
if (fileName == null) {
fileName = getFileName(miBpt);
}
// Try to find matching platform breakpoint
ICBreakpoint plBpt = getPlatformBreakpoint(miBpt, fileName);
drm.done(plBpt);
}
});
}
} else {
drm.done((ICBreakpoint) null);
}
}
private void doTargetBreakpointModified(final MIBreakpoint miBpt, RequestMonitor rm) {
if (isCatchpoint(miBpt)) {
rm.done();
return;
}
findPlatformBreakpoint(miBpt, new DataRequestMonitor<IBreakpoint>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
IBreakpointsTargetDMContext bpTargetDMC = getBreakpointsTargetContext(miBpt);
if (bpTargetDMC == null) {
rm.done();
return;
}
IBreakpoint breakpoint = getData();
if (!(breakpoint instanceof ICBreakpoint)) {
// Platform breakpoint hasn't been created yet. Store the latest
// modification data, it will be picked up later.
Map<String, MIBreakpoint> map = fPendingModifications.get(bpTargetDMC);
if (map == null) {
map = new HashMap<>();
fPendingModifications.put(bpTargetDMC, map);
}
map.put(miBpt.getNumber(), miBpt);
rm.done();
} else {
ICBreakpoint plBpt = (ICBreakpoint) breakpoint;
targetBreakpointModified(bpTargetDMC, plBpt, miBpt);
delayDone(100, rm);
}
}
});
}
private void targetBreakpointModified(IBreakpointsTargetDMContext bpTargetDMC, ICBreakpoint plBpt,
MIBreakpoint miBpt) {
Map<String, MIBreakpointDMData> contextBreakpoints = getBreakpointsService().getBreakpointMap(bpTargetDMC);
MIBreakpointDMData oldData = contextBreakpoints.get(miBpt.getNumber());
contextBreakpoints.put(miBpt.getNumber(), fBreakpointsService.createMIBreakpointDMData(miBpt));
try {
if (plBpt.isEnabled() != miBpt.isEnabled()) {
plBpt.setEnabled(miBpt.isEnabled());
}
if (!plBpt.getCondition().equals(miBpt.getCondition())) {
plBpt.setCondition(miBpt.getCondition());
}
// oldData can be null for notifications of breakpoints that are inserted using DSF but
// not with breakpoint service
if (oldData != null && oldData.isPending() != miBpt.isPending()) {
if (miBpt.isPending()) {
plBpt.decrementInstallCount();
} else {
plBpt.incrementInstallCount();
}
}
if (plBpt instanceof ICTracepoint && miBpt.isTracepoint()) {
ICTracepoint plTpt = (ICTracepoint) plBpt;
if (plTpt.getPassCount() != miBpt.getPassCount()) {
// GDB (up to 7.5) doesn't emit notification when the pass count is modified.
plTpt.setPassCount(miBpt.getPassCount());
}
if (!miBpt.getCommands()
.equals(plBpt.getMarker().getAttribute(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE))) {
StringBuilder sb = new StringBuilder();
boolean first = true;
String[] commands = miBpt.getCommands().split(TracepointActionManager.TRACEPOINT_ACTION_DELIMITER);
for (ITracepointAction action : getActionsFromCommands(commands)) {
if (first) {
first = false;
} else {
sb.append(TracepointActionManager.TRACEPOINT_ACTION_DELIMITER);
}
sb.append(action.getName());
}
// Target breakpoints and platform breakpoints use the same format
// to store trace commands. This format is different than the format
// used by GDB. We need to switch to the platform format to avoid unnecessary
// modifications of target breakpoints.
miBpt.setCommands(sb.toString());
plBpt.getMarker().setAttribute(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE, sb.toString());
}
} else if (plBpt instanceof ICDynamicPrintf && miBpt.isDynamicPrintf()) {
// Cannot synchronize the string as there is a bug in GDB 7.7 that corrupts it.
// https://sourceware.org/bugzilla/show_bug.cgi?id=15806
// If we were to synchronize here, we would overwrite the string defined by
// the user with the corrupted one!
// Truth is that we don't need to synchronize the string anyway because there
// is currently no way to change a dprintf string in GDB; instead a new
// dprintf must be created. That means that there will be no =breakpoint-modifed
// event that indicates a real dprintf string change; only the other fields can
// change and are handled as any other breakpoint.
//
// ICDynamicPrintf plDPrintf = (ICDynamicPrintf)plBpt;
// if (!plDPrintf.getPrintfString().equals(miBpt.getPrintfString())) {
// plDPrintf.setPrintfString(miBpt.getPrintfString());
// }
}
} catch (CoreException e) {
if (oldData != null)
contextBreakpoints.put(miBpt.getNumber(), oldData);
else
contextBreakpoints.remove(miBpt.getNumber());
GdbPlugin.log(e.getStatus());
}
}
private void setThreadSpecificBreakpoint(final ICBreakpoint plBpt, MIBreakpoint miBpt) {
try {
IMIProcesses processes = getServicesTracker().getService(IMIProcesses.class);
if (processes == null) {
return;
}
String threadId = miBpt.getThreadId();
IContainerDMContext contDMC = processes.createContainerContextFromThreadId(getCommandControl().getContext(),
threadId);
if (contDMC == null) {
return;
}
IProcessDMContext procDmc = DMContexts.getAncestorOfType(contDMC, IProcessDMContext.class);
if (procDmc == null) {
return;
}
IDsfBreakpointExtension bpExtension = fBreakpointsManager.getFilterExtension(plBpt);
IExecutionDMContext[] execDMCs = bpExtension.getThreadFilters(contDMC);
if (execDMCs == null) {
execDMCs = new IExecutionDMContext[0];
}
for (IExecutionDMContext execDMC : execDMCs) {
String ctxThreadId = ((IMIExecutionDMContext) execDMC).getThreadId();
if (execDMC instanceof IMIExecutionDMContext && ctxThreadId.equals(threadId)) {
// The platform breakpoint is already restricted to the given thread.
return;
}
}
IExecutionDMContext[] newExecDMCs = new IExecutionDMContext[execDMCs.length + 1];
System.arraycopy(execDMCs, 0, newExecDMCs, 0, execDMCs.length);
newExecDMCs[execDMCs.length] = processes.createExecutionContext(contDMC,
processes.createThreadContext(procDmc, threadId), threadId);
bpExtension.setThreadFilters(newExecDMCs);
} catch (CoreException e) {
GdbPlugin.log(e);
}
}
private ICBreakpoint getPlatformBreakpoint(MIBreakpoint miBpt, String fileName) {
for (IBreakpoint b : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) {
if (b instanceof ICBreakpoint) {
ICBreakpoint cBreakpoint = (ICBreakpoint) b;
if (isPlatformBreakpoint(cBreakpoint, miBpt, fileName)) {
return cBreakpoint;
}
}
}
return null;
}
/**
* Return true if the target breakpoint is the same as the platform breakpoint.
*
* Whether breakpoints are considered the same depends on their type and their key attributes, with each type
* defining its own key attributes.
*
* @param platformBreakpoint
* @param targetBreakpoint
* @param fileName
* source lookup resolved file name. The file name in targetBreakpoint will be the file name as GDB
* understands it, and fileName will be the resolved value as Eclipse (and therefore the platform
* breakpoint) understands it.
* @return if platformBreakpoint and targetBreakpoint match
* @since 5.3
*/
protected boolean isPlatformBreakpoint(ICBreakpoint platformBreakpoint, MIBreakpoint targetBreakpoint,
String fileName) {
if (platformBreakpoint instanceof ICTracepoint && targetBreakpoint.isTracepoint()
&& isPlatformTracepoint((ICTracepoint) platformBreakpoint, targetBreakpoint, fileName)) {
return true;
}
if (platformBreakpoint instanceof ICDynamicPrintf && targetBreakpoint.isDynamicPrintf()
&& isPlatformDynamicPrintf((ICDynamicPrintf) platformBreakpoint, targetBreakpoint, fileName)) {
return true;
}
if (platformBreakpoint instanceof ICWatchpoint && targetBreakpoint.isWatchpoint()
&& isPlatformWatchpoint((ICWatchpoint) platformBreakpoint, targetBreakpoint)) {
return true;
}
if (platformBreakpoint instanceof ICLineBreakpoint && !targetBreakpoint.isWatchpoint()
&& !isCatchpoint(targetBreakpoint) && !targetBreakpoint.isTracepoint()
&& !targetBreakpoint.isDynamicPrintf()
&& isPlatformLineBreakpoint((ICLineBreakpoint) platformBreakpoint, targetBreakpoint, fileName)) {
return true;
}
return false;
}
private ICBreakpoint createPlatformBreakpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
ICBreakpoint bp = createPlatformBreakpoint0(fileName, miBpt);
DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(bp);
return bp;
}
/**
* Create the platform breakpoint, but don't register it with breakpoint manager. This method is called when the
* synchronizer needs to create a new platform breakpoint for a new breakpoint created by the user in the CLI
* interface (in response to =breakpoint-created event).
*
* If further fine tuning on the created breakpoint is needed, consider overriding
* {@link #targetBreakpointCreated(MIBreakpoint)} or even replacing the entire breakpoint synchronizer.
*
* @param fileName
* the name of the file that breakpoint was inserted into, as determined by source lookup
* @param miBpt
* the MI breakpoint created
* @return the newly created Platform breakpoint
* @throws CoreException
* @since 5.3
*/
protected ICBreakpoint createPlatformBreakpoint0(String fileName, MIBreakpoint miBpt) throws CoreException {
if (miBpt.isWatchpoint()) {
return createPlatformWatchpoint(fileName, miBpt);
} else if (miBpt.isTracepoint()) {
return createPlatformTracepoint(fileName, miBpt);
} else if (miBpt.isDynamicPrintf()) {
return createPlatformDynamicPrintf(fileName, miBpt);
} else {
return createPlatformLocationBreakpoint(fileName, miBpt);
}
}
private ICBreakpoint createPlatformLocationBreakpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
if (isAddressBreakpoint(miBpt)) {
return createPlatformAddressBreakpoint(fileName, miBpt);
} else if (isFunctionBreakpoint(miBpt)) {
return createPlatformFunctionBreakpoint(fileName, miBpt);
} else {
return createPlatformLineBreakpoint(fileName, miBpt);
}
}
private ICBreakpoint createPlatformAddressBreakpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
try {
return CDIDebugModel.createAddressBreakpoint(null, null, resource, type,
getPlatformAddress(miBpt.getAddress()), miBpt.isEnabled(), miBpt.getIgnoreCount(),
miBpt.getCondition(), false);
} catch (NumberFormatException e) {
throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.getUniqueIdentifier(),
String.format("Invalid breakpoint address: %s", miBpt.getAddress()))); //$NON-NLS-1$
}
}
private ICBreakpoint createPlatformFunctionTracepoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createFunctionTracepoint(fileName, resource, type, getFunctionName(miBpt), -1, -1,
getLineNumber(miBpt), miBpt.isEnabled(), miBpt.getIgnoreCount(), miBpt.getCondition(), false);
}
private ICBreakpoint createPlatformLineTracepoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createLineTracepoint(fileName, resource, type, getLineNumber(miBpt), miBpt.isEnabled(),
miBpt.getIgnoreCount(), miBpt.getCondition(), false);
}
private ICBreakpoint createPlatformTracepoint(String fileName, MIBreakpoint miBpt) throws CoreException {
if (isAddressBreakpoint(miBpt)) {
return createPlatformAddressTracepoint(fileName, miBpt);
} else if (isFunctionBreakpoint(miBpt)) {
return createPlatformFunctionTracepoint(fileName, miBpt);
} else {
return createPlatformLineTracepoint(fileName, miBpt);
}
}
private ICBreakpoint createPlatformAddressTracepoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
try {
return CDIDebugModel.createAddressTracepoint(null, null, resource, type, getLineNumber(miBpt),
getPlatformAddress(miBpt.getAddress()), miBpt.isEnabled(), miBpt.getIgnoreCount(),
miBpt.getCondition(), false);
} catch (NumberFormatException e) {
throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.getUniqueIdentifier(),
String.format("Invalid breakpoint address: %s", miBpt.getAddress()))); //$NON-NLS-1$
}
}
/**
* Create a new platform breakpoint for the function breakpoint. This method is
* called when =breakpoint-created is received from GDB and there is not already
* a matching platform breakpoint
*
* @param fileName
* resolved filename
* @param miBpt
* breakpoint info from GDB, must be one for which
* {@link #isFunctionBreakpoint(MIBreakpoint)} returns true.
* @return new platform breakpoint
* @throws CoreException
*/
private ICBreakpoint createPlatformFunctionBreakpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource;
String resolvedFileName;
if (userRequestedSpecificFile(miBpt)) {
resource = getResource(fileName);
resolvedFileName = fileName;
} else {
resource = ResourcesPlugin.getWorkspace().getRoot();
resolvedFileName = null;
}
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createFunctionBreakpoint(resolvedFileName, resource, type, getFunctionName(miBpt), -1, -1,
getLineNumber(miBpt), miBpt.isEnabled(), miBpt.getIgnoreCount(), miBpt.getCondition(), false);
}
/**
* If the user inserted the breakpoint with a filename (e.g. "b main.c:main")
* then create the breakpoint with that file, otherwise the function breakpoint
* should be inserted in the same way as if it was done with the UI "Add
* Function Breakpoint (C/C++)".
*
* @param miBpt
* an MI Breakpoint that is a function breakpoint
* @return true if the user specified file and function, false if just a
* function was specified.
*/
private boolean userRequestedSpecificFile(MIBreakpoint miBpt) {
assert isFunctionBreakpoint(miBpt);
String originalLocation = miBpt.getOriginalLocation();
return originalLocation != null && originalLocation.contains(":"); //$NON-NLS-1$
}
private ICBreakpoint createPlatformLineBreakpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createLineBreakpoint(fileName, resource, type, getLineNumber(miBpt), miBpt.isEnabled(),
miBpt.getIgnoreCount(), miBpt.getCondition(), false);
}
private ICBreakpoint createPlatformDynamicPrintf(String fileName, MIBreakpoint miBpt) throws CoreException {
if (isAddressBreakpoint(miBpt)) {
return createPlatformAddressDynamicPrintf(fileName, miBpt);
}
// TODO This is currently causing problems because we think a normal dprintf is a function one
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=400628#c16 which says:
// "synchronization of function dprintf does not work"
// else if (isFunctionBreakpoint(miBpt)) {
// return createPlatformFunctionDynamicPrintf(fileName, miBpt);
// }
else {
return createPlatformLineDynamicPrintf(fileName, miBpt);
}
}
private ICBreakpoint createPlatformAddressDynamicPrintf(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
try {
return CDIDebugModel.createAddressDynamicPrintf(null, null, resource, type, getLineNumber(miBpt),
getPlatformAddress(miBpt.getAddress()), miBpt.isEnabled(), miBpt.getIgnoreCount(),
miBpt.getCondition(), miBpt.getPrintfString(), false);
} catch (NumberFormatException e) {
throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.getUniqueIdentifier(),
String.format("Invalid breakpoint address: %s", miBpt.getAddress()))); //$NON-NLS-1$
}
}
// Unused, see TODO in createPlatformDynamicPrintf and Bug 400628 Comment 16
@SuppressWarnings("unused")
private ICBreakpoint createPlatformFunctionDynamicPrintf(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createFunctionDynamicPrintf(fileName, resource, type, getFunctionName(miBpt), -1, -1,
getLineNumber(miBpt), miBpt.isEnabled(), miBpt.getIgnoreCount(), miBpt.getCondition(),
miBpt.getPrintfString(), false);
}
private ICBreakpoint createPlatformLineDynamicPrintf(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createLineDynamicPrintf(fileName, resource, type, getLineNumber(miBpt), miBpt.isEnabled(),
miBpt.getIgnoreCount(), miBpt.getCondition(), miBpt.getPrintfString(), false);
}
private ICBreakpoint createPlatformWatchpoint(String fileName, MIBreakpoint miBpt) throws CoreException {
IResource resource = getResource(fileName);
int type = 0;
if (miBpt.isTemporary()) {
type |= ICBreakpointType.TEMPORARY;
}
if (miBpt.isHardware()) {
type |= ICBreakpointType.HARDWARE;
}
return CDIDebugModel.createWatchpoint(fileName, resource, type,
miBpt.isAccessWatchpoint() || miBpt.isWriteWatchpoint(),
miBpt.isAccessWatchpoint() || miBpt.isReadWatchpoint(), miBpt.getExpression(), miBpt.isEnabled(),
miBpt.getIgnoreCount(), miBpt.getCondition(), false);
}
/**
* Retrieve the breakpoint context from the given target breakpoint
* @param miBpt target breakpoint
* @return breakpoint context, or {@code null} if not available
* @since 5.3
*/
protected IBreakpointsTargetDMContext getBreakpointsTargetContext(MIBreakpoint miBpt) {
IMIProcesses processes = getServicesTracker().getService(IMIProcesses.class);
if (processes == null || getCommandControl() == null) {
return null;
}
// For GDB < 7.4, each process is its own breakpointTargetDMC so we need to find a the proper process
// based on the threadId. For GDB >= 7.4, this does not matter as we'll always end up with the global bpTargetDMC
String threadId = (miBpt != null) ? miBpt.getThreadId() : null;
IContainerDMContext contContext = processes.createContainerContextFromThreadId(getCommandControl().getContext(),
threadId);
return DMContexts.getAncestorOfType(contContext, IBreakpointsTargetDMContext.class);
}
public void getTargetBreakpoint(IBreakpointsTargetDMContext context, Map<String, Object> attributes,
DataRequestMonitor<MIBreakpoint> rm) {
Collection<MIBreakpoint> targetBreakpoints = getCreatedTargetBreakpoints(context);
if (targetBreakpoints == null) {
rm.done();
return;
}
String type = (String) attributes.get(MIBreakpoints.BREAKPOINT_TYPE);
if (MIBreakpoints.BREAKPOINT.equals(type)) {
rm.done(getTargetLineBreakpoint(targetBreakpoints, (String) attributes.get(MIBreakpoints.FILE_NAME),
(Integer) attributes.get(MIBreakpoints.LINE_NUMBER),
(String) attributes.get(MIBreakpoints.FUNCTION), (String) attributes.get(MIBreakpoints.ADDRESS),
(Boolean) attributes.get(MIBreakpointDMData.IS_HARDWARE),
(Boolean) attributes.get(MIBreakpointDMData.IS_TEMPORARY)));
} else if (MIBreakpoints.TRACEPOINT.equals(type)) {
rm.done(getTargetTracepoint(targetBreakpoints, (String) attributes.get(MIBreakpoints.FILE_NAME),
(Integer) attributes.get(MIBreakpoints.LINE_NUMBER),
(String) attributes.get(MIBreakpoints.FUNCTION), (String) attributes.get(MIBreakpoints.ADDRESS),
(Boolean) attributes.get(MIBreakpointDMData.IS_HARDWARE),
(Boolean) attributes.get(MIBreakpointDMData.IS_TEMPORARY)));
} else if (MIBreakpoints.DYNAMICPRINTF.equals(type)) {
rm.done(getTargetDPrintf(targetBreakpoints, (String) attributes.get(MIBreakpoints.FILE_NAME),
(Integer) attributes.get(MIBreakpoints.LINE_NUMBER),
(String) attributes.get(MIBreakpoints.FUNCTION), (String) attributes.get(MIBreakpoints.ADDRESS),
(Boolean) attributes.get(MIBreakpointDMData.IS_HARDWARE),
(Boolean) attributes.get(MIBreakpointDMData.IS_TEMPORARY)));
} else if (MIBreakpoints.WATCHPOINT.equals(type)) {
rm.done(getTargetWatchpoint(targetBreakpoints, (String) attributes.get(MIBreakpoints.EXPRESSION),
(Boolean) attributes.get(MIBreakpoints.READ), (Boolean) attributes.get(MIBreakpoints.WRITE),
(Boolean) attributes.get(MIBreakpointDMData.IS_HARDWARE),
(Boolean) attributes.get(MIBreakpointDMData.IS_TEMPORARY)));
} else {
rm.done();
}
}
private MIBreakpoint getTargetLineBreakpoint(Collection<MIBreakpoint> targetBreakpoints, String fileName,
Integer lineNumber, String function, String address, Boolean isHardware, Boolean isTemporary) {
for (MIBreakpoint miBpt : targetBreakpoints) {
if (!miBpt.isWatchpoint() && !isCatchpoint(miBpt) && !miBpt.isTracepoint() && !miBpt.isDynamicPrintf()
&& compareBreakpointAttributes(miBpt, fileName, lineNumber, function, address, isHardware,
isTemporary)) {
return miBpt;
}
}
return null;
}
private MIBreakpoint getTargetTracepoint(Collection<MIBreakpoint> targetBreakpoints, String fileName,
Integer lineNumber, String function, String address, Boolean isHardware, Boolean isTemporary) {
for (MIBreakpoint miBpt : targetBreakpoints) {
if (miBpt.isTracepoint() && compareBreakpointAttributes(miBpt, fileName, lineNumber, function, address,
isHardware, isTemporary)) {
return miBpt;
}
}
return null;
}
private MIBreakpoint getTargetDPrintf(Collection<MIBreakpoint> targetBreakpoints, String fileName,
Integer lineNumber, String function, String address, Boolean isHardware, Boolean isTemporary) {
for (MIBreakpoint miBpt : targetBreakpoints) {
if (miBpt.isDynamicPrintf() && compareBreakpointAttributes(miBpt, fileName, lineNumber, function, address,
isHardware, isTemporary)) {
return miBpt;
}
}
return null;
}
private MIBreakpoint getTargetWatchpoint(Collection<MIBreakpoint> targetBreakpoints, String expression,
boolean readAccess, boolean writeAccess, Boolean isHardware, Boolean isTemporary) {
for (MIBreakpoint miBpt : targetBreakpoints) {
if (!miBpt.isWatchpoint()) {
continue;
}
if (expression == null || !expression.equals(miBpt.getExpression())) {
continue;
}
if (readAccess && writeAccess && !miBpt.isAccessWatchpoint()) {
continue;
}
if (readAccess && !writeAccess && !miBpt.isReadWatchpoint()) {
continue;
}
if (!readAccess && writeAccess && !miBpt.isWriteWatchpoint()) {
continue;
}
if (!compareBreakpointTypeAttributes(miBpt, isHardware, isTemporary)) {
continue;
}
return miBpt;
}
return null;
}
private boolean compareBreakpointAttributes(MIBreakpoint miBpt, String fileName, Integer lineNumber,
String function, String address, Boolean isHardware, Boolean isTemporary) {
return compareBreakpointLocationAttributes(miBpt, fileName, lineNumber, function, address)
&& compareBreakpointTypeAttributes(miBpt, isHardware, isTemporary);
}
private boolean compareBreakpointLocationAttributes(MIBreakpoint miBpt, String fileName, Integer lineNumber,
String function, String address) {
if (isFunctionBreakpoint(miBpt) && (function == null || !function.equals(getFunctionName(miBpt)))) {
return false;
}
if (isAddressBreakpoint(miBpt)
&& (address == null || !address.equals(getPlatformAddress(miBpt.getAddress()).toHexAddressString()))) {
return false;
}
if (isLineBreakpoint(miBpt)) {
String miBptFileName = getFileName(miBpt);
if (fileName == null || miBptFileName == null || !new File(fileName).equals(new File(miBptFileName))) {
return false;
}
if (lineNumber == null || lineNumber.intValue() != getLineNumber(miBpt)) {
return false;
}
}
return true;
}
private boolean compareBreakpointTypeAttributes(MIBreakpoint miBpt, Boolean isHardware, Boolean isTemporary) {
if ((isHardware == null && miBpt.isHardware())
|| (isHardware != null && isHardware.booleanValue() != miBpt.isHardware())) {
return false;
}
if ((isTemporary == null && miBpt.isTemporary())
|| (isTemporary != null && isTemporary.booleanValue() != miBpt.isTemporary())) {
return false;
}
return true;
}
public void removeCreatedTargetBreakpoint(IBreakpointsTargetDMContext context, MIBreakpoint miBpt) {
Map<String, MIBreakpoint> map = fCreatedTargetBreakpoints.get(context);
if (map != null) {
map.remove(miBpt.getNumber());
}
}
private boolean isPlatformLineBreakpoint(ICLineBreakpoint plBpt, MIBreakpoint miBpt, String fileName) {
if (plBpt instanceof ICAddressBreakpoint) {
return isAddressBreakpoint(miBpt) ? isPlatformAddressBreakpoint((ICAddressBreakpoint) plBpt, miBpt) : false;
}
if (plBpt instanceof ICFunctionBreakpoint) {
return isFunctionBreakpoint(miBpt)
? isPlatformFunctionBreakpoint((ICFunctionBreakpoint) plBpt, miBpt, fileName)
: false;
}
try {
if (fileName == null || plBpt.getSourceHandle() == null
|| !new File(fileName).equals(new File(plBpt.getSourceHandle()))) {
return false;
}
if (plBpt.getLineNumber() != getLineNumber(miBpt)) {
return false;
}
return true;
} catch (CoreException e) {
GdbPlugin.log(e.getStatus());
}
return false;
}
private boolean isPlatformFunctionBreakpoint(ICFunctionBreakpoint plBpt, MIBreakpoint miBpt, String fileName) {
try {
if (!Objects.equals(plBpt.getFunction(), getFunctionName(miBpt))) {
return false;
}
if (userRequestedSpecificFile(miBpt)) {
if (fileName == null || plBpt.getSourceHandle() == null
|| !new File(fileName).equals(new File(plBpt.getSourceHandle()))) {
return false;
}
} else {
if (plBpt.getSourceHandle() != null) {
return false;
}
}
return true;
} catch (CoreException e) {
GdbPlugin.log(e.getStatus());
}
return false;
}
private boolean isPlatformAddressBreakpoint(ICAddressBreakpoint plBpt, MIBreakpoint miBpt) {
try {
return (plBpt.getAddress() != null
&& plBpt.getAddress().equals(getPlatformAddress(miBpt.getAddress()).toHexAddressString()));
} catch (CoreException e) {
GdbPlugin.log(e.getStatus());
}
return false;
}
private boolean isPlatformWatchpoint(ICWatchpoint plBpt, MIBreakpoint miBpt) {
try {
if (plBpt.getExpression() != null && plBpt.getExpression().equals(miBpt.getExpression())) {
if (miBpt.isAccessWatchpoint()) {
return plBpt.isWriteType() && plBpt.isReadType();
} else if (miBpt.isReadWatchpoint()) {
return !plBpt.isWriteType() && plBpt.isReadType();
} else if (miBpt.isWriteWatchpoint()) {
return plBpt.isWriteType() && !plBpt.isReadType();
}
}
} catch (CoreException e) {
GdbPlugin.log(e.getStatus());
}
return false;
}
private boolean isPlatformTracepoint(ICTracepoint plBpt, MIBreakpoint miBpt, String fileName) {
return isPlatformLineBreakpoint(plBpt, miBpt, fileName);
}
private boolean isPlatformDynamicPrintf(ICDynamicPrintf plBpt, MIBreakpoint miBpt, String fileName) {
return isPlatformLineBreakpoint(plBpt, miBpt, fileName);
}
/** @since 5.0 */
public boolean isTargetBreakpointDeleted(IBreakpointsTargetDMContext context, String bpId, boolean remove) {
Set<String> set = fDeletedTargetBreakpoints.get(context);
if (set != null) {
return (remove) ? set.remove(bpId) : set.contains(bpId);
}
return false;
}
/**
* Returns the list of tracepoint actions generated from the given command string.
* If the corresponding action for a command doesn't exist in TracepointActionManager
* the new action is created and added.
*
* @param commands list of gdb tracepoint commands separated by TracepointActionManager.TRACEPOINT_ACTION_DELIMITER
*/
private ITracepointAction[] getActionsFromCommands(String[] commands) {
List<ITracepointAction> list = new ArrayList<>();
TracepointActionManager tam = TracepointActionManager.getInstance();
WhileSteppingAction whileStepping = null;
List<ITracepointAction> subActions = null;
for (String command : commands) {
// Check if an action for this command exists
boolean found = false;
for (ITracepointAction action : tam.getActions()) {
if (command.equals(action.getSummary())) {
if (whileStepping == null || subActions == null) {
list.add(action);
} else {
subActions.add(action);
}
found = true;
break;
}
}
if (!found) {
// Create a new action if an action for this command doesn't exists
ITracepointAction action = null;
if (command.startsWith(TC_COLLECT)) {
action = createCollectAction(command.substring(TC_COLLECT.length()));
} else if (command.startsWith(TC_TEVAL)) {
action = createEvaluateAction(command.substring(TC_TEVAL.length()));
} else if (command.startsWith(TC_WHILE_STEPPING)) {
whileStepping = createWhileSteppingAction(command.substring(TC_WHILE_STEPPING.length()));
if (whileStepping != null) {
subActions = new ArrayList<>();
}
} else if (command.equals(TC_END)) {
if (whileStepping == null || subActions == null) {
continue;
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (ITracepointAction a : subActions) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append(a.getName());
}
whileStepping.setSubActionsNames(sb.toString());
whileStepping.setSubActionsContent(sb.toString());
action = whileStepping;
// Search for existing action for this 'while-stepping' command
for (ITracepointAction a : tam.getActions()) {
if (whileStepping.getSummary().equals(a.getSummary())) {
action = a;
found = true;
break;
}
}
whileStepping = null;
subActions.clear();
subActions = null;
}
if (action != null) {
if (!found) {
TracepointActionManager.getInstance().addAction(action);
}
if (whileStepping == null || subActions == null) {
list.add(action);
} else {
subActions.add(action);
}
}
}
TracepointActionManager.getInstance().saveActionData();
}
return list.toArray(new ITracepointAction[list.size()]);
}
private CollectAction createCollectAction(String collectStr) {
CollectAction action = new CollectAction();
action.setName(TracepointActionManager.getInstance().makeUniqueActionName(action.getDefaultName()));
action.setCollectString(collectStr);
return action;
}
private EvaluateAction createEvaluateAction(String evalStr) {
EvaluateAction action = new EvaluateAction();
action.setName(TracepointActionManager.getInstance().makeUniqueActionName(action.getDefaultName()));
action.setEvalString(evalStr);
return action;
}
private WhileSteppingAction createWhileSteppingAction(String str) {
WhileSteppingAction action = new WhileSteppingAction();
action.setName(TracepointActionManager.getInstance().makeUniqueActionName(action.getDefaultName()));
try {
action.setStepCount(Integer.parseInt(str.trim()));
} catch (NumberFormatException e) {
return null;
}
return action;
}
protected void getSource(IBreakpointsTargetDMContext bpTargetDMC, final String debuggerPath,
final DataRequestMonitor<String> rm) {
ISourceLookup sourceLookup = getServicesTracker().getService(ISourceLookup.class);
if (sourceLookup == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Source lookup service is not available")); //$NON-NLS-1$
rm.done();
return;
}
ISourceLookupDMContext srcDmc = DMContexts.getAncestorOfType(bpTargetDMC, ISourceLookupDMContext.class);
if (srcDmc == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "No source lookup context")); //$NON-NLS-1$
rm.done();
return;
}
if (debuggerPath == null || debuggerPath.isEmpty()) {
rm.done();
return;
}
sourceLookup.getSource(srcDmc, debuggerPath, new DataRequestMonitor<>(getExecutor(), rm) {
@Override
@ConfinedToDsfExecutor("fExecutor")
protected void handleCompleted() {
String fileName = null;
if (isSuccess()) {
if (getData() instanceof IFile) {
fileName = ((IFile) getData()).getLocation().toOSString();
} else if (getData() instanceof File) {
fileName = ((File) getData()).getAbsolutePath();
} else if (getData() instanceof ITranslationUnit) {
IPath location = ((ITranslationUnit) getData()).getLocation();
if (location != null) {
fileName = location.toOSString();
}
} else if (getData() instanceof LocalFileStorage) {
fileName = ((LocalFileStorage) getData()).getFile().getAbsolutePath();
}
}
rm.setData((fileName != null && !fileName.isEmpty()) ? fileName : debuggerPath);
rm.done();
}
});
}
/**
* Return true if target breakpoint is a function breakpoint
* @param miBpt target breakpoint
* @return true if this is a function breakpoint
* @since 5.3
*/
protected boolean isFunctionBreakpoint(MIBreakpoint miBpt) {
String origFunction = getFunctionFromOriginalLocation(miBpt.getOriginalLocation());
if (miBpt.getFunction().isEmpty()) {
return !origFunction.isEmpty();
}
String function = miBpt.getFunction();
// For C++ the function name for "break x" is reported as "x()".
// To compare it to the name retrieved from the original location
// we need to remove "()".
int index = function.indexOf('(');
if (index > 0 && origFunction.indexOf('(') == -1) {
return function.substring(0, index).equals(origFunction);
}
return function.equals(origFunction);
}
/**
* Return true if target breakpoint is an address breakpoint
* @param miBpt target breakpoint
* @return true if this is an address breakpoint
* @since 5.3
*/
protected boolean isAddressBreakpoint(MIBreakpoint miBpt) {
return miBpt.getOriginalLocation().startsWith("*"); //$NON-NLS-1$
}
/**
* Return true if target breakpoint is a line breakpoint
* @param miBpt target breakpoint
* @return true if this is a line breakpoint
* @since 5.3
*/
protected boolean isLineBreakpoint(MIBreakpoint miBpt) {
return !isFunctionBreakpoint(miBpt) && !isAddressBreakpoint(miBpt);
}
private IAddress getPlatformAddress(String miAddress) {
int radix = 10;
if (miAddress.startsWith("0x")) { //$NON-NLS-1$
radix = 16;
miAddress = miAddress.substring(2);
}
return new Addr64(new BigInteger(miAddress, radix));
}
private boolean isBreakpointTargetTracked(IBreakpointsTargetDMContext btTargetDMC) {
return fTrackedTargets.contains(btTargetDMC);
}
/**
* Obtain the file name of the target breakpoint.
*
* @param miBpt target breakpoint
* @return file name
* @since 5.3
*/
protected String getFileName(MIBreakpoint miBpt) {
String fileName = (miBpt.getFullName() != null && !miBpt.getFullName().isEmpty()) ? miBpt.getFullName()
: miBpt.getFile();
if (fileName != null && !fileName.isEmpty()) {
return fileName;
}
// When a breakpoint is set from the console on an invalid file both
// 'file' and 'fullname' attributes are not available, we need to parse
// the 'original-location' attribute to retrieve the file name.
String origLocation = miBpt.getOriginalLocation();
if (origLocation.isEmpty()) {
// Shouldn't happen
GdbPlugin.log(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Invalid 'original-location' attribute")); //$NON-NLS-1$
return ""; //$NON-NLS-1$
}
if (origLocation.startsWith("*")) { //$NON-NLS-1$
// Address breakpoint
return ""; //$NON-NLS-1$
}
int index = origLocation.lastIndexOf(':');
return (index > 0) ? origLocation.substring(0, index) : ""; //$NON-NLS-1$
}
/**
* Obtain the line number of the target breakpoint.
*
* @param miBpt target breakpoint
* @return line number
* @since 5.3
*/
protected int getLineNumber(MIBreakpoint miBpt) {
int lineNumber = miBpt.getLine();
if (lineNumber != -1) {
return lineNumber;
}
// When a breakpoint is set from the console on an invalid file
// the 'line' attributes is not available, we need to parse
// the 'original-location' attribute to retrieve the line number.
String origLocation = miBpt.getOriginalLocation();
if (origLocation.isEmpty()) {
// Shouldn't happen
GdbPlugin.log(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Invalid 'original-location' attribute")); //$NON-NLS-1$
return -1;
}
if (origLocation.startsWith("*")) { //$NON-NLS-1$
// Address breakpoint
return -1;
}
int index = origLocation.lastIndexOf(':');
if (index > 0 && origLocation.length() > index + 1) {
try {
return Integer.valueOf(origLocation.substring(index + 1, origLocation.length())).intValue();
} catch (NumberFormatException e) {
// not a line breakpoint
}
}
return -1;
}
/**
* Obtain the function name of the target breakpoint.
*
* @param miBpt target breakpoint
* @return function name
* @since 5.3
*/
protected String getFunctionName(MIBreakpoint miBpt) {
if (miBpt.getFunction() != null && !miBpt.getFunction().isEmpty())
return miBpt.getFunction();
// When a function breakpoint is set from the console, the symbol associated with
// the function may not be known to GDB. In this case the 'function' attribute is
// not available, we need to parse the 'original-location' attribute to retrieve
// the function name.
return getFunctionFromOriginalLocation(miBpt.getOriginalLocation());
}
private IResource getResource(String fileName) {
IResource resource = null;
if (fileName == null || fileName.isEmpty()) {
resource = ResourcesPlugin.getWorkspace().getRoot();
} else {
IFile[] files = ResourcesPlugin.getWorkspace().getRoot()
.findFilesForLocationURI(new File(fileName).toURI());
if (files.length > 0) {
resource = files[0];
} else {
resource = ResourcesPlugin.getWorkspace().getRoot();
}
}
return resource;
}
@DsfServiceEventHandler
public void eventDispatched(IExitedDMEvent e) {
if (e.getDMContext() instanceof IBreakpointsTargetDMContext) {
// Remove breakpoint entries when a breakpoint target is removed.
// This will happen for GDB < 7.4 where the container is the breakpoint target.
// For GDB >= 7.4, GDB is the breakpoint target and will not be removed.
IBreakpointsTargetDMContext bpTargetDMContext = (IBreakpointsTargetDMContext) e.getDMContext();
Map<String, MIBreakpoint> createdBreakpoints = fCreatedTargetBreakpoints.remove(bpTargetDMContext);
if (createdBreakpoints != null) {
createdBreakpoints.clear();
}
Map<String, MIBreakpoint> modifications = fPendingModifications.remove(bpTargetDMContext);
if (modifications != null) {
modifications.clear();
}
Set<String> deletedBreakpoints = fDeletedTargetBreakpoints.remove(bpTargetDMContext);
if (deletedBreakpoints != null) {
deletedBreakpoints.clear();
}
}
}
private String getFunctionFromOriginalLocation(String origLocation) {
if (origLocation.isEmpty()) {
// Shouldn't happen
GdbPlugin.log(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, "Invalid 'original-location' attribute")); //$NON-NLS-1$
return ""; //$NON-NLS-1$
}
if (origLocation.startsWith("*")) { //$NON-NLS-1$
// Address breakpoint
return ""; //$NON-NLS-1$
}
int index = origLocation.lastIndexOf(':');
String function = (index >= 0) ? origLocation.substring(index + 1) : origLocation;
try {
//TODO This does not work for dprintf since the output of the orginal location can look like this:
//original-location="/home/lmckhou/runtime-TestDSF/Producer/src/Producer.cpp:100,\\"Hit line %d of /home/lmckhou/runtime-TestDSF/Producer/src/Producer.cpp\\\\n\\",100"
Integer.valueOf(function);
// Line breakpoint
return ""; //$NON-NLS-1$
} catch (NumberFormatException e) {
// possible function breakpoint
}
return function;
}
protected boolean isCatchpoint(MIBreakpoint miBpt) {
// Since we are using the CLI 'catch' command to set catchpoints GDB will emit
// the 'breakpoint-created' notification even if the catchpoint is set from UI.
// In case of 'catch' and 'throw' events the value of the 'type' attribute in
// the breakpoint notification's data is 'breakpoint' instead of 'catchpoint'.
// In this cases to identify the correct type we need to check the content of
// the 'what' attribute.
return (miBpt.isCatchpoint() || (!miBpt.isWatchpoint() && (CE_EXCEPTION_CATCH.equals(miBpt.getExpression())
|| CE_EXCEPTION_THROW.equals(miBpt.getExpression()))));
}
}