blob: 5c598ca948ef1d03f04e78b26b2cd2de538e2ba1 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}
}
}