Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Xamarin #34

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mono-debug.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
<Compile Include="src\DebugSession.cs" />
<Compile Include="src\MonoDebug.cs" />
<Compile Include="src\Protocol.cs" />
<Compile Include="src\XamarinConnectionProvider.cs" />
<Compile Include="src\XamarinDebuggerArgs.cs" />
<Compile Include="src\XamarinTcpConnection.cs" />
<Compile Include="src\XamarinVirtualMachineManager.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
88 changes: 85 additions & 3 deletions src/MonoDebugSession.cs
Original file line number Diff line number Diff line change
@@ -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.
*--------------------------------------------------------------------------------------------*/
Expand All @@ -9,7 +9,7 @@
using System.Linq;
using System.Net;
using Mono.Debugging.Client;

using System.Diagnostics;

namespace VSCodeDebug
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
44 changes: 44 additions & 0 deletions src/XamarinConnectionProvider.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
15 changes: 15 additions & 0 deletions src/XamarinDebuggerArgs.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
46 changes: 46 additions & 0 deletions src/XamarinTcpConnection.cs
Original file line number Diff line number Diff line change
@@ -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 ();
}
}
}
60 changes: 60 additions & 0 deletions src/XamarinVirtualMachineManager.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}