blob: fa9af09a30b6e142304ffe76e2f803c000dadab0 [file] [log] [blame]
/*
* (c) Copyright QNX Software Systems Ltd. 2002.
* All Rights Reserved.
*/
package org.eclipse.cdt.debug.mi.core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Observable;
import org.eclipse.cdt.debug.mi.core.command.Command;
import org.eclipse.cdt.debug.mi.core.command.CommandFactory;
import org.eclipse.cdt.debug.mi.core.command.MIGDBExit;
import org.eclipse.cdt.debug.mi.core.command.MIGDBSet;
import org.eclipse.cdt.debug.mi.core.event.MIEvent;
import org.eclipse.cdt.debug.mi.core.event.MIGDBExitEvent;
import org.eclipse.cdt.debug.mi.core.output.MIOutput;
import org.eclipse.cdt.debug.mi.core.output.MIParser;
import org.eclipse.cdt.utils.pty.PTY;
/**
* Represents a GDB/MI session.
* Note that on GNU/Linux the target stream is not
* preceded by the token '@' until this is fix, on GNU/Linux
* there a good change to confuse the parser.
*/
public class MISession extends Observable {
/**
* Normal program debuging.
*/
public final static int PROGRAM = 0;
/**
* Attach to a running process debuging.
*/
public final static int ATTACH = 1;
/**
* PostMortem analysis.
*/
public final static int CORE = 2;
boolean terminated;
// hold the type of the session(post-mortem, attach etc ..)
int sessionType;
Process sessionProcess;
Process gdbProcess;
InputStream inChannel;
OutputStream outChannel;
TxThread txThread;
RxThread rxThread;
EventThread eventThread;
CommandQueue txQueue;
CommandQueue rxQueue;
Queue eventQueue;
PipedInputStream miInConsolePipe;
PipedOutputStream miOutConsolePipe;
PipedInputStream miInLogPipe;
PipedOutputStream miOutLogPipe;
CommandFactory factory;
MIParser parser;
long cmdTimeout;
MIInferior inferior;
/**
* Create the gdb session.
*
* @param Process gdb Process.
* @param pty Terminal to use for the inferior.
* @param timeout time in milliseconds to wait for command response.
* @param type the type of debugin session.
*/
public MISession(Process process, PTY pty, int timeout, int type, int launchTimeout) throws MIException {
gdbProcess = process;
inChannel = process.getInputStream();
outChannel = process.getOutputStream();
cmdTimeout = timeout;
sessionType = type;
factory = new CommandFactory();
parser = new MIParser();
inferior = new MIInferior(this, pty);
txQueue = new CommandQueue();
rxQueue = new CommandQueue();
eventQueue = new Queue();
// The Process may have terminated earlier because
// of bad arguments etc .. check this here and bail out.
try {
process.exitValue();
InputStream err = process.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(err));
String line = null;
try {
line = reader.readLine();
reader.close();
} catch (Exception e) {
// the reader may throw a NPE.
}
if (line == null) {
line = "Process Terminated";
}
throw new MIException(line);
} catch (IllegalThreadStateException e) {
// Ok, it means the process is alive.
}
txThread = new TxThread(this);
rxThread = new RxThread(this);
eventThread = new EventThread(this);
txThread.start();
rxThread.start();
eventThread.start();
// Disable a certain number of irritations from gdb.
// Like confirmation and screen size.
try {
MIGDBSet confirm = new MIGDBSet(new String[]{"confirm", "off"});
postCommand(confirm, launchTimeout);
confirm.getMIInfo();
MIGDBSet width = new MIGDBSet(new String[]{"width", "0"});
postCommand(width, launchTimeout);
confirm.getMIInfo();
MIGDBSet height = new MIGDBSet(new String[]{"height", "0"});
postCommand(height, launchTimeout);
confirm.getMIInfo();
} catch (MIException exc) {
// Kill the Transmition thread.
if (txThread.isAlive()) {
txThread.interrupt();
}
// Kill the Receiving Thread.
if (rxThread.isAlive()) {
rxThread.interrupt();
}
// Kill the event Thread.
if (eventThread.isAlive()) {
eventThread.interrupt();
}
// rethrow up the exception.
throw exc;
}
}
/**
* get MI Console Stream.
* The parser will make available the MI console stream output.
*/
public InputStream getMIConsoleStream() {
if (miInConsolePipe == null) {
try {
miOutConsolePipe = new PipedOutputStream();
miInConsolePipe = new PipedInputStream(miOutConsolePipe);
} catch (IOException e) {
}
}
return miInConsolePipe;
}
/**
* get MI Console Stream.
* The parser will make available the MI console stream output.
*/
public InputStream getMILogStream() {
if (miInLogPipe == null) {
try {
miOutLogPipe = new PipedOutputStream();
miInLogPipe = new PipedInputStream(miOutLogPipe);
} catch (IOException e) {
}
}
return miInLogPipe;
}
/**
* For example the CDI/MI bridge uses the command
* factory to create MI commands this allow overloading.
*/
public CommandFactory getCommandFactory() {
return factory;
}
/**
* Set a new factory to use for command.
*/
public void setCommandFactory(CommandFactory f) {
factory = f;
}
/**
* Return the MI parser.
*/
public MIParser getMIParser() {
return parser;
}
/**
* Reset the MI parser.
*/
public void setMIParser(MIParser p) {
parser = p;
}
/**
* Set the type of session this is.
* Certain action will base on that, for example
* the inferior will not try to kill/destroy a
* attach session disconnected.
*/
public int getSessionType() {
return sessionType;
}
public void setSessionType(int type) {
sessionType = type;
}
/**
* The debug session is a program being debug.
*/
public boolean isProgramSession() {
return sessionType == PROGRAM;
}
/**
* The debug session is a program being attach to.
*/
public boolean isAttachSession() {
return sessionType == ATTACH;
}
/**
* The debug session is a core being analysed.
*/
public boolean isCoreSession() {
return sessionType == CORE;
}
/**
* Reset the default Command Timeout.
*/
public void setCommandTimeout(long timeout) {
cmdTimeout = timeout;
}
/**
* Return the default Command Timeout, default 10 secs.
*/
public long getCommandTimeout() {
return cmdTimeout;
}
/**
* equivalent to:
* postCommand(cmd, cmdTimeout)
*/
public void postCommand(Command cmd) throws MIException {
postCommand(cmd, cmdTimeout);
}
/**
* Sends a command to gdb, and wait(timeout) for a response.
*/
public synchronized void postCommand(Command cmd, long timeout) throws MIException {
// Test if we are in a sane state.
if (!txThread.isAlive() || !rxThread.isAlive()) {
throw new MIException("{R,T}xThread terminated");
}
// Test if we are in the right state?
if (inferior.isRunning()) {
// REMINDER: if we support -exec-interrupt
// Let it throught:
// if (cmd instanceof MIExecInterrupt) { }
// else
throw new MIException("Target is not suspended");
}
if (isTerminated()) {
throw new MIException("Session terminated");
}
// TRACING: print the command;
MIPlugin.getDefault().debugLog(cmd.toString());
txQueue.addCommand(cmd);
// Wait for the response or timedout
synchronized (cmd) {
// RxThread will set the MIOutput on the cmd
// when the response arrive.
while (cmd.getMIOutput() == null) {
try {
cmd.wait(timeout);
if (cmd.getMIOutput() == null) {
throw new MIException("Target is not responding (timed out)");
}
} catch (InterruptedException e) {
}
}
}
}
/**
* Return the inferior "Process".
*/
public MIInferior getMIInferior() {
return inferior;
}
/**
* Return the "gdb" Process.
*/
public Process getGDBProcess() {
return gdbProcess;
}
/**
* Return a "fake" Process that will
* encapsulate the call input/output of gdb.
*/
public Process getSessionProcess() {
if (sessionProcess == null) {
sessionProcess = new SessionProcess(this);
}
return sessionProcess;
}
/**
* Check if the gdb session is terminated.
*/
public boolean isTerminated() {
return terminated;
}
/**
* Terminate the MISession.
*/
public void terminate() {
// Sanity check.
if (isTerminated()) {
return;
}
terminated = true;
// Destroy any MI Inferior(Process) and streams.
inferior.destroy();
// {in,out}Channel is use as predicate/condition
// in the {RX,TX,Event}Thread to detect termination
// and bail out. So they are set to null.
InputStream inGDB = inChannel;
inChannel = null;
OutputStream outGDB = outChannel;
outChannel = null;
// Although we will close the pipe(). It is cleaner
// to give a chance to gdb to cleanup.
// send the exit(-gdb-exit).
MIGDBExit exit = factory.createMIGDBExit();
txQueue.addCommand(exit);
// Wait for the response
synchronized (exit) {
// RxThread will set the MIOutput on the cmd
// when the response arrive.
try {
exit.wait(2000);
} catch (InterruptedException e) {
}
}
// Make sure gdb is killed.
// FIX: the destroy() must be call before closing gdb streams
// on windows if the order is not follow the close() will hang.
if (gdbProcess != null) {
gdbProcess.destroy();
}
// Close the input GDB prompt
try {
if (inGDB != null)
inGDB.close();
} catch (IOException e) {
}
// Close the output GDB prompt
try {
if (outGDB != null)
outGDB.close();
} catch (IOException e) {
}
// Destroy the MI console stream.
try {
miInConsolePipe = null;
if (miOutConsolePipe != null) {
miOutConsolePipe.close();
}
} catch (IOException e) {
}
// Destroy the MI log stream.
try {
miInLogPipe = null;
if (miOutLogPipe != null) {
miOutLogPipe.close();
}
} catch (IOException e) {
}
// Kill the Transmition thread.
try {
if (txThread.isAlive()) {
txThread.interrupt();
txThread.join(cmdTimeout);
}
} catch (InterruptedException e) {
}
// Kill the Receiving Thread.
try {
if (rxThread.isAlive()) {
rxThread.interrupt();
rxThread.join(cmdTimeout);
}
} catch (InterruptedException e) {
}
// Allow (10 secs) for the EventThread to finish processing the queue.
Queue queue = getEventQueue();
for (int i = 0; !queue.isEmpty() && i < 5; i++) {
try {
java.lang.Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
// Kill the event Thread.
try {
if (eventThread.isAlive()) {
eventThread.interrupt();
eventThread.join(cmdTimeout);
}
} catch (InterruptedException e) {
}
// Tell the observers that the session is terminated
notifyObservers(new MIGDBExitEvent(0));
}
/**
* Notify the observers of new MI OOB events.
*/
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(arg);
}
OutputStream getConsolePipe() {
return miOutConsolePipe;
}
OutputStream getLogPipe() {
return miOutLogPipe;
}
CommandQueue getTxQueue() {
return txQueue;
}
CommandQueue getRxQueue() {
return rxQueue;
}
Queue getEventQueue() {
return eventQueue;
}
RxThread getRxThread() {
return rxThread;
}
InputStream getChannelInputStream() {
return inChannel;
}
OutputStream getChannelOutputStream() {
return outChannel;
}
MIOutput parse(String buffer) {
return parser.parse(buffer);
}
public void fireEvents(MIEvent[] events) {
if (events != null && events.length > 0) {
for (int i = 0; i < events.length; i++) {
fireEvent(events[i]);
}
}
}
public void fireEvent(MIEvent event) {
if (event != null) {
getEventQueue().addItem(event);
}
}
}