4 // Lluis Sanchez Gual <lluis@novell.com>
6 // Copyright (c) 2008 Novell, Inc (http://www.novell.com)
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
28 // Uncomment to see the commands sent to gdb and the output it generates
32 using System
.Globalization
;
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
51 ProcessWrapper console
;
52 GdbCommandResult lastResult
;
55 int currentThread
= -1;
56 int activeThread
= -1;
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;
70 object syncLock
= new object ();
71 object eventLock
= new object ();
72 object gdbLock
= new object ();
74 protected override void OnRun (DebuggerStartInfo startInfo
)
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";
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');
97 if (File
.Exists (script
))
99 if (File
.Exists (ttyfile
))
100 File
.Delete (ttyfile
);
101 if (File
.Exists (ttyfileDone
))
102 File
.Delete (ttyfileDone
);
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 ();
125 RunCommand ("-exec-run");
129 protected override void OnAttachToProcess (int processId
)
133 currentProcessName
= "PID " + processId
.ToString ();
134 RunCommand ("attach", processId
.ToString ());
135 currentThread
= activeThread
= 1;
136 CheckIsMonoProcess ();
138 FireTargetEvent (TargetEventType
.TargetStopped
, null);
142 public bool IsMonoProcess
{
143 get { return isMonoProcess; }
146 void CheckIsMonoProcess ()
149 RunCommand ("-data-evaluate-expression", "mono_pmip");
150 isMonoProcess
= true;
152 isMonoProcess
= false;
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;
168 sout
= proc
.StandardOutput
;
169 sin
= proc
.StandardInput
;
171 thread
= new Thread (OutputInterpreter
);
172 thread
.IsBackground
= true;
176 public override void Dispose ()
178 if (console
!= null && !console
.HasExited
) {
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 ()
201 RunCommand ("detach");
202 FireTargetEvent (TargetEventType
.TargetExited
, null);
206 protected override void OnExit ()
211 TargetEventArgs args
= new TargetEventArgs (TargetEventType
.TargetExited
);
212 OnTargetEvent (args
);
214 TargetEventArgs args = new TargetEventArgs (TargetEventType.TargetExited);
215 OnTargetEvent (args);
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
;
256 bool dres
= InternalStop ();
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");
273 RunCommand ("-break-disable", bh
.ToString ());
274 breakpoints
[bh
] = bp
;
277 InternalResume (dres
);
282 bool CheckBreakpoint (int handle
)
284 Breakpoint bp
= breakpoints
[handle
];
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
);
305 void NotifyBreakEventUpdate (int eventHandle
, int hitCount
, string lastTrace
)
309 WaitCallback nc
= delegate {
311 UpdateHitCount (eventHandle
, hitCount
);
312 if (lastTrace
!= null)
313 UpdateLastTraceValue (eventHandle
, lastTrace
);
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
;
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
)
348 void UpdateHitCountData ()
350 foreach (Breakpoint bp
in breakpointsWithHitCount
) {
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");
356 NotifyBreakEventUpdate ((int)h
, int.Parse (val
), null);
358 NotifyBreakEventUpdate ((int)h
, 0, null);
361 breakpointsWithHitCount
.Clear ();
364 protected override void OnRemoveBreakEvent (object handle
)
367 bool dres
= InternalStop ();
368 Breakpoint bp
= breakpoints
[(int)handle
];
369 breakpointsWithHitCount
.Remove (bp
);
370 breakpoints
.Remove ((int)handle
);
372 RunCommand ("-break-delete", handle
.ToString ());
374 InternalResume (dres
);
379 protected override void OnEnableBreakEvent (object handle
, bool enable
)
382 bool dres
= InternalStop ();
385 RunCommand ("-break-enable", handle
.ToString ());
387 RunCommand ("-break-disable", handle
.ToString ());
389 InternalResume (dres
);
394 protected override object OnUpdateBreakEvent (object handle
, BreakEvent be
)
396 Breakpoint bp
= be
as Breakpoint
;
400 bool ss
= InternalStop ();
403 if (bp
.HitCount
> 0) {
404 RunCommand ("-break-after", handle
.ToString (), bp
.HitCount
.ToString ());
405 breakpointsWithHitCount
.Add (bp
);
407 breakpointsWithHitCount
.Remove (bp
);
409 if (!string.IsNullOrEmpty (bp
.ConditionExpression
) && !bp
.BreakIfConditionChanges
)
410 RunCommand ("-break-condition", handle
.ToString (), bp
.ConditionExpression
);
412 RunCommand ("-break-condition", handle
.ToString ());
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
> ();
460 ResultData data
= null;
462 data
= RunCommand ("-data-disassemble", "-f", file
, "-l", cline
.ToString (), "--", "1");
466 ResultData asm_insns
= data
.GetObject ("asm_insns");
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
)
485 return lines
.ToArray ();
488 public ResultData
SelectThread (int id
)
490 if (id
== currentThread
)
493 return RunCommand ("-thread-select", id
.ToString ());
496 public GdbCommandResult
RunCommand (string command
, params string[] args
)
507 Console
.WriteLine ("gdb<: " + command
+ " " + string.Join (" ", args
));
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
);
526 Syscall
.kill (proc
.Id
, Signum
.SIGINT
);
527 if (!Monitor
.Wait (eventLock
, 4000))
528 throw new InvalidOperationException ("Target could not be interrupted.");
533 void InternalResume (bool resume
)
536 RunCommand ("-exec-continue");
539 void OutputInterpreter ()
542 while ((line
= sout
.ReadLine ()) != null) {
544 ProcessOutput (line
);
545 } catch (Exception ex
) {
546 Console
.WriteLine (ex
);
551 void ProcessOutput (string line
)
554 Console
.WriteLine ("dbg>: '" + line
+ "'");
559 lastResult
= new GdbCommandResult (line
);
560 running
= (lastResult
.Status
== CommandStatus
.Running
);
561 Monitor
.PulseAll (syncLock
);
567 if (line
.Length
> 1 && line
[1] == '"')
568 line
= line
.Substring (2, line
.Length
- 5);
569 ThreadPool
.QueueUserWorkItem (delegate {
570 OnTargetOutput (false, line
+ "\n");
578 ev
= new GdbEvent (line
);
579 if (ev
.GetValue ("thread-id") != null)
580 currentThread
= activeThread
= ev
.GetInt ("thread-id");
581 Monitor
.PulseAll (eventLock
);
583 internalStop
= false;
587 ThreadPool
.QueueUserWorkItem (delegate {
590 } catch (Exception ex
) {
591 Console
.WriteLine (ex
);
598 void HandleEvent (GdbEvent ev
)
600 if (ev
.Name
!= "stopped") {
601 Console
.WriteLine ("Unknown event: " + ev
.Name
);
605 CleanTempVariableObjects ();
607 TargetEventType type
;
609 case "breakpoint-hit":
610 type
= TargetEventType
.TargetHitBreakpoint
;
611 if (!CheckBreakpoint (ev
.GetInt ("bkptno"))) {
612 RunCommand ("-exec-continue");
616 case "signal-received":
617 if (ev
.GetValue ("signal-name") == "SIGINT")
618 type
= TargetEventType
.TargetInterrupted
;
620 type
= TargetEventType
.TargetSignaled
;
623 case "exited-signalled":
624 case "exited-normally":
625 type
= TargetEventType
.TargetExited
;
628 type
= TargetEventType
.TargetStopped
;
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 ();