blob: 93bac3fb00f3af60f42d37d07250e276ab11804b [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.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.debug.mi.core.command.Command;
import org.eclipse.cdt.debug.mi.core.command.MIExecContinue;
import org.eclipse.cdt.debug.mi.core.command.MIExecFinish;
import org.eclipse.cdt.debug.mi.core.command.MIExecNext;
import org.eclipse.cdt.debug.mi.core.command.MIExecNextInstruction;
import org.eclipse.cdt.debug.mi.core.command.MIExecReturn;
import org.eclipse.cdt.debug.mi.core.command.MIExecStep;
import org.eclipse.cdt.debug.mi.core.command.MIExecStepInstruction;
import org.eclipse.cdt.debug.mi.core.command.MIExecUntil;
import org.eclipse.cdt.debug.mi.core.event.MIBreakpointHitEvent;
import org.eclipse.cdt.debug.mi.core.event.MIErrorEvent;
import org.eclipse.cdt.debug.mi.core.event.MIEvent;
import org.eclipse.cdt.debug.mi.core.event.MIFunctionFinishedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIInferiorExitEvent;
import org.eclipse.cdt.debug.mi.core.event.MIInferiorSignalExitEvent;
import org.eclipse.cdt.debug.mi.core.event.MILocationReachedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIRunningEvent;
import org.eclipse.cdt.debug.mi.core.event.MISharedLibEvent;
import org.eclipse.cdt.debug.mi.core.event.MISignalEvent;
import org.eclipse.cdt.debug.mi.core.event.MISteppingRangeEvent;
import org.eclipse.cdt.debug.mi.core.event.MIStoppedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIWatchpointScopeEvent;
import org.eclipse.cdt.debug.mi.core.event.MIWatchpointTriggerEvent;
import org.eclipse.cdt.debug.mi.core.output.MIAsyncRecord;
import org.eclipse.cdt.debug.mi.core.output.MIConsoleStreamOutput;
import org.eclipse.cdt.debug.mi.core.output.MIConst;
import org.eclipse.cdt.debug.mi.core.output.MIExecAsyncOutput;
import org.eclipse.cdt.debug.mi.core.output.MILogStreamOutput;
import org.eclipse.cdt.debug.mi.core.output.MINotifyAsyncOutput;
import org.eclipse.cdt.debug.mi.core.output.MIOOBRecord;
import org.eclipse.cdt.debug.mi.core.output.MIOutput;
import org.eclipse.cdt.debug.mi.core.output.MIResult;
import org.eclipse.cdt.debug.mi.core.output.MIResultRecord;
import org.eclipse.cdt.debug.mi.core.output.MIStatusAsyncOutput;
import org.eclipse.cdt.debug.mi.core.output.MIStreamRecord;
import org.eclipse.cdt.debug.mi.core.output.MITargetStreamOutput;
import org.eclipse.cdt.debug.mi.core.output.MIValue;
/**
* Receiving thread of gdb response output.
*/
public class RxThread extends Thread {
final MISession session;
List oobList;
public RxThread(MISession s) {
super("MI RX Thread");
session = s;
oobList = new ArrayList();
}
/*
* Get the response, parse the output, dispatch for OOB
* search for the corresponding token in rxQueue for the ResultRecord.
*/
public void run() {
BufferedReader reader = new BufferedReader(new InputStreamReader(session.getChannelInputStream()));
try {
String line;
while ((line = reader.readLine()) != null) {
// TRACING: print the output.
MIPlugin.getDefault().debugLog(line);
processMIOutput(line + "\n");
}
} catch (IOException e) {
//e.printStackTrace();
}
// This code should be executed when gdb been abruptly
// or unxepectedly killed. This is detected by checking
// if the channelInputStream is not null. In normal case
// session.terminate() will set the channelInputStream to null.
if (session.getChannelInputStream() != null) {
Runnable cleanup = new Runnable() {
public void run() {
// Change the state of the inferior.
session.getMIInferior().setTerminated();
session.terminate();
}
};
Thread clean = new Thread(cleanup, "GDB Died");
clean.setDaemon(true);
clean.start();
}
// Clear the queue and notify any command waiting, we are going down.
CommandQueue rxQueue = session.getRxQueue();
if (rxQueue != null) {
Command[] cmds = rxQueue.clearCommands();
for (int i = 0; i < cmds.length; i++) {
synchronized (cmds[i]) {
cmds[i].notifyAll();
}
}
}
}
/**
* Search for the command in the RxQueue, set the MIOutput
* and notify() the other end.
* Any OOBs are consider like event and dipatch to the
* listeners/observers in different thread.
*/
void processMIOutput(String buffer) {
MIOutput response = session.parse(buffer);
if (response != null) {
List list = new ArrayList();
CommandQueue rxQueue = session.getRxQueue();
// Notify any command waiting for a ResultRecord.
MIResultRecord rr = response.getMIResultRecord();
if (rr != null) {
int id = rr.getToken();
Command cmd = rxQueue.removeCommand(id);
// Clear the accumulate oobList on each new Result Command
// response.
MIOOBRecord[] oobRecords = (MIOOBRecord[]) oobList.toArray(new MIOOBRecord[0]);
oobList.clear();
// Check if the state changed.
String state = rr.getResultClass();
if ("running".equals(state)) {
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;
}
session.getMIInferior().setRunning();
MIEvent event = new MIRunningEvent(id, type);
session.fireEvent(event);
} else if ("exit".equals(state)) {
// No need to do anything, terminate() will.
session.getMIInferior().setTerminated();
} else if ("connected".equals(state)) {
session.getMIInferior().setConnected();
} else if ("error".equals(state)) {
if (session.getMIInferior().isRunning()) {
session.getMIInferior().setSuspended();
MIEvent event = new MIErrorEvent(rr, oobRecords);
session.fireEvent(event);
}
}
// Notify the waiting command.
if (cmd != null) {
synchronized (cmd) {
// Set the accumulate console Stream
response.setMIOOBRecords(oobRecords);
cmd.setMIOutput(response);
cmd.notifyAll();
}
}
// Some result record contains informaton specific to oob.
// This will happen when CLI-Command is use, for example
// doing "run" will block and return a breakpointhit
processMIOOBRecord(rr, list);
} else {
// Process OOBs
MIOOBRecord[] oobs = response.getMIOOBRecords();
for (int i = 0; i < oobs.length; i++) {
processMIOOBRecord(oobs[i], list);
}
}
MIEvent[] events = (MIEvent[]) list.toArray(new MIEvent[list.size()]);
session.fireEvents(events);
} // if response != null
}
/**
* Dispatch a thread to deal with the listeners.
*/
void processMIOOBRecord(MIOOBRecord oob, List list) {
if (oob instanceof MIAsyncRecord) {
processMIOOBRecord((MIAsyncRecord) oob, list);
oobList.clear();
} else if (oob instanceof MIStreamRecord) {
processMIOOBRecord((MIStreamRecord) oob);
}
}
void processMIOOBRecord(MIAsyncRecord async, List list) {
if (async instanceof MIExecAsyncOutput) {
MIExecAsyncOutput exec = (MIExecAsyncOutput) async;
// Change of state.
String state = exec.getAsyncClass();
if ("stopped".equals(state)) {
MIEvent e = 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("reason")) {
if (val instanceof MIConst) {
String reason = ((MIConst) val).getString();
e = createEvent(reason, exec);
if (e != null) {
list.add(e);
}
}
}
}
// GDB does not have reason when stopping on shared, hopefully
// this will be fix in newer version meanwhile, we will use a hack
// to cope. On most platform we can detect by looking at the
// console stream for phrase:
// ~"Stopped due to shared library event\n"
//
// Althought it is a _real_ bad idea to do this, we do not have
// any other alternatives.
String[] logs = getStreamRecords();
for (int i = 0; i < logs.length; i++) {
if (logs[i].equalsIgnoreCase("Stopped due to shared library event")) {
session.getMIInferior().setSuspended();
e = new MISharedLibEvent(exec);
list.add(e);
}
}
// We were stopped for some unknown reason, for example
// GDB for temporary breakpoints will not send the
// "reason" ??? still fire a stopped event.
if (e == null) {
session.getMIInferior().setSuspended();
e = new MIStoppedEvent(exec);
list.add(e);
}
}
} else if (async instanceof MIStatusAsyncOutput) {
// Nothing done .. but what about +download??
} else if (async instanceof MINotifyAsyncOutput) {
// Nothing
}
}
void processMIOOBRecord(MIStreamRecord stream) {
if (stream instanceof MIConsoleStreamOutput) {
OutputStream console = session.getConsolePipe();
if (console != null) {
MIConsoleStreamOutput out = (MIConsoleStreamOutput) stream;
String str = out.getString();
if (str != null) {
try {
console.write(str.getBytes());
console.flush();
} catch (IOException e) {
}
}
}
// Accumulate the Console Stream Output response for parsing.
// Some commands will put valuable info in the Console Stream.
oobList.add(stream);
} else if (stream instanceof MITargetStreamOutput) {
OutputStream target = session.getMIInferior().getPipedOutputStream();
if (target != null) {
MITargetStreamOutput out = (MITargetStreamOutput) stream;
String str = out.getString();
if (str != null) {
try {
target.write(str.getBytes());
target.flush();
} catch (IOException e) {
}
}
}
} else if (stream instanceof MILogStreamOutput) {
// This is meant for the gdb console.
OutputStream log = session.getLogPipe();
if (log != null) {
MILogStreamOutput out = (MILogStreamOutput) stream;
String str = out.getString();
if (str != null) {
try {
log.write(str.getBytes());
log.flush();
} catch (IOException e) {
}
}
}
// Accumulate the Log Stream Output response for parsing.
// Some commands will put valuable info in the Log Stream.
oobList.add(stream);
}
}
/**
* Dispatch a thread to deal with the listeners.
*/
void processMIOOBRecord(MIResultRecord rr, List list) {
MIResult[] results = rr.getMIResults();
for (int i = 0; i < results.length; i++) {
String var = results[i].getVariable();
if (var.equals("reason")) {
MIValue value = results[i].getMIValue();
if (value instanceof MIConst) {
String reason = ((MIConst) value).getString();
MIEvent event = createEvent(reason, rr);
if (event != null) {
list.add(event);
}
}
}
}
}
MIEvent createEvent(String reason, MIExecAsyncOutput exec) {
return createEvent(reason, null, exec);
}
MIEvent createEvent(String reason, MIResultRecord rr) {
return createEvent(reason, rr, null);
}
MIEvent createEvent(String reason, MIResultRecord rr, MIExecAsyncOutput exec) {
MIEvent event = null;
if ("breakpoint-hit".equals(reason)) {
if (exec != null) {
event = new MIBreakpointHitEvent(exec);
} else if (rr != null) {
event = new MIBreakpointHitEvent(rr);
}
session.getMIInferior().setSuspended();
} else if (
"watchpoint-trigger".equals(reason)
|| "read-watchpoint-trigger".equals(reason)
|| "access-watchpoint-trigger".equals(reason)) {
if (exec != null) {
event = new MIWatchpointTriggerEvent(exec);
} else if (rr != null) {
event = new MIWatchpointTriggerEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("watchpoint-scope".equals(reason)) {
if (exec != null) {
event = new MIWatchpointScopeEvent(exec);
} else if (rr != null) {
event = new MIWatchpointScopeEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("end-stepping-range".equals(reason)) {
if (exec != null) {
event = new MISteppingRangeEvent(exec);
} else if (rr != null) {
event = new MISteppingRangeEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("signal-received".equals(reason)) {
if (exec != null) {
event = new MISignalEvent(exec);
} else if (rr != null) {
event = new MISignalEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("location-reached".equals(reason)) {
if (exec != null) {
event = new MILocationReachedEvent(exec);
} else if (rr != null) {
event = new MILocationReachedEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("function-finished".equals(reason)) {
if (exec != null) {
event = new MIFunctionFinishedEvent(exec);
} else if (rr != null) {
event = new MIFunctionFinishedEvent(rr);
}
session.getMIInferior().setSuspended();
} else if ("exited-normally".equals(reason) || "exited".equals(reason)) {
if (exec != null) {
event = new MIInferiorExitEvent(exec);
} else if (rr != null) {
event = new MIInferiorExitEvent(rr);
}
session.getMIInferior().setTerminated();
} else if ("exited-signalled".equals(reason)) {
if (exec != null) {
event = new MIInferiorSignalExitEvent(exec);
} else if (rr != null) {
event = new MIInferiorSignalExitEvent(rr);
}
session.getMIInferior().setTerminated();
}
return event;
}
String[] getStreamRecords() {
List streamRecords = new ArrayList();
MIOOBRecord[] oobRecords = (MIOOBRecord[]) oobList.toArray(new MIOOBRecord[0]);
for (int i = 0; i < oobRecords.length; i++) {
if (oobRecords[i] instanceof MIStreamRecord) {
String s = ((MIStreamRecord) oobRecords[i]).getString().trim();
if (s != null && s.length() > 0) {
streamRecords.add(s);
}
}
}
return (String[]) streamRecords.toArray(new String[0]);
}
}