/*******************************************************************************
* Copyright (c) 2000, 2010 QNX Software 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:
* QNX Software Systems - Initial API and implementation
* Hewlett-Packard Development Company - fix for bug 109733
* ENEA Software AB - CLI command extension - fix for bug 190277
*******************************************************************************/
package org.eclipse.cdt.debug.mi.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.CLIExecAbort;
import org.eclipse.cdt.debug.mi.core.command.MIExecInterrupt;
import org.eclipse.cdt.debug.mi.core.command.MIGDBShowExitCode;
import org.eclipse.cdt.debug.mi.core.command.CLIInfoProc;
import org.eclipse.cdt.debug.mi.core.command.CLIInfoProgram;
import org.eclipse.cdt.debug.mi.core.event.MIInferiorExitEvent;
import org.eclipse.cdt.debug.mi.core.output.MIGDBShowExitCodeInfo;
import org.eclipse.cdt.debug.mi.core.output.CLIInfoProcInfo;
import org.eclipse.cdt.debug.mi.core.output.CLIInfoProgramInfo;
/**
*/
public class MIInferior extends Process {
final static int SUSPENDED = 1;
final static int RUNNING = 2;
final static int TERMINATED = 4;
boolean connected = false;
boolean exitCodeKnown = false;
int exitCode = 0;
int state = 0;
MISession session;
OutputStream out;
InputStream in;
PipedOutputStream inPiped;
PipedInputStream err;
PipedOutputStream errPiped;
IMITTY tty;
int inferiorPID;
/** See {@link #isRemoteInferior()} */
private boolean fIsRemoteInferior;
public MIInferior(MISession mi, IMITTY p) {
session = mi;
tty = p;
if (tty != null) {
out = tty.getOutputStream();
in = tty.getInputStream();
}
}
/**
* @see java.lang.Process#getOutputStream()
*/
public OutputStream getOutputStream() {
if (out == null) {
out = new OutputStream() {
public void write(int b) throws IOException {
if (!isRunning()) {
throw new IOException(MIPlugin.getResourceString("src.MIInferior.target_is_suspended")); //$NON-NLS-1$
}
OutputStream channel = session.getChannelOutputStream();
if (channel == null) {
throw new IOException(MIPlugin.getResourceString("src.MIInferior.No_session")); //$NON-NLS-1$
}
channel.write(b);
}
};
}
return out;
}
/**
* @see java.lang.Process#getInputStream()
*/
public InputStream getInputStream() {
if (in == null) {
try {
inPiped = new PipedOutputStream();
in = new PipedInputStream(inPiped);
} catch (IOException e) {
}
}
return in;
}
/**
* @see java.lang.Process#getErrorStream()
*/
public InputStream getErrorStream() {
// FIXME: We do not have any err stream from gdb/mi
// so this gdb err channel instead.
if (err == null) {
try {
errPiped = new PipedOutputStream();
err = new PipedInputStream(errPiped);
} catch (IOException e) {
}
}
return err;
}
public synchronized void waitForSync() throws InterruptedException {
while (state != TERMINATED) {
wait();
}
}
/**
* @see java.lang.Process#waitFor()
*/
public int waitFor() throws InterruptedException {
waitForSync();
return exitValue();
}
/**
* @see java.lang.Process#exitValue()
*/
public int exitValue() {
if (isTerminated()) {
if (!session.isTerminated()) {
if (!exitCodeKnown) {
CommandFactory factory = session.getCommandFactory();
MIGDBShowExitCode code = factory.createMIGDBShowExitCode();
try {
session.postCommand(code);
MIGDBShowExitCodeInfo info = code.getMIGDBShowExitCodeInfo();
exitCode = info.getCode();
} catch (MIException e) {
// no rethrown.
}
exitCodeKnown = true;
}
}
return exitCode;
}
throw new IllegalThreadStateException();
}
/**
* @see java.lang.Process#destroy()
*/
public void destroy() {
try {
terminate();
} catch (MIException e) {
// do nothing.
}
}
public void terminate() throws MIException {
// An inferior will be destroy():interrupt and kill if
// - For attach session:
// the inferior was not disconnected yet (no need to try
// to kill a disconnected program).
// - For Program session:
// if the inferior was not terminated.
// - For PostMortem(Core): send event
// else noop
if ((session.isAttachSession() && isConnected()) || (session.isProgramSession() && !isTerminated())) {
// Try to interrupt the inferior, first.
if (isRunning()) {
interrupt();
}
int token = 0;
if (isSuspended()) {
try {
CommandFactory factory = session.getCommandFactory();
CLIExecAbort abort = factory.createCLIExecAbort();
session.postCommand0(abort, -1);
// do not wait for the answer.
//abort.getMIInfo();
token = abort.getToken();
} catch (MIException e) {
// ignore the error
}
}
setTerminated(token, true);
} else if (session.isCoreSession() && !isTerminated()){
setTerminated();
}
}
public void interrupt() throws MIException {
MIProcess gdb = session.getGDBProcess();
// Check if they can handle the interrupt
// Try the exec-interrupt; this will be for "gdb --async"
CommandFactory factory = session.getCommandFactory();
MIExecInterrupt interrupt = factory.createMIExecInterrupt();
if (interrupt != null) {
try {
session.postCommand(interrupt);
// call getMIInfo() even if we discard the value;
interrupt.getMIInfo();
// Allow MI command timeout for the interrupt to propagate.
long maxSec = session.getCommandTimeout()/1000 + 1;
synchronized(this) {
for (int i = 0;(state == RUNNING) && i < maxSec; i++) {
try {
wait(1000);
} catch (InterruptedException e) {
}
}
}
} catch (MIException e) {
}
} else if (gdb.canInterrupt(this)) {
gdb.interrupt(this);
}
// If we've failed throw an exception up.
if (state == RUNNING) {
throw new MIException(MIPlugin.getResourceString("src.MIInferior.Failed_to_interrupt")); //$NON-NLS-1$
}
}
public boolean isSuspended() {
return state == SUSPENDED;
}
public boolean isRunning() {
return state == RUNNING;
}
public boolean isTerminated() {
return state == TERMINATED;
}
public boolean isConnected() {
return connected;
}
public synchronized void setConnected() {
connected = true;
}
public synchronized void setDisconnected() {
connected = false;
}
public synchronized void setSuspended() {
state = SUSPENDED;
notifyAll();
}
public synchronized void setRunning() {
state = RUNNING;
notifyAll();
}
public synchronized void setTerminated() {
setTerminated(0, true);
}
synchronized void setTerminated(int token, boolean fireEvent) {
state = TERMINATED;
// Close the streams.
try {
if (inPiped != null) {
inPiped.close();
inPiped = null;
}
} catch (IOException e) {
//e.printStackTrace();
}
try {
if (errPiped != null) {
errPiped.close();
errPiped = null;
}
} catch (IOException e) {
//e.printStackTrace();
}
// If tty is not null then we are using a master/slave terminal
// emulation close the master to notify the slave.
if (tty != null) {
if (in != null) {
try {
in.close();
} catch (IOException e) {
//e.printStackTrace();
}
in = null;
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
//e.printStackTrace();
}
out = null;
}
}
if (fireEvent) {
session.fireEvent(new MIInferiorExitEvent(session, token));
}
notifyAll();
}
public OutputStream getPipedOutputStream() {
return inPiped;
}
public OutputStream getPipedErrorStream() {
return errPiped;
}
public IMITTY getTTY() {
return tty;
}
public void update() {
if (getInferiorPID() == 0) {
int pid = 0;
// Do not try this on attach session.
if (!isConnected()) {
// Try to discover the pid using GDB/CLI Command "info proc"
CommandFactory factory = session.getCommandFactory();
CLIInfoProc proc = factory.createCLIInfoProc();
try {
RxThread rxThread = session.getRxThread();
rxThread.setEnableConsole(false);
session.postCommand(proc);
CLIInfoProcInfo infoProc = proc.getMIInfoProcInfo();
pid = infoProc.getPID();
} catch (MIException e) {
// no rethrown.
}
// Try to discover the pid using GDB/CLI Command "info program" if "info proc" failed
try {
if(pid <= 0){
CLIInfoProgram prog = factory.createCLIInfoProgram();
session.postCommand(prog);
CLIInfoProgramInfo info = prog.getMIInfoProgramInfo();
pid = info.getPID();
}
} catch (MIException e) {
// no rethrown.
} finally {
RxThread rxThread = session.getRxThread();
rxThread.setEnableConsole(true);
}
}
// We fail permantely.
setInferiorPID((pid == 0) ? -1: pid);
}
}
public int resetInferiorPID() {
int pid = inferiorPID;
inferiorPID = 0;
return pid;
}
public void setInferiorPID(int pid) {
inferiorPID = pid;
}
public int getInferiorPID() {
return inferiorPID;
}
/**
* Called early on in the debug session to mark the inferior process as being
* under the control of a gdbserver.
*
* @since 7.0
*/
public void setIsRemoteInferior(boolean value) {
fIsRemoteInferior = value;
}
/**
* Is the inferior process being debugged remotely through gdbserver?
*
* @since 7.0
*/
public boolean isRemoteInferior() {
return fIsRemoteInferior;
}
/**
* Was the inferior process attached to by gdb (as opposed to launched by
* gdb).
*
* @since 7.0
*/
public boolean isAttachedInferior() {
return session.isAttachSession();
}
}