* Makefile.am:
[monodevelop.git] / extras / MonoDevelop.Debugger.Gdb / GdbSession.cs
blobccde7af8d9ef323d49c90cfe841cc840ca9fe40e
1 // GdbSession.cs
2 //
3 // Author:
4 // Lluis Sanchez Gual <lluis@novell.com>
5 //
6 // Copyright (c) 2008 Novell, Inc (http://www.novell.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
28 // Uncomment to see the commands sent to gdb and the output it generates
29 // #define GDB_OUTPUT
31 using System;
32 using System.Globalization;
33 using System.Text;
34 using System.IO;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Diagnostics;
38 using System.Threading;
39 using Mono.Debugging.Client;
40 using MonoDevelop.Core;
41 using MonoDevelop.Core.Execution;
42 using Mono.Unix.Native;
44 namespace MonoDevelop.Debugger.Gdb
46 class GdbSession: DebuggerSession
48 Process proc;
49 StreamReader sout;
50 StreamWriter sin;
51 ProcessWrapper console;
52 GdbCommandResult lastResult;
53 bool running;
54 Thread thread;
55 int currentThread = -1;
56 int activeThread = -1;
57 bool isMonoProcess;
58 string currentProcessName;
59 List<string> tempVariableObjects = new List<string> ();
60 Dictionary<int,Breakpoint> breakpoints = new Dictionary<int,Breakpoint> ();
61 List<Breakpoint> breakpointsWithHitCount = new List<Breakpoint> ();
63 DateTime lastBreakEventUpdate = DateTime.Now;
64 Dictionary<int, WaitCallback> breakUpdates = new Dictionary<int,WaitCallback> ();
65 bool breakUpdateEventsQueued;
66 const int BreakEventUpdateNotifyDelay = 500;
68 bool internalStop;
70 object syncLock = new object ();
71 object eventLock = new object ();
72 object gdbLock = new object ();
74 protected override void OnRun (DebuggerStartInfo startInfo)
76 lock (gdbLock) {
77 // Create a script to be run in a terminal
78 string script = Path.GetTempFileName ();
79 string ttyfile = Path.GetTempFileName ();
80 string ttyfileDone = ttyfile + "_done";
81 string tty;
83 try {
84 File.WriteAllText (script, "tty > " + ttyfile + "\ntouch " + ttyfileDone + "\nsleep 10000d");
85 Mono.Unix.Native.Syscall.chmod (script, FilePermissions.ALLPERMS);
87 console = Runtime.ProcessService.StartConsoleProcess (script, "", ".", ExternalConsoleFactory.Instance.CreateConsole (true), null);
88 DateTime tim = DateTime.Now;
89 while (!File.Exists (ttyfileDone)) {
90 System.Threading.Thread.Sleep (100);
91 if ((DateTime.Now - tim).TotalSeconds > 4)
92 throw new InvalidOperationException ("Console could not be created.");
94 tty = File.ReadAllText (ttyfile).Trim (' ','\n');
95 } finally {
96 try {
97 if (File.Exists (script))
98 File.Delete (script);
99 if (File.Exists (ttyfile))
100 File.Delete (ttyfile);
101 if (File.Exists (ttyfileDone))
102 File.Delete (ttyfileDone);
103 } catch {
104 // Ignore
108 StartGdb ();
110 // Initialize the terminal
111 RunCommand ("-inferior-tty-set", tty);
112 RunCommand ("-file-exec-and-symbols", startInfo.Command);
114 RunCommand ("-environment-directory", startInfo.WorkingDirectory);
116 // Set inferior arguments
117 if (!string.IsNullOrEmpty (startInfo.Arguments))
118 RunCommand ("-exec-arguments", startInfo.Arguments);
120 currentProcessName = startInfo.Command + " " + startInfo.Arguments;
122 CheckIsMonoProcess ();
123 OnStarted ();
125 RunCommand ("-exec-run");
129 protected override void OnAttachToProcess (int processId)
131 lock (gdbLock) {
132 StartGdb ();
133 currentProcessName = "PID " + processId.ToString ();
134 RunCommand ("attach", processId.ToString ());
135 currentThread = activeThread = 1;
136 CheckIsMonoProcess ();
137 OnStarted ();
138 FireTargetEvent (TargetEventType.TargetStopped, null);
142 public bool IsMonoProcess {
143 get { return isMonoProcess; }
146 void CheckIsMonoProcess ()
148 try {
149 RunCommand ("-data-evaluate-expression", "mono_pmip");
150 isMonoProcess = true;
151 } catch {
152 isMonoProcess = false;
153 // Ignore
157 void StartGdb ()
159 proc = new Process ();
160 proc.StartInfo.FileName = "gdb";
161 proc.StartInfo.Arguments = "-quiet -fullname -i=mi2";
162 proc.StartInfo.UseShellExecute = false;
163 proc.StartInfo.RedirectStandardInput = true;
164 proc.StartInfo.RedirectStandardOutput = true;
165 proc.StartInfo.RedirectStandardError = true;
166 proc.Start ();
168 sout = proc.StandardOutput;
169 sin = proc.StandardInput;
171 thread = new Thread (OutputInterpreter);
172 thread.IsBackground = true;
173 thread.Start ();
176 public override void Dispose ()
178 if (console != null && !console.HasExited) {
179 console.Kill ();
180 console = null;
183 if (thread != null)
184 thread.Abort ();
187 protected override void OnSetActiveThread (int processId, int threadId)
189 activeThread = threadId;
192 protected override void OnStop ()
194 Syscall.kill (proc.Id, Signum.SIGINT);
197 protected override void OnDetach ()
199 lock (gdbLock) {
200 InternalStop ();
201 RunCommand ("detach");
202 FireTargetEvent (TargetEventType.TargetExited, null);
206 protected override void OnExit ()
208 lock (gdbLock) {
209 InternalStop ();
210 RunCommand ("kill");
211 TargetEventArgs args = new TargetEventArgs (TargetEventType.TargetExited);
212 OnTargetEvent (args);
213 /* proc.Kill ();
214 TargetEventArgs args = new TargetEventArgs (TargetEventType.TargetExited);
215 OnTargetEvent (args);
216 */ }
219 protected override void OnStepLine ()
221 SelectThread (activeThread);
222 RunCommand ("-exec-step");
225 protected override void OnNextLine ()
227 SelectThread (activeThread);
228 RunCommand ("-exec-next");
231 protected override void OnStepInstruction ()
233 SelectThread (activeThread);
234 RunCommand ("-exec-step-instruction");
237 protected override void OnNextInstruction ()
239 SelectThread (activeThread);
240 RunCommand ("-exec-next-instruction");
243 protected override void OnFinish ()
245 SelectThread (activeThread);
246 RunCommand ("-exec-finish");
249 protected override object OnInsertBreakEvent (BreakEvent be, bool activate)
251 Breakpoint bp = be as Breakpoint;
252 if (bp == null)
253 return null;
255 lock (gdbLock) {
256 bool dres = InternalStop ();
257 try {
258 string extraCmd = string.Empty;
259 if (bp.HitCount > 0) {
260 extraCmd += "-i " + bp.HitCount;
261 breakpointsWithHitCount.Add (bp);
263 if (!string.IsNullOrEmpty (bp.ConditionExpression)) {
264 if (!bp.BreakIfConditionChanges)
265 extraCmd += " -c " + bp.ConditionExpression;
267 RunCommand ("-environment-directory", Path.GetDirectoryName (bp.FileName));
268 GdbCommandResult res = RunCommand ("-break-insert", extraCmd.Trim (), bp.FileName + ":" + bp.Line);
269 if (CommandStatus.Error == res.Status)
270 res = RunCommand ("-break-insert", extraCmd.Trim (), Path.GetFileName (bp.FileName) + ":" + bp.Line);
271 int bh = res.GetObject ("bkpt").GetInt ("number");
272 if (!activate)
273 RunCommand ("-break-disable", bh.ToString ());
274 breakpoints [bh] = bp;
275 return bh;
276 } finally {
277 InternalResume (dres);
282 bool CheckBreakpoint (int handle)
284 Breakpoint bp = breakpoints [handle];
285 if (bp == null)
286 return true;
288 if (!string.IsNullOrEmpty (bp.ConditionExpression) && bp.BreakIfConditionChanges) {
289 // Update the condition expression
290 GdbCommandResult res = RunCommand ("-data-evaluate-expression", bp.ConditionExpression);
291 string val = res.GetValue ("value");
292 RunCommand ("-break-condition", handle.ToString (), "(" + bp.ConditionExpression + ") != " + val);
295 if (bp.HitAction == HitAction.PrintExpression) {
296 GdbCommandResult res = RunCommand ("-data-evaluate-expression", bp.TraceExpression);
297 string val = res.GetValue ("value");
298 OnDebuggerOutput (false, val + "\n");
299 NotifyBreakEventUpdate (handle, 0, val);
300 return false;
302 return true;
305 void NotifyBreakEventUpdate (int eventHandle, int hitCount, string lastTrace)
307 bool notify = false;
309 WaitCallback nc = delegate {
310 if (hitCount != -1)
311 UpdateHitCount (eventHandle, hitCount);
312 if (lastTrace != null)
313 UpdateLastTraceValue (eventHandle, lastTrace);
316 lock (breakUpdates)
318 int span = (int) (DateTime.Now - lastBreakEventUpdate).TotalMilliseconds;
319 if (span >= BreakEventUpdateNotifyDelay && !breakUpdateEventsQueued) {
320 // Last update was more than 0.5s ago. The update can be sent.
321 lastBreakEventUpdate = DateTime.Now;
322 notify = true;
323 } else {
324 // Queue the event notifications to avoid wasting too much time
325 breakUpdates [eventHandle] = nc;
326 if (!breakUpdateEventsQueued) {
327 breakUpdateEventsQueued = true;
329 ThreadPool.QueueUserWorkItem (delegate {
330 Thread.Sleep (BreakEventUpdateNotifyDelay - span);
331 List<WaitCallback> copy;
332 lock (breakUpdates) {
333 copy = new List<WaitCallback> (breakUpdates.Values);
334 breakUpdates.Clear ();
335 breakUpdateEventsQueued = false;
336 lastBreakEventUpdate = DateTime.Now;
338 foreach (WaitCallback wc in copy)
339 wc (null);
344 if (notify)
345 nc (null);
348 void UpdateHitCountData ()
350 foreach (Breakpoint bp in breakpointsWithHitCount) {
351 object h;
352 if (GetBreakpointHandle (bp, out h)) {
353 GdbCommandResult res = RunCommand ("-break-info", h.ToString ());
354 string val = res.GetObject ("BreakpointTable").GetObject ("body").GetObject (0).GetObject ("bkpt").GetValue ("ignore");
355 if (val != null)
356 NotifyBreakEventUpdate ((int)h, int.Parse (val), null);
357 else
358 NotifyBreakEventUpdate ((int)h, 0, null);
361 breakpointsWithHitCount.Clear ();
364 protected override void OnRemoveBreakEvent (object handle)
366 lock (gdbLock) {
367 bool dres = InternalStop ();
368 Breakpoint bp = breakpoints [(int)handle];
369 breakpointsWithHitCount.Remove (bp);
370 breakpoints.Remove ((int)handle);
371 try {
372 RunCommand ("-break-delete", handle.ToString ());
373 } finally {
374 InternalResume (dres);
379 protected override void OnEnableBreakEvent (object handle, bool enable)
381 lock (gdbLock) {
382 bool dres = InternalStop ();
383 try {
384 if (enable)
385 RunCommand ("-break-enable", handle.ToString ());
386 else
387 RunCommand ("-break-disable", handle.ToString ());
388 } finally {
389 InternalResume (dres);
394 protected override object OnUpdateBreakEvent (object handle, BreakEvent be)
396 Breakpoint bp = be as Breakpoint;
397 if (bp == null)
398 return null;
400 bool ss = InternalStop ();
402 try {
403 if (bp.HitCount > 0) {
404 RunCommand ("-break-after", handle.ToString (), bp.HitCount.ToString ());
405 breakpointsWithHitCount.Add (bp);
406 } else
407 breakpointsWithHitCount.Remove (bp);
409 if (!string.IsNullOrEmpty (bp.ConditionExpression) && !bp.BreakIfConditionChanges)
410 RunCommand ("-break-condition", handle.ToString (), bp.ConditionExpression);
411 else
412 RunCommand ("-break-condition", handle.ToString ());
413 } finally {
414 InternalResume (ss);
417 return handle;
420 protected override void OnContinue ()
422 SelectThread (activeThread);
423 RunCommand ("-exec-continue");
426 protected override ThreadInfo[] OnGetThreads (int processId)
428 List<ThreadInfo> list = new List<ThreadInfo> ();
429 ResultData data = RunCommand ("-thread-list-ids").GetObject ("thread-ids");
430 foreach (string id in data.GetAllValues ("thread-id"))
431 list.Add (GetThread (int.Parse (id)));
432 return list.ToArray ();
435 protected override ProcessInfo[] OnGetPocesses ()
437 ProcessInfo p = new ProcessInfo (0, currentProcessName);
438 return new ProcessInfo [] { p };
441 ThreadInfo GetThread (int id)
443 return new ThreadInfo (0, id, "Thread #" + id, null);
446 protected override Backtrace OnGetThreadBacktrace (int processId, int threadId)
448 ResultData data = SelectThread (threadId);
449 GdbCommandResult res = RunCommand ("-stack-info-depth");
450 int fcount = int.Parse (res.GetValue ("depth"));
451 GdbBacktrace bt = new GdbBacktrace (this, threadId, fcount, data != null ? data.GetObject ("frame") : null);
452 return new Backtrace (bt);
455 protected override AssemblyLine[] OnDisassembleFile (string file)
457 List<AssemblyLine> lines = new List<AssemblyLine> ();
458 int cline = 1;
459 do {
460 ResultData data = null;
461 try {
462 data = RunCommand ("-data-disassemble", "-f", file, "-l", cline.ToString (), "--", "1");
463 } catch {
464 break;
466 ResultData asm_insns = data.GetObject ("asm_insns");
467 int newLine = cline;
468 for (int n=0; n<asm_insns.Count; n++) {
469 ResultData src_and_asm_line = asm_insns.GetObject (n).GetObject ("src_and_asm_line");
470 newLine = src_and_asm_line.GetInt ("line");
471 ResultData line_asm_insn = src_and_asm_line.GetObject ("line_asm_insn");
472 for (int i=0; i<line_asm_insn.Count; i++) {
473 ResultData asm = line_asm_insn.GetObject (i);
474 long addr = long.Parse (asm.GetValue ("address").Substring (2), NumberStyles.HexNumber);
475 string code = asm.GetValue ("inst");
476 lines.Add (new AssemblyLine (addr, code, newLine));
479 if (newLine <= cline)
480 break;
481 cline = newLine + 1;
483 } while (true);
485 return lines.ToArray ();
488 public ResultData SelectThread (int id)
490 if (id == currentThread)
491 return null;
492 currentThread = id;
493 return RunCommand ("-thread-select", id.ToString ());
496 public GdbCommandResult RunCommand (string command, params string[] args)
498 lock (gdbLock) {
499 lock (syncLock) {
500 lastResult = null;
502 lock (eventLock) {
503 running = true;
506 #if GDB_OUTPUT
507 Console.WriteLine ("gdb<: " + command + " " + string.Join (" ", args));
508 #endif
509 sin.WriteLine (command + " " + string.Join (" ", args));
511 if (!Monitor.Wait (syncLock, 4000))
512 throw new InvalidOperationException ("Command execution timeout.");
513 if (lastResult.Status == CommandStatus.Error)
514 throw new InvalidOperationException (lastResult.ErrorMessage);
515 return lastResult;
520 bool InternalStop ()
522 lock (eventLock) {
523 if (!running)
524 return false;
525 internalStop = true;
526 Syscall.kill (proc.Id, Signum.SIGINT);
527 if (!Monitor.Wait (eventLock, 4000))
528 throw new InvalidOperationException ("Target could not be interrupted.");
530 return true;
533 void InternalResume (bool resume)
535 if (resume)
536 RunCommand ("-exec-continue");
539 void OutputInterpreter ()
541 string line;
542 while ((line = sout.ReadLine ()) != null) {
543 try {
544 ProcessOutput (line);
545 } catch (Exception ex) {
546 Console.WriteLine (ex);
551 void ProcessOutput (string line)
553 #if GDB_OUTPUT
554 Console.WriteLine ("dbg>: '" + line + "'");
555 #endif
556 switch (line [0]) {
557 case '^':
558 lock (syncLock) {
559 lastResult = new GdbCommandResult (line);
560 running = (lastResult.Status == CommandStatus.Running);
561 Monitor.PulseAll (syncLock);
563 break;
565 case '~':
566 case '&':
567 if (line.Length > 1 && line[1] == '"')
568 line = line.Substring (2, line.Length - 5);
569 ThreadPool.QueueUserWorkItem (delegate {
570 OnTargetOutput (false, line + "\n");
572 break;
574 case '*':
575 GdbEvent ev;
576 lock (eventLock) {
577 running = false;
578 ev = new GdbEvent (line);
579 if (ev.GetValue ("thread-id") != null)
580 currentThread = activeThread = ev.GetInt ("thread-id");
581 Monitor.PulseAll (eventLock);
582 if (internalStop) {
583 internalStop = false;
584 return;
587 ThreadPool.QueueUserWorkItem (delegate {
588 try {
589 HandleEvent (ev);
590 } catch (Exception ex) {
591 Console.WriteLine (ex);
594 break;
598 void HandleEvent (GdbEvent ev)
600 if (ev.Name != "stopped") {
601 Console.WriteLine ("Unknown event: " + ev.Name);
602 return;
605 CleanTempVariableObjects ();
607 TargetEventType type;
608 switch (ev.Reason) {
609 case "breakpoint-hit":
610 type = TargetEventType.TargetHitBreakpoint;
611 if (!CheckBreakpoint (ev.GetInt ("bkptno"))) {
612 RunCommand ("-exec-continue");
613 return;
615 break;
616 case "signal-received":
617 if (ev.GetValue ("signal-name") == "SIGINT")
618 type = TargetEventType.TargetInterrupted;
619 else
620 type = TargetEventType.TargetSignaled;
621 break;
622 case "exited":
623 case "exited-signalled":
624 case "exited-normally":
625 type = TargetEventType.TargetExited;
626 break;
627 default:
628 type = TargetEventType.TargetStopped;
629 break;
632 ResultData curFrame = ev.GetObject ("frame");
633 FireTargetEvent (type, curFrame);
636 void FireTargetEvent (TargetEventType type, ResultData curFrame)
638 UpdateHitCountData ();
640 TargetEventArgs args = new TargetEventArgs (type);
642 if (type != TargetEventType.TargetExited) {
643 GdbCommandResult res = RunCommand ("-stack-info-depth");
644 int fcount = int.Parse (res.GetValue ("depth"));
646 GdbBacktrace bt = new GdbBacktrace (this, activeThread, fcount, curFrame);
647 args.Backtrace = new Backtrace (bt);
648 args.Thread = GetThread (activeThread);
650 OnTargetEvent (args);
653 internal void RegisterTempVariableObject (string var)
655 tempVariableObjects.Add (var);
658 void CleanTempVariableObjects ()
660 foreach (string s in tempVariableObjects)
661 RunCommand ("-var-delete", s);
662 tempVariableObjects.Clear ();