From 96488b102dd1fec2f8f17d5a25ed7adad4ad835b Mon Sep 17 00:00:00 2001 From: Martin Baulig Date: Thu, 22 Aug 2002 20:20:35 +0000 Subject: [PATCH] Initial revision svn path=/trunk/debugger/; revision=6903 --- backends/gdb/GDB.cs | 691 ++++++++++++++++++++++++++ backends/gdb/mono-debugger.gdbinit | 1 + classes/SourceFileFactory.cs | 78 +++ frontends/command-line/Interpreter.cs | 242 +++++++++ frontends/gui/SimpleViewer.cs | 378 ++++++++++++++ interfaces/IBreakPoint.cs | 42 ++ interfaces/IDebuggerBackend.cs | 123 +++++ interfaces/ILanguage.cs | 18 + interfaces/ILanguageCSharp.cs | 23 + interfaces/ISourceFile.cs | 21 + interfaces/IStackFrame.cs | 15 + interfaces/ITargetLocation.cs | 21 + 12 files changed, 1653 insertions(+) create mode 100644 backends/gdb/GDB.cs create mode 100644 backends/gdb/mono-debugger.gdbinit create mode 100644 classes/SourceFileFactory.cs create mode 100644 frontends/command-line/Interpreter.cs create mode 100644 frontends/gui/SimpleViewer.cs create mode 100644 interfaces/IBreakPoint.cs create mode 100644 interfaces/IDebuggerBackend.cs create mode 100644 interfaces/ILanguage.cs create mode 100644 interfaces/ILanguageCSharp.cs create mode 100644 interfaces/ISourceFile.cs create mode 100644 interfaces/IStackFrame.cs create mode 100644 interfaces/ITargetLocation.cs diff --git a/backends/gdb/GDB.cs b/backends/gdb/GDB.cs new file mode 100644 index 00000000..ce5bea03 --- /dev/null +++ b/backends/gdb/GDB.cs @@ -0,0 +1,691 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Globalization; +using System.Reflection; +using System.Threading; +using System.Diagnostics; +using System.Collections; + +using Mono.Debugger.Languages; + +namespace Mono.Debugger.Backends +{ + public class GDB : IDebuggerBackend, ILanguageCSharp, IDisposable + { + public const string Path_GDB = "/usr/bin/gdb"; + public const string Path_Mono = "/home/martin/MONO-LINUX/bin/mono"; + + Process process; + StreamWriter gdb_pipe; + Stream gdb_output; + Stream gdb_errors; + + Thread gdb_output_thread; + Thread gdb_error_thread; + + Hashtable symbols; + Hashtable breakpoints; + + Assembly application; + + ISourceFileFactory source_file_factory; + + ManualResetEvent gdb_event; + + int last_breakpoint_id; + + public GDB (string application, string[] arguments) + : this (Path_GDB, Path_Mono, application, arguments) + { } + + public GDB (string gdb_path, string mono_path, string application, string[] arguments) + { + this.application = Assembly.LoadFrom (application); + + MethodInfo main = this.application.EntryPoint; + string main_name = main.DeclaringType + ":" + main.Name; + + string args = "-n -nw -q -x mono-debugger.gdbinit --annotate=2 --async " + + "--args " + mono_path + " --break " + main_name + " --debug=dwarf " + + "--noinline --precompile @" + application + " " + application + " " + + String.Join (" ", arguments); + + ProcessStartInfo start_info = new ProcessStartInfo (gdb_path, args); + + start_info.RedirectStandardInput = true; + start_info.RedirectStandardOutput = true; + start_info.RedirectStandardError = true; + start_info.UseShellExecute = false; + + process = Process.Start (start_info); + gdb_pipe = process.StandardInput; + gdb_pipe.AutoFlush = true; + + gdb_event = new ManualResetEvent (false); + + gdb_output = process.StandardOutput.BaseStream; + gdb_errors = process.StandardError.BaseStream; + + gdb_output_thread = new Thread (new ThreadStart (check_gdb_output)); + gdb_output_thread.Start (); + + gdb_error_thread = new Thread (new ThreadStart (check_gdb_errors)); + gdb_error_thread.Start (); + + send_gdb_command ("run"); + send_gdb_command ("call mono_debug_make_symbols ()"); + send_gdb_command ("add-symbol-file " + application + ".o"); + + symbols = new Hashtable (); + breakpoints = new Hashtable (); + } + + // + // IDebuggerBackend + // + + TargetState target_state = TargetState.NO_TARGET; + public TargetState State { + get { + return target_state; + } + } + + public void Run () + { + gdb_pipe.WriteLine ("run"); + } + + public void Continue () + { + gdb_pipe.WriteLine ("continue"); + } + + public void Quit () + { + gdb_pipe.WriteLine ("quit"); + } + + public void Abort () + { + gdb_pipe.WriteLine ("signal SIGTERM"); + } + + public void Kill () + { + gdb_pipe.WriteLine ("kill"); + } + + public void Frame () + { + gdb_pipe.WriteLine ("frame"); + } + + public void Step () + { + gdb_pipe.WriteLine ("step"); + } + + public void Next () + { + gdb_pipe.WriteLine ("next"); + gdb_pipe.WriteLine ("frame"); + } + + public IBreakPoint AddBreakPoint (ITargetLocation location) + { + long address = location.Location; + if (address == -1) + return null; + + last_breakpoint_id = -1; + + wait_for = WaitForOutput.ADD_BREAKPOINT; + send_gdb_command ("break *" + address); + + if (last_breakpoint_id == -1) + return null; + + BreakPoint breakpoint = new BreakPoint (this, location, last_breakpoint_id); + breakpoints.Add (last_breakpoint_id, breakpoint); + return (IBreakPoint) breakpoint; + } + + TargetOutputHandler target_output = null; + TargetOutputHandler target_error = null; + StateChangedHandler state_changed = null; + StackFrameHandler frame_event = null; + + public event TargetOutputHandler TargetOutput { + add { + target_output += value; + } + + remove { + target_output -= value; + } + } + + public event TargetOutputHandler TargetError { + add { + target_error += value; + } + + remove { + target_error -= value; + } + } + + public event StateChangedHandler StateChanged { + add { + state_changed += value; + } + + remove { + state_changed -= value; + } + } + + public event StackFrameHandler FrameEvent { + add { + frame_event += value; + } + + remove { + frame_event -= value; + } + } + + public ISourceFileFactory SourceFileFactory { + get { + return source_file_factory; + } + + set { + source_file_factory = value; + } + } + + // + // ILanguageCSharp + // + + ITargetLocation ISourceLanguage.MainLocation { + get { + return ILanguageCSharp.CreateLocation (application.EntryPoint); + } + } + + ITargetLocation ILanguageCSharp.CreateLocation (MethodInfo method) + { + return new TargetAddress (this, method.DeclaringType + "." + method.Name); + } + + Assembly ILanguageCSharp.CurrentAssembly { + get { + return application; + } + } + + // + // private interface implementations. + // + + protected class TargetAddress : ITargetLocation + { + public readonly string SymbolName; + public readonly GDB GDB; + + public TargetAddress (GDB gdb, string symbol) + { + this.SymbolName = symbol; + this.GDB = gdb; + } + + long ITargetLocation.Location { + get { + if (GDB.symbols.Contains (SymbolName)) + return (long) GDB.symbols [SymbolName]; + + GDB.wait_for = WaitForOutput.INFO_ADDRESS; + GDB.send_gdb_command ("info address " + SymbolName); + + if (GDB.symbols.Contains (SymbolName)) + return (long) GDB.symbols [SymbolName]; + + return -1; + } + } + } + + protected class BreakPoint : IBreakPoint + { + public readonly GDB GDB; + + ITargetLocation address; + int hit_count = 0; + bool enabled = true; + int ID; + + public BreakPoint (GDB gdb, ITargetLocation address, int id) + { + this.GDB = gdb; + this.address = address; + this.ID = id; + } + + public void EmitHitEvent () + { + ++hit_count; + + if (Hit != null) + Hit (this); + } + + ITargetLocation IBreakPoint.TargetLocation { + get { + return address; + } + } + + int IBreakPoint.HitCount { + get { + return hit_count; + } + } + + bool IBreakPoint.Enabled { + get { + return enabled; + } + + set { + enabled = value; + if (enabled) + GDB.send_gdb_command ("enable " + ID); + else + GDB.send_gdb_command ("disable " + ID); + } + } + + public event BreakPointHandler Hit; + } + + protected class StackFrame : IStackFrame + { + public readonly GDB GDB; + + public ISourceFile SourceFile = null; + public int Row = 0; + + public StackFrame (GDB gdb) + { + this.GDB = gdb; + } + + ISourceFile IStackFrame.SourceFile { + get { + return SourceFile; + } + } + + int IStackFrame.Row { + get { + return Row; + } + } + } + + // + // private. + // + + enum WaitForOutput { + UNKNOWN, + INFO_ADDRESS, + ADD_BREAKPOINT, + BREAKPOINT, + FRAME, + SOURCE_FILE, + SOURCE_LINE + } + + WaitForOutput wait_for = WaitForOutput.UNKNOWN; + + StackFrame current_frame = null; + string source_file = null; + + void HandleAnnotation (string annotation, string[] args) + { + // Console.WriteLine ("ANNOTATION: |" + annotation + "|"); + + switch (annotation) { + case "starting": + target_state = TargetState.RUNNING; + if (state_changed != null) + state_changed (target_state); + break; + + case "stopped": + if (target_state != TargetState.RUNNING) + break; + target_state = TargetState.STOPPED; + if (state_changed != null) + state_changed (target_state); + break; + + case "exited": + target_state = TargetState.EXITED; + if (state_changed != null) + state_changed (target_state); + break; + + case "prompt": + wait_for = WaitForOutput.UNKNOWN; + gdb_event.Set (); + break; + + case "breakpoint": + wait_for = WaitForOutput.BREAKPOINT; + break; + + case "frame-begin": + wait_for = WaitForOutput.FRAME; + current_frame = new StackFrame (this); + break; + + case "frame-source-file": + wait_for = WaitForOutput.SOURCE_FILE; + source_file = null; + break; + + case "frame-source-file-end": + if ((current_frame == null) || (source_file_factory == null) || + (source_file == null)) + break; + + current_frame.SourceFile = source_file_factory.FindFile (source_file); + break; + + case "frame-source-line": + wait_for = WaitForOutput.SOURCE_LINE; + break; + + case "frame-end": + if ((current_frame != null) && (frame_event != null)) + frame_event (current_frame); + break; + + default: + break; + } + } + + bool check_info_no_symbol (string line) + { + if (!line.StartsWith ("No symbol\"")) + return false; + int idx = line.IndexOf ('"', 11); + if (idx == 0) + return false; + + string symbol = line.Substring (11, idx-11); + + line = line.Substring (idx+1); + if (!line.StartsWith (" in current context")) + return false; + + symbols.Remove (symbol); + + return true; + } + + bool check_info_symbol (string line) + { + if (!line.StartsWith ("Symbol \"")) + return false; + int idx = line.IndexOf ('"', 8); + if (idx == 0) + return false; + + string symbol = line.Substring (8, idx-8); + + line = line.Substring (idx+1); + if (!line.StartsWith (" is a function at address ")) + return false; + + string address = line.Substring (28, line.Length-29); + + long addr; + try { + addr = Int64.Parse (address, NumberStyles.HexNumber); + } catch { + return false; + } + + if (symbols.Contains (symbol)) + symbols.Remove (symbol); + symbols.Add (symbol, addr); + + return true; + } + + bool check_add_breakpoint (string line) + { + if (!line.StartsWith ("Breakpoint ")) + return false; + + int idx = line.IndexOf (' ', 11); + if (idx == 0) + return false; + if (!line.Substring (idx).StartsWith (" at ")) + return false; + + string id_str = line.Substring (11, idx-11); + + int id; + try { + id = Int32.Parse (id_str); + } catch { + return false; + } + + last_breakpoint_id = id; + + return true; + } + + bool check_breakpoint (string line) + { + if (!line.StartsWith ("Breakpoint ")) + return false; + int idx = line.IndexOf (',', 11); + if (idx < 0) + return false; + + int id; + try { + id = Int32.Parse (line.Substring (11, idx-11)); + } catch { + return false; + } + + BreakPoint breakpoint = (BreakPoint) breakpoints [id]; + if (breakpoint != null) { + Console.WriteLine ("HIT BREAKPOINT: " + id + " " + breakpoint); + + breakpoint.EmitHitEvent (); + } + + return true; + } + + bool HandleOutput (string line) + { + switch (wait_for) { + case WaitForOutput.INFO_ADDRESS: + if (check_info_symbol (line) || check_info_no_symbol (line)) + return true; + break; + case WaitForOutput.ADD_BREAKPOINT: + if (check_add_breakpoint (line)) + return true; + break; + + case WaitForOutput.BREAKPOINT: + if (check_breakpoint (line)) + return true; + break; + + case WaitForOutput.SOURCE_FILE: + source_file = line; + wait_for = WaitForOutput.FRAME; + return true; + + case WaitForOutput.SOURCE_LINE: + try { + if (current_frame != null) + current_frame.Row = Int32.Parse (line); + return true; + } catch { + } + break; + + case WaitForOutput.FRAME: + return true; + + default: + break; + } + + return false; + } + + public void SendUserCommand (string command) + { + send_gdb_command (command); + } + + void send_gdb_command (string command) + { + Console.WriteLine ("SENDING `{0}'", command); + gdb_event.Reset (); + gdb_pipe.WriteLine (command); + gdb_event.WaitOne (); + Console.WriteLine ("DONE"); + } + + string read_one_line (Stream stream) + { + StringBuilder text = new StringBuilder (); + + while (true) { + int c = stream.ReadByte (); + + if (c == -1) { // end of stream + if (text.Length == 0) + return null; + + break; + } + + if (c == '\n') { // newline + if ((text.Length > 0) && (text [text.Length - 1] == '\r')) + text.Length--; + break; + } + + text.Append ((char) c); + } + + return text.ToString (); + } + + void check_gdb_output (bool is_stderr) + { + while (true) { + string line = read_one_line (is_stderr ? gdb_errors : gdb_output); + + if (line == "") + continue; + if (line == null) + break; + + if ((line.Length > 2) && (line [0] == 26) && (line [1] == 26)) { + string annotation = line.Substring (2); + string[] args; + + int idx = annotation.IndexOf (' '); + if (idx > 0) { + args = annotation.Substring (idx+1).Split (' '); + annotation = annotation.Substring (0, idx); + } else + args = new string [0]; + + HandleAnnotation (annotation, args); + } else if (!HandleOutput (line)) { + if (is_stderr) { + if (target_error != null) + target_error (line); + } else { + if (target_output != null) + target_output (line); + } + } + } + } + + void check_gdb_output () + { + check_gdb_output (false); + } + + void check_gdb_errors () + { + check_gdb_output (true); + } + + // + // IDisposable + // + + private bool disposed = false; + + protected virtual void Dispose (bool disposing) + { + // Check to see if Dispose has already been called. + if (!this.disposed) { + // If this is a call to Dispose, + // dispose all managed resources. + if (disposing) { + // Do stuff here + Quit (); + + gdb_output_thread.Abort (); + gdb_error_thread.Abort (); + } + + // Release unmanaged resources + this.disposed = true; + + lock (this) { + process.Kill (); + } + } + } + + public void Dispose () + { + Dispose (true); + // Take yourself off the Finalization queue + GC.SuppressFinalize (this); + } + + ~GDB () + { + Dispose (false); + } + } +} diff --git a/backends/gdb/mono-debugger.gdbinit b/backends/gdb/mono-debugger.gdbinit new file mode 100644 index 00000000..d8b7f26b --- /dev/null +++ b/backends/gdb/mono-debugger.gdbinit @@ -0,0 +1 @@ +set prompt diff --git a/classes/SourceFileFactory.cs b/classes/SourceFileFactory.cs new file mode 100644 index 00000000..ce8711ca --- /dev/null +++ b/classes/SourceFileFactory.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; +using Mono.Debugger; + +public class SourceFile : ISourceFile +{ + FileInfo file_info; + string contents; + + public SourceFile (FileInfo file_info) + { + this.file_info = file_info; + ReadFile (); + } + + void ReadFile () + { + try { + FileStream stream = file_info.OpenRead (); + + char[] result = new char [stream.Length]; + + long count = 0; + while (true) { + int b = stream.ReadByte (); + if (b == -1) + break; + + result [count++] = (char) b; + }; + + stream.Close (); + + Console.WriteLine ("DONE: " + count); + + contents = new String (result); + } catch { + return; + } + } + + public FileInfo FileInfo { + get { + return file_info; + } + } + + public string FileContents { + get { + return contents; + } + } +} + +public class SourceFileFactory : ISourceFileFactory +{ + Hashtable files = new Hashtable (); + + public ISourceFile FindFile (string name) + { + Console.WriteLine ("FIND FILE: |" + name + "|" + files); + if (files.Contains (name)) { + Console.WriteLine ("FOUND: " + files [name]); + return (ISourceFile) files [name]; + } + + FileInfo file_info = new FileInfo (name); + + if (!file_info.Exists) + return null; + + ISourceFile retval = new SourceFile (file_info); + files.Add (name, retval); + return retval; + } +} diff --git a/frontends/command-line/Interpreter.cs b/frontends/command-line/Interpreter.cs new file mode 100644 index 00000000..dca092b0 --- /dev/null +++ b/frontends/command-line/Interpreter.cs @@ -0,0 +1,242 @@ +using System; +using System.Text; +using System.Reflection; +using System.Threading; +using Mono.Debugger; +using Mono.Debugger.Backends; +using Mono.Debugger.Languages; + +namespace Mono.Debugger.Frontends.CommandLine +{ + /// + /// This is a very simple command-line interpreter for the Mono Debugger. + /// + public class Interpreter + { + GDB backend; + + Interpreter (string application, string[] arguments) + { + backend = new GDB (application, arguments); + + backend.TargetOutput += new TargetOutputHandler (TargetOutput); + backend.TargetError += new TargetOutputHandler (TargetError); + backend.StateChanged += new StateChangedHandler (StateChanged); + + backend.SourceFileFactory = new SourceFileFactory (); + } + + void ShowHelp () + { + Console.WriteLine ("Commands:"); + Console.WriteLine (" q, quit,exit Quit the debugger"); + Console.WriteLine (" r, run Start the target"); + Console.WriteLine (" c, continue Continue the target"); + Console.WriteLine (" abort Abort the target"); + Console.WriteLine (" kill Kill the target"); + Console.WriteLine (" b, break-method Add breakpoint for a CSharp method"); + Console.WriteLine (" !, gdb Send command to gdb"); + } + + StringBuilder output_builder = null; + + bool DoOneCommand (string command, string[] args) + { + switch (command) { + case "h": + case "help": + ShowHelp (); + break; + + case "q": + case "quit": + case "exit": + backend.Quit (); + return false; + + case "r": + case "run": + backend.Run (); + break; + + case "c": + case "continue": + backend.Continue (); + break; + + case "abort": + backend.Abort (); + break; + + case "kill": + backend.Kill (); + break; + + case "sleep": + Thread.Sleep (50000); + break; + + case "b": + case "break-method": { + if (args.Length != 2) { + Console.WriteLine ("Command requires an argument"); + break; + } + + ILanguageCSharp csharp = backend as ILanguageCSharp; + if (csharp == null) { + Console.WriteLine ("Debugger doesn't support C#"); + break; + } + + Type type = csharp.CurrentAssembly.GetType (args [0]); + if (type == null) { + Console.WriteLine ("No such type: `{0}'", args [0]); + break; + } + + MethodInfo method = type.GetMethod (args [1]); + if (method == null) { + Console.WriteLine ("Can't find method `{0}' in type `{1}'", + args [1], args [0]); + break; + } + + ITargetLocation location = csharp.CreateLocation (method); + if (location == null) { + Console.WriteLine ("Can't get location for method: {0}.{1}", + args [0], args [1]); + break; + } + + IBreakPoint break_point = backend.AddBreakPoint (location); + + if (break_point != null) + Console.WriteLine ("Added breakpoint: " + break_point); + else + Console.WriteLine ("Unable to add breakpoint!"); + + break; + } + + case "!": + case "gdb": { + GDB gdb = backend as GDB; + if (gdb == null) { + Console.WriteLine ("This command is only available when using " + + "gdb as backend"); + break; + } + + output_builder = new StringBuilder (); + gdb.SendUserCommand (String.Join (" ", args)); + Console.WriteLine (output_builder.ToString ()); + output_builder = null; + break; + } + + default: + Console.WriteLine ("Unknown command: " + command); + break; + } + + return true; + } + + void TargetOutput (string output) + { + if (output_builder != null) + output_builder.Append (output); + else + Console.WriteLine ("OUTPUT: |" + output + "|"); + } + + void TargetError (string output) + { + Console.WriteLine ("ERROR : |" + output + "|"); + } + + void StateChanged (TargetState new_state) + { + Console.WriteLine ("STATE CHANGED: " + new_state); + } + + public void MainLoop () + { + bool keep_running = true; + + while (keep_running) { + Console.Write ("$ "); + + string line = Console.ReadLine (); + if (line == null) + break; + + if (line == "") + continue; + + string[] args = line.Split (' ', '\t'); + string[] new_args = new string [args.Length - 1]; + Array.Copy (args, 1, new_args, 0, args.Length - 1); + + keep_running = DoOneCommand (args [0], new_args); + } + + backend.Dispose (); + } + + // + // IDisposable + // + + private bool disposed = false; + + protected virtual void Dispose (bool disposing) + { + // Check to see if Dispose has already been called. + if (!this.disposed) { + // If this is a call to Dispose, + // dispose all managed resources. + if (disposing) { + // Do stuff here + backend.Dispose (); + } + + // Release unmanaged resources + this.disposed = true; + } + } + + public void Dispose () + { + Dispose (true); + // Take yourself off the Finalization queue + GC.SuppressFinalize (this); + } + + ~Interpreter () + { + Dispose (false); + } + + // + // Main + // + static void Main (string[] args) + { + if (args.Length < 1) { + Console.WriteLine ("Usage: {0} application.exe [args]", + AppDomain.CurrentDomain.FriendlyName); + Environment.Exit (1); + } + + string[] new_args = new string [args.Length - 1]; + Array.Copy (args, 1, new_args, 0, args.Length - 1); + + Interpreter interpreter = new Interpreter (args [0], new_args); + + interpreter.MainLoop (); + interpreter.Dispose (); + } + } +} diff --git a/frontends/gui/SimpleViewer.cs b/frontends/gui/SimpleViewer.cs new file mode 100644 index 00000000..4a5fdc97 --- /dev/null +++ b/frontends/gui/SimpleViewer.cs @@ -0,0 +1,378 @@ +using GLib; +using Gtk; +using Gdk; +using GtkSharp; +using Gnome; +using System; +using System.IO; +using System.Text; +using System.Drawing; +using System.Threading; +using System.Reflection; + +using Mono.Debugger; +using Mono.Debugger.Backends; +using Mono.Debugger.Languages; +using Mono.Debugger.GUI; + +namespace Mono.Debugger.GUI { + + public class SimpleViewer + { + Gtk.Statusbar status_bar; + Gtk.TextView text_view; + Gtk.TextView output_area; + Gtk.TextBuffer text_buffer; + Gtk.TextBuffer output_buffer; + Gtk.Entry command_entry; + Gtk.TextTag frame_tag; + Gtk.TextMark frame_mark; + + Program kit; + GDB backend; + + SimpleViewer (string application, string[] arguments) + { + string[] args = new string [0]; + kit = new Program ("simple-viewer", "0.0.1", Modules.UI, args); + + Gtk.Window win = CreateWindow (); + win.ShowAll (); + + backend = new GDB (application, arguments); + + backend.TargetOutput += new TargetOutputHandler (TargetOutput); + backend.TargetError += new TargetOutputHandler (TargetError); + backend.StateChanged += new StateChangedHandler (StateChanged); + backend.FrameEvent += new StackFrameHandler (FrameEvent); + + backend.SourceFileFactory = new SourceFileFactory (); + } + + StringBuilder output_builder = null; + + void TargetOutput (string output) + { + if (output_builder != null) + output_builder.Append (output); + else + AddOutput (output); + } + + void TargetError (string output) + { + AddOutput (output); + } + + public void AddOutput (string output) + { + output_buffer.Insert (output_buffer.EndIter, output + "\n", + output.Length+1); + output_area.ScrollToMark (output_buffer.InsertMark, 0.4, true, 0.0, 1.0); + } + + void StateChanged (TargetState new_state) + { + if (new_state == TargetState.RUNNING) + text_buffer.RemoveTag (frame_tag, text_buffer.StartIter, text_buffer.EndIter); + + Console.WriteLine ("STATE CHANGED: " + new_state); + } + + ISourceFile current_file = null; + + void FrameEvent (IStackFrame frame) + { + if ((frame.SourceFile == null) || (frame.SourceFile.FileContents == null)) + return; + + Gtk.TextBuffer buffer = text_view.Buffer; + + if (current_file != frame.SourceFile) { + current_file = frame.SourceFile; + + text_buffer.Delete (text_buffer.StartIter, text_buffer.EndIter); + + text_buffer.Insert (text_buffer.EndIter, frame.SourceFile.FileContents, + frame.SourceFile.FileContents.Length); + } + + Gtk.TextIter start_iter, end_iter; + text_buffer.GetIterAtLineOffset (out start_iter, frame.Row - 1, 0); + text_buffer.GetIterAtLineOffset (out end_iter, frame.Row, 0); + + text_buffer.RemoveTag (frame_tag, text_buffer.StartIter, text_buffer.EndIter); + text_buffer.ApplyTag (frame_tag, start_iter, end_iter); + + text_buffer.MoveMark (frame_mark, start_iter); + + text_view.ScrollToMark (frame_mark, 0.0, true, 0.0, 0.5); + + string text = frame.SourceFile.FileInfo.Name + " line " + frame.Row; + + uint id = status_bar.GetContextId ("frame"); + status_bar.Push (id, text); + } + + Gtk.TextView CreateSourceView () + { + Gtk.TextView text = new Gtk.TextView (); + + text.Editable = false; + + frame_tag = new Gtk.TextTag ("frame"); + frame_tag.Background = "red"; + + text.Buffer.TagTable.Add (frame_tag); + + frame_mark = text.Buffer.CreateMark ("frame", text.Buffer.StartIter, true); + + return text; + } + + Gtk.TextView CreateOutputArea () + { + Gtk.TextView text = new Gtk.TextView (); + + text.Editable = true; + text.WrapMode = Gtk.WrapMode.None; + + return text; + } + + Gtk.Statusbar CreateStatusBar () + { + Gtk.Statusbar status = new Gtk.Statusbar (); + + status.HasResizeGrip = false; + + return status; + } + + Gtk.Entry CreateCommandEntry () + { + Gtk.Entry entry = new Gtk.Entry (); + + entry.ActivatesDefault = true; + entry.Activated += new EventHandler (DoOneCommand); + + return entry; + } + + void ShowHelp () + { + AddOutput ("Commands:"); + AddOutput (" q, quit,exit Quit the debugger"); + AddOutput (" r, run Start the target"); + AddOutput (" c, continue Continue the target"); + AddOutput (" abort Abort the target"); + AddOutput (" kill Kill the target"); + AddOutput (" b, break-method Add breakpoint for a CSharp method"); + AddOutput (" f, frame Get current stack frame"); + AddOutput (" s, step Single-step"); + AddOutput (" n, next Single-step"); + AddOutput (" !, gdb Send command to gdb"); + } + + void DoOneCommand (object sender, EventArgs event_args) + { + string line = command_entry.Text; + command_entry.Text = ""; + + if (line == "") + return; + + string[] tmp_args = line.Split (' ', '\t'); + string[] args = new string [tmp_args.Length - 1]; + Array.Copy (tmp_args, 1, args, 0, tmp_args.Length - 1); + string command = tmp_args [0]; + + switch (command) { + case "h": + case "help": + ShowHelp (); + break; + + case "q": + case "quit": + case "exit": + backend.Quit (); + Application.Quit (); + break; + + case "r": + case "run": + backend.Run (); + break; + + case "c": + case "continue": + backend.Continue (); + break; + + case "f": + case "frame": + backend.Frame (); + break; + + case "s": + case "step": + backend.Step (); + break; + + case "n": + case "next": + backend.Next (); + break; + + case "abort": + backend.Abort (); + break; + + case "kill": + backend.Kill (); + break; + + case "sleep": + Thread.Sleep (50000); + break; + + case "b": + case "break-method": { + if (args.Length != 2) { + AddOutput ("Command requires an argument"); + break; + } + + ILanguageCSharp csharp = backend as ILanguageCSharp; + if (csharp == null) { + AddOutput ("Debugger doesn't support C#"); + break; + } + + Type type = csharp.CurrentAssembly.GetType (args [0]); + if (type == null) { + AddOutput ("No such type: `" + args [0] + "'"); + break; + } + + MethodInfo method = type.GetMethod (args [1]); + if (method == null) { + AddOutput ("Can't find method `" + args [1] + "' in type `" + + args [0] + "'"); + break; + } + + ITargetLocation location = csharp.CreateLocation (method); + if (location == null) { + AddOutput ("Can't get location for method: " + + args [0] + "." + args [1]); + break; + } + + IBreakPoint break_point = backend.AddBreakPoint (location); + + if (break_point != null) + AddOutput ("Added breakpoint: " + break_point); + else + AddOutput ("Unable to add breakpoint!"); + + break; + } + + case "!": + case "gdb": { + GDB gdb = backend as GDB; + if (gdb == null) { + AddOutput ("This command is only available when using " + + "gdb as backend"); + break; + } + + output_builder = new StringBuilder (); + gdb.SendUserCommand (String.Join (" ", args)); + AddOutput (output_builder.ToString ()); + output_builder = null; + break; + } + + default: + AddOutput ("Unknown command: " + command); + break; + } + } + + public Gtk.Window CreateWindow () + { + Gnome.App win = new Gnome.App ("simple-viewer", "Mono Debugger"); + win.DeleteEvent += new DeleteEventHandler (Window_Delete); + + text_view = CreateSourceView (); + output_area = CreateOutputArea (); + status_bar = CreateStatusBar (); + command_entry = CreateCommandEntry (); + + output_buffer = output_area.Buffer; + text_buffer = text_view.Buffer; + + VBox vbox = new VBox (false, 0); + + Gtk.Frame command_frame = new Gtk.Frame ("Command"); + command_frame.Add (command_entry); + + Gtk.Frame source_frame = new Gtk.Frame ("Source code"); + Gtk.ScrolledWindow source_sw = new Gtk.ScrolledWindow (); + source_frame.Add (source_sw); + source_sw.Add (text_view); + + Gtk.Frame output_frame = new Gtk.Frame ("Output"); + Gtk.ScrolledWindow output_sw = new Gtk.ScrolledWindow (); + output_sw.VscrollbarPolicy = Gtk.PolicyType.Always; + output_sw.HscrollbarPolicy = Gtk.PolicyType.Always; + output_frame.Add (output_sw); + output_sw.Add (output_area); + + vbox.PackStart (command_frame, false, true, 4); + vbox.PackStart (source_frame, true, true, 4); + vbox.PackStart (output_frame, true, true, 4); + vbox.PackStart (status_bar, false, true, 4); + + win.Contents = vbox; + + win.DefaultSize = new Size (800, 500); + + return win; + } + + public void Run () + { + kit.Run (); + } + + static void Window_Delete (object obj, DeleteEventArgs args) + { + SignalArgs sa = (SignalArgs) args; + Application.Quit (); + sa.RetVal = true; + } + + // + // Main + // + static void Main (string[] args) + { + if (args.Length < 1) { + Console.WriteLine ("Usage: {0} application.exe [args]", + AppDomain.CurrentDomain.FriendlyName); + Environment.Exit (1); + } + + string[] new_args = new string [args.Length - 1]; + Array.Copy (args, 1, new_args, 0, args.Length - 1); + + SimpleViewer viewer = new SimpleViewer (args [0], new_args); + + viewer.Run (); + } + } +} diff --git a/interfaces/IBreakPoint.cs b/interfaces/IBreakPoint.cs new file mode 100644 index 00000000..29ef6366 --- /dev/null +++ b/interfaces/IBreakPoint.cs @@ -0,0 +1,42 @@ +using System; + +namespace Mono.Debugger +{ + public delegate void BreakPointHandler (IBreakPoint breakpoint); + + /// + /// This denotes a breakpoint. + /// + public interface IBreakPoint + { + // + // The source location of this breakpoint. + // + ITargetLocation TargetLocation { + get; + } + + // + // This event is hit each time the breakpoint is hit. + // + event BreakPointHandler Hit; + + // + // The number of times this breakpoint has already + // been hit. + // + int HitCount { + get; + } + + // + // Whether this breakpoint is enabled. If a + // breakpoint is disabled, the application won't + // stop when it's hit, but the debugger still knows + // about it. + // + bool Enabled { + get; set; + } + } +} diff --git a/interfaces/IDebuggerBackend.cs b/interfaces/IDebuggerBackend.cs new file mode 100644 index 00000000..204a1d29 --- /dev/null +++ b/interfaces/IDebuggerBackend.cs @@ -0,0 +1,123 @@ +using System; + +namespace Mono.Debugger +{ + public delegate void TargetOutputHandler (string otuput); + public delegate void StateChangedHandler (TargetState new_state); + public delegate void StackFrameHandler (IStackFrame frame); + + /// + /// State of the target (the application we're debugging). + /// + public enum TargetState + { + // + // There is no target to debug. + // + NO_TARGET, + + // + // The target is running. + // + RUNNING, + + // + // The target is stopped. + // + STOPPED, + + // + // The target has exited. + // + EXITED + } + + /// + /// This denotes a single debuggable target in some debugger. + /// + /// A debugger implements this interface to denote one single debuggable target + /// (an application the user is debugging). If a debugger may debug more than + /// one application at a time, it will create multiple instances of a class which + /// implements this interface. + /// + public interface IDebuggerBackend : IDisposable + { + // + // Get the state of the target we're debugging. + // + TargetState State { + get; + } + + // + // Start the target. + // + void Run (); + + // + // Continue the target if it was previously stopped. + // + void Continue (); + + // + // Tell the debugger that we're finished debugging. This kills the target + // and terminates the current debugging session. If the backend is talking + // to an external debugger like gdb, it'll also quit this process. + // + // This is not the same than Dispose() - you're still allowed to call Run() + // to start a new debugging session with the same breakpoint settings etc. + // + void Quit (); + + // + // Aborts the target being debugged, but gives it time to terminate cleanly. + // On Unix systems, this'll send a SIGTERM to the target process. + // + void Abort (); + + // + // Forcibly kills the target without giving it any time to terminate. + // On Unix systems, this'll send a SIGKILL to the target process. + // + void Kill (); + + void Frame (); + + void Step (); + + void Next (); + + // + // Adds a breakpoint at the specified target location. + // + IBreakPoint AddBreakPoint (ITargetLocation location); + + // + // This event is called when the target we're currently debugging has sent any + // output to stdout. + // + event TargetOutputHandler TargetOutput; + + // + // This event is called when the target we're currently debugging has sent any + // error messages to stderr. + // + event TargetOutputHandler TargetError; + + // + // This event is called when the state of the target we're currently debugging + // has changed, for instance when the target has stopped or exited. + // + event StateChangedHandler StateChanged; + + event StackFrameHandler FrameEvent; + + // + // A source file factory is responsible for finding source files and creating + // ISourceFile instances for them. + // + ISourceFileFactory SourceFileFactory { + get; set; + } + } +} diff --git a/interfaces/ILanguage.cs b/interfaces/ILanguage.cs new file mode 100644 index 00000000..2b270c75 --- /dev/null +++ b/interfaces/ILanguage.cs @@ -0,0 +1,18 @@ +using System; + +namespace Mono.Debugger +{ + // + // This denotes a programming language which can be debugged + // by the current debugger backend. + // + public interface ISourceLanguage + { + // + // Create a target location for the `main' method. + // + ITargetLocation MainLocation { + get; + } + } +} diff --git a/interfaces/ILanguageCSharp.cs b/interfaces/ILanguageCSharp.cs new file mode 100644 index 00000000..dd486316 --- /dev/null +++ b/interfaces/ILanguageCSharp.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; + +namespace Mono.Debugger.Languages +{ + // + // The C# programming language. + // + public interface ILanguageCSharp : ISourceLanguage + { + // + // Create a target location for the specified method. + // + ITargetLocation CreateLocation (MethodInfo method); + + // + // Get the current assembly. + // + Assembly CurrentAssembly { + get; + } + } +} diff --git a/interfaces/ISourceFile.cs b/interfaces/ISourceFile.cs new file mode 100644 index 00000000..de51b366 --- /dev/null +++ b/interfaces/ISourceFile.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; + +namespace Mono.Debugger +{ + public interface ISourceFileFactory + { + ISourceFile FindFile (string name); + } + + public interface ISourceFile + { + FileInfo FileInfo { + get; + } + + string FileContents { + get; + } + } +} diff --git a/interfaces/IStackFrame.cs b/interfaces/IStackFrame.cs new file mode 100644 index 00000000..5d718ed0 --- /dev/null +++ b/interfaces/IStackFrame.cs @@ -0,0 +1,15 @@ +using System; + +namespace Mono.Debugger +{ + public interface IStackFrame + { + ISourceFile SourceFile { + get; + } + + int Row { + get; + } + } +} diff --git a/interfaces/ITargetLocation.cs b/interfaces/ITargetLocation.cs new file mode 100644 index 00000000..d247ee9a --- /dev/null +++ b/interfaces/ITargetLocation.cs @@ -0,0 +1,21 @@ +using System; + +namespace Mono.Debugger +{ + // + // This interface denotes an address in the target's address + // space. An instance of this interface can be obtained by + // doing a symbol lookup or by calling CreateLocation() on + // one of the ISourceLanguage derivatives. + // backend. + // + public interface ITargetLocation + { + // + // Address of this location in the target's address space. + // + long Location { + get; + } + } +}