diff --git a/mono-debug.csproj b/mono-debug.csproj index 51393e2..7e22a94 100644 --- a/mono-debug.csproj +++ b/mono-debug.csproj @@ -66,6 +66,10 @@ + + + + diff --git a/package.json b/package.json index 80acada..25dc8f5 100644 --- a/package.json +++ b/package.json @@ -144,12 +144,16 @@ ], "configurationAttributes": { "launch": { - "required": ["program"], + "required": [], "properties": { "program": { "type": "string", "description": "%mono.launch.program.description%" }, + "packageName":{ + "type": "string", + "description": "%mono.launch.packageName.description%" + }, "args": { "type": "array", "description": "%mono.launch.args.description%", diff --git a/package.nls.json b/package.nls.json index 71436cd..901916f 100644 --- a/package.nls.json +++ b/package.nls.json @@ -17,6 +17,7 @@ "mono.attach.config.name": "Attach", "mono.launch.program.description": "Absolute path to the program.", + "mono.launch.packageName.description": "Android package name.", "mono.launch.args.description": "Command line arguments passed to the program.", "mono.launch.cwd.description": "Absolute path to the working directory of the program being debugged.", "mono.launch.runtimeExecutable.description": "Absolute path to the runtime executable to be used. Default is the runtime executable on the PATH.", diff --git a/src/MonoDebugSession.cs b/src/MonoDebugSession.cs index 72c94f9..ec7242e 100644 --- a/src/MonoDebugSession.cs +++ b/src/MonoDebugSession.cs @@ -1,4 +1,4 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ @@ -9,7 +9,7 @@ using System.Linq; using System.Net; using Mono.Debugging.Client; - +using System.Diagnostics; namespace VSCodeDebug { @@ -180,7 +180,19 @@ public override void Initialize(Response response, dynamic args) SendEvent(new InitializedEvent()); } - public override async void Launch(Response response, dynamic args) + public override void Launch(Response response, dynamic args) + { + if (args.packageName != null) + { + LaunchXamarinAndroid(response, args); + } + else + { + LaunchMono(response, args); + } + } + + public async void LaunchMono(Response response, dynamic args) { _attachMode = false; @@ -415,6 +427,76 @@ public override void Attach(Response response, dynamic args) SendResponse(response); } + public void LaunchXamarinAndroid(Response response, dynamic args) + { + _attachMode = true; + + SetExceptionBreakpoints(args.__exceptionOptions); + + var packageName = getString(args, "packageName", null); + if (packageName == null) { + SendErrorResponse(response, 3008, "Property 'packageName' is missing."); + return; + } + + var forwardOutput = RunAdbForResult("forward tcp:0 tcp:10000"); + var port = int.Parse(forwardOutput); + RunAdbForResult("shell setprop debug.mono.connect port=10000,timeout=2000000000"); + RunAdbForResult($"shell am force-stop {packageName}"); + RunAdbForResult($"shell monkey -p {packageName} -c android.intent.category.LAUNCHER 1"); + System.Threading.Thread.Sleep(500); + + lock (_lock) { + + _debuggeeKilled = false; + var console = RunAdb("shell logcat mono-stdout:D *:S"); + var args0 = new XamarinDebuggerArgs(port, console) { + MaxConnectionAttempts = MAX_CONNECTION_ATTEMPTS, + TimeBetweenConnectionAttempts = CONNECTION_ATTEMPT_INTERVAL + }; + + _session.Run(new Mono.Debugging.Soft.SoftDebuggerStartInfo(args0), _debuggerSessionOptions); + + _debuggeeExecuting = true; + } + + SendResponse(response); + } + + private string RunAdbForResult(string args) + { + var adbProcessInfo = new ProcessStartInfo + { + FileName = "adb", + Arguments = args, + RedirectStandardOutput = true, + UseShellExecute = false + }; + var adbProcess = Process.Start(adbProcessInfo); + var result = adbProcess.StandardOutput.ReadToEnd(); + adbProcess.WaitForExit(); + return result; + } + + private StreamReader RunAdb(string args) + { + var adbProcessInfo = new ProcessStartInfo + { + FileName = "adb", + Arguments = args, + RedirectStandardOutput = true, + UseShellExecute = false + }; + var adbProcess = Process.Start(adbProcessInfo); + adbProcess.ErrorDataReceived += (o, e) => { + Console.WriteLine(e.Data); + }; + adbProcess.OutputDataReceived += (o, e) => { + Console.WriteLine(e.Data); + }; + return adbProcess.StandardOutput; + } + public override void Disconnect(Response response, dynamic args) { if (_attachMode) { diff --git a/src/XamarinConnectionProvider.cs b/src/XamarinConnectionProvider.cs new file mode 100644 index 0000000..6e7d197 --- /dev/null +++ b/src/XamarinConnectionProvider.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using Mono.Debugger.Soft; +using Mono.Debugging.Client; +using Mono.Debugging.Soft; + + +namespace VSCodeDebug +{ + public class XamarinConnectionProvider : ISoftDebuggerConnectionProvider + { + private readonly IPEndPoint _endPoint; + private readonly StreamReader _console; + + public XamarinConnectionProvider(int port, StreamReader console = null) + { + _endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port); + _console = console; + } + + public IAsyncResult BeginConnect(DebuggerStartInfo dsi, AsyncCallback callback) + { + return XamarinVirtualMachineManager.BeginConnect(_endPoint, _console, callback); + } + + public void CancelConnect(IAsyncResult result) + { + XamarinVirtualMachineManager.CancelConnection(result); + } + + public void EndConnect(IAsyncResult result, out VirtualMachine vm, out string appName) + { + vm = XamarinVirtualMachineManager.EndConnect(result); + appName = null; + } + + public bool ShouldRetryConnection(Exception ex) + { + return false; + } + } +} \ No newline at end of file diff --git a/src/XamarinDebuggerArgs.cs b/src/XamarinDebuggerArgs.cs new file mode 100644 index 0000000..db6cfb8 --- /dev/null +++ b/src/XamarinDebuggerArgs.cs @@ -0,0 +1,15 @@ +using Mono.Debugging.Soft; +using System.IO; + +namespace VSCodeDebug +{ + public class XamarinDebuggerArgs : SoftDebuggerStartArgs + { + public override ISoftDebuggerConnectionProvider ConnectionProvider { get; } + + public XamarinDebuggerArgs(int port, StreamReader deviceConsole = null) + { + ConnectionProvider = new XamarinConnectionProvider(port, deviceConsole); + } + } +} \ No newline at end of file diff --git a/src/XamarinTcpConnection.cs b/src/XamarinTcpConnection.cs new file mode 100644 index 0000000..288884c --- /dev/null +++ b/src/XamarinTcpConnection.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Net; +using System.Net.Sockets; +using Mono.Debugger.Soft; + +namespace VSCodeDebug +{ + class XamarinTcpConnection : Connection + { + Socket socket; + + internal XamarinTcpConnection (Socket socket, TextWriter logWriter) + : base (logWriter) + { + this.socket = socket; + //socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.NoDelay, 1); + } + + internal EndPoint EndPoint { + get { + return socket.RemoteEndPoint; + } + } + + protected override int TransportSend (byte[] buf, int buf_offset, int len) + { + return socket.Send (buf, buf_offset, len, SocketFlags.None); + } + + protected override int TransportReceive (byte[] buf, int buf_offset, int len) + { + return socket.Receive (buf, buf_offset, len, SocketFlags.None); + } + + protected override void TransportSetTimeouts (int send_timeout, int receive_timeout) + { + socket.SendTimeout = send_timeout; + socket.ReceiveTimeout = receive_timeout; + } + + protected override void TransportClose () + { + socket.Close (); + } + } +} \ No newline at end of file diff --git a/src/XamarinVirtualMachineManager.cs b/src/XamarinVirtualMachineManager.cs new file mode 100644 index 0000000..7010aec --- /dev/null +++ b/src/XamarinVirtualMachineManager.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.Remoting.Messaging; +using System.Text; +using Mono.Debugger.Soft; + +namespace VSCodeDebug +{ + + public static class XamarinVirtualMachineManager + { + private const string START_DEBUGGER_COMMAND = "start debugger: sdb"; + private const string CONNECT_STDOUT_COMMAND = "connect stdout"; + + private delegate VirtualMachine ConnectWithConsoleOutputCallback (Socket dbg_sock, IPEndPoint dbg_ep, StreamReader console, TextWriter logWriter); + + private static VirtualMachine ConnectWithConsoleOutput(Socket dbg_sock, IPEndPoint dbg_ep, StreamReader console, TextWriter logWriter = null) { + dbg_sock.Connect (dbg_ep); + SendCommand(dbg_sock, START_DEBUGGER_COMMAND); + Connection transport = new XamarinTcpConnection (dbg_sock, logWriter); + return VirtualMachineManager.Connect (transport, console, null); + } + + private static void SendCommand(Socket socket, string command) + { + byte[] commandBin = System.Text.Encoding.ASCII.GetBytes(command); + byte[] commandLenght = new byte[] { (byte)commandBin.Length }; + socket.Send(commandLenght, 0, commandLenght.Length, SocketFlags.None); + socket.Send(commandBin, 0, commandBin.Length, SocketFlags.None); + } + + public static IAsyncResult BeginConnect(IPEndPoint debugEndPoint, StreamReader console, AsyncCallback callback, TextWriter logWriter = null) { + Socket debugSocket = null; + debugSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + ConnectWithConsoleOutputCallback c = new ConnectWithConsoleOutputCallback (ConnectWithConsoleOutput); + return c.BeginInvoke (debugSocket, debugEndPoint, console, logWriter, callback, debugSocket); + } + + public static VirtualMachine EndConnect(IAsyncResult asyncResult) { + if (asyncResult == null) + throw new ArgumentNullException ("asyncResult"); + + if (!asyncResult.IsCompleted) + asyncResult.AsyncWaitHandle.WaitOne (); + + AsyncResult result = (AsyncResult) asyncResult; + ConnectWithConsoleOutputCallback cb = (ConnectWithConsoleOutputCallback) result.AsyncDelegate; + return cb.EndInvoke(asyncResult); + } + + public static void CancelConnection(IAsyncResult asyncResult) + { + ((IDisposable) asyncResult.AsyncState).Dispose(); + } + } +}