| /******************************************************************************* |
| * Copyright (c) 2006, 2013 Wind River 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: |
| * Wind River Systems - initial API and implementation |
| * Ericsson - Version 7.0 |
| * Mikhail Khodjaiants (Mentor Graphics) - Refactor common code in GDBControl* classes (bug 372795) |
| * Marc Khouzam (Ericsson) - Display exit code in process console (Bug 402054) |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.mi.service.command; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| 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.debug.service.IProcesses.IProcessDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; |
| 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.command.ICommand; |
| 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.MIExecContinue; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecFinish; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecNext; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecNextInstruction; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecReturn; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecStep; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecStepInstruction; |
| import org.eclipse.cdt.dsf.mi.service.command.commands.MIExecUntil; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIFunctionFinishedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorExitEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIInferiorSignalExitEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MILocationReachedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MISharedLibEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MISignalEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MISteppingRangeEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadCreatedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadExitEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupAddedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupCreatedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointScopeEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIConsoleStreamOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIConst; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIExecAsyncOutput; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MINotifyAsyncOutput; |
| 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.mi.service.command.output.MIResult; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIValue; |
| import org.eclipse.cdt.dsf.service.DsfServicesTracker; |
| |
| /** |
| * MI debugger output listener that listens for the parsed MI output, and |
| * generates corresponding MI events. The generated MI events are then |
| * received by other services and clients. |
| * @since 1.1 |
| */ |
| public class MIRunControlEventProcessor_7_0 implements IEventProcessor { |
| private static final String STOPPED_REASON = "stopped"; //$NON-NLS-1$ |
| private static final String RUNNING_REASON = "running"; //$NON-NLS-1$ |
| |
| private Integer fLastRunningCmdType = null; |
| /** |
| * The connection service that this event processor is registered with. |
| */ |
| private final AbstractMIControl fCommandControl; |
| |
| /** |
| * Container context used as the context for the run control events generated |
| * by this processor. |
| */ |
| private final ICommandControlDMContext fControlDmc; |
| |
| private final DsfServicesTracker fServicesTracker; |
| |
| /** |
| * Creates the event processor and registers it as listener with the debugger |
| * control. |
| * @param connection |
| * @param inferior |
| */ |
| public MIRunControlEventProcessor_7_0(AbstractMIControl connection, ICommandControlDMContext controlDmc) { |
| fCommandControl = connection; |
| fControlDmc = controlDmc; |
| fServicesTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fCommandControl.getSession().getId()); |
| connection.addEventListener(this); |
| connection.addCommandListener(this); |
| } |
| |
| /** |
| * This processor must be disposed before the control service is un-registered. |
| */ |
| @Override |
| public void dispose() { |
| fCommandControl.removeEventListener(this); |
| fCommandControl.removeCommandListener(this); |
| fServicesTracker.dispose(); |
| } |
| |
| @Override |
| public void eventReceived(Object output) { |
| for (MIOOBRecord oobr : ((MIOutput) output).getMIOOBRecords()) { |
| List<MIEvent<?>> events = new LinkedList<>(); |
| if (oobr instanceof MIExecAsyncOutput) { |
| MIExecAsyncOutput exec = (MIExecAsyncOutput) oobr; |
| // Change of state. |
| String state = exec.getAsyncClass(); |
| if (STOPPED_REASON.equals(state)) { |
| // Re-set the thread and stack level to -1 when stopped event is recvd. |
| // This is to synchronize the state between GDB back-end and AbstractMIControl. |
| fCommandControl.resetCurrentThreadLevel(); |
| fCommandControl.resetCurrentStackLevel(); |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("reason")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| String reason = ((MIConst) val).getString(); |
| MIEvent<?> e = createEvent(reason, exec); |
| if (e != null) { |
| events.add(e); |
| continue; |
| } |
| } |
| } |
| } |
| // We were stopped for some unknown reason, for example |
| // GDB for temporary breakpoints will not send the |
| // "reason" ??? still fire a stopped event. |
| if (events.isEmpty()) { |
| MIEvent<?> e = createEvent(STOPPED_REASON, exec); |
| if (e != null) { |
| events.add(e); |
| } |
| } |
| |
| for (MIEvent<?> event : events) { |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if (RUNNING_REASON.equals(state)) { |
| MIEvent<?> event = createEvent(RUNNING_REASON, exec); |
| if (event != null) { |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } |
| } else if (oobr instanceof MINotifyAsyncOutput) { |
| // Parse the string and dispatch the corresponding event |
| MINotifyAsyncOutput exec = (MINotifyAsyncOutput) oobr; |
| String miEvent = exec.getAsyncClass(); |
| if ("thread-created".equals(miEvent) || "thread-exited".equals(miEvent)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| String threadId = null; |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("group-id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString(); |
| } |
| } else if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| threadId = ((MIConst) val).getString(); |
| } |
| } |
| } |
| |
| // Until GDB is officially supporting multi-process, we may not get |
| // a groupId. In this case, we are running single process and we'll |
| // need its groupId |
| if (groupId == null) { |
| groupId = MIProcesses.UNIQUE_GROUP_ID; |
| } |
| |
| // Here, threads are created and removed. We cannot use the IMIProcesses service |
| // to map a threadId to a groupId, because there would be a race condition. |
| // Since we have the groupId anyway, we have no problems. |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| |
| if (procService != null) { |
| IContainerDMContext processContainerDmc = procService |
| .createContainerContextFromGroupId(fControlDmc, groupId); |
| |
| MIEvent<?> event = null; |
| if ("thread-created".equals(miEvent)) { //$NON-NLS-1$ |
| event = new MIThreadCreatedEvent(processContainerDmc, exec.getToken(), threadId); |
| } else if ("thread-exited".equals(miEvent)) { //$NON-NLS-1$ |
| event = new MIThreadExitEvent(processContainerDmc, exec.getToken(), threadId); |
| } else { |
| assert false; // earlier check should have guaranteed this isn't possible |
| } |
| |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if ("thread-group-added".equals(miEvent)) { //$NON-NLS-1$ |
| // With GDB >= 7.2 |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString().trim(); |
| } |
| } |
| } |
| |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (procService != null) { |
| // When a thread-group is first added, there is no process and therefore no pid, so we use UNKNOWN_PROCESS_ID |
| IProcessDMContext processDmc = procService.createProcessContext(fCommandControl.getContext(), |
| MIProcesses.UNKNOWN_PROCESS_ID); |
| MIEvent<?> event = new MIThreadGroupAddedEvent(processDmc, exec.getToken(), groupId); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if ("thread-group-created".equals(miEvent) || "thread-group-started".equals(miEvent)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| // =thread-group-created was used for GDB 7.0 and 7.1, |
| // but then became =thread-group-started starting with GDB 7.2 |
| String groupId = null; |
| String pId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString().trim(); |
| } |
| } else if (var.equals("pid")) { //$NON-NLS-1$ |
| // Available starting with GDB 7.2 |
| if (val instanceof MIConst) { |
| pId = ((MIConst) val).getString().trim(); |
| } |
| } |
| } |
| |
| if (pId == null) { |
| // Before GDB 7.2, the groupId was the pid of the process |
| pId = groupId; |
| } |
| |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (pId != null && procService != null) { |
| IProcessDMContext procDmc = procService.createProcessContext(fControlDmc, pId); |
| |
| MIEvent<?> event = new MIThreadGroupCreatedEvent(procDmc, exec.getToken(), groupId); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } else if ("thread-group-exited".equals(miEvent)) { //$NON-NLS-1$ |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString().trim(); |
| } |
| } |
| } |
| |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (procService != null) { |
| IContainerDMContext containerDmc = procService.createContainerContextFromGroupId(fControlDmc, |
| groupId); |
| IProcessDMContext procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class); |
| |
| MIEvent<?> event = new MIThreadGroupExitedEvent(procDmc, exec.getToken(), exec.getMIResults()); |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } |
| } else if (oobr instanceof MIConsoleStreamOutput) { |
| MIConsoleStreamOutput stream = (MIConsoleStreamOutput) oobr; |
| if (stream.getCString().startsWith("Program terminated with signal")) {//$NON-NLS-1$ |
| |
| /* |
| * The string should be in the form "Program terminated with signal <signal>, <reason>." |
| * For Example: Program terminated with signal SIGABRT, Aborted. |
| */ |
| |
| // Parse the <signal> and the <reason> |
| Pattern pattern = Pattern.compile("Program terminated with signal (.*), (.*)\\..*"); //$NON-NLS-1$ |
| Matcher matcher = pattern.matcher(stream.getCString()); |
| if (matcher.matches()) { |
| MIExecAsyncOutput exec = new MIExecAsyncOutput(); |
| |
| MIResult name = new MIResult(); |
| name.setVariable("signal-name"); //$NON-NLS-1$ |
| MIConst nameValue = new MIConst(); |
| nameValue.setCString(matcher.group(1)); |
| name.setMIValue(nameValue); |
| |
| MIResult meaning = new MIResult(); |
| meaning.setVariable("signal-meaning"); //$NON-NLS-1$ |
| MIConst meaningValue = new MIConst(); |
| meaningValue.setCString(matcher.group(2)); |
| meaning.setMIValue(meaningValue); |
| |
| exec.setMIResults(new MIResult[] { name, meaning }); |
| MIEvent<?> event = createEvent("signal-received", exec); //$NON-NLS-1$ |
| fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties()); |
| } |
| } |
| } |
| } |
| } |
| |
| @ConfinedToDsfExecutor("") |
| protected MIEvent<?> createEvent(String reason, MIExecAsyncOutput exec) { |
| MIEvent<?> event = null; |
| |
| if ("exited-normally".equals(reason) || "exited".equals(reason)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| event = MIInferiorExitEvent.parse(fCommandControl.getContext(), exec.getToken(), exec.getMIResults()); |
| } else if ("exited-signalled".equals(reason)) { //$NON-NLS-1$ |
| event = MIInferiorSignalExitEvent.parse(fCommandControl.getContext(), exec.getToken(), exec.getMIResults()); |
| } else { |
| |
| String threadId = null; |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| |
| if (var.equals("thread-id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| threadId = ((MIConst) val).getString(); |
| } |
| } else if (var.equals("group-id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString(); |
| } |
| } |
| } |
| |
| IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class); |
| if (procService == null) { |
| return null; |
| } |
| |
| IProcessDMContext procDmc = null; |
| IContainerDMContext containerDmc = null; |
| if (groupId == null) { |
| // MI does not currently provide the group-id in these events |
| |
| // In some cases, gdb sends a bare stopped event. Likely a bug, but |
| // we need to react to it all the same. See bug 311118. |
| if (threadId == null) { |
| threadId = "all"; //$NON-NLS-1$ |
| } |
| |
| containerDmc = procService.createContainerContextFromThreadId(fControlDmc, threadId); |
| procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class); |
| } else { |
| // This code would only trigger if the groupId was provided by MI |
| containerDmc = procService.createContainerContextFromGroupId(fControlDmc, groupId); |
| } |
| |
| IExecutionDMContext execDmc = containerDmc; |
| if (threadId != null && !threadId.equals("all")) { //$NON-NLS-1$ |
| IThreadDMContext threadDmc = procService.createThreadContext(procDmc, threadId); |
| execDmc = procService.createExecutionContext(containerDmc, threadDmc, threadId); |
| } |
| |
| if (execDmc == null) { |
| // Badly formatted event |
| return null; |
| } |
| |
| if ("breakpoint-hit".equals(reason)) { //$NON-NLS-1$ |
| event = MIBreakpointHitEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("watchpoint-trigger".equals(reason) //$NON-NLS-1$ |
| || "read-watchpoint-trigger".equals(reason) //$NON-NLS-1$ |
| || "access-watchpoint-trigger".equals(reason)) { //$NON-NLS-1$ |
| event = MIWatchpointTriggerEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("watchpoint-scope".equals(reason)) { //$NON-NLS-1$ |
| event = MIWatchpointScopeEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("end-stepping-range".equals(reason)) { //$NON-NLS-1$ |
| event = MISteppingRangeEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("signal-received".equals(reason)) { //$NON-NLS-1$ |
| event = MISignalEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("location-reached".equals(reason)) { //$NON-NLS-1$ |
| event = MILocationReachedEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("function-finished".equals(reason)) { //$NON-NLS-1$ |
| event = MIFunctionFinishedEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if ("solib-event".equals(reason)) { //$NON-NLS-1$ |
| event = MISharedLibEvent.parse(execDmc, exec.getToken(), exec.getMIResults(), null); |
| } else if (STOPPED_REASON.equals(reason)) { |
| event = MIStoppedEvent.parse(execDmc, exec.getToken(), exec.getMIResults()); |
| } else if (RUNNING_REASON.equals(reason)) { |
| // Retrieve the type of command from what we last stored |
| int type = MIRunningEvent.CONTINUE; |
| if (fLastRunningCmdType != null) { |
| type = fLastRunningCmdType; |
| fLastRunningCmdType = null; |
| } |
| event = new MIRunningEvent(execDmc, exec.getToken(), type); |
| } |
| } |
| return event; |
| } |
| |
| @Override |
| public void commandQueued(ICommandToken token) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void commandSent(ICommandToken token) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void commandRemoved(ICommandToken token) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void commandDone(ICommandToken token, ICommandResult result) { |
| ICommand<?> cmd = token.getCommand(); |
| MIInfo cmdResult = (MIInfo) result; |
| MIOutput output = cmdResult.getMIOutput(); |
| MIResultRecord rr = output.getMIResultRecord(); |
| if (rr != null) { |
| // Check if the state changed. |
| String state = rr.getResultClass(); |
| if (RUNNING_REASON.equals(state)) { |
| // Store the type of command that is the trigger for the coming |
| // *running event |
| if (cmd instanceof MIExecNext) { |
| fLastRunningCmdType = MIRunningEvent.NEXT; |
| } else if (cmd instanceof MIExecNextInstruction) { |
| fLastRunningCmdType = MIRunningEvent.NEXTI; |
| } else if (cmd instanceof MIExecStep) { |
| fLastRunningCmdType = MIRunningEvent.STEP; |
| } else if (cmd instanceof MIExecStepInstruction) { |
| fLastRunningCmdType = MIRunningEvent.STEPI; |
| } else if (cmd instanceof MIExecUntil) { |
| fLastRunningCmdType = MIRunningEvent.UNTIL; |
| } else if (cmd instanceof MIExecFinish) { |
| fLastRunningCmdType = MIRunningEvent.FINISH; |
| } else if (cmd instanceof MIExecReturn) { |
| fLastRunningCmdType = MIRunningEvent.RETURN; |
| } else if (cmd instanceof MIExecContinue) { |
| fLastRunningCmdType = MIRunningEvent.CONTINUE; |
| } else { |
| fLastRunningCmdType = MIRunningEvent.CONTINUE; |
| } |
| } |
| } |
| } |
| } |