| /* |
| * (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); |
| } |
| } |
| |
| } |