| /******************************************************************************* |
| * Copyright (c) 2000, 2013 QNX Software Systems 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: |
| * QNX Software Systems - Initial API and implementation |
| * Wind River Systems - Modified for new DSF Reference Implementation |
| * Ericsson AB - Additional handling of events |
| * Mikhail Khodjaiants (Mentor Graphics) - Refactor common code in GDBControl* classes (bug 372795) |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.dsf.mi.service.command; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor; |
| import org.eclipse.cdt.dsf.datamodel.DMContexts; |
| import org.eclipse.cdt.dsf.datamodel.IDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.ISignals.ISignalsDMContext; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandResult; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandToken; |
| import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; |
| import org.eclipse.cdt.dsf.mi.service.IMIProcesses; |
| import org.eclipse.cdt.dsf.mi.service.MIProcesses; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIInterpreterExecConsole; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointChangedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIDetachedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MISignalChangedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadCreatedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIConsoleStreamOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput; |
| import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; |
| import org.eclipse.cdt.dsf.service.DsfServicesTracker; |
| |
| /** |
| * GDB debugger output listener. |
| */ |
| @ConfinedToDsfExecutor("fConnection#getExecutor") |
| public class CLIEventProcessor implements IEventProcessor { |
| private final ICommandControlService fCommandControl; |
| private final ICommandControlDMContext fControlDmc; |
| |
| // Last Thread ID created |
| private int fLastThreadId; |
| |
| private final DsfServicesTracker fServicesTracker; |
| |
| /** |
| * @since 1.1 |
| */ |
| public CLIEventProcessor(ICommandControlService connection, ICommandControlDMContext controlDmc) { |
| fCommandControl = connection; |
| fControlDmc = controlDmc; |
| fServicesTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fCommandControl.getSession().getId()); |
| connection.addCommandListener(this); |
| connection.addEventListener(this); |
| |
| fCommandControl.getSession().addServiceEventListener(this, null); |
| } |
| |
| @Override |
| public void dispose() { |
| fCommandControl.getSession().removeServiceEventListener(this); |
| fCommandControl.removeCommandListener(this); |
| fCommandControl.removeEventListener(this); |
| fServicesTracker.dispose(); |
| } |
| |
| @Override |
| public void commandSent(ICommandToken token) { |
| if (token.getCommand() instanceof CLICommand<?>) { |
| processStateChanges((CLICommand<?>) token.getCommand()); |
| } else if (token.getCommand() instanceof MIInterpreterExecConsole<?>) { |
| processStateChanges((MIInterpreterExecConsole<?>) token.getCommand()); |
| } |
| } |
| |
| @Override |
| public void commandDone(ICommandToken token, ICommandResult result) { |
| if (token.getCommand() instanceof CLICommand<?>) { |
| processSettingChanges((CLICommand<?>) token.getCommand()); |
| } else if (token.getCommand() instanceof MIInterpreterExecConsole<?>) { |
| processSettingChanges((MIInterpreterExecConsole<?>) token.getCommand()); |
| } |
| } |
| |
| @Override |
| public void commandQueued(ICommandToken token) { |
| // No action |
| } |
| |
| @Override |
| public void commandRemoved(ICommandToken token) { |
| // No action |
| } |
| |
| @Override |
| public void eventReceived(Object output) { |
| for (MIOOBRecord oobr : ((MIOutput) output).getMIOOBRecords()) { |
| if (oobr instanceof MIConsoleStreamOutput) { |
| // Process Events of type DsfMIConsoleStreamOutput here |
| MIConsoleStreamOutput exec = (MIConsoleStreamOutput) oobr; |
| |
| // Look for events that indicate a new thread: |
| // Examples: |
| // [New Thread 1077300144 (LWP 7973) // POSIX |
| // [New thread 4092.0x8c4] // cygwin and mingw |
| // Since we currently don't use any of the information in the |
| // message, we'll use a simple regex pattern |
| Pattern pattern = Pattern.compile("^\\[New [Tt]hread\\s+"); //$NON-NLS-1$ |
| Matcher matcher = pattern.matcher(exec.getCString()); |
| if (matcher.find()) { |
| String threadId = Integer.toString(++fLastThreadId); |
| |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (procService != null) { |
| IContainerDMContext processContainerDmc = procService |
| .createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID); |
| MIEvent<?> e = new MIThreadCreatedEvent(processContainerDmc, threadId); |
| fCommandControl.getSession().dispatchEvent(e, fCommandControl.getProperties()); |
| } |
| } |
| |
| // For GDB thread exit events, we won't use the events generated by GDB. This event is |
| // raised in GDBRunControl class by polling and comparing the ExecutionContexts returned by |
| // -thread-list-ids command. This is done as threads reported by exit event are still reported till |
| // they completely exit the system. |
| } |
| } |
| } |
| |
| private void processStateChanges(CLICommand<? extends ICommandResult> cmd) { |
| String operation = cmd.getOperation().trim(); |
| // In refactoring we are no longer generating the token id as |
| // part of the command. It is passed here and stored away and |
| // then never really used. So it has just been changed to 0. |
| processStateChanges(0, operation); |
| } |
| |
| private void processStateChanges(MIInterpreterExecConsole<? extends ICommandResult> exec) { |
| String[] operations = exec.getParameters(); |
| if (operations != null && operations.length > 0) { |
| // In refactoring we are no longer generating the token id as |
| // part of the command. It is passed here and stored away and |
| // then never really used. So it has just been changed to 0. |
| processStateChanges(0, operations[0]); |
| } |
| } |
| |
| private void processStateChanges(int token, String operation) { |
| // Check the type of command |
| int type = getSteppingOperationKind(operation); |
| if (type != -1) { |
| // if it was a step instruction set state running |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (procService != null) { |
| IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, |
| MIProcesses.UNIQUE_GROUP_ID); |
| MIEvent<?> event = new MIRunningEvent(processContainerDmc, token, type); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } |
| } |
| |
| /** |
| * An attempt to discover the command type and |
| * fire an event if necessary. |
| */ |
| private void processSettingChanges(CLICommand<?> cmd) { |
| String operation = cmd.getOperation().trim(); |
| // In refactoring we are no longer generating the token id as |
| // part of the command. It is passed here and stored away and |
| // then never really used. So it has just been changed to 0. |
| processSettingChanges(cmd.getContext(), 0, operation); |
| } |
| |
| private void processSettingChanges(MIInterpreterExecConsole<?> exec) { |
| String[] operations = exec.getParameters(); |
| if (operations != null && operations.length > 0) { |
| // In refactoring we are no longer generating the token id as |
| // part of the command. It is passed here and stored away and |
| // then never really used. So it has just been changed to 0. |
| processSettingChanges(exec.getContext(), 0, operations[0]); |
| } |
| } |
| |
| private void processSettingChanges(IDMContext dmc, int token, String operation) { |
| // Get the command name. |
| int indx = operation.indexOf(' '); |
| if (indx != -1) { |
| operation = operation.substring(0, indx).trim(); |
| } else { |
| operation = operation.trim(); |
| } |
| |
| // Check the type of command |
| |
| if (isSettingBreakpoint(operation) || isSettingWatchpoint(operation) || isChangeBreakpoint(operation) |
| || isDeletingBreakpoint(operation)) { |
| // We know something change, we just do not know what. |
| // So the easiest way is to let the top layer handle it. |
| IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(dmc, |
| IBreakpointsTargetDMContext.class); |
| if (bpTargetDmc != null) { |
| MIEvent<?> event = new MIBreakpointChangedEvent(bpTargetDmc, 0); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if (isSettingSignal(operation)) { |
| // We do no know which signal let the upper layer find it. |
| ISignalsDMContext signalDmc = DMContexts.getAncestorOfType(dmc, ISignalsDMContext.class); |
| if (signalDmc != null) { |
| MIEvent<?> event = new MISignalChangedEvent(signalDmc, ""); //$NON-NLS-1$ |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if (isDetach(operation)) { |
| // if it was a "detach" command change the state. |
| ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); |
| if (controlDmc != null) { |
| MIEvent<?> event = new MIDetachedEvent(controlDmc, token); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } |
| } |
| |
| private static int getSteppingOperationKind(String operation) { |
| // Get the command name. |
| int indx = operation.indexOf(' '); |
| if (indx != -1) { |
| operation = operation.substring(0, indx).trim(); |
| } else { |
| operation = operation.trim(); |
| } |
| |
| int type = -1; |
| /* execution commands: n, next, s, step, si, stepi, u, until, finish, return, |
| c, continue, fg */ |
| if (operation.equals("n") || operation.equals("next")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.NEXT; |
| } else if (operation.equals("ni") || operation.equals("nexti")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.NEXTI; |
| } else if (operation.equals("s") || operation.equals("step")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.STEP; |
| } else if (operation.equals("si") || operation.equals("stepi")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.STEPI; |
| } else if (operation.equals("u") || //$NON-NLS-1$ |
| (operation.startsWith("unt") && "until".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.UNTIL; |
| } else if (operation.startsWith("fin") && "finish".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.FINISH; |
| } else if (operation.startsWith("ret") && "return".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.RETURN; |
| } else if (operation.equals("c") || operation.equals("fg") || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("cont") && "continue".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.CONTINUE; |
| } else if (operation.startsWith("sig") && "signal".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.CONTINUE; |
| } else if (operation.startsWith("j") && "jump".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.CONTINUE; |
| } else if (operation.equals("r") || operation.equals("run")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| type = MIRunningEvent.CONTINUE; |
| } |
| return type; |
| } |
| |
| /** |
| * Return true if the operation is a stepping operation. |
| * |
| * @param operation |
| * @return |
| */ |
| public static boolean isSteppingOperation(String operation) { |
| int type = getSteppingOperationKind(operation); |
| return type != -1; |
| } |
| |
| /** |
| * Return true if the operation is a attaching operation. |
| * |
| * @param operation |
| * @return |
| * @since 4.0 |
| */ |
| public static boolean isAttachingOperation(String operation) { |
| // Get the command name. |
| int indx = operation.indexOf(' '); |
| if (indx != -1) { |
| operation = operation.substring(0, indx).trim(); |
| } else { |
| operation = operation.trim(); |
| } |
| /* attach: at, att, atta, attac, attach */ |
| return (operation.startsWith("at") && "attach".indexOf(operation) != -1); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| private boolean isSettingBreakpoint(String operation) { |
| boolean isbreak = false; |
| /* breakpoints: b, break, hbreak, tbreak, rbreak, thbreak */ |
| /* watchpoints: watch, rwatch, awatch, tbreak, rbreak, thbreak */ |
| if ((operation.startsWith("b") && "break".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("tb") && "tbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("hb") && "hbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("thb") && "thbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("rb") && "rbreak".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| isbreak = true; |
| } |
| return isbreak; |
| } |
| |
| private boolean isSettingWatchpoint(String operation) { |
| boolean isWatch = false; |
| /* watchpoints: watch, rwatch, awatch, tbreak, rbreak, thbreak */ |
| if ((operation.startsWith("wa") && "watch".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("rw") && "rwatch".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("aw") && "awatch".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| isWatch = true; |
| } |
| return isWatch; |
| } |
| |
| private boolean isDeletingBreakpoint(String operation) { |
| boolean isDelete = false; |
| /* deleting breaks: clear, delete */ |
| if ((operation.startsWith("cl") && "clear".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.equals("d") || (operation.startsWith("del") && "delete".indexOf(operation) != -1))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| isDelete = true; |
| } |
| return isDelete; |
| } |
| |
| private boolean isChangeBreakpoint(String operation) { |
| boolean isChange = false; |
| /* changing breaks: enable, disable */ |
| if ((operation.equals("dis") || operation.equals("disa") || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("disa") && "disable".indexOf(operation) != -1)) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.equals("en") || (operation.startsWith("en") && "enable".indexOf(operation) != -1)) || //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| (operation.startsWith("ig") && "ignore".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$ |
| (operation.startsWith("cond") && "condition".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| isChange = true; |
| } |
| return isChange; |
| } |
| |
| private boolean isSettingSignal(String operation) { |
| boolean isChange = false; |
| /* changing signal: handle, signal */ |
| if (operation.startsWith("ha") && "handle".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$ |
| isChange = true; |
| } |
| return isChange; |
| } |
| |
| /** |
| * @param operation |
| * @return |
| */ |
| private boolean isDetach(String operation) { |
| return (operation.startsWith("det") && "detach".indexOf(operation) != -1); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** @since 4.0 */ |
| @DsfServiceEventHandler |
| public void eventDispatched(IStartedDMEvent e) { |
| if (e.getDMContext() instanceof IContainerDMContext) { |
| // If a process restarts, we must reset the thread id |
| // No need to worry about multi-process in this version. |
| fLastThreadId = 0; |
| } |
| } |
| |
| } |