/******************************************************************************* * Copyright (c) 2012, 2016 Mentor Graphics 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: * Mentor Graphics - Initial API and implementation *******************************************************************************/ package org.eclipse.cdt.tests.dsf.gdb.tests; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint; import org.eclipse.cdt.debug.core.model.ICBreakpoint; import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint; import org.eclipse.cdt.debug.core.model.ICLineBreakpoint; import org.eclipse.cdt.debug.core.model.ICWatchpoint; import org.eclipse.cdt.debug.internal.core.breakpoints.CBreakpoint; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.Query; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsAddedEvent; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsChangedEvent; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsRemovedEvent; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsUpdatedEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; 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.mi.service.command.output.MIInfo; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfServicesTracker; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.cdt.tests.dsf.gdb.framework.BaseParametrizedTestCase; import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil; import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin; import org.eclipse.cdt.utils.Addr64; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.model.IBreakpoint; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * This test case verifies whether breakpoints or watchpoints set from GDB console * are properly synchronized with platform breakpoints. */ @SuppressWarnings( "restriction" ) @RunWith(Parameterized.class) public class GDBConsoleBreakpointsTest extends BaseParametrizedTestCase { final static protected String SOURCE_NAME = "GDBMIGenericTestApp.cc"; final static private int DEFAULT_TIMEOUT = 20000; final static private TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS; final static private String SOURCE_NAME_VALID = new Path(SOURCE_PATH + SOURCE_NAME).toFile().getAbsolutePath(); final static private int LINE_NUMBER_VALID = 8; final static private String SOURCE_NAME_INVALID = new Path("x.c").toFile().getAbsolutePath(); final static private int LINE_NUMBER_INVALID = 2; final static private String FUNCTION_VALID = "main()"; final static private String FUNCTION_INVALID = "xxx"; final static private String EXPRESSION_VALID = "path"; final static private String ATTR_FILE_NAME = "FILE_NAME"; final static private String ATTR_LINE_NUMBER = "LINE_NUMBER"; final static private String ATTR_FUNCTION = "FUNCTION"; final static private String ATTR_ADDRESS = "ADDRESS"; final static private String ATTR_EXPRESSION = "EXPRESSION"; final static private String ATTR_READ = "READ"; final static private String ATTR_WRITE = "WRITE"; private DsfSession fSession; private DsfServicesTracker fServicesTracker; protected IBreakpointsTargetDMContext fBreakpointsDmc; private IGDBControl fCommandControl; private List<IBreakpointsChangedEvent> fBreakpointEvents = new ArrayList<IBreakpointsChangedEvent>(); @Override @Before public void doBeforeTest() throws Exception { deleteAllPlatformBreakpoints(); super.doBeforeTest(); Runnable runnable = new Runnable() { @Override public void run() { fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId()); Assert.assertTrue(fServicesTracker != null); fCommandControl = fServicesTracker.getService(IGDBControl.class); Assert.assertTrue(fCommandControl != null); // Register to breakpoint events fSession.addServiceEventListener(GDBConsoleBreakpointsTest.this, null); } }; fSession = getGDBLaunch().getSession(); fSession.getExecutor().submit(runnable).get(); IContainerDMContext containerDmc = SyncUtil.getContainerContext(); fBreakpointsDmc = DMContexts.getAncestorOfType(containerDmc, IBreakpointsTargetDMContext.class); Assert.assertTrue(fBreakpointsDmc != null); } @Override @After public void doAfterTest() throws Exception { if (fSession != null) { fSession.getExecutor().submit(() -> fSession.removeServiceEventListener(GDBConsoleBreakpointsTest.this)) .get(); } fBreakpointEvents.clear(); if (fServicesTracker != null) { fServicesTracker.dispose(); fServicesTracker = null; } super.doAfterTest(); deleteAllPlatformBreakpoints(); } @Test public void testValidLineBreakpoints() throws Throwable { testConsoleBreakpoint( ICLineBreakpoint.class, getLocationBreakpointAttributes(ICLineBreakpoint.class, true)); } @Test public void testInvalidLineBreakpoints() throws Throwable { testConsoleBreakpoint( ICLineBreakpoint.class, getLocationBreakpointAttributes(ICLineBreakpoint.class, false)); } @Test public void testValidFunctionBreakpoints() throws Throwable { testConsoleBreakpoint( ICFunctionBreakpoint.class, getLocationBreakpointAttributes(ICFunctionBreakpoint.class, true)); } @Test public void testInvalidFunctionBreakpoints() throws Throwable { testConsoleBreakpoint( ICFunctionBreakpoint.class, getLocationBreakpointAttributes(ICFunctionBreakpoint.class, false)); } @Test public void testValidAddressBreakpoints() throws Throwable { testConsoleBreakpoint( ICAddressBreakpoint.class, getLocationBreakpointAttributes(ICAddressBreakpoint.class, true)); } @Test public void testAddressBreakpointsAtZeroAddress() throws Throwable { testConsoleBreakpoint( ICAddressBreakpoint.class, getLocationBreakpointAttributes(ICAddressBreakpoint.class, false)); } @Test public void testWriteWatchpoints() throws Throwable { testConsoleBreakpoint( ICWatchpoint.class, getWatchpointAttributes(ICWatchpoint.class, false, true)); } @Test public void testReadWatchpoints() throws Throwable { testConsoleBreakpoint( ICWatchpoint.class, getWatchpointAttributes(ICWatchpoint.class, true, false)); } @Test public void testAccessWatchpoints() throws Throwable { testConsoleBreakpoint( ICWatchpoint.class, getWatchpointAttributes(ICWatchpoint.class, true, true)); } @DsfServiceEventHandler public void eventDispatched(IBreakpointsChangedEvent e) { synchronized(this) { fBreakpointEvents.add(e); notifyAll(); } } private void testConsoleBreakpoint(Class<? extends ICBreakpoint> type, Map<String, Object> attributes) throws Throwable { // Set a console breakpoint and verify that // the corresponding platform breakpoint is created // and its install count is 1 if the breakpoint is installed // and 0 if the breakpoint is pending. // Check for a duplicate target breakpoint. setConsoleBreakpoint(type, attributes); MIBreakpoint[] miBpts = getTargetBreakpoints(); Assert.assertTrue(miBpts.length == 1); waitForBreakpointEvent(IBreakpointsAddedEvent.class); Assert.assertTrue(getPlatformBreakpointCount() == 1); ICBreakpoint plBpt = findPlatformBreakpoint(type, attributes); Assert.assertTrue(plBpt instanceof CBreakpoint); // We can't rely on IBreakpointsAddedEvent because it is fired // before the install count is incremented. if (!miBpts[0].isPending()) { // If the target breakpoint is not pending wait // until the install count becomes 1. waitForInstallCountChange((CBreakpoint)plBpt, 1); } else { // For pending breakpoints the install count is expected to remain // unchanged. Give it some time and verify that it is 0. Thread.sleep(1000); Assert.assertTrue(((CBreakpoint)plBpt).getInstallCount() == 0); } // Disable the console breakpoint and verify that // the platform breakpoint is disabled. enableConsoleBreakpoint(miBpts[0].getNumber(), false); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(!plBpt.isEnabled()); // Enable the console breakpoint and verify that // the platform breakpoint is enabled. enableConsoleBreakpoint(miBpts[0].getNumber(), true); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(plBpt.isEnabled()); // Set the ignore count of the console breakpoint and // verify that the platform breakpoint's ignore count // is updated. setConsoleBreakpointIgnoreCount(miBpts[0].getNumber(), 5); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(plBpt.getIgnoreCount() == 5); // Reset the ignore count of the console breakpoint and // verify that the platform breakpoint's ignore count // is updated. setConsoleBreakpointIgnoreCount(miBpts[0].getNumber(), 0); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(plBpt.getIgnoreCount() == 0); // Set the condition of the console breakpoint and // verify that the platform breakpoint's condition // is updated. setConsoleBreakpointCondition(miBpts[0].getNumber(), "path==0"); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(plBpt.getCondition().equals("path==0")); // Reset the condition of the console breakpoint and // verify that the platform breakpoint's condition // is updated. setConsoleBreakpointCondition(miBpts[0].getNumber(), ""); waitForBreakpointEvent(IBreakpointsUpdatedEvent.class); Assert.assertTrue(plBpt.getCondition().isEmpty()); // Delete the console breakpoint and verify that // the install count of the platform breakpoint is 0. deleteConsoleBreakpoint(miBpts[0].getNumber()); waitForBreakpointEvent(IBreakpointsRemovedEvent.class); Assert.assertTrue(getPlatformBreakpointCount() == 1); plBpt = findPlatformBreakpoint(type, attributes); Assert.assertTrue(plBpt instanceof CBreakpoint); waitForInstallCountChange((CBreakpoint)plBpt, 0); // Make sure the breakpoint does not get re-installed // once it gets a notification that the platform bp changed // (through its install count changing) Bug 433044 // Give it some time and verify that it is still 0. Thread.sleep(3000); // One second was not enough Assert.assertTrue("Install count no longer 0", ((CBreakpoint)plBpt).getInstallCount() == 0); // Set the console breakpoint again and verify that // the install count of the platform breakpoint is 1 // for installed breakpoints and 0 for pending breakpoints. setConsoleBreakpoint(type, attributes); miBpts = getTargetBreakpoints(); Assert.assertTrue(miBpts.length == 1); waitForBreakpointEvent(IBreakpointsAddedEvent.class); Assert.assertTrue(getPlatformBreakpointCount() == 1); // Give a little delay to allow queued Executor operations // to complete before deleting the breakpoint again. // If we don't we may delete it so fast that the MIBreakpointsManager // has not yet updated its data structures // Bug 438934 comment 10 Thread.sleep(500); plBpt = findPlatformBreakpoint(type, attributes); Assert.assertTrue(plBpt instanceof CBreakpoint); if (!miBpts[0].isPending()) { waitForInstallCountChange((CBreakpoint)plBpt, 1); } else { // For pending breakpoints the install count is expected to remain // unchanged. Give it some time and verify that it is 0. Thread.sleep(1000); Assert.assertTrue(((CBreakpoint)plBpt).getInstallCount() == 0); } // Remove the platform breakpoint and verify that // the target breakpoint is deleted. deletePlatformBreakpoint(plBpt); // Don't fail right away if we don't get the breakpoint event // as we can't tell the true cause. // Let further checks happen to help figure things out. String failure = ""; try { waitForBreakpointEvent(IBreakpointsRemovedEvent.class); } catch (Exception e) { failure += e.getMessage(); } int platformBp = getPlatformBreakpointCount(); if (platformBp != 0) { if (!failure.isEmpty()) failure += ", "; failure += "Platform breakpoints remaining: " + platformBp; } miBpts = getTargetBreakpoints(); if (miBpts.length != 0) { if (!failure.isEmpty()) failure += ", "; failure += "Target breakpoints remaining: " + miBpts.length; } Assert.assertTrue(failure, failure.isEmpty()); } private void setConsoleLineBreakpoint(String fileName, int lineNumber) throws Throwable { queueConsoleCommand(String.format("break %s:%d", fileName, lineNumber)); } private void setConsoleFunctionBreakpoint(String fileName, String function) throws Throwable { queueConsoleCommand(String.format("break %s:%s", fileName, function)); } private void setConsoleAddressBreakpoint(String address) throws Throwable { queueConsoleCommand(String.format("break *%s", address)); } private void setConsoleWatchpoint(String expression, boolean read, boolean write) throws Throwable { String command = (write) ? ((read) ? "awatch" : "watch") : "rwatch"; queueConsoleCommand(String.format("%s %s", command, expression)); } private void deleteConsoleBreakpoint(String bpId) throws Throwable { queueConsoleCommand(String.format("delete %s", bpId)); } private void enableConsoleBreakpoint(String bpId, boolean enable) throws Throwable { String cmd = (enable) ? "enable" : "disable"; queueConsoleCommand(String.format("%s %s", cmd, bpId)); } private void setConsoleBreakpointIgnoreCount(String bpId, int ignoreCount) throws Throwable { Assert.assertTrue(ignoreCount >= 0); queueConsoleCommand(String.format("ignore %s %d", bpId, ignoreCount)); } private void setConsoleBreakpointCondition(String bpId, String condition) throws Throwable { queueConsoleCommand(String.format("condition %s %s", bpId, condition)); } private MIBreakpoint[] getTargetBreakpoints() throws Throwable { return getTargetBreakpoints(DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); } private MIBreakpoint[] getTargetBreakpoints(int timeout, TimeUnit unit) throws Throwable { Query<MIBreakListInfo> query = new Query<MIBreakListInfo>() { @Override protected void execute(DataRequestMonitor<MIBreakListInfo> rm) { fCommandControl.queueCommand( fCommandControl.getCommandFactory().createMIBreakList(fBreakpointsDmc), rm); } }; fSession.getExecutor().execute(query); return query.get(timeout, unit).getMIBreakpoints(); } private void waitForBreakpointEvent(Class<? extends IBreakpointsChangedEvent> eventType) throws Exception { waitForBreakpointEvent(eventType, DEFAULT_TIMEOUT); } private void waitForBreakpointEvent(Class<? extends IBreakpointsChangedEvent> eventType, int timeout) throws Exception { if (!breakpointEventReceived(eventType)) { synchronized(this) { try { wait(timeout); } catch (InterruptedException ex) { } } if (!breakpointEventReceived(eventType)) { throw new Exception(String.format("Timed out waiting for '%s' to occur.", eventType.getName())); } } } private void queueConsoleCommand(String command) throws Throwable { queueConsoleCommand(command, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT); } private void queueConsoleCommand(final String command, int timeout, TimeUnit unit) throws Throwable { Query<MIInfo> query = new Query<MIInfo>() { @Override protected void execute(DataRequestMonitor<MIInfo> rm) { fCommandControl.queueCommand( fCommandControl.getCommandFactory().createMIInterpreterExecConsole( fCommandControl.getContext(), command), rm); } }; fSession.getExecutor().execute(query); query.get(timeout, unit); } private ICLineBreakpoint findPlatformLineBreakpoint(String fileName, int lineNumber) throws Throwable { for(IBreakpoint b : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) { if (b instanceof ICLineBreakpoint && fileName.equals(((ICLineBreakpoint)b).getSourceHandle()) && lineNumber == ((ICLineBreakpoint)b).getLineNumber()) { return (ICLineBreakpoint)b; } } return null; } private ICFunctionBreakpoint findPlatformFunctionBreakpoint(String fileName, String function) throws Throwable { for(IBreakpoint b : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) { if (b instanceof ICFunctionBreakpoint && fileName.equals(((ICLineBreakpoint)b).getSourceHandle()) && function.equals(((ICLineBreakpoint)b).getFunction())) { return (ICFunctionBreakpoint)b; } } return null; } private ICAddressBreakpoint findPlatformAddressBreakpoint(String address) throws Throwable { Addr64 a = new Addr64(address); for(IBreakpoint b : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) { if (b instanceof ICAddressBreakpoint && a.toHexAddressString().equals(((ICAddressBreakpoint)b).getAddress())) { return (ICAddressBreakpoint)b; } } return null; } private ICWatchpoint findPlatformWatchpoint(String expression, boolean read, boolean write) throws Throwable { for(IBreakpoint b : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) { if (b instanceof ICWatchpoint && ((ICWatchpoint)b).isReadType() == read && ((ICWatchpoint)b).isWriteType() == write) { return (ICWatchpoint)b; } } return null; } private void deletePlatformBreakpoint(final IBreakpoint plBpt) throws Throwable { new Job("") { @Override protected IStatus run(IProgressMonitor monitor) { IStatus result = Status.OK_STATUS; try { DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint(plBpt, true); } catch(CoreException e) { result = e.getStatus(); } return result; } }.schedule(); } private int getPlatformBreakpointCount() { return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints().length; } private void waitForInstallCountChange(CBreakpoint plBpt, int expected) throws Throwable { waitForInstallCountChange(plBpt, expected, DEFAULT_TIMEOUT); } private void waitForInstallCountChange(CBreakpoint plBpt, int expected, long timeout) throws Throwable { long startMs = System.currentTimeMillis(); while(plBpt.getInstallCount() != expected) { synchronized(this) { try { wait(30); } catch (InterruptedException ex) { } if (System.currentTimeMillis() - startMs > timeout) { throw new Exception("Timed out waiting for breakpoint's install count to change"); } } } } private synchronized boolean breakpointEventReceived(Class<? extends IBreakpointsChangedEvent> eventType) { for (IBreakpointsChangedEvent e : fBreakpointEvents) { if (eventType.isAssignableFrom(e.getClass())) { return fBreakpointEvents.remove(e); } } return false; } private Map<String, Object> getLocationBreakpointAttributes(Class<? extends ICBreakpoint> type, boolean valid) { Map<String, Object> map = new HashMap<String, Object>(); if (ICFunctionBreakpoint.class.equals(type)) { map.put(ATTR_FILE_NAME, (valid) ? SOURCE_NAME_VALID : SOURCE_NAME_INVALID); map.put(ATTR_FUNCTION, (valid) ? FUNCTION_VALID : FUNCTION_INVALID); } else if (ICAddressBreakpoint.class.equals(type)) { // '0x0" is not invalid address map.put(ATTR_ADDRESS, (valid) ? getInitialStoppedEvent().getFrame().getAddress() : new Addr64("0x0").toHexAddressString()); } else if (ICLineBreakpoint.class.equals(type)) { map.put(ATTR_FILE_NAME, (valid) ? SOURCE_NAME_VALID : SOURCE_NAME_INVALID); map.put(ATTR_LINE_NUMBER, (valid) ? LINE_NUMBER_VALID : LINE_NUMBER_INVALID); } return map; } public Map<String, Object> getWatchpointAttributes(Class<? extends ICWatchpoint> type, boolean read, boolean write) throws Throwable { Assert.assertTrue(read || write); Map<String, Object> map = new HashMap<String, Object>(); map.put(ATTR_EXPRESSION, EXPRESSION_VALID); map.put(ATTR_READ, Boolean.valueOf(read)); map.put(ATTR_WRITE, Boolean.valueOf(write)); return map; } private void setConsoleBreakpoint(Class<? extends ICBreakpoint> type, Map<String, Object> attributes) throws Throwable { if (ICFunctionBreakpoint.class.equals(type)) { setConsoleFunctionBreakpoint( (String)attributes.get(ATTR_FILE_NAME), (String)attributes.get(ATTR_FUNCTION)); } else if (ICAddressBreakpoint.class.equals(type)) { setConsoleAddressBreakpoint((String)attributes.get(ATTR_ADDRESS)); } else if (ICLineBreakpoint.class.equals(type)) { setConsoleLineBreakpoint( (String)attributes.get(ATTR_FILE_NAME), ((Integer)attributes.get(ATTR_LINE_NUMBER)).intValue()); } else if (ICWatchpoint.class.equals(type)) { setConsoleWatchpoint( (String)attributes.get(ATTR_EXPRESSION), ((Boolean)attributes.get(ATTR_READ)).booleanValue(), ((Boolean)attributes.get(ATTR_WRITE)).booleanValue()); } } private ICBreakpoint findPlatformBreakpoint(Class<? extends ICBreakpoint> type, Map<String, Object> attributes) throws Throwable { if (ICFunctionBreakpoint.class.equals(type)) { return findPlatformFunctionBreakpoint( (String)attributes.get(ATTR_FILE_NAME), (String)attributes.get(ATTR_FUNCTION)); } else if (ICAddressBreakpoint.class.equals(type)) { return findPlatformAddressBreakpoint((String)attributes.get(ATTR_ADDRESS)); } else if (ICLineBreakpoint.class.equals(type)) { return findPlatformLineBreakpoint( (String)attributes.get(ATTR_FILE_NAME), ((Integer)attributes.get(ATTR_LINE_NUMBER)).intValue()); } else if (ICWatchpoint.class.equals(type)) { return findPlatformWatchpoint( (String)attributes.get(ATTR_EXPRESSION), ((Boolean)attributes.get(ATTR_READ)).booleanValue(), ((Boolean)attributes.get(ATTR_WRITE)).booleanValue()); } throw new Exception(String.format("Invalid breakpoint type: %s", type.getName())); } private void deleteAllPlatformBreakpoints() throws Exception { IBreakpointManager bm = DebugPlugin.getDefault().getBreakpointManager(); for (IBreakpoint b : bm.getBreakpoints()) { bm.removeBreakpoint(b, true); } } }