PageRenderTime 45ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/monodevelop-2.8.8.4/src/addins/MonoDevelop.Debugger.Soft/MonoDevelop.Debugger.Soft/CustomSoftDebuggerEngine.cs

#
C# | 366 lines | 274 code | 55 blank | 37 comment | 37 complexity | e328cfc01bb707d5b87548a3242bb116 MD5 | raw file
Possible License(s): LGPL-2.1
  1. //
  2. // CustomSoftDebuggerEngine.cs
  3. //
  4. // Author:
  5. // Michael Hutchinson <mhutchinson@novell.com>
  6. //
  7. // Copyright (c) 2010 Novell, Inc.
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. using System;
  27. using System.Diagnostics;
  28. using Mono.Debugging.Client;
  29. using MonoDevelop.Core;
  30. using MonoDevelop.Core.Execution;
  31. using System.Net;
  32. using System.Collections.Generic;
  33. using Mono.Debugging.Soft;
  34. namespace MonoDevelop.Debugger.Soft
  35. {
  36. class CustomSoftDebuggerEngine: IDebuggerEngine
  37. {
  38. bool? available;
  39. public bool CanDebugCommand (ExecutionCommand cmd)
  40. {
  41. // This isn't polished enough to show it by default. GUI needs work, and it should be exposed
  42. // via "run with->custom parameters", not a toplevel command and dialog.
  43. // That would also make it possible to save settings.
  44. if (!available.HasValue) {
  45. available = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MONODEVELOP_SDB_TEST"));
  46. }
  47. return available.Value;
  48. }
  49. public DebuggerSession CreateSession ()
  50. {
  51. return new CustomSoftDebuggerSession ();
  52. }
  53. public ProcessInfo[] GetAttachableProcesses ()
  54. {
  55. return new ProcessInfo[0];
  56. }
  57. public DebuggerStartInfo CreateDebuggerStartInfo (ExecutionCommand c)
  58. {
  59. //WORKAROUND: explicit generic type argument works around a gmcs 2.6.x type inference bug
  60. return InvokeSynch<SoftDebuggerStartInfo> (GetDebuggerInfo) ??
  61. //HACK: flag object so we can cancel the session
  62. new DebuggerStartInfo ();
  63. }
  64. static SoftDebuggerStartInfo GetDebuggerInfo ()
  65. {
  66. var dlg = new DebuggerOptionsDialog ();
  67. try {
  68. return dlg.Run ();
  69. } finally {
  70. dlg.Destroy ();
  71. }
  72. }
  73. static T InvokeSynch<T> (Func<T> func)
  74. {
  75. if (MonoDevelop.Ide.DispatchService.IsGuiThread)
  76. return func ();
  77. var ev = new System.Threading.ManualResetEvent (false);
  78. T val = default (T);
  79. Exception caught = null;
  80. Gtk.Application.Invoke (delegate {
  81. try {
  82. val = func ();
  83. } catch (Exception ex) {
  84. caught = ex;
  85. } finally {
  86. ev.Set ();
  87. }
  88. });
  89. ev.WaitOne ();
  90. if (caught != null)
  91. throw caught;
  92. return val;
  93. }
  94. class CustomSoftDebuggerSession : SoftDebuggerSession
  95. {
  96. IProcessAsyncOperation process;
  97. bool usingExternalConsole;
  98. protected override void OnRun (DebuggerStartInfo startInfo)
  99. {
  100. var info = startInfo as SoftDebuggerStartInfo;
  101. if (info == null) {
  102. EndSession ();
  103. return;
  104. }
  105. StartProcess (info);
  106. if (info.StartArgs is SoftDebuggerConnectArgs) {
  107. //connecting to the process, so give it a moment to start listening
  108. System.Threading.Thread.Sleep (200);
  109. }
  110. base.OnRun (startInfo);
  111. }
  112. void StartProcess (SoftDebuggerStartInfo info)
  113. {
  114. if (string.IsNullOrEmpty (info.Command))
  115. return;
  116. if (info.UseExternalConsole) {
  117. usingExternalConsole = true;
  118. var console = ExternalConsoleFactory.Instance.CreateConsole (info.CloseExternalConsoleOnExit);
  119. process = Runtime.ProcessService.StartConsoleProcess (
  120. info.Command, info.Arguments, info.WorkingDirectory, info.EnvironmentVariables,
  121. console, null);
  122. } else {
  123. var psi = new ProcessStartInfo (info.Command, info.Arguments) {
  124. WorkingDirectory = info.WorkingDirectory,
  125. };
  126. foreach (KeyValuePair<string,string> kvp in info.EnvironmentVariables)
  127. psi.EnvironmentVariables [kvp.Key] = kvp.Value;
  128. process = Runtime.ProcessService.StartProcess (psi, ProcessOutput, ProcessError, null);
  129. }
  130. }
  131. void ProcessOutput (object sender, string message)
  132. {
  133. OnTargetOutput (true, message);
  134. }
  135. void ProcessError (object sender, string message)
  136. {
  137. OnTargetOutput (false, message);
  138. }
  139. protected override void EndSession ()
  140. {
  141. base.EndSession ();
  142. EndProcess ();
  143. }
  144. void EndProcess ()
  145. {
  146. if (process == null)
  147. return;
  148. var p = process;
  149. process = null;
  150. if (usingExternalConsole || p.IsCompleted)
  151. return;
  152. try {
  153. p.Cancel ();
  154. } catch {}
  155. }
  156. protected override void EnsureExited ()
  157. {
  158. EndProcess ();
  159. }
  160. }
  161. class DebuggerOptionsDialog : Gtk.Dialog
  162. {
  163. MonoDevelop.Components.FileEntry commandEntry = new MonoDevelop.Components.FileEntry ();
  164. Gtk.Entry argsEntry = new Gtk.Entry ();
  165. Gtk.Entry ipEntry = new Gtk.Entry ();
  166. Gtk.Entry portEntry = new Gtk.Entry ();
  167. Gtk.Entry consolePortEntry = new Gtk.Entry ();
  168. Gtk.Button listenButton = new Gtk.Button ("Listen");
  169. Gtk.Button connectButton = new Gtk.Button ("Connect");
  170. const Gtk.ResponseType listenResponse = Gtk.ResponseType.Yes;
  171. const Gtk.ResponseType connectResponse = Gtk.ResponseType.Ok;
  172. IPAddress ip;
  173. string command, args;
  174. int? port, consolePort;
  175. Properties properties;
  176. //TODO: dropdown menus for picking string substitutions. also substitutions for port, ip
  177. public DebuggerOptionsDialog () : base (
  178. "Launch Soft Debugger", MonoDevelop.Ide.MessageService.RootWindow,
  179. Gtk.DialogFlags.DestroyWithParent | Gtk.DialogFlags.Modal)
  180. {
  181. properties = PropertyService.Get ("MonoDevelop.Debugger.Soft.CustomSoftDebugger", new Properties());
  182. AddActionWidget (new Gtk.Button (Gtk.Stock.Cancel), Gtk.ResponseType.Cancel);
  183. AddActionWidget (listenButton, listenResponse);
  184. AddActionWidget (connectButton, connectResponse);
  185. var table = new Gtk.Table (5, 2, false);
  186. table.BorderWidth = 6;
  187. VBox.PackStart (table, true, true, 0);
  188. table.Attach (new Gtk.Label ("Command:") { Xalign = 0 }, 0, 1, 0, 1);
  189. table.Attach (new Gtk.Label ("Arguments:") { Xalign = 0 }, 0, 1, 1, 2);
  190. table.Attach (new Gtk.Label ("IP:") { Xalign = 0 }, 0, 1, 2, 3);
  191. table.Attach (new Gtk.Label ("Port:") { Xalign = 0 }, 0, 1, 3, 4);
  192. table.Attach (new Gtk.Label ("Output:") { Xalign = 0 }, 0, 1, 4, 5);
  193. table.Attach (commandEntry, 1, 2, 0, 1);
  194. table.Attach (argsEntry, 1, 2, 1, 2);
  195. table.Attach (ipEntry, 1, 2, 2, 3);
  196. table.Attach (portEntry, 1, 2, 3, 4);
  197. table.Attach (consolePortEntry, 1, 2, 4, 5);
  198. argsEntry.WidthRequest = 500;
  199. commandEntry.PathChanged += delegate {
  200. try {
  201. //check it parses
  202. MonoDevelop.Core.StringParserService.Parse (commandEntry.Path);
  203. command = commandEntry.Path;
  204. } catch {
  205. command = null;
  206. }
  207. CheckValid ();
  208. };
  209. argsEntry.Changed += delegate {
  210. try {
  211. //check it parses
  212. MonoDevelop.Core.StringParserService.Parse (argsEntry.Text);
  213. args = argsEntry.Text;
  214. } catch {
  215. args = null;
  216. }
  217. CheckValid ();
  218. };
  219. ipEntry.Changed += delegate {
  220. if (string.IsNullOrEmpty (ipEntry.Text)) {
  221. ip = IPAddress.Loopback;
  222. } else if (!IPAddress.TryParse (ipEntry.Text, out ip)) {
  223. ip = null;
  224. }
  225. CheckValid ();
  226. };
  227. portEntry.Changed += delegate {
  228. port = ParsePort (portEntry.Text);
  229. CheckValid ();
  230. };
  231. consolePortEntry.Changed += delegate {
  232. consolePort = ParsePort (consolePortEntry.Text);
  233. CheckValid ();
  234. };
  235. command = properties.Get ("Command", "");
  236. args = properties.Get ("Arguments", "");
  237. if (!IPAddress.TryParse (properties.Get ("IpAddress", "127.0.0.1"), out ip) || ip == null)
  238. ip = IPAddress.Loopback;
  239. string portStr = properties.Get<string> ("Port");
  240. port = ParsePort (portStr) ?? 10000;
  241. string consolePortStr = properties.Get<string> ("ConsolePort");
  242. consolePort = ParsePort (consolePortStr);
  243. commandEntry.Path = command;
  244. argsEntry.Text = args;
  245. ipEntry.Text = ip.ToString ();
  246. portEntry.Text = PortToString (port) ?? "";
  247. consolePortEntry.Text = PortToString (consolePort) ?? "";
  248. CheckValid ();
  249. VBox.ShowAll ();
  250. }
  251. int? ParsePort (string port)
  252. {
  253. if (string.IsNullOrEmpty (port))
  254. return null;
  255. int value;
  256. if (!int.TryParse (port, out value))
  257. return -1;
  258. return value;
  259. }
  260. string PortToString (int? port)
  261. {
  262. return port.HasValue? port.Value.ToString () : null;
  263. }
  264. public new SoftDebuggerStartInfo Run ()
  265. {
  266. var response = (Gtk.ResponseType) MonoDevelop.Ide.MessageService.RunCustomDialog (this);
  267. if (response != listenResponse && response != connectResponse)
  268. return null;
  269. properties.Set ("Command", command);
  270. properties.Set ("Arguments", args);
  271. properties.Set ("IpAddress", ip.ToString ());
  272. properties.Set ("Port", PortToString (port));
  273. properties.Set ("ConsolePort", PortToString (consolePort));
  274. var name = string.IsNullOrEmpty (command)? "" : command;
  275. bool listen = response == listenResponse;
  276. var agentArgs = string.Format ("transport=dt_socket,address={0}:{1}{2}", ip, port, listen?"":",server=y");
  277. var customArgsTags = new string[,] {
  278. { "AgentArgs", agentArgs },
  279. { "IP", ip.ToString () },
  280. { "Port", PortToString (port) },
  281. { "Console", PortToString (consolePort) },
  282. };
  283. SoftDebuggerRemoteArgs startArgs;
  284. if (listen) {
  285. startArgs = (SoftDebuggerRemoteArgs) new SoftDebuggerListenArgs (name, ip, port.Value, consolePort ?? -1);
  286. } else {
  287. startArgs = new SoftDebuggerConnectArgs (name, ip, port.Value, consolePort ?? -1) {
  288. //infinite connection retries (user can cancel), 800ms between them
  289. TimeBetweenConnectionAttempts = 800,
  290. MaxConnectionAttempts = -1,
  291. };
  292. };
  293. var dsi = new SoftDebuggerStartInfo (startArgs) {
  294. Command = StringParserService.Parse (command),
  295. Arguments = StringParserService.Parse (args, customArgsTags),
  296. };
  297. //FIXME: GUI for env vars
  298. //dsi.EnvironmentVariables [kvp.Key] = kvp.Value;
  299. return dsi;
  300. }
  301. void CheckValid ()
  302. {
  303. bool valid = ip != null
  304. && (port.HasValue && port.Value >= 0)
  305. && (!consolePort.HasValue || consolePort >= 0);
  306. listenButton.Sensitive = valid;
  307. connectButton.Sensitive = valid && port > 0 && (!consolePort.HasValue || consolePort > 0);
  308. }
  309. }
  310. }
  311. }