/*******************************************************************************
* Copyright (c) 2006, 2010 Wind River Systems 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:
* Wind River Systems - initial API and implementation
* Ericsson - Additional handling of events
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service.command;
import java.util.LinkedList;
import java.util.List;
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;
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.ICommandListener;
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBBackend;
import org.eclipse.cdt.dsf.mi.service.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.MIProcesses;
import org.eclipse.cdt.dsf.mi.service.MIProcesses.ContainerExitedDMEvent;
import org.eclipse.cdt.dsf.mi.service.MIProcesses.ContainerStartedDMEvent;
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
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.MICatchpointHitEvent;
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.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.MIWatchpointScopeEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent;
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.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.MIStreamRecord;
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.
*/
public class MIRunControlEventProcessor
implements IEventListener, ICommandListener
{
private static final String STOPPED_REASON = "stopped"; //$NON-NLS-1$
/**
* 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
* @since 1.1
*/
public MIRunControlEventProcessor(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.
*/
public void dispose() {
fCommandControl.removeEventListener(this);
fCommandControl.removeCommandListener(this);
fServicesTracker.dispose();
}
public void eventReceived(Object output) {
for (MIOOBRecord oobr : ((MIOutput)output).getMIOOBRecords()) {
List<MIEvent<?>> events = new LinkedList<MIEvent<?>>();
if (oobr instanceof MIExecAsyncOutput) {
MIExecAsyncOutput exec = (MIExecAsyncOutput) oobr;
// Change of state.
String state = exec.getAsyncClass();
if ("stopped".equals(state)) { //$NON-NLS-1$
// 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;
}
}
}
}
// GDB < 7.0 does not provide a reason when stopping on a
// catchpoint. However, the reason is contained in the
// stream records that precede the exec async output one.
// This is ugly, but we don't really have an alternative.
if (events.isEmpty()) {
MIStreamRecord[] streamRecords = ((MIOutput)output).getStreamRecords();
for (MIStreamRecord streamRecord : streamRecords) {
String log = streamRecord.getString();
if (log.startsWith("Catchpoint ")) { //$NON-NLS-1$
events.add(MICatchpointHitEvent.parse(getExecutionContext(exec), exec.getToken(), results, streamRecord));
}
}
}
// 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());
}
}
}
}
// Now check for a oob command result. This happens on Windows when interrupting GDB.
// In this case, GDB before 7.0 does not always send a *stopped event, so we must do it ourselves
// Bug 304096 (if you have the patience to go through it :-))
MIResultRecord rr = ((MIOutput)output).getMIResultRecord();
if (rr != null) {
int id = rr.getToken();
String state = rr.getResultClass();
if ("error".equals(state)) { //$NON-NLS-1$
MIResult[] results = rr.getMIResults();
for (int i = 0; i < results.length; i++) {
String var = results[i].getVariable();
MIValue val = results[i].getMIValue();
if (var.equals("msg")) { //$NON-NLS-1$
if (val instanceof MIConst) {
String message = ((MIConst) val).getString();
if (message.toLowerCase().startsWith("quit")) { //$NON-NLS-1$
IRunControl runControl = fServicesTracker.getService(IRunControl.class);
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (runControl != null && procService != null) {
// We don't know which thread stopped so we simply create a container event.
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
if (runControl.isSuspended(processContainerDmc) == false) {
// Create an MISignalEvent because that is what the *stopped event should have been
MIEvent<?> event = MISignalEvent.parse(processContainerDmc, id, rr.getMIResults());
fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties());
}
}
}
}
}
}
}
}
}
/**
* Create an execution context given an exec-async-output OOB record
*
* @since 3.0
*/
protected IExecutionDMContext getExecutionContext(MIExecAsyncOutput exec) {
String threadId = 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();
}
}
}
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (procService == null) {
return null;
}
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
IExecutionDMContext execDmc = processContainerDmc;
if (threadId != null) {
IProcessDMContext procDmc = DMContexts.getAncestorOfType(processContainerDmc, IProcessDMContext.class);
IThreadDMContext threadDmc = procService.createThreadContext(procDmc, threadId);
execDmc = procService.createExecutionContext(processContainerDmc, threadDmc, threadId);
}
return execDmc;
}
@ConfinedToDsfExecutor("")
protected MIEvent<?> createEvent(String reason, MIExecAsyncOutput exec) {
IExecutionDMContext execDmc = getExecutionContext(exec);
MIEvent<?> event = 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 ("exited-normally".equals(reason) || "exited".equals(reason)) { //$NON-NLS-1$ //$NON-NLS-2$
event = MIInferiorExitEvent.parse(fCommandControl.getContext(), exec.getToken(), exec.getMIResults());
// Until we clean up the handling of all these events, we need to send the containerExited event
// Only needed GDB < 7.0, because GDB itself does not yet send an MI event about the inferior terminating
sendContainerExitedEvent();
} else if ("exited-signalled".equals(reason)) { //$NON-NLS-1$
event = MIInferiorSignalExitEvent.parse(fCommandControl.getContext(), exec.getToken(), exec.getMIResults());
// Until we clean up the handling of all these events, we need to send the containerExited event
// Only needed GDB < 7.0, because GDB itself does not yet send an MI event about the inferior terminating
sendContainerExitedEvent();
} else if (STOPPED_REASON.equals(reason)) {
event = MIStoppedEvent.parse(execDmc, exec.getToken(), exec.getMIResults());
}
return event;
}
private void sendContainerExitedEvent() {
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (procService != null) {
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
fCommandControl.getSession().dispatchEvent(
new ContainerExitedDMEvent(processContainerDmc), fCommandControl.getProperties());
}
}
public void commandQueued(ICommandToken token) {
// Do nothing.
}
public void commandSent(ICommandToken token) {
// Do nothing.
}
public void commandRemoved(ICommandToken token) {
// Do nothing.
}
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) {
int id = rr.getToken();
// Check if the state changed.
String state = rr.getResultClass();
if ("running".equals(state)) { //$NON-NLS-1$
int type = 0;
// Check the type of command
// if it was a step instruction set state stepping
if (cmd instanceof MIExecNext) { type = MIRunningEvent.NEXT; }
else if (cmd instanceof MIExecNextInstruction) { type = MIRunningEvent.NEXTI; }
else if (cmd instanceof MIExecStep) { type = MIRunningEvent.STEP; }
else if (cmd instanceof MIExecStepInstruction) { type = MIRunningEvent.STEPI; }
else if (cmd instanceof MIExecUntil) { type = MIRunningEvent.UNTIL; }
else if (cmd instanceof MIExecFinish) { type = MIRunningEvent.FINISH; }
else if (cmd instanceof MIExecReturn) { type = MIRunningEvent.RETURN; }
else if (cmd instanceof MIExecContinue) { type = MIRunningEvent.CONTINUE; }
else { type = MIRunningEvent.CONTINUE; }
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (procService != null) {
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
fCommandControl.getSession().dispatchEvent(
new MIRunningEvent(processContainerDmc, id, type), fCommandControl.getProperties());
}
} else if ("exit".equals(state)) { //$NON-NLS-1$
// No need to do anything, terminate() will.
// Send exited?
} else if ("connected".equals(state)) { //$NON-NLS-1$
// This will happen for a CORE or REMOTE session.
// For a CORE session this is the only indication
// that the inferior has 'started'. So we use
// it to trigger the ContainerStarted event.
// In the case of a REMOTE session, it is a proper
// indicator as well but not if it is a remote attach.
// For an attach session, it only indicates
// that we are connected to a remote node but we still
// need to wait until we are attached to the process before
// sending the event, which will happen in the attaching code.
IGDBBackend backendService = fServicesTracker.getService(IGDBBackend.class);
if (backendService != null && backendService.getIsAttachSession() == false) {
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (procService != null) {
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
fCommandControl.getSession().dispatchEvent(
new ContainerStartedDMEvent(processContainerDmc), fCommandControl.getProperties());
}
}
} else if ("error".equals(state)) { //$NON-NLS-1$
} else if ("done".equals(state)) { //$NON-NLS-1$
// For GDBs older than 7.0, GDB does not trigger a *stopped event
// when it stops due to a CLI command. We have to trigger the
// MIStoppedEvent ourselves
if (cmd instanceof CLICommand<?>) {
// It is important to limit this to runControl operations (e.g., 'next', 'continue', 'jump')
// There are other CLI commands that we use that could still be sent when the target is considered
// running, due to timing issues.
boolean isAttachingOperation = CLIEventProcessor.isAttachingOperation(((CLICommand<?>)cmd).getOperation());
boolean isSteppingOperation = CLIEventProcessor.isSteppingOperation(((CLICommand<?>)cmd).getOperation());
if (isSteppingOperation || isAttachingOperation) {
IRunControl runControl = fServicesTracker.getService(IRunControl.class);
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
if (runControl != null && procService != null) {
// We don't know which thread stopped so we simply create a container event.
IContainerDMContext processContainerDmc = procService.createContainerContextFromGroupId(fControlDmc, MIProcesses.UNIQUE_GROUP_ID);
// An attaching operation is debugging a new inferior and always stops it.
// We should not check that the container is suspended, because at startup, we are considered
// suspended, even though we can get a *stopped event.
if (isAttachingOperation || runControl.isSuspended(processContainerDmc) == false) {
MIEvent<?> event = MIStoppedEvent.parse(processContainerDmc, id, rr.getMIResults());
fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties());
}
}
}
}
}
}
}
}