1
/*******************************************************************************
2
* Copyright (c) 2009 QNX Software Systems and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* QNX Software Systems - Initial API and implementation
10
* Hewlett-Packard Development Company - fix for bug 109733
11
* Wind River Systems - Modified for new DSF Reference Implementation
12
*******************************************************************************/
14
package org.eclipse.cdt.dsf.mi.service.command;
16
import java.io.IOException;
17
import java.io.InputStream;
18
import java.io.OutputStream;
19
import java.io.PipedInputStream;
20
import java.io.PipedOutputStream;
21
import java.util.concurrent.CancellationException;
22
import java.util.concurrent.ExecutionException;
23
import java.util.concurrent.RejectedExecutionException;
25
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
26
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
27
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
28
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
29
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
30
import org.eclipse.cdt.dsf.concurrent.Query;
31
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
32
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
33
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
34
import org.eclipse.cdt.dsf.debug.service.command.ICommandListener;
35
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
36
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
37
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
38
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
39
import org.eclipse.cdt.dsf.mi.service.MIProcesses.ContainerExitedDMEvent;
40
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
41
import org.eclipse.cdt.dsf.mi.service.command.commands.CLIExecAbort;
42
import org.eclipse.cdt.dsf.mi.service.command.commands.MIGDBShowExitCode;
43
import org.eclipse.cdt.dsf.mi.service.command.output.MIConst;
44
import org.eclipse.cdt.dsf.mi.service.command.output.MIExecAsyncOutput;
45
import org.eclipse.cdt.dsf.mi.service.command.output.MIGDBShowExitCodeInfo;
46
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
47
import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord;
48
import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput;
49
import org.eclipse.cdt.dsf.mi.service.command.output.MIResult;
50
import org.eclipse.cdt.dsf.mi.service.command.output.MIResultRecord;
51
import org.eclipse.cdt.dsf.mi.service.command.output.MITargetStreamOutput;
52
import org.eclipse.cdt.dsf.mi.service.command.output.MIValue;
53
import org.eclipse.cdt.dsf.service.DsfSession;
54
import org.eclipse.cdt.utils.pty.PTY;
55
import org.eclipse.core.runtime.CoreException;
56
import org.eclipse.core.runtime.IStatus;
57
import org.eclipse.core.runtime.Status;
60
* This Process implementation tracks the process that is being debugged
61
* by GDB. The process object is displayed in Debug view and is used to
62
* channel the STDIO of the interior process to the console view.
64
* @see org.eclipse.debug.core.model.IProcess
66
public class MIInferiorProcess extends Process
67
implements IEventListener, ICommandListener
70
public enum State { RUNNING, STOPPED, TERMINATED }
72
private final OutputStream fOutputStream;
73
private final InputStream fInputStream;
75
private final PipedOutputStream fInputStreamPiped;
77
private final PipedInputStream fErrorStream;
78
private final PipedOutputStream fErrorStreamPiped;
80
private final DsfSession fSession;
81
private final PTY fPty;
83
private final ICommandControlService fCommandControl;
85
private IContainerDMContext fContainerDMContext;
87
@ConfinedToDsfExecutor("fSession#getExecutor")
88
private boolean fDisposed = false;
91
* Counter for tracking console commands sent by services.
93
* The CLI 'monitor' command produces target output which should
94
* not be written to the target console, since it is in response to a CLI
95
* command. In fact, CLI commands should never have their output sent
96
* to the target console.
98
* This counter is incremented any time a CLI command is seen. It is
99
* decremented whenever a CLI command is finished. When counter
100
* value is 0, the inferior process writes the target output.
102
private int fSuppressTargetOutputCounter = 0;
104
Integer fExitCode = null;
106
private State fState = State.RUNNING;
108
private String fInferiorPid = null;
111
* Creates an inferior process object which uses the given output stream
112
* to write the user standard input into.
114
* @param commandControl Command control that this inferior process belongs to.
115
* @param inferiorExecCtx The execution context controlling the execution
116
* state of the inferior process.
117
* @param gdbOutputStream The output stream to use to write user IO into.
120
@ConfinedToDsfExecutor("fSession#getExecutor")
121
public MIInferiorProcess(ICommandControlService commandControl, OutputStream gdbOutputStream) {
122
this(commandControl, gdbOutputStream, null);
126
* Creates an inferior process object which uses the given terminal
127
* to write the user standard input into.
129
* @param commandControl Command control that this inferior process belongs to.
130
* @param inferiorExecCtx The execution context controlling the execution
131
* state of the inferior process.
132
* @param p The terminal to use to write user IO into.
135
@ConfinedToDsfExecutor("fSession#getExecutor")
136
public MIInferiorProcess(ICommandControlService commandControl, PTY p) {
137
this(commandControl, null, p);
140
@ConfinedToDsfExecutor("fSession#getExecutor")
141
private MIInferiorProcess(ICommandControlService commandControl, final OutputStream gdbOutputStream, PTY p) {
142
fCommandControl = commandControl;
143
fSession = commandControl.getSession();
145
commandControl.addEventListener(this);
146
commandControl.addCommandListener(this);
150
fOutputStream = fPty.getOutputStream();
151
fInputStream = fPty.getInputStream();
152
fInputStreamPiped = null;
154
fOutputStream = new OutputStream() {
156
public void write(int b) throws IOException {
157
// Have to re-dispatch to dispatch thread to check state
158
if (getState() != State.RUNNING) {
159
throw new IOException("Target is not running"); //$NON-NLS-1$
161
gdbOutputStream.write(b);
165
fInputStreamPiped = new PipedOutputStream();
166
PipedInputStream inputStream = null;
168
// Using a LargePipedInputStream see https://bugs.eclipse.org/bugs/show_bug.cgi?id=223154
169
inputStream = new LargePipedInputStream(fInputStreamPiped);
170
} catch (IOException e) {
172
fInputStream = inputStream;
176
// Note: We do not have any err stream from gdb/mi so this gdb
177
// err channel instead.
178
fErrorStreamPiped = new PipedOutputStream();
179
PipedInputStream errorStream = null;
181
// Using a LargePipedInputStream see https://bugs.eclipse.org/bugs/show_bug.cgi?id=223154
182
errorStream = new LargePipedInputStream(fErrorStreamPiped);
183
} catch (IOException e) {
185
fErrorStream = errorStream;
188
@ConfinedToDsfExecutor("fSession#getExecutor")
189
public void dispose() {
190
fCommandControl.removeEventListener(this);
191
fCommandControl.removeCommandListener(this);
195
setState(State.TERMINATED);
200
protected DsfSession getSession() {
207
protected ICommandControlService getCommandControlService() { return fCommandControl; }
209
protected boolean isDisposed() { return fDisposed; }
212
public OutputStream getOutputStream() {
213
return fOutputStream;
217
public InputStream getInputStream() {
222
public InputStream getErrorStream() {
226
public synchronized void waitForSync() throws InterruptedException {
227
while (getState() != State.TERMINATED) {
233
* @see java.lang.Process#waitFor()
236
public int waitFor() throws InterruptedException {
242
* @see java.lang.Process#exitValue()
245
public int exitValue() {
246
if (fExitCode != null) {
251
Query<Integer> exitCodeQuery = new Query<Integer>() {
253
protected void execute(final DataRequestMonitor<Integer> rm) {
254
// Guard against session disposed.
255
if (!DsfSession.isSessionActive(fSession.getId())) {
263
} else if (getState() != State.TERMINATED) {
264
// This will cause ExecutionException to be thrown with a CoreException,
265
// which will in turn contain the IllegalThreadStateException.
266
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "GDB is still running.", new IllegalThreadStateException())); //$NON-NLS-1$
269
getCommandControlService().queueCommand(
270
new MIGDBShowExitCode(getCommandControlService().getContext()),
271
new DataRequestMonitor<MIGDBShowExitCodeInfo>(fSession.getExecutor(), rm) {
273
protected void handleSuccess() {
274
rm.setData(getData().getCode());
282
fSession.getExecutor().execute(exitCodeQuery);
283
fExitCode = exitCodeQuery.get();
285
} catch (RejectedExecutionException e) {
286
} catch (InterruptedException e) {
287
} catch (CancellationException e) {
288
} catch (ExecutionException e) {
290
if (e.getCause() instanceof CoreException &&
291
((CoreException)e.getCause()).getStatus().getException() instanceof RuntimeException )
293
throw (RuntimeException)((CoreException)e.getCause()).getStatus().getException();
300
* @see java.lang.Process#destroy()
303
public void destroy() {
305
fSession.getExecutor().execute(new DsfRunnable() {
310
} catch (RejectedExecutionException e) {
316
private void closeIO() {
318
fOutputStream.close();
319
} catch (IOException e) {}
321
fInputStream.close();
322
} catch (IOException e) {}
324
if (fInputStreamPiped != null) fInputStreamPiped.close();
325
} catch (IOException e) {}
327
fErrorStream.close();
328
} catch (IOException e) {}
330
fErrorStreamPiped.close();
331
} catch (IOException e) {}
334
@ConfinedToDsfExecutor("fSession#getExecutor")
335
private void doDestroy() {
336
if (isDisposed() || !fSession.isActive() || getState() == State.TERMINATED) return;
338
// To avoid a RejectedExecutionException, use an executor that
339
// immediately executes in the same dispatch cycle.
340
CLIExecAbort cmd = new CLIExecAbort(getCommandControlService().getContext());
341
getCommandControlService().queueCommand(
343
new DataRequestMonitor<MIInfo>(ImmediateExecutor.getInstance(), null) {
345
protected void handleCompleted() {
346
setState(MIInferiorProcess.State.TERMINATED);
352
public State getState() {
356
public IExecutionDMContext getExecutionContext() {
357
return fContainerDMContext;
363
public void setContainerContext(IContainerDMContext containerDmc) {
364
fContainerDMContext = containerDmc;
367
synchronized void setState(State state) {
368
if (fState == State.TERMINATED) return;
370
if (fState == State.TERMINATED) {
371
if (fContainerDMContext != null) {
372
// This may not be necessary in 7.0 because of the =thread-group-exited event
373
getSession().dispatchEvent(
374
new ContainerExitedDMEvent(fContainerDMContext),
375
getCommandControlService().getProperties());
382
public OutputStream getPipedOutputStream() {
383
return fInputStreamPiped;
386
public OutputStream getPipedErrorStream() {
387
return fErrorStreamPiped;
390
public PTY getPTY() {
397
public String getPid() {
404
public void setPid(String pid) {
408
public void eventReceived(Object output) {
409
for (MIOOBRecord oobr : ((MIOutput)output).getMIOOBRecords()) {
410
if (oobr instanceof MIExecAsyncOutput) {
411
MIExecAsyncOutput async = (MIExecAsyncOutput)oobr;
413
String state = async.getAsyncClass();
414
if ("stopped".equals(state)) { //$NON-NLS-1$
415
boolean handled = false;
416
MIResult[] results = async.getMIResults();
417
for (int i = 0; i < results.length; i++) {
418
String var = results[i].getVariable();
419
if (var.equals("reason")) { //$NON-NLS-1$
420
MIValue value = results[i].getMIValue();
421
if (value instanceof MIConst) {
422
String reason = ((MIConst) value).getString();
423
if ("exited-signalled".equals(reason) || "exited-normally".equals(reason) || "exited".equals(reason)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
424
setState(State.TERMINATED);
426
setState(State.STOPPED);
434
setState(State.STOPPED);
437
} else if (oobr instanceof MITargetStreamOutput) {
438
if (fSuppressTargetOutputCounter > 0) return;
439
MITargetStreamOutput tgtOut = (MITargetStreamOutput)oobr;
440
if (fInputStreamPiped != null && tgtOut.getString() != null) {
442
fInputStreamPiped.write(tgtOut.getString().getBytes());
443
fInputStreamPiped.flush();
444
} catch (IOException e) {
451
public void commandQueued(ICommandToken token) {
455
public void commandSent(ICommandToken token) {
456
if (token.getCommand() instanceof CLICommand<?>) {
457
fSuppressTargetOutputCounter++;
461
public void commandRemoved(ICommandToken token) {
465
public void commandDone(ICommandToken token, ICommandResult result) {
466
if (token.getCommand() instanceof CLICommand<?>) {
467
fSuppressTargetOutputCounter--;
470
MIInfo cmdResult = (MIInfo) result ;
471
MIOutput output = cmdResult.getMIOutput();
472
MIResultRecord rr = output.getMIResultRecord();
474
// Check if the state changed.
475
String state = rr.getResultClass();
477
if ("running".equals(state)) { setState(State.RUNNING); }//$NON-NLS-1$
478
else if ("exit".equals(state)) { setState(State.TERMINATED); }//$NON-NLS-1$
479
else if ("error".equals(state)) { setState(State.STOPPED); }//$NON-NLS-1$