Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 16cb62e4af5061cfb698f308fb6a921515fb2e6e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
/*******************************************************************************
 * Copyright (c) 2000, 2007 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
 *     Norbert Ploett, Siemens AG - fix for bug 119370
 *     Hewlett-Packard Development Company - fix for bug 109733 (null check in setPrompt)
 *******************************************************************************/
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.CLICommand;
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.command.MIInterpreterExecConsole;
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.MIParser;
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;
	CLIProcessor cli;
	int prompt = 1; // 1 --> Primary prompt "(gdb)"; 2 --> Secondary Prompt ">"
	boolean fEnableConsole = true;

	public RxThread(MISession s) {
		super("MI RX Thread"); //$NON-NLS-1$
		session = s;
		cli = new CLIProcessor(session);
		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.
				if (MIPlugin.getDefault().isDebugging()) {
					MIPlugin.getDefault().debugLog(line);
				}
				
				if (session.isVerboseModeEnabled())
					session.writeToConsole(line + "\n"); //$NON-NLS-1$

				setPrompt(line);
				processMIOutput(line + "\n"); //$NON-NLS-1$
			}
		} 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"); //$NON-NLS-1$
			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();
				}
			}
		}
	}

	void setPrompt(String line) {
		MIParser parser = session.getMIParser();
		prompt = 0;
		// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=109733
		if (line == null || parser == null)
			return;
		line = line.trim();
		if (line.equals(parser.primaryPrompt)) {
			prompt = 1;
		} else if (line.equals(parser.secondaryPrompt)) {
			prompt = 2;
		}
	}

	public boolean inPrimaryPrompt() {
		return prompt == 1;
	}

	public boolean inSecondaryPrompt() {
		return prompt == 2;
	}

	public void setEnableConsole(boolean enable) {
		fEnableConsole = enable;
	}

	public boolean isEnableConsole() {
		return fEnableConsole;
	}

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

			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 = new MIOOBRecord[oobList.size()];
				oobList.toArray(oobRecords);

				// Check if the state changed.
				String state = rr.getResultClass();
				if ("running".equals(state)) { //$NON-NLS-1$
					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(session, id, type);
					if (cmd != null && cmd.isQuiet())
						event.setPropagate(false);
					list.add(event);
				} else if ("exit".equals(state)) { //$NON-NLS-1$
					// No need to do anything, terminate() will.
					session.getMIInferior().setTerminated();
				} else if ("connected".equals(state)) { //$NON-NLS-1$
					session.getMIInferior().setConnected();
				} else if ("error".equals(state)) { //$NON-NLS-1$
					if (session.getMIInferior().isRunning()) {
						session.getMIInferior().setSuspended();
						MIEvent event = new MIErrorEvent(session, rr, oobRecords);
						list.add(event);
					}
				} else if ("done".equals(state) && cmd instanceof CLICommand) { //$NON-NLS-1$
					// "done" usually mean that gdb returns after some CLI command
					// The result record may 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);
				}

				// Set the accumulate console Stream
				response.setMIOOBRecords(oobRecords);

				// Notify the waiting command.
				// Notify any command waiting for a ResultRecord.
				if (cmd != null) {
					// Process the Command line to recognise patterns we may need to fire event.
					if (cmd instanceof CLICommand) {
						cli.processSettingChanges((CLICommand)cmd);
					} else if (cmd instanceof MIInterpreterExecConsole) {
						cli.processSettingChanges((MIInterpreterExecConsole)cmd);
					}

					synchronized (cmd) {
						cmd.setMIOutput(response);
						cmd.notifyAll();
					}
				}

				// Clear the accumulate oobList on each new Result Command response.
				oobList.clear();

			} 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)) { //$NON-NLS-1$
				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")) { //$NON-NLS-1$
						if (val instanceof MIConst) {
							String reason = ((MIConst) val).getString();
							MIEvent 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.
				if (list.isEmpty()) {
					String[] logs = getStreamRecords();
					for (int i = 0; i < logs.length; i++) {
						if (logs[i].equalsIgnoreCase("Stopped due to shared library event")) { //$NON-NLS-1$
							session.getMIInferior().setSuspended();
							MIEvent e = new MISharedLibEvent(session, 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 (list.isEmpty()) {
					session.getMIInferior().setSuspended();
					MIEvent e = new MIStoppedEvent(session, 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();
				// Process the console stream too.
				setPrompt(str);
				if (str != null && isEnableConsole()) {
					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.getCString();
				if (str != null) {
					try {
						target.write(str.getBytes());
						target.flush();
					} catch (IOException e) {
					}
				}
			}
			// Accumulate the Target Stream Output response for parsing.
			// Some commands, e.g. 'monitor' will put valuable info  in the Console Stream.
			// This fixes bug 119370.
			oobList.add(stream);
		} 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 && isEnableConsole()) {
					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);
		}
	}

	/**
	 * Check for any info that we can gather form the console.
	 */
	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")) { //$NON-NLS-1$
				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);
					}
				}
			}
		}
		// 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 this state by looking at the
		// console stream for the 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.
		if (list.isEmpty()) {
			String[] logs = getStreamRecords();
			for (int i = 0; i < logs.length; i++) {
				if (logs[i].equalsIgnoreCase("Stopped due to shared library event")) { //$NON-NLS-1$
					session.getMIInferior().setSuspended();
					MIEvent e = new MISharedLibEvent(session, rr);
					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 (list.isEmpty()) {
			if (session.getMIInferior().isRunning()) {
				session.getMIInferior().setSuspended();
				MIEvent event = new MIStoppedEvent(session, rr);
				session.fireEvent(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)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MIBreakpointHitEvent(session, exec);
			} else if (rr != null) {
				event = new MIBreakpointHitEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if (
			"watchpoint-trigger".equals(reason) //$NON-NLS-1$
				|| "read-watchpoint-trigger".equals(reason) //$NON-NLS-1$
				|| "access-watchpoint-trigger".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MIWatchpointTriggerEvent(session, exec);
			} else if (rr != null) {
				event = new MIWatchpointTriggerEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("watchpoint-scope".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MIWatchpointScopeEvent(session, exec);
			} else if (rr != null) {
				event = new MIWatchpointScopeEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("end-stepping-range".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MISteppingRangeEvent(session, exec);
			} else if (rr != null) {
				event = new MISteppingRangeEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("signal-received".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MISignalEvent(session, exec);
			} else if (rr != null) {
				event = new MISignalEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("location-reached".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MILocationReachedEvent(session, exec);
			} else if (rr != null) {
				event = new MILocationReachedEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("function-finished".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MIFunctionFinishedEvent(session, exec);
			} else if (rr != null) {
				event = new MIFunctionFinishedEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		} else if ("exited-normally".equals(reason) || "exited".equals(reason)) { //$NON-NLS-1$ //$NON-NLS-2$
			if (exec != null) {
				event = new MIInferiorExitEvent(session, exec);
			} else if (rr != null) {
				event = new MIInferiorExitEvent(session, rr);
			}
			session.getMIInferior().setTerminated(0,false);
		} else if ("exited-signalled".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MIInferiorSignalExitEvent(session, exec);
			} else if (rr != null) {
				event = new MIInferiorSignalExitEvent(session, rr);
			}
			session.getMIInferior().setTerminated(0,false);
		} else if ("shlib-event".equals(reason)) { //$NON-NLS-1$
			if (exec != null) {
				event = new MISharedLibEvent(session, exec);
			} else if (rr != null) {
				event = new MISharedLibEvent(session, rr);
			}
			session.getMIInferior().setSuspended();
		}
		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]);
	}

}

Back to the top