--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+ <PropertyGroup>\r
+ <OutputType>Library</OutputType>\r
+ <TargetFramework>netcoreapp2.1</TargetFramework>\r
+ </PropertyGroup>\r
+\r
+</Project>\r
--- /dev/null
+using System.IO;
+using System.Diagnostics;
+
+namespace LocalDebugger
+{
+ public class LocalDebuggerProcess
+ {
+ public LocalDebuggerProcess(string debuggerPath, string debuggerArg)
+ {
+ DebuggerProcess = new Process();
+ DebuggerProcess.StartInfo.FileName = debuggerPath;
+ DebuggerProcess.StartInfo.Arguments = debuggerArg;
+ DebuggerProcess.StartInfo.UseShellExecute = false;
+ DebuggerProcess.StartInfo.RedirectStandardInput = true;
+ DebuggerProcess.StartInfo.RedirectStandardOutput = true;
+ Input = null;
+ Output = null;
+ }
+
+ public void Start()
+ {
+ DebuggerProcess.Start();
+ Input = DebuggerProcess.StandardInput;
+ Output = DebuggerProcess.StandardOutput;
+ }
+
+ public void Close()
+ {
+ DebuggerProcess.Kill();
+ DebuggerProcess.WaitForExit();
+ DebuggerProcess.Dispose();
+ }
+
+ public StreamWriter Input;
+ public StreamReader Output;
+ public Process DebuggerProcess;
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ </PropertyGroup>
+
+</Project>
--- /dev/null
+using System;
+using System.IO;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.MI;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+
+namespace NetcoreDbgTest.Script
+{
+ // Context includes methods and constants which
+ // will be move to debugger API
+ class Context
+ {
+ public static void Prepare()
+ {
+ Assert.Equal(MIResultClass.Done,
+ MIDebugger.Request("-file-exec-and-symbols "
+ + DebuggeeInfo.CorerunPath).Class);
+
+ Assert.Equal(MIResultClass.Done,
+ MIDebugger.Request("-exec-arguments "
+ + DebuggeeInfo.TargetAssemblyPath).Class);
+
+ Assert.Equal(MIResultClass.Running, MIDebugger.Request("-exec-run").Class);
+ }
+
+ public static bool IsStoppedEvent(MIOutOfBandRecord record)
+ {
+ if (record.Type != MIOutOfBandRecordType.Async) {
+ return false;
+ }
+
+ var asyncRecord = (MIAsyncRecord)record;
+
+ if (asyncRecord.Class != MIAsyncRecordClass.Exec ||
+ asyncRecord.Output.Class != MIAsyncOutputClass.Stopped) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void WasEntryPointHit()
+ {
+ // TODO: Implement API for comfortable searching
+ // of out-of-band records
+ var records = MIDebugger.Receive();
+
+ foreach (MIOutOfBandRecord record in records) {
+ if (!IsStoppedEvent(record)) {
+ continue;
+ }
+
+ var output = ((MIAsyncRecord)record).Output;
+
+ // Currently let's believe that all *stopped events have
+ // a reason of stopping
+ var reason = (MIConst)output["reason"];
+
+ if (reason.CString != "entry-point-hit") {
+ continue;
+ }
+
+ var frame = (MITuple)(output["frame"]);
+ var func = (MIConst)(frame["func"]);
+ if (func.CString == DebuggeeInfo.TestName + ".Program.Main()") {
+ return;
+ }
+ }
+
+ throw new NetcoreDbgTestCore.ResultNotSuccessException();
+ }
+
+ public static void WasBreakpointHit(Breakpoint breakpoint)
+ {
+ var records = MIDebugger.Receive();
+ var bp = (LineBreakpoint)breakpoint;
+
+ foreach (MIOutOfBandRecord record in records) {
+ if (!IsStoppedEvent(record)) {
+ continue;
+ }
+
+ var output = ((MIAsyncRecord)record).Output;
+ var reason = (MIConst)output["reason"];
+
+ if (reason.CString != "breakpoint-hit") {
+ continue;
+ }
+
+ var frame = (MITuple)(output["frame"]);
+ var fileName = (MIConst)(frame["file"]);
+ var numLine = (MIConst)(frame["line"]);
+
+ if (fileName.CString == bp.FileName &&
+ numLine.CString == bp.NumLine.ToString()) {
+ return;
+ }
+ }
+
+ throw new NetcoreDbgTestCore.ResultNotSuccessException();
+ }
+
+ public static void WasExit()
+ {
+ var records = MIDebugger.Receive();
+
+ foreach (MIOutOfBandRecord record in records) {
+ if (!IsStoppedEvent(record)) {
+ continue;
+ }
+
+ var output = ((MIAsyncRecord)record).Output;
+ var reason = (MIConst)output["reason"];
+
+ if (reason.CString != "exited") {
+ continue;
+ }
+
+ var exitCode = (MIConst)output["exit-code"];
+
+ if (exitCode.CString == "0") {
+ return;
+ } else {
+ throw new NetcoreDbgTestCore.ResultNotSuccessException();
+ }
+ }
+
+ throw new NetcoreDbgTestCore.ResultNotSuccessException();
+ }
+
+ public static void EnableBreakpoint(string bpName)
+ {
+ Breakpoint bp = DebuggeeInfo.Breakpoints[bpName];
+
+ Assert.Equal(BreakpointType.Line, bp.Type);
+
+ var lbp = (LineBreakpoint)bp;
+
+ Assert.Equal(MIResultClass.Done,
+ MIDebugger.Request("-break-insert -f "
+ + lbp.FileName + ":" + lbp.NumLine).Class);
+ }
+
+ public static void Continue()
+ {
+ Assert.Equal(MIResultClass.Running, MIDebugger.Request("-exec-continue").Class);
+ }
+
+ static MIDebugger MIDebugger = new MIDebugger();
+ }
+}
+
+namespace MIExampleTest
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // first checkpoint (initialization) must provide "init" as id
+ Label.Checkpoint("init", "bp_test", () => {
+ Context.Prepare();
+ Context.WasEntryPointHit();
+ Context.EnableBreakpoint("bp");
+ Context.EnableBreakpoint("bp2");
+ Context.Continue();
+ });
+
+ Console.WriteLine("A breakpoint \"bp\" is set on this line"); Label.Breakpoint("bp");
+
+ Label.Checkpoint("bp_test", "bp2_test", () => {
+ Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp"]);
+ Context.Continue();
+ });
+
+ MIExampleTest2.Program.testfunc();
+
+ // last checkpoint must provide "finish" as id or empty string ("") as next checkpoint id
+ Label.Checkpoint("finish", "", () => {
+ Context.WasExit();
+ });
+ }
+ }
+}
--- /dev/null
+using System;
+using System.IO;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.MI;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+
+namespace MIExampleTest2
+{
+ class Program
+ {
+ public static void testfunc()
+ {
+ Console.WriteLine("A breakpoint \"bp2\" is set on this line"); Label.Breakpoint("bp2");
+
+ Label.Checkpoint("bp2_test", "finish", () => {
+ Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp2"]);
+ Context.Continue();
+ });
+ }
+ }
+}
--- /dev/null
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+ public enum BreakpointType {
+ None,
+ Line
+ }
+
+ public class Breakpoint
+ {
+ public Breakpoint(string name, BreakpointType type = BreakpointType.None)
+ {
+ Name = name;
+ Type = type;
+ }
+
+ public string Name;
+ public BreakpointType Type;
+ }
+
+ public class LineBreakpoint : Breakpoint
+ {
+ public LineBreakpoint(string name,
+ string fileName,
+ int numLine,
+ ProtocolType protocol = ProtocolType.None)
+ :base(name, BreakpointType.Line)
+ {
+ FileName = fileName;
+ NumLine = numLine;
+ Protocol = protocol;
+ }
+
+ public string FileName;
+ public int NumLine;
+ public ProtocolType Protocol;
+ }
+}
--- /dev/null
+namespace NetcoreDbgTestCore
+{
+ public delegate void Checkpoint();
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+
+using NetcoreDbgTest;
+
+namespace NetcoreDbgTestCore
+{
+ public static class Debuggee
+ {
+ public static DebuggerClient DebuggerClient = null;
+ public static Dictionary<string, Breakpoint> Breakpoints = null;
+ public static string TestName = null;
+ public static string SourceFilesPath = null;
+ public static string TargetAssemblyPath = null;
+ public static string CorerunPath = null;
+
+ public static void Invoke(string id, string next_id, Checkpoint checkpoint)
+ {
+ checkpoint();
+ }
+
+ public static void Run(DebuggeeScript script,
+ DebuggerClient debugger,
+ NetcoreDbgTestCore.Environment env)
+ {
+ DebuggerClient = debugger;
+ Breakpoints = script.Breakpoints;
+ TestName = env.TestName;
+ SourceFilesPath = env.SourceFilesPath;
+ TargetAssemblyPath = env.TargetAssemblyPath;
+ CorerunPath = env.CorerunPath;
+
+ script.ExecuteCheckPoints();
+
+ DebuggerClient = null;
+ Breakpoints = null;
+ TestName = null;
+ SourceFilesPath = null;
+ TargetAssemblyPath = null;
+ CorerunPath = null;
+ }
+ }
+
+ public class Environment
+ {
+ public string TestName = null;
+ public string SourceFilesPath = null;
+ public string TargetAssemblyPath = null;
+ public string CorerunPath = "dotnet";
+ }
+}
--- /dev/null
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+ public static class DebuggeeInfo
+ {
+ public static System.Collections.Generic.Dictionary<string, Breakpoint> Breakpoints
+ {
+ get { return Debuggee.Breakpoints; }
+ }
+
+ public static string TestName
+ {
+ get { return Debuggee.TestName; }
+ }
+
+ public static string SourceFilesPath
+ {
+ get { return Debuggee.SourceFilesPath; }
+ }
+
+ public static string TargetAssemblyPath
+ {
+ get { return Debuggee.TargetAssemblyPath; }
+ }
+
+ public static string CorerunPath
+ {
+ get { return Debuggee.CorerunPath; }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Runtime;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+using NetcoreDbgTest;
+
+namespace NetcoreDbgTestCore
+{
+ public class ScriptNotBuiltException : Exception
+ {
+ public ScriptNotBuiltException(EmitResult result)
+ {
+ Result = result;
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ IEnumerable<Diagnostic> failures = Result.Diagnostics.Where(diagnostic =>
+ diagnostic.IsWarningAsError ||
+ diagnostic.Severity == DiagnosticSeverity.Error);
+
+ foreach (Diagnostic diagnostic in failures) {
+ sb.Append(String.Format("{0}: {1}\n", diagnostic.Id, diagnostic.GetMessage()));
+ }
+
+ return sb.ToString();
+ }
+
+ EmitResult Result;
+ }
+
+ public class DebuggeeScript
+ {
+ public DebuggeeScript(string pathToTestFiles, ProtocolType protocolType)
+ {
+ DebuggeeScriptDummyText =
+@"using System;
+using System.IO;
+using System.Diagnostics;
+using System.Collections.Generic;
+using NetcoreDbgTest;
+using NetcoreDbgTest.Script;
+using Xunit;
+using Newtonsoft.Json;
+
+using NetcoreDbgTest." + protocolType.ToString() + @";
+
+namespace NetcoreDbgTest.Script
+{
+}
+
+namespace NetcoreDbgTestCore
+{
+ public class GeneratedScript {
+ public static void ExecuteCheckPoints() {}
+ }
+}";
+
+ // we may have list of files separated by ';' symbol
+ string[] pathToFiles = pathToTestFiles.Split(';');
+ List<SyntaxTree> trees = new List<SyntaxTree>();
+
+ foreach (string pathToFile in pathToFiles) {
+ if (String.IsNullOrEmpty(pathToFile)) {
+ continue;
+ }
+ string testSource = File.ReadAllText(pathToFile);
+ SyntaxTree testTree = CSharpSyntaxTree.ParseText(testSource)
+ .WithFilePath(pathToFile);
+ trees.Add(testTree);
+ }
+
+ TestLabelsInfo = CollectTestLabelsInfo(trees, protocolType);
+ ScriptDeclarations = CollectScriptDeclarations(trees);
+ SyntaxTree = BuildTree();
+ Compilation compilation = CompileTree(SyntaxTree);
+ Assembly scriptAssembly = MakeAssembly(compilation);
+ generatedScriptClass = scriptAssembly.GetType("NetcoreDbgTestCore.GeneratedScript");
+ Breakpoints = TestLabelsInfo.Breakpoints;
+ }
+
+ public Dictionary<string, Breakpoint> Breakpoints;
+
+ public SyntaxTree SyntaxTree;
+
+ public void ExecuteCheckPoints()
+ {
+ MethodInfo minfo = generatedScriptClass.GetMethod("ExecuteCheckPoints");
+ try {
+ minfo.Invoke(null, null);
+ } catch (TargetInvocationException e) {
+ throw e.InnerException;
+ }
+ }
+
+ private TestLabelsInfo CollectTestLabelsInfo(List<SyntaxTree> trees, ProtocolType protocolType)
+ {
+ var visitor = new TestLabelsInfoCollector(protocolType);
+
+ foreach (SyntaxTree tree in trees) {
+ visitor.Visit(tree.GetRoot());
+ }
+
+ return visitor.TestLabelsInfo;
+ }
+
+ private SyntaxList<MemberDeclarationSyntax> CollectScriptDeclarations(List<SyntaxTree> trees)
+ {
+ var visitor = new ScriptDeclarationsCollector();
+
+ foreach (SyntaxTree tree in trees) {
+ visitor.Visit(tree.GetRoot());
+ }
+
+ return visitor.ScriptDeclarations;
+ }
+
+ private SyntaxTree BuildTree()
+ {
+ var tree = CSharpSyntaxTree.ParseText(DebuggeeScriptDummyText);
+
+ CSharpSyntaxRewriter visitor;
+
+ visitor = new GeneratedScriptInvokesBuilder(TestLabelsInfo);
+ tree = ((GeneratedScriptInvokesBuilder) (visitor)).Visit(tree.GetRoot()).SyntaxTree;
+ visitor = new GeneratedScriptDeclarationsBuilder(ScriptDeclarations);
+ tree = ((GeneratedScriptDeclarationsBuilder) (visitor)).Visit(tree.GetRoot()).SyntaxTree;
+
+ return tree;
+ }
+
+ private MetadataReference[] GetMetadataReferences()
+ {
+ var systemPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
+ var libList = new List<MetadataReference>();
+
+ string[] libNames = {
+ "System.Private.CoreLib.dll",
+ "System.Runtime.dll",
+ "System.Collections.dll",
+ "System.IO.FileSystem.dll",
+ "System.Diagnostics.Process.dll",
+ "System.ComponentModel.Primitives.dll",
+ "netstandard.dll",
+ };
+
+ foreach (var name in libNames) {
+ libList.Add(MetadataReference.CreateFromFile(Path.Combine(systemPath, name)));
+ }
+
+ libList.Add(MetadataReference.CreateFromFile(typeof(Label).Assembly.Location));
+ libList.Add(MetadataReference.CreateFromFile(typeof(Xunit.Assert).Assembly.Location));
+ libList.Add(MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonConvert).Assembly.Location));
+
+ return libList.ToArray();
+ }
+
+ private Compilation CompileTree(SyntaxTree tree)
+ {
+ var compilation = CSharpCompilation.Create(
+ "DebuggeeScript",
+ new SyntaxTree[] {tree},
+ GetMetadataReferences(),
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ );
+
+ return compilation;
+ }
+
+ private Assembly MakeAssembly(Compilation compilation)
+ {
+ using (var ms = new MemoryStream())
+ {
+ EmitResult result = compilation.Emit(ms);
+
+ if (!result.Success) {
+ Console.WriteLine("*** Build Error log start ***");
+ foreach (var line in result.Diagnostics) {
+ Console.WriteLine(line);
+ }
+ Console.WriteLine("*** Build Error log end ***");
+ throw new ScriptNotBuiltException(result);
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+ return Assembly.Load(ms.ToArray());
+ }
+ }
+
+ private Type generatedScriptClass = null;
+ private TestLabelsInfo TestLabelsInfo = null;
+ private SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+ private static string DebuggeeScriptDummyText;
+ }
+
+ public class TestLabelsInfo
+ {
+ public TestLabelsInfo()
+ {
+ CheckPointInvokes = new Dictionary<string, Tuple<string, InvocationExpressionSyntax>>();
+ Breakpoints = new Dictionary<string, Breakpoint>();
+ }
+
+ public Dictionary<string, Tuple<string, InvocationExpressionSyntax>> CheckPointInvokes;
+ public Dictionary<string, Breakpoint> Breakpoints;
+ }
+
+ public class TestLabelsInfoCollector : CSharpSyntaxWalker
+ {
+ public TestLabelsInfoCollector(ProtocolType protocolType)
+ {
+ TestLabelsInfo = new TestLabelsInfo();
+ TypeClassBP = Type.GetType("NetcoreDbgTest." + protocolType.ToString() + "."
+ + protocolType.ToString() +"LineBreakpoint");
+ }
+
+ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
+ {
+ string fileName = Path.GetFileName(node.SyntaxTree.FilePath);
+ FileLinePositionSpan lineSpan = node.SyntaxTree.GetLineSpan(node.Span);
+ int numLine = lineSpan.StartLinePosition.Line + 1;
+
+ switch (node.Expression.ToString()) {
+ case "Label.Breakpoint":
+ string name = node.ArgumentList.Arguments[0].Expression.ToString();
+ // "name" -> name
+ name = name.Trim(new char[]{ '\"' });
+
+ LineBreakpoint lbp = (LineBreakpoint)Activator.CreateInstance(TypeClassBP,
+ name, fileName, numLine);
+
+ TestLabelsInfo.Breakpoints.Add(name, lbp);
+ break;
+ case "Label.Checkpoint":
+ string id = node.ArgumentList.Arguments[0].Expression.ToString();
+ string next_id = node.ArgumentList.Arguments[1].Expression.ToString();
+ // "id" -> id
+ id = id.Trim(new char[]{ '\"' });
+ next_id = next_id.Trim(new char[]{ '\"' });
+
+ TestLabelsInfo.CheckPointInvokes.Add(
+ id, new Tuple<string, InvocationExpressionSyntax>(next_id, node));
+ break;
+ }
+ }
+
+ public TestLabelsInfo TestLabelsInfo;
+ private Type TypeClassBP;
+ }
+
+ public class GeneratedScriptInvokesBuilder : CSharpSyntaxRewriter
+ {
+ public GeneratedScriptInvokesBuilder(TestLabelsInfo testLabels)
+ {
+ TestLabelsInfo = testLabels;
+ }
+
+ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
+ {
+ var debuggeeEnterMember =
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Debuggee"),
+ SyntaxFactory.IdentifierName("Invoke")
+ );
+
+ List<InvocationExpressionSyntax> invokes = new List<InvocationExpressionSyntax>();
+
+ switch (node.Identifier.ToString()) {
+ case "ExecuteCheckPoints":
+ string key = "init";
+ Tuple<string, InvocationExpressionSyntax> Value;
+ do {
+ if (TestLabelsInfo.CheckPointInvokes.TryGetValue(key, out Value)) {
+ invokes.Add(Value.Item2);
+ if (key == "finish") {
+ key = null;
+ } else {
+ key = Value.Item1;
+ }
+ } else {
+ Console.Error.WriteLine("Error! Can't find \"" + key + "\" checkpoint");
+ throw new ResultNotSuccessException();
+ }
+ } while (!String.IsNullOrEmpty(key));
+ break;
+ default:
+ return node;
+ }
+
+ List<StatementSyntax> statements = new List<StatementSyntax>();
+
+ foreach (var invoke in invokes) {
+ var invokeEnter =
+ SyntaxFactory.InvocationExpression(debuggeeEnterMember)
+ .WithArgumentList(invoke.ArgumentList);
+
+ statements.Add(SyntaxFactory.ExpressionStatement(invokeEnter));
+ }
+
+ return node.WithBody(SyntaxFactory.Block(statements.ToArray()));
+ }
+
+ private TestLabelsInfo TestLabelsInfo;
+ }
+
+ public class ScriptDeclarationsCollector : CSharpSyntaxWalker
+ {
+ public ScriptDeclarationsCollector()
+ {
+ ScriptDeclarations = new SyntaxList<MemberDeclarationSyntax>(
+ new List<MemberDeclarationSyntax>()
+ );
+ }
+
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+ {
+ if (node.Name.ToString() == "NetcoreDbgTest.Script") {
+ ScriptDeclarations = ScriptDeclarations.AddRange(node.Members);
+ return;
+ }
+
+ foreach (SyntaxNode subNode in node.Members) {
+ this.Visit(subNode);
+ }
+ }
+
+ public SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+ }
+
+ public class GeneratedScriptDeclarationsBuilder : CSharpSyntaxRewriter
+ {
+ public GeneratedScriptDeclarationsBuilder(SyntaxList<MemberDeclarationSyntax> declarations)
+ {
+ ScriptDeclarations = declarations;
+ }
+
+ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+ {
+ if (node.Name.ToString() == "NetcoreDbgTest.Script") {
+ return node.AddMembers(ScriptDeclarations.ToArray());
+ }
+
+ return node;
+ }
+
+ private SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+ }
+}
--- /dev/null
+namespace NetcoreDbgTestCore
+{
+ public enum ProtocolType {
+ None,
+ MI,
+ VSCode,
+ }
+
+ public class DebuggerClient
+ {
+ public DebuggerClient(ProtocolType protocol)
+ {
+ Protocol = protocol;
+ }
+
+ // Protocol specific handshake gives a guarantee
+ // of a debugger ability to receive commands and
+ // response messages
+ public virtual bool DoHandshake(int timeout)
+ {
+ return false;
+ }
+
+ // Send command to debugger
+ public virtual bool Send(string cmd)
+ {
+ return false;
+ }
+
+ // Receive protocol specific response
+ public virtual string[] Receive(int timeout)
+ {
+ return null;
+ }
+
+ // Close session with debugger
+ public virtual void Close()
+ {
+ }
+
+ public ProtocolType Protocol;
+ }
+}
--- /dev/null
+namespace NetcoreDbgTestCore
+{
+ public class Exception : System.Exception
+ {
+ }
+
+ public class ResultNotSuccessException : Exception
+ {
+ }
+
+ public class DebuggerNotResponsesException : Exception
+ {
+ }
+}
--- /dev/null
+using System.Diagnostics;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+ static public class Label
+ {
+ [Conditional("TEST_LABEL")]
+ public static void Breakpoint(string name)
+ {
+ }
+
+ [Conditional("TEST_LABEL")]
+ public static void Checkpoint(string id, string next_id, Checkpoint checkpoint)
+ {
+ }
+ }
+}
--- /dev/null
+using System;
+
+namespace NetcoreDbgTest
+{
+ public static class Logger
+ {
+ public static void LogLine(string line)
+ {
+ Console.WriteLine(line);
+ }
+
+ public static void LogError(string error)
+ {
+ Console.Error.WriteLine(error);
+ }
+ }
+}
--- /dev/null
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+ namespace MI
+ {
+ class MILineBreakpoint : LineBreakpoint
+ {
+ public MILineBreakpoint(string name, string srcName, int lineNum)
+ : base(name, srcName, lineNum, ProtocolType.MI)
+ {
+ }
+
+ public override string ToString()
+ {
+ return System.String.Format("{0}:{1}", FileName, NumLine);
+ }
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.MI;
+
+namespace NetcoreDbgTest.MI
+{
+ public class MIDebugger
+ {
+ public MIResultRecord Request(string command, int timeout = -1)
+ {
+ MIResultRecord resultRecord = null;
+
+ Logger.LogLine("> " + command);
+
+ if (!Debuggee.DebuggerClient.Send(command)) {
+ throw new DebuggerNotResponsesException();
+ }
+
+ while (true) {
+ string[] response = Debuggee.DebuggerClient.Receive(timeout);
+
+ if (response == null) {
+ throw new DebuggerNotResponsesException();
+ }
+
+ foreach (string line in response) {
+ Logger.LogLine("< " + line);
+ }
+
+ MIOutput output = MIParser.ParseOutput(response);
+
+ if (output.ResultRecord != null) {
+ resultRecord = output.ResultRecord;
+ break;
+ }
+ }
+
+ return resultRecord;
+ }
+
+ public MIOutOfBandRecord[] Receive(int timeout = -1)
+ {
+ string[] response = Debuggee.DebuggerClient.Receive(timeout);
+
+ if (response == null) {
+ throw new DebuggerNotResponsesException();
+ }
+
+ foreach (string line in response) {
+ Logger.LogLine("< " + line);
+ }
+
+ MIOutput output = MIParser.ParseOutput(response);
+
+ if (output.ResultRecord != null) {
+ // this output must hasn't result record
+ throw new MIParserException();
+ }
+
+ OutOfBandRecords.AddRange(output.OutOfBandRecords);
+
+ return OutOfBandRecords.ToArray();
+ }
+
+ List<MIOutOfBandRecord> OutOfBandRecords = new List<MIOutOfBandRecord>();
+ MIParser MIParser = new MIParser();
+ }
+}
--- /dev/null
+using System.IO;
+using System.Collections.Generic;
+using System.Threading;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTestCore.MI
+{
+ public class MILocalDebuggerClient : DebuggerClient
+ {
+ public MILocalDebuggerClient(StreamWriter input, StreamReader output)
+ : base(ProtocolType.MI)
+ {
+ DebuggerInput = input;
+ DebuggerOutput = output;
+ GetInput = new AutoResetEvent(false);
+ GotInput = new AutoResetEvent(false);
+ InputThread = new Thread(ReaderThread);
+ InputThread.IsBackground = true;
+ InputThread.Start();
+ }
+
+ public override bool DoHandshake(int timeout)
+ {
+ string[] output = ReceiveOutputLines(timeout);
+
+ return output != null && output.Length == 1;
+ }
+
+ public override bool Send(string command)
+ {
+ DebuggerInput.WriteLine(command);
+
+ return true;
+ }
+
+ public override string[] Receive(int timeout)
+ {
+ return ReceiveOutputLines(timeout);
+ }
+
+ public override void Close()
+ {
+ DebuggerInput.Close();
+ DebuggerOutput.Close();
+ }
+
+ private void ReaderThread() {
+ while (true) {
+ GetInput.WaitOne();
+ InputString = DebuggerOutput.ReadLine();
+ GotInput.Set();
+ }
+ }
+
+ string[] ReceiveOutputLines(int timeout)
+ {
+ var output = new List<string>();
+
+ while (true) {
+ GetInput.Set();
+ bool success = GotInput.WaitOne(timeout);
+ if (!success)
+ throw new DebuggerNotResponsesException();
+
+ if (InputString == null) {
+ return null;
+ }
+
+ output.Add(InputString);
+
+ if (InputString == "(gdb)") {
+ break;
+ }
+ }
+
+ return output.ToArray();
+ }
+
+ StreamWriter DebuggerInput;
+ StreamReader DebuggerOutput;
+ private Thread InputThread;
+ private AutoResetEvent GetInput, GotInput;
+ private string InputString;
+ }
+}
--- /dev/null
+using System;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.MI
+{
+ public enum MIValueType
+ {
+ Const,
+ Tuple,
+ List,
+ }
+
+ public enum MIListElementType
+ {
+ Value,
+ Result,
+ }
+
+ public enum MIOutOfBandRecordType
+ {
+ Async,
+ Stream,
+ }
+
+ public class MIOutput
+ {
+ public MIOutput(MIOutOfBandRecord[] outOfBandRecords,
+ MIResultRecord resultRecord)
+ {
+ OutOfBandRecords = outOfBandRecords;
+ ResultRecord = resultRecord;
+ }
+
+ public MIOutOfBandRecord[] OutOfBandRecords;
+ public MIResultRecord ResultRecord;
+ }
+
+ public class MIResultRecord
+ {
+ public MIResultRecord(MIToken token,
+ MIResultClass resultClass,
+ MIResult[] results)
+ {
+ Token = token;
+ Class = resultClass;
+ Results = new Dictionary<string, MIValue>();
+
+ if (results == null) {
+ return;
+ }
+
+ foreach (MIResult result in results) {
+ Results.Add(result.Variable, result.Value);
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder ();
+
+ if (Token != null) {
+ sb.Append(Token.ToString());
+ }
+
+ sb.Append("^");
+
+ sb.Append(Class.ToString());
+
+ foreach (KeyValuePair<string, MIValue>pair in Results) {
+ sb.Append("," + pair.Value.ToString());
+ }
+
+ return sb.ToString();
+ }
+
+ public MIValue this[string variable]
+ {
+ get { return Results[variable]; }
+ }
+
+ public MIToken Token = null;
+ public MIResultClass Class = MIResultClass.Done;
+ private Dictionary<string, MIValue> Results;
+ }
+
+ public class MIOutOfBandRecord
+ {
+ public MIOutOfBandRecord(MIOutOfBandRecordType type)
+ {
+ Type = type;
+ }
+
+ public override string ToString()
+ {
+ return null;
+ }
+
+ public MIOutOfBandRecordType Type;
+ }
+
+ public class MIToken
+ {
+ public MIToken(ulong number)
+ {
+ Number = number;
+ }
+
+ public override string ToString()
+ {
+ return Number.ToString();
+ }
+
+ public ulong Number;
+ }
+
+ public class MIListElement
+ {
+ public MIListElement(MIListElementType type)
+ {
+ ElementType = type;
+ }
+
+ public override string ToString()
+ {
+ return null;
+ }
+
+ public MIListElementType ElementType;
+ }
+
+ public class MIValue : MIListElement
+ {
+ public MIValue(MIValueType type)
+ : base(MIListElementType.Value)
+ {
+ }
+
+ public override string ToString()
+ {
+ return null;
+ }
+
+ public MIListElementType Type;
+ }
+
+ public class MITuple : MIValue, IEnumerable
+ {
+ public MITuple() : base(MIValueType.Tuple)
+ {
+ Results = new Dictionary<string, MIValue>();
+ }
+
+ public MITuple(MIResult[] results) : base(MIValueType.Tuple)
+ {
+ Results = new Dictionary<string, MIValue>();
+
+ if (results == null) {
+ return;
+ }
+
+ foreach (MIResult result in results) {
+ Results.Add(result.Variable, result.Value);
+ }
+ }
+
+ public override string ToString()
+ {
+ bool firstElement = true;
+ var sb = new StringBuilder("{");
+
+ foreach (KeyValuePair<string, MIValue> pair in Results)
+ {
+ if (firstElement) {
+ firstElement = false;
+ } else {
+ sb.Append(",");
+ }
+
+ sb.Append(pair.Key + "=" + pair.Value.ToString());
+ }
+
+ sb.Append("}");
+
+ return sb.ToString();
+ }
+
+ public void Add(string variable, MIValue val)
+ {
+ Results.Add(variable, val);
+ }
+
+ public void Add(string variable, string val)
+ {
+ Results.Add(variable, new MIConst(val));
+ }
+
+ public MIValue this[string Variable]
+ {
+ get {
+ return Results[Variable];
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return Results.GetEnumerator();
+ }
+
+ private Dictionary<string, MIValue>Results;
+ }
+
+ public class MIList : MIValue, IEnumerable
+ {
+ public MIList() : base(MIValueType.List)
+ {
+ Elements = new List<MIListElement>();
+ }
+
+ public MIList(List<MIListElement> elements, MIListElementType elementType)
+ : base(MIValueType.List)
+ {
+ ElementsType = elementType;
+ Elements = elements;
+ }
+
+ public MIList(MIValue[] values) : base(MIValueType.List)
+ {
+ ElementsType = MIListElementType.Value;
+
+ Elements = new List<MIListElement>(values);
+ }
+
+ public MIListElement this[int index]
+ {
+ get {
+ return Elements[index];
+ }
+ }
+
+ public void Add(string cstring)
+ {
+ if (Elements.Count == 0) {
+ ElementsType = MIListElementType.Value;
+ }
+
+ Elements.Add(new MIConst(cstring));
+ }
+
+ public void Add(MIListElement element)
+ {
+ if (Elements.Count == 0) {
+ ElementsType = element.ElementType;
+ }
+
+ if (ElementsType != element.ElementType) {
+ throw new Exception();
+ }
+
+ Elements.Add(element);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return Elements.GetEnumerator();
+ }
+
+ public override string ToString()
+ {
+ bool firstElement = true;
+ var sb = new StringBuilder("[");
+
+ foreach (MIListElement element in Elements) {
+ if (firstElement) {
+ firstElement = false;
+ } else {
+ sb.Append(",");
+ }
+
+ sb.Append(element.ToString());
+ }
+
+ sb.Append("]");
+
+ return sb.ToString();
+ }
+
+ public MIListElement[] ToArray()
+ {
+ return Elements.ToArray();
+ }
+
+ public int Count
+ {
+ get { return Elements.Count; }
+ }
+
+ MIListElementType ElementsType;
+ List<MIListElement> Elements;
+ }
+
+ public class MIConst : MIValue
+ {
+ public MIConst(string cstring) : base(MIValueType.Const)
+ {
+ CString = cstring;
+ }
+
+ public override string ToString()
+ {
+ return "\"" + CString + "\"";
+ }
+
+ public string CString;
+
+ // return c-string without escape sequences
+ // https://en.wikipedia.org/wiki/Escape_sequences_in_C
+ // throw exception for invalid c-string
+ public string String
+ {
+ get {
+ var sb = new StringBuilder();
+ try {
+ for (int i = 0; i < CString.Length; ) {
+ if (CString[i] == '\\') {
+ char c;
+ int hex;
+ switch (CString[i + 1]) {
+ case 'a':
+ c = '\a'; i += 2; break;
+ case 'b':
+ c = '\b'; i += 2; break;
+ case 'f':
+ c = '\f'; i += 2; break;
+ case 'n':
+ c = '\n'; i += 2; break;
+ case 'r':
+ c = '\r'; i += 2; break;
+ case 't':
+ c = '\t'; i += 2; break;
+ case 'v':
+ c = '\v'; i += 2; break;
+ case '\\':
+ c = '\\'; i += 2; break;
+ case '\'':
+ c = '\''; i += 2; break;
+ case '\"':
+ c = '\"'; i += 2; break;
+ case '?':
+ c = '\u003f'; i += 2; break;
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
+ int num2 = CString[i + 2] - '0';
+ int num1 = CString[i + 3] - '0';
+ int num0 = CString[i + 4] - '0';
+ c = (char)(num2 * 64 + num1 * 8 + num0);
+ i += 5;
+ break;
+ case 'e':
+ c = '\u001b'; i += 2; break;
+ case 'U':
+ hex = Int32.Parse(CString.Substring(i + 2, i + 10),
+ System.Globalization.NumberStyles.HexNumber);
+ c = (char)hex;
+ i += 11;
+ break;
+ case 'u':
+ hex = Int32.Parse(CString.Substring(i + 2, i + 6),
+ System.Globalization.NumberStyles.HexNumber);
+ c = (char)hex;
+ i += 7;
+ break;
+ default:
+ throw new FormatException();
+ }
+ sb.Append(c);
+ } else {
+ sb.Append(CString[i]);
+ i++;
+ }
+ }
+ }
+ catch {
+ throw new FormatException("Invalid c-string");
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ public int Int
+ {
+ get { return Int32.Parse(CString); }
+ }
+ }
+
+ public class MIResult : MIListElement
+ {
+ public MIResult(string variable, MIValue val) : base(MIListElementType.Result)
+ {
+ Variable = variable;
+ Value = val;
+ }
+
+ public MIResult(string variable, string cstring) : base(MIListElementType.Result)
+ {
+ Variable = variable;
+ Value = new MIConst(cstring);
+ }
+
+ public override string ToString()
+ {
+ return Variable + "=" + Value.ToString();
+ }
+
+ public string Variable;
+ public MIValue Value;
+ }
+
+ public class MIAsyncRecord : MIOutOfBandRecord
+ {
+ public MIAsyncRecord(MIToken token, MIAsyncRecordClass cl, MIAsyncOutput output)
+ : base(MIOutOfBandRecordType.Async)
+ {
+ Token = token;
+ Class = cl;
+ Output = output;
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ if (Token != null) {
+ sb.Append(Token.ToString());
+ }
+
+ sb.Append(Class.ToString() + Output.ToString());
+
+ return sb.ToString();
+ }
+
+ public MIToken Token;
+ public MIAsyncRecordClass Class;
+ public MIAsyncOutput Output;
+ }
+
+ public class MIStreamRecord : MIOutOfBandRecord
+ {
+ public MIStreamRecord(MIStreamRecordClass cl, MIConst constant)
+ : base(MIOutOfBandRecordType.Stream)
+ {
+ Class = cl;
+ Const = constant;
+ }
+
+ public override string ToString()
+ {
+ return Class.ToString() + Const.ToString();
+ }
+
+ public MIStreamRecordClass Class;
+ public MIConst Const;
+ }
+
+ public class MIAsyncOutput
+ {
+ public MIAsyncOutput(MIAsyncOutputClass cl, MIResult[] results)
+ {
+ Class = cl;
+ Results = new Dictionary<string, MIValue>();
+
+ if (results == null) {
+ return;
+ }
+
+ foreach(MIResult result in results) {
+ Results.Add(result.Variable, result.Value);
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder(Class.ToString());
+
+ foreach (KeyValuePair<string, MIValue> result in Results) {
+ sb.Append("," + result.Value.ToString());
+ }
+
+ return sb.ToString();
+ }
+
+ public MIValue this[string variable]
+ {
+ get { return Results[variable]; }
+ }
+
+ public MIAsyncOutputClass Class;
+ private Dictionary<string, MIValue> Results;
+ }
+
+ public class MIResultClass
+ {
+ public static MIResultClass Done =
+ new MIResultClass("done");
+
+ public static MIResultClass Running =
+ new MIResultClass("running");
+
+ public static MIResultClass Connected =
+ new MIResultClass("connected");
+
+ public static MIResultClass Error =
+ new MIResultClass("error");
+
+ public static MIResultClass Exit =
+ new MIResultClass("exit");
+
+ public override string ToString()
+ {
+ return Representation;
+ }
+
+ private MIResultClass(string reprsentation)
+ {
+ Representation = reprsentation;
+ }
+
+ private string Representation;
+ }
+
+ public class MIAsyncOutputClass
+ {
+ public static MIAsyncOutputClass Stopped =
+ new MIAsyncOutputClass("stopped");
+
+ public static MIAsyncOutputClass Others(string representation)
+ {
+ return new MIAsyncOutputClass(representation);
+ }
+
+ public override string ToString()
+ {
+ return Represenation;
+ }
+
+ private MIAsyncOutputClass(string representation)
+ {
+ Represenation = representation;
+ }
+
+ private string Represenation;
+ }
+
+ public class MIAsyncRecordClass
+ {
+ public static MIAsyncRecordClass Exec =
+ new MIAsyncRecordClass("*");
+
+ public static MIAsyncRecordClass Status =
+ new MIAsyncRecordClass("+");
+
+ public static MIAsyncRecordClass Notify =
+ new MIAsyncRecordClass("=");
+
+ public override string ToString()
+ {
+ return Represenation;
+ }
+
+ private MIAsyncRecordClass(string represenation)
+ {
+ Represenation = represenation;
+ }
+
+ string Represenation;
+ }
+
+ public class MIStreamRecordClass
+ {
+ public override string ToString()
+ {
+ return Represenation;
+ }
+
+ public static MIStreamRecordClass Console =
+ new MIStreamRecordClass("~");
+
+ public static MIStreamRecordClass Target =
+ new MIStreamRecordClass("@");
+
+ public static MIStreamRecordClass Log =
+ new MIStreamRecordClass("&");
+
+ private MIStreamRecordClass(string representation)
+ {
+ Represenation = representation;
+ }
+
+ string Represenation;
+ }
+}
--- /dev/null
+using System;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+using NetcoreDbgTest.MI;
+
+namespace NetcoreDbgTestCore.MI
+{
+ public class MIParserException : NetcoreDbgTestCore.Exception
+ {
+ }
+
+ public class MIParser
+ {
+ public MIOutput ParseOutput(string[] output)
+ {
+ var outOfBandRecordList = new List<MIOutOfBandRecord>();
+ MIResultRecord resultRecord = null;
+ int i = 0;
+
+ while (IsOutOfBandRecord(output[i])) {
+ outOfBandRecordList.Add(ParseOutOfBandRecord(output[i]));
+ i++;
+ }
+
+ if (IsResultRecord(output[i])) {
+ resultRecord = ParseResultRecord(output[i]);
+ i++;
+ }
+
+ if (!IsEnd(output[i])) {
+ throw new MIParserException();
+ }
+
+ return new MIOutput(outOfBandRecordList.ToArray(), resultRecord);
+ }
+
+ private MIResultRecord ParseResultRecord(string response)
+ {
+ int endIndex;
+ var token = ParseToken(response, 0, out endIndex);
+
+ if (response[endIndex] != '^') {
+ throw new MIParserException();
+ }
+
+ var resultClass = ParseResultClass(response, endIndex + 1, out endIndex);
+ var results = new List<MIResult>();
+
+ while (endIndex != response.Length) {
+ if (response[endIndex] != ',') {
+ throw new MIParserException();
+ }
+
+ results.Add(ParseResult(response, endIndex + 1, out endIndex));
+ }
+
+ return new MIResultRecord(token, resultClass, results.ToArray());
+ }
+
+ private MIOutOfBandRecord ParseOutOfBandRecord(string response)
+ {
+ MIOutOfBandRecord outOfBandRecord;
+ int endIndex;
+
+ if (IsStreamRecord(response)) {
+ outOfBandRecord = ParseStreamRecord(response, 0, out endIndex);
+ } else {
+ outOfBandRecord = ParseAsyncRecord(response, 0, out endIndex);
+ }
+
+ if (endIndex != response.Length) {
+ throw new MIParserException();
+ }
+
+ return outOfBandRecord;
+ }
+
+ private MIToken ParseToken(string response, int beginIndex, out int endIndex)
+ {
+ endIndex = beginIndex;
+
+ while (Char.IsDigit(response[endIndex])) {
+ endIndex++;
+ }
+
+ if (beginIndex == endIndex) {
+ return null;
+ }
+
+ return new MIToken(
+ Convert.ToUInt64(response.Substring(beginIndex, endIndex - beginIndex), 10)
+ );
+ }
+
+ private MIResultClass ParseResultClass(string response, int beginIndex, out int endIndex)
+ {
+ var resClasses = new MIResultClass[] {
+ MIResultClass.Done,
+ MIResultClass.Running,
+ MIResultClass.Connected,
+ MIResultClass.Error,
+ MIResultClass.Exit,
+ };
+
+ foreach (MIResultClass resClass in resClasses)
+ {
+ string strClass = resClass.ToString();
+ int len = Math.Min(response.Length - beginIndex, strClass.Length);
+
+ if (String.Compare(response, beginIndex, strClass, 0, len) == 0) {
+ endIndex = beginIndex + strClass.Length;
+ return resClass;
+ }
+ }
+
+ throw new MIParserException();
+ }
+
+ private MIResult ParseResult(string response, int beginIndex, out int endIndex)
+ {
+ endIndex = response.IndexOf('=', beginIndex);
+ string variable = response.Substring(beginIndex, endIndex - beginIndex);
+ MIValue miValue = ParseValue(response, endIndex + 1, out endIndex);
+
+ return new MIResult(variable, miValue);
+ }
+
+ private MIValue ParseValue(string response, int beginIndex, out int endIndex)
+ {
+ if (response[beginIndex] == '{') {
+ return ParseTuple(response, beginIndex, out endIndex);
+ } else if (response[beginIndex] == '[') {
+ return ParseList(response, beginIndex, out endIndex);
+ } else if (response[beginIndex] == '"') {
+ return ParseConst(response, beginIndex, out endIndex);
+ }
+
+ throw new MIParserException();
+ }
+
+ private MITuple ParseTuple(string response, int beginIndex, out int endIndex)
+ {
+ beginIndex++; // eat '{'
+
+ if (response[beginIndex] == '}') {
+ endIndex = beginIndex + 1;
+ return new MITuple(null);
+ }
+
+ var results = new List<MIResult>();
+ results.Add(ParseResult(response, beginIndex, out endIndex));
+
+ while (response[endIndex] == ',') {
+ results.Add(ParseResult(response, endIndex + 1, out endIndex));
+ }
+
+ if (response[endIndex] == '}') {
+ endIndex++;
+ return new MITuple(results.ToArray());
+ }
+
+ throw new MIParserException();
+ }
+
+ private MIListElement ParseListElement(MIListElementType type,
+ string response,
+ int beginIndex,
+ out int endIndex)
+ {
+ switch (type) {
+ case MIListElementType.Value:
+ return ParseValue(response, beginIndex, out endIndex);
+ case MIListElementType.Result:
+ return ParseResult(response, beginIndex, out endIndex);
+ }
+
+ throw new MIParserException();
+ }
+
+ private MIList ParseList(string response, int beginIndex, out int endIndex)
+ {
+ var elements = new List<MIListElement>();
+ MIListElementType type;
+
+ beginIndex++; // eat '['
+
+ if (response[beginIndex] == ']') {
+ endIndex = beginIndex + 1;
+ // Element type of empty list can be either
+ return new MIList(elements, MIListElementType.Value);
+ }
+
+ if (response[beginIndex] == '{' ||
+ response[beginIndex] == '[' ||
+ response[beginIndex] == '"'
+ ) {
+ type = MIListElementType.Value;
+ } else {
+ type = MIListElementType.Result;
+ }
+
+ elements.Add(ParseListElement(type, response, beginIndex, out endIndex));
+
+ while (response[endIndex] == ',') {
+ elements.Add(ParseListElement(type, response, endIndex + 1, out endIndex));
+ }
+
+ if (response[endIndex] == ']') {
+ endIndex++;
+ return new MIList(elements, type);
+ }
+
+ throw new MIParserException();
+ }
+
+ private MIConst ParseConst(string response, int beginIndex, out int endIndex)
+ {
+ for (endIndex = beginIndex + 1; endIndex < response.Length; endIndex++) {
+ if (response[endIndex] == '"' && response[endIndex - 1] != '\\') {
+ break;
+ }
+ }
+
+ var cstring = response.Substring(beginIndex + 1, endIndex - beginIndex - 1);
+
+ endIndex++;
+
+ return new MIConst(cstring);
+ }
+
+ private MIOutOfBandRecord ParseAsyncRecord(string response, int beginIndex, out int endIndex)
+ {
+ MIToken token = ParseToken(response, beginIndex, out endIndex);
+ MIAsyncRecordClass asyncRecordClass = ParseAsyncRecordClass(response, endIndex, out endIndex);
+ MIAsyncOutput asyncOutput = ParseAsyncOutput(response, endIndex, out endIndex);
+
+ return new MIAsyncRecord(token, asyncRecordClass, asyncOutput);
+ }
+
+ private MIAsyncRecordClass ParseAsyncRecordClass(string response, int beginIndex, out int endIndex)
+ {
+ endIndex = beginIndex + 1;
+
+ switch (response[beginIndex]) {
+ case '*': return MIAsyncRecordClass.Exec;
+ case '+': return MIAsyncRecordClass.Status;
+ case '=': return MIAsyncRecordClass.Notify;
+ }
+
+ throw new MIParserException();
+ }
+
+ private MIAsyncOutput ParseAsyncOutput(string response, int beginIndex, out int endIndex)
+ {
+ MIAsyncOutputClass asyncClass = ParseAsyncOutputClass(response, beginIndex, out endIndex);
+
+ List<MIResult>results = new List<MIResult>();
+
+ while (endIndex != response.Length) {
+ if (response[endIndex] != ',') {
+ break;
+ }
+
+ results.Add(ParseResult(response, endIndex + 1, out endIndex));
+ }
+
+ return new MIAsyncOutput(asyncClass, results.ToArray());
+ }
+
+ private MIAsyncOutputClass ParseAsyncOutputClass(string response, int beginIndex, out int endIndex)
+ {
+ endIndex = beginIndex;
+
+ while (endIndex < response.Length) {
+ if (response[endIndex] == ',') {
+ break;
+ }
+ endIndex++;
+ }
+
+ string strClass = response.Substring(beginIndex, endIndex - beginIndex);
+
+ if (strClass == "stopped") {
+ return MIAsyncOutputClass.Stopped;
+ }
+
+ return MIAsyncOutputClass.Others(strClass);
+ }
+
+ private MIStreamRecord ParseStreamRecord(string response, int beginIndex, out int endIndex)
+ {
+ MIStreamRecordClass streamRecordClass =
+ ParseStreamRecordClass(response, beginIndex, out endIndex);
+ MIConst constant = ParseConst(response, endIndex, out endIndex);
+
+ return new MIStreamRecord(streamRecordClass, constant);
+ }
+
+ private MIStreamRecordClass ParseStreamRecordClass(string response, int beginIndex, out int endIndex)
+ {
+ endIndex = beginIndex + 1;
+
+ switch (response[beginIndex]) {
+ case '~': return MIStreamRecordClass.Console;
+ case '@': return MIStreamRecordClass.Target;
+ case '&': return MIStreamRecordClass.Log;
+ }
+
+ throw new MIParserException();
+ }
+
+ private bool IsOutOfBandRecord(string response)
+ {
+ return IsStreamRecord(response) ||
+ IsAsyncRecord(response);
+ }
+
+ private bool IsStreamRecord(string response)
+ {
+ return response[0] == '~' ||
+ response[0] == '@' ||
+ response[0] == '&';
+ }
+
+ private bool IsAsyncRecord(string response)
+ {
+ int i = 0;
+ while (Char.IsDigit(response[i])) {
+ i++;
+ }
+
+ return response[i] == '*' ||
+ response[i] == '+' ||
+ response[i] == '=';
+ }
+
+ private bool IsResultRecord(string response)
+ {
+ int i = 0;
+ while (Char.IsDigit(response[i])) {
+ i++;
+ }
+
+ return response[i] == '^';
+ }
+
+ private bool IsEnd(string response)
+ {
+ return response == "(gdb)";
+ }
+ }
+}
--- /dev/null
+using System.Net.Sockets;
+using System.Text;
+
+namespace NetcoreDbgTestCore
+{
+ namespace MI
+ {
+ public class MITcpDebuggerClient : DebuggerClient
+ {
+ public MITcpDebuggerClient(string addr, int port) : base(ProtocolType.MI)
+ {
+ client = new TcpClient(addr, port);
+ stream = client.GetStream();
+ PendingOutput = "";
+ }
+
+ public override bool DoHandshake(int timeout)
+ {
+ string[] output = ReceiveOutputLines(timeout);
+
+ // output must consist of one line "(gdb)"
+ return output != null && output.Length == 1;
+ }
+
+ public override bool Send(string cmd)
+ {
+ SendCommandLine(cmd);
+
+ return true;
+ }
+
+ public override string[] Receive(int timeout)
+ {
+ return ReceiveOutputLines(timeout);
+ }
+
+ public override void Close()
+ {
+ stream.Close();
+ client.Close();
+ }
+
+ void SendCommandLine(string str)
+ {
+ byte[] bytes = Encoding.ASCII.GetBytes(str + "\n");
+ stream.Write(bytes, 0, bytes.Length);
+ }
+
+ string[] ReceiveOutputLines(int timeout)
+ {
+ int lenOfGdb = "(gdb)".Length;
+ var sb = new StringBuilder(PendingOutput);
+ int indexOfGdb = PendingOutput.IndexOf("(gdb)");
+
+ while (indexOfGdb == -1) {
+ string availableData = LoadAvailableData(timeout);
+
+ if (availableData == null) {
+ return null;
+ }
+
+ sb.Append(availableData);
+ PendingOutput = sb.ToString();
+ indexOfGdb = PendingOutput.IndexOf("(gdb)");
+ }
+
+ var packedOutputLines = PendingOutput.Substring(0, indexOfGdb + lenOfGdb);
+ PendingOutput = PendingOutput.Substring(indexOfGdb + lenOfGdb + 1);
+
+ return packedOutputLines.TrimEnd('\r', '\n').Split("\n");
+ }
+
+ string LoadAvailableData(int timeout)
+ {
+ byte[] recvBuffer = new byte[64];
+ StringBuilder sb = new StringBuilder();
+ int recvCount = 0;
+ string response;
+
+ stream.ReadTimeout = timeout;
+
+ try {
+ do {
+ int readCount = stream.Read(recvBuffer, 0, recvBuffer.Length);
+ response = Encoding.UTF8.GetString(recvBuffer, 0, readCount);
+ sb.Append(response);
+ recvCount += readCount;
+ } while (stream.DataAvailable);
+ }
+
+ catch {
+ }
+
+ stream.ReadTimeout = -1;
+
+ if (recvCount == 0) {
+ return null;
+ }
+
+ return sb.ToString();
+ }
+
+ TcpClient client;
+ NetworkStream stream;
+ string PendingOutput;
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Library</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis" Version="2.10.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
+ <PackageReference Include="Xunit" Version="2.4.1" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
+ </ItemGroup>
+</Project>
--- /dev/null
+namespace NetcoreDbgTest
+{
+ namespace Script
+ {
+ // Declarations defined by user in test script
+ }
+}
--- /dev/null
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+ namespace VSCode
+ {
+ class VSCodeLineBreakpoint : LineBreakpoint
+ {
+ public VSCodeLineBreakpoint(string name, string srcName, int lineNum)
+ : base(name, srcName, lineNum, ProtocolType.VSCode)
+ {
+ }
+
+ public override string ToString()
+ {
+ return System.String.Format("{0}:{1}", FileName, NumLine);
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.IO;
+using System.Collections.Generic;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.VSCode;
+
+using Newtonsoft.Json;
+
+namespace NetcoreDbgTest.VSCode
+{
+ public class VSCodeResult : Tuple<bool, string>
+ {
+ public VSCodeResult(bool Success, string ResponseStr)
+ :base(Success, ResponseStr)
+ {
+ }
+
+ public bool Success { get{ return this.Item1; } }
+ public string ResponseStr { get{ return this.Item2; } }
+ }
+
+ public class VSCodeDebugger
+ {
+ public bool isResponseContainProperty(string stringJSON, string testField, string testValue)
+ {
+ JsonTextReader reader = new JsonTextReader(new StringReader(stringJSON));
+ while (reader.Read()) {
+ if (reader.Value != null
+ && reader.TokenType.ToString() == "PropertyName"
+ && reader.Value.ToString() == testField
+ && reader.Read()
+ && reader.Value != null
+ && reader.Value.ToString() == testValue) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public object GetResponsePropertyValue(string stringJSON, string testField)
+ {
+ JsonTextReader reader = new JsonTextReader(new StringReader(stringJSON));
+ while (reader.Read()) {
+ if (reader.Value != null
+ && reader.TokenType.ToString() == "PropertyName"
+ && reader.Value.ToString() == testField
+ && reader.Read()) {
+ return reader.Value;
+ }
+ }
+
+ return null;
+ }
+ public VSCodeResult Request(Request command, int timeout = -1)
+ {
+ string stringJSON = JsonConvert.SerializeObject(command,
+ Formatting.None,
+ new JsonSerializerSettings {
+ NullValueHandling = NullValueHandling.Ignore});
+
+ Logger.LogLine("-> (C) " + stringJSON);
+ Int64 RequestSeq = (Int64)GetResponsePropertyValue(stringJSON, "seq");
+
+ if (!Debuggee.DebuggerClient.Send(stringJSON)) {
+ throw new DebuggerNotResponsesException();
+ }
+
+ while (true) {
+ string[] response = Debuggee.DebuggerClient.Receive(timeout);
+ if (response == null) {
+ throw new DebuggerNotResponsesException();
+ }
+ string line = response[0];
+
+ if (isResponseContainProperty(line, "type", "response")
+ && (Int64)GetResponsePropertyValue(line, "request_seq") == RequestSeq) {
+ Logger.LogLine("<- (R) " + line);
+ return new VSCodeResult((bool)GetResponsePropertyValue(line, "success"), line);
+ } else {
+ Logger.LogLine("<- (E) " + line);
+ EventList.Add(line);
+ }
+ }
+ }
+
+ public string Receive(int timeout = -1)
+ {
+ while (true) {
+ string[] response = Debuggee.DebuggerClient.Receive(timeout);
+ if (response == null) {
+ throw new DebuggerNotResponsesException();
+ }
+ string line = response[0];
+
+ Logger.LogLine("<- (E) " + line);
+ EventList.Add(line);
+
+ foreach (var Event in StopEvents) {
+ if (isResponseContainProperty(line, "event", Event)) {
+ return line;
+ }
+ }
+ }
+ }
+
+ public List<string> EventList = new List<string>();
+ string[] StopEvents = {"stopped",
+ "terminated"};
+ }
+}
--- /dev/null
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Threading;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTestCore.VSCode
+{
+ public class VSCodeLocalDebuggerClient : DebuggerClient
+ {
+ public VSCodeLocalDebuggerClient(StreamWriter input, StreamReader output)
+ : base(ProtocolType.VSCode)
+ {
+ DebuggerInput = input;
+ DebuggerOutput = output;
+ GetInput = new AutoResetEvent(false);
+ GotInput = new AutoResetEvent(false);
+ InputThread = new Thread(ReaderThread);
+ InputThread.IsBackground = true;
+ InputThread.Start();
+ }
+
+ public override bool DoHandshake(int timeout)
+ {
+ return true;
+ }
+
+ public override bool Send(string command)
+ {
+ DebuggerInput.Write(CONTENT_LENGTH + command.Length.ToString() + TWO_CRLF + command);
+ DebuggerInput.Flush();
+
+ return true;
+ }
+
+ public override string[] Receive(int timeout)
+ {
+ string line = ReceiveOutputLine(timeout);
+ if (line == null) {
+ return null;
+ }
+ return new string[1]{line};
+ }
+
+ public override void Close()
+ {
+ DebuggerInput.Close();
+ DebuggerOutput.Close();
+ }
+
+ string ReadData()
+ {
+ string header = "";
+
+ while (true) {
+ // Read until "\r\n\r\n"
+ int res = DebuggerOutput.Read();
+ if (res < 0) {
+ return null;
+ }
+
+ header += (char)res;
+
+ if (header.Length < TWO_CRLF.Length) {
+ continue;
+ }
+
+ if (header.Substring(header.Length - TWO_CRLF.Length, TWO_CRLF.Length) != TWO_CRLF) {
+ continue;
+ }
+
+ // Extract Content-Length
+ int lengthIndex = header.IndexOf(CONTENT_LENGTH);
+ if (lengthIndex == -1) {
+ continue;
+ }
+
+ int contentLength = Int32.Parse(header.Substring(lengthIndex + CONTENT_LENGTH.Length));
+
+ char[] result = new char[contentLength];
+ if (DebuggerOutput.Read(result, 0, contentLength) == -1) {
+ return null;
+ }
+
+ return new string(result);
+ }
+ // unreachable
+ }
+ void ReaderThread()
+ {
+ while (true) {
+ GetInput.WaitOne();
+ InputString = ReadData();
+ GotInput.Set();
+ }
+ }
+
+ string ReceiveOutputLine(int timeout)
+ {
+ GetInput.Set();
+ bool success = GotInput.WaitOne(timeout);
+ if (!success) {
+ throw new DebuggerNotResponsesException();
+ }
+
+ if (InputString == null) {
+ return null;
+ }
+
+ return InputString;
+ }
+
+ StreamWriter DebuggerInput;
+ StreamReader DebuggerOutput;
+ Thread InputThread;
+ AutoResetEvent GetInput, GotInput;
+ string InputString;
+ static string TWO_CRLF = "\r\n\r\n";
+ static string CONTENT_LENGTH = "Content-Length: ";
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+ // https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json
+ // https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts
+ public class ProtocolMessage {
+ public int seq;
+ public string type;
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+ public class Event : ProtocolMessage {
+ }
+
+ public class ThreadEvent : Event {
+ public ThreadEventBody body;
+ }
+
+ public class ThreadEventBody {
+ public string reason;
+ public int threadId;
+ }
+
+ public class StoppedEvent : Event {
+ public StoppedEventBody body;
+ }
+
+ public class StoppedEventBody {
+ public string reason;
+ public string description;
+ public int ?threadId;
+ public bool ?preserveFocusHint;
+ public string text;
+ public bool ?allThreadsStopped;
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+ public class Request : ProtocolMessage {
+ public Request()
+ {
+ seq = RequestSeq++;
+ type = "request";
+ }
+ public string command;
+ static public int RequestSeq = 1;
+ }
+
+
+ public class InitializeRequest : Request {
+ public InitializeRequest()
+ {
+ command = "initialize";
+ }
+ public InitializeRequestArguments arguments = new InitializeRequestArguments();
+ }
+
+ public class InitializeRequestArguments {
+ public string clientID;
+ public string clientName;
+ public string adapterID;
+ public string locale;
+ public bool ?linesStartAt1;
+ public bool ?columnsStartAt1;
+ public string pathFormat;
+ public bool ?supportsVariableType;
+ public bool ?supportsVariablePaging;
+ public bool ?supportsRunInTerminalRequest;
+ }
+
+ public class LaunchRequest : Request {
+ public LaunchRequest()
+ {
+ command = "launch";
+ }
+ public LaunchRequestArguments arguments = new LaunchRequestArguments();
+ }
+
+ public class LaunchRequestArguments {
+ public string name;
+ public string type;
+ public string preLaunchTask;
+ public string program;
+ public List<string> args;
+ public string cwd;
+ public string console;
+ public bool stopAtEntry;
+ public string internalConsoleOptions;
+ public string __sessionId;
+ }
+
+ public class AttachRequest : Request {
+ public AttachRequest()
+ {
+ command = "attach";
+ }
+ public AttachRequestArguments arguments = new AttachRequestArguments();
+ }
+
+ public class AttachRequestArguments {
+ public int processId;
+ }
+
+ public class ConfigurationDoneRequest : Request {
+ public ConfigurationDoneRequest()
+ {
+ command = "configurationDone";
+ }
+ public ConfigurationDoneArguments arguments;
+ }
+
+ public class ConfigurationDoneArguments {
+ }
+
+ public class ContinueRequest : Request {
+ public ContinueRequest()
+ {
+ command = "continue";
+ }
+ public ContinueArguments arguments = new ContinueArguments();
+ }
+
+ public class ContinueArguments {
+ public int threadId;
+ }
+
+ public class DisconnectRequest : Request {
+ public DisconnectRequest()
+ {
+ command = "disconnect";
+ }
+ public DisconnectArguments arguments;
+ }
+
+ public class DisconnectArguments {
+ public bool ?restart;
+ public bool ?terminateDebuggee;
+ }
+
+ public class SetBreakpointsRequest : Request {
+ public SetBreakpointsRequest()
+ {
+ command = "setBreakpoints";
+ }
+ public SetBreakpointsArguments arguments = new SetBreakpointsArguments();
+ }
+
+ public class SetBreakpointsArguments {
+ public Source source = new Source();
+ public List<SourceBreakpoint> breakpoints = new List<SourceBreakpoint>();
+ public List<int> lines = new List<int>();
+ public bool ?sourceModified;
+ }
+
+ public class SourceBreakpoint {
+ public SourceBreakpoint(int bpLine, string Condition = null)
+ {
+ line = bpLine;
+ condition = Condition;
+ }
+ public int line;
+ public int ?column;
+ public string condition;
+ public string hitCondition;
+ public string logMessage;
+ }
+
+ public class Source {
+ public string name;
+ public string path;
+ public int ?sourceReference;
+ public string presentationHint; // "normal" | "emphasize" | "deemphasize"
+ public string origin;
+ public List<Source> sources = new List<Source>();
+ public dynamic adapterData = null;
+ public List<Checksum> checksums = new List<Checksum>();
+ }
+
+ public class Checksum {
+ public string algorithm; // "MD5" | "SHA1" | "SHA256" | "timestamp"
+ public string checksum;
+ }
+
+ public class SetFunctionBreakpointsRequest : Request {
+ public SetFunctionBreakpointsRequest()
+ {
+ command = "setFunctionBreakpoints";
+ }
+ public SetFunctionBreakpointsArguments arguments = new SetFunctionBreakpointsArguments();
+ }
+
+ public class SetFunctionBreakpointsArguments {
+ public List<FunctionBreakpoint> breakpoints = new List<FunctionBreakpoint>();
+ }
+
+ public class FunctionBreakpoint {
+ public FunctionBreakpoint(string funcName, string Condition = null)
+ {
+ name = funcName;
+ condition = Condition;
+ }
+ public string name;
+ public string condition;
+ public string hitCondition;
+ }
+
+ public class StackTraceRequest : Request {
+ public StackTraceRequest()
+ {
+ command = "stackTrace";
+ }
+ public StackTraceArguments arguments = new StackTraceArguments();
+ }
+
+ public class StackTraceArguments {
+ public int threadId;
+ public int ?startFrame;
+ public int ?levels;
+ public StackFrameFormat format;
+ }
+
+ public class ValueFormat {
+ public bool ?hex;
+ }
+
+ public class StackFrameFormat : ValueFormat {
+ public bool ?parameters;
+ public bool ?parameterTypes;
+ public bool ?parameterNames;
+ public bool ?parameterValues;
+ public bool ?line;
+ public bool ?module;
+ public bool ?includeAll;
+ }
+
+ public class PauseRequest : Request {
+ public PauseRequest()
+ {
+ command = "pause";
+ }
+ public PauseArguments arguments = new PauseArguments();
+ }
+
+ public class PauseArguments {
+ public int threadId;
+ }
+
+ public class ThreadsRequest : Request {
+ public ThreadsRequest()
+ {
+ command = "threads";
+ }
+ }
+
+ public class ScopesRequest : Request {
+ public ScopesRequest()
+ {
+ command = "scopes";
+ }
+ public ScopesArguments arguments = new ScopesArguments();
+ }
+
+ public class ScopesArguments {
+ public Int64 frameId;
+ }
+
+ public class VariablesRequest : Request {
+ public VariablesRequest()
+ {
+ command = "variables";
+ }
+ public VariablesArguments arguments = new VariablesArguments();
+ }
+
+ public class VariablesArguments {
+ public int variablesReference;
+ public string filter; // "indexed" | "named"
+ public int ?start;
+ public int ?count;
+ public ValueFormat format;
+ }
+
+ public class EvaluateRequest : Request {
+ public EvaluateRequest()
+ {
+ command = "evaluate";
+ }
+ public EvaluateArguments arguments = new EvaluateArguments();
+ }
+
+ public class EvaluateArguments {
+ public string expression;
+ public Int64 ?frameId;
+ public string context;
+ public ValueFormat format;
+ }
+
+ public class SetVariableRequest : Request {
+ public SetVariableRequest()
+ {
+ command = "setVariable";
+ }
+ public SetVariableArguments arguments = new SetVariableArguments();
+ }
+
+ public class SetVariableArguments {
+ public int variablesReference;
+ public string name;
+ public string value;
+ public ValueFormat format;
+ }
+
+ public class NextRequest : Request {
+ public NextRequest()
+ {
+ command = "next";
+ }
+ public NextArguments arguments = new NextArguments();
+ }
+
+ public class NextArguments {
+ public int threadId;
+ }
+
+ public class StepInRequest : Request {
+ public StepInRequest()
+ {
+ command = "stepIn";
+ }
+ public StepInArguments arguments = new StepInArguments();
+ }
+
+ public class StepInArguments {
+ public int threadId;
+ public int ?targetId;
+ }
+
+ public class StepOutRequest : Request {
+ public StepOutRequest()
+ {
+ command = "stepOut";
+ }
+ public StepOutArguments arguments = new StepOutArguments();
+ }
+
+ public class StepOutArguments {
+ public int threadId;
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+ public class Response : ProtocolMessage {
+ public int request_seq;
+ public bool success;
+ public string command;
+ public string message;
+ }
+
+ public class StackTraceResponse : Response {
+ public StackTraceResponseBody body;
+ }
+
+ public class StackTraceResponseBody {
+ public List<StackFrame> stackFrames;
+ public int ?totalFrames;
+ };
+
+ public class StackFrame {
+ public Int64 id;
+ public string name;
+ public Source source;
+ public int line;
+ public int column;
+ public int ?endLine;
+ public int ?endColumn;
+ public dynamic moduleId = null;
+ public string presentationHint; // "normal" | "label" | "subtle"
+ }
+
+ public class ThreadsResponse : Response {
+ public ThreadsResponseBody body;
+ }
+
+ public class ThreadsResponseBody {
+ public List<Thread> threads;
+ };
+
+ public class Thread {
+ public int id;
+ public string name;
+ }
+
+ public class ScopesResponse : Response {
+ public ScopesResponseBody body;
+ }
+
+ public class ScopesResponseBody {
+ public List<Scope> scopes;
+ };
+
+ public class Scope {
+ public string name;
+ public int ?variablesReference;
+ public int ?namedVariables;
+ public int ?indexedVariables;
+ public bool ?expensive;
+ public Source source;
+ public int ?line;
+ public int ?column;
+ public int ?endLine;
+ public int ?endColumn;
+ }
+
+ public class VariablesResponse : Response {
+ public VariablesResponseBody body;
+ }
+
+ public class VariablesResponseBody {
+ public List<Variable> variables;
+ };
+
+ public class Variable {
+ public string name;
+ public string value;
+ public string type;
+ public VariablePresentationHint presentationHint;
+ public string evaluateName;
+ public int variablesReference;
+ public int ?namedVariables;
+ public int ?indexedVariables;
+ }
+
+ public class VariablePresentationHint {
+ public string kind;
+ public List<string> attributes;
+ public string visibility;
+ }
+
+ public class EvaluateResponse : Response {
+ public EvaluateResponseBody body;
+ }
+
+ public class EvaluateResponseBody {
+ public string result;
+ public string type;
+ public VariablePresentationHint presentationHint;
+ public int variablesReference;
+ public int ?namedVariables;
+ public int ?indexedVariables;
+ };
+
+ public class SetVariableResponse : Response {
+ public SetVariableResponseBody body;
+ }
+
+ public class SetVariableResponseBody {
+ public string value;
+ public string type;
+ public int ?variablesReference;
+ public int ?namedVariables;
+ public int ?indexedVariables;
+ };
+}
--- /dev/null
+using System;
+using System.Net.Sockets;
+using System.Text;
+
+namespace NetcoreDbgTestCore
+{
+ namespace VSCode
+ {
+ public class VSCodeTcpDebuggerClient : DebuggerClient
+ {
+ public VSCodeTcpDebuggerClient(string addr, int port) : base(ProtocolType.VSCode)
+ {
+ client = new TcpClient(addr, port);
+
+ stream = client.GetStream();
+ }
+
+ public override bool DoHandshake(int timeout)
+ {
+ return true;
+ }
+
+ public override bool Send(string cmd)
+ {
+ SendCommandLine(CONTENT_LENGTH + cmd.Length.ToString() + TWO_CRLF + cmd);
+
+ return true;
+ }
+
+ public override string[] Receive(int timeout)
+ {
+ string line = ReceiveOutputLine(timeout);
+ if (line == null) {
+ return null;
+ }
+ return new string[1]{line};
+ }
+
+ public override void Close()
+ {
+ stream.Close();
+ client.Close();
+ }
+
+ void SendCommandLine(string str)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(str);
+ stream.Write(bytes, 0, bytes.Length);
+ }
+
+ string ReceiveOutputLine(int timeout)
+ {
+ string header = "";
+ byte[] recvBuffer = new byte[1];
+
+ while (true) {
+ // Read until "\r\n\r\n"
+ int readCount = stream.Read(recvBuffer, 0, recvBuffer.Length);
+ header += Encoding.ASCII.GetString(recvBuffer, 0, readCount);
+
+ if (header.Length < TWO_CRLF.Length) {
+ continue;
+ }
+
+ if (header.Substring(header.Length - TWO_CRLF.Length, TWO_CRLF.Length) != TWO_CRLF) {
+ continue;
+ }
+
+ // Extract Content-Length
+ int lengthIndex = header.IndexOf(CONTENT_LENGTH);
+ if (lengthIndex == -1) {
+ continue;
+ }
+
+ int contentLength = Int32.Parse(header.Substring(lengthIndex + CONTENT_LENGTH.Length));
+ byte[] result = new byte[contentLength];
+ if (stream.Read(result, 0, contentLength) == -1) {
+ return null;
+ }
+
+ return System.Text.Encoding.UTF8.GetString(result);
+ }
+ // unreachable
+ }
+
+ TcpClient client;
+ NetworkStream stream;
+ static string TWO_CRLF = "\r\n\r\n";
+ static string CONTENT_LENGTH = "Content-Length: ";
+ }
+ }
+}
--- /dev/null
+# How to launch MIExampleTest for tcp netcoredbg server
+
+- launch target and install netcoredbg;
+
+- launch netcoredbg as tcp server on target via sdb shell:
+```
+ $ sdb shell nohup <path-to-netcoredbg> --server
+```
+
+- forward tcp port from target to host:
+```
+ $ sdb forward tcp:4712 tcp:4711
+```
+
+- move to test-suite directory;
+
+- build MIExampleTest target assembly and TestRunner:
+```
+ $ dotnet build MIExampleTest
+ $ dotnet build TestRunner
+```
+or you can build solution which consists of TestRunner and all tests:
+```
+ $ dotnet build
+```
+
+- push dll and pdb files into target /tmp/ disrectory:
+```
+ $ sdb push MIExampleTest/bin/Debug/netcoreapp2.1/MIExampleTest{pdb,dll} /tmp/
+```
+
+- change smack permissions:
+```
+ $ sdb shell chsmack -a "_" /tmp/MIExampleTest.{pdb,dll}
+```
+
+- launch TestRunner with tcp client (repsonses will be parsed only as GDB/MI outputs):
+
+```
+ $ dotnet run --project TestRunner -- \
+ --tcp localhost 4712 \
+ --dotnet /usr/share/dotnet/corerun \
+ --test MIExampleTest \
+ --sources MIExampleTest/Program.cs \
+ --assembly /tmp/MIExampleTest.dll
+```
+
+- expect successfull passing of test.
+
+Also after preparing sdb target with netcoredbg you can run follow script
+to make all necessary steps.
+
+- On Linux:
+```
+ launch all tests:
+ $ ./sdb_run_tests.sh
+ or
+ $ ./sdb_run_tests.sh <test-name> [<test-name>]
+```
+- On Windows:
+```
+ launch all tests:
+ > powershell.exe -executionpolicy bypass -File sdb_run_tests.ps1
+ or
+ > powershell.exe -executionpolicy bypass -File sdb_run_tests.ps1 <test-name> [<test-name>]
+```
+
+# How to launch tests locally
+
+- On Linux:
+```
+ launch all tests:
+ $ ./run_tests.sh
+ or
+ $ ./run_tests.sh <test-name> [<test-name>]
+ or
+ $ NETCOREDBG=<path-to-netcoredbg> ./run_tests.sh <test-name> [<test-name>]
+```
+
+- On Windows:
+```
+ launch all tests:
+ $ powershell.exe -executionpolicy bypass -File run_tests.ps1
+ or
+ $ powershell.exe -executionpolicy bypass -File run_tests.ps1 <test-name> [<test-name>]
+```
+
+# How to add new test
+
+- move to test-suite directory;
+
+- create new project in test-suite folder:
+```
+ $ dotnet new console -o NewTest
+```
+
+- add reference to NetcoreDbgTest library:
+```
+ $ dotnet add NewTest/NewTest.csproj reference NetcoreDbgTest/NetcoreDbgTest.csproj
+```
+
+- add project NewTest to solution:
+```
+ $ dotnet sln add NewTest/NewTest.csproj
+```
+
+- add test name into ALL_TEST_NAMES list in "run_tests.sh", "run_tests.ps1", "sdb_run_tests.sh" and "sdb_run_tests.ps1" scripts;
+
+- in MIExampleTest implemented small scenario of NetCoreDbgTest library using.
--- /dev/null
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+using LocalDebugger;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.MI;
+using NetcoreDbgTestCore.VSCode;
+
+namespace TestRunner
+{
+ class Program
+ {
+ public static int Main(string[] args)
+ {
+ var cli = new CLInterface(args);
+ DebuggerClient debugger = null;
+ DebuggeeScript script = null;
+ LocalDebuggerProcess localDebugger = null;
+
+ if (cli.NeedHelp) {
+ cli.PrintHelp();
+ return 1;
+ }
+
+ if (cli.ClientInfo == null) {
+ Console.Error.WriteLine("Please define client type");
+ return 1;
+ }
+
+ try {
+ switch (cli.Protocol) {
+ case ProtocolType.MI:
+ switch (cli.ClientInfo.Type) {
+ case ClientType.Local:
+ var localClientInfo = (LocalClientInfo)cli.ClientInfo;
+ localDebugger = new LocalDebuggerProcess(
+ localClientInfo.DebuggerPath, @" --interpreter=mi");
+ localDebugger.Start();
+
+ debugger = new MILocalDebuggerClient(localDebugger.Input,
+ localDebugger.Output);
+ break;
+ case ClientType.Tcp:
+ var tcpClientInfo = (TcpClientInfo)cli.ClientInfo;
+ debugger = new MITcpDebuggerClient(tcpClientInfo.Addr,
+ tcpClientInfo.Port);
+ break;
+ default:
+ Console.Error.WriteLine("Only tcp and local debuggers are supported now");
+ return 1;
+ }
+
+ break;
+
+ case ProtocolType.VSCode:
+ switch (cli.ClientInfo.Type) {
+ case ClientType.Local:
+ var localClientInfo = (LocalClientInfo)cli.ClientInfo;
+ localDebugger = new LocalDebuggerProcess(
+ localClientInfo.DebuggerPath, @" --interpreter=vscode");
+ localDebugger.Start();
+
+ debugger = new VSCodeLocalDebuggerClient(localDebugger.Input,
+ localDebugger.Output);
+ break;
+ case ClientType.Tcp:
+ var tcpClientInfo = (TcpClientInfo)cli.ClientInfo;
+ debugger = new VSCodeTcpDebuggerClient(tcpClientInfo.Addr,
+ tcpClientInfo.Port);
+ break;
+ default:
+ Console.Error.WriteLine("Only tcp and local debuggers are supported now");
+ return 1;
+ }
+
+ break;
+
+ default:
+ Console.Error.WriteLine("Only GDB/MI and VSCode protocols is supported now");
+ return 1;
+ }
+ }
+ catch {
+ Console.Error.WriteLine("Can't create debugger client");
+ return 1;
+ }
+
+ if (!debugger.DoHandshake(200)) {
+ Console.Error.WriteLine("Handshake is failed");
+ if (localDebugger != null) {
+ localDebugger.Close();
+ }
+ return 1;
+ }
+
+ try {
+ script = new DebuggeeScript(cli.Environment.SourceFilesPath, debugger.Protocol);
+ }
+ catch (ScriptNotBuiltException e) {
+ Console.Error.WriteLine("Script is not built:");
+ Console.Error.WriteLine(e.ToString());
+ debugger.Close();
+ if (localDebugger != null) {
+ localDebugger.Close();
+ }
+ return 1;
+ }
+
+ try {
+ Debuggee.Run(script, debugger, cli.Environment);
+ Console.WriteLine("Success: Test case \"{0}\" is passed!!!",
+ cli.Environment.TestName);
+ }
+ catch (System.Exception e) {
+ Console.Error.WriteLine("Script running is failed. Got exception:\n" + e.ToString());
+ return 1;
+ }
+
+ debugger.Close();
+ return 0;
+ }
+ }
+
+ enum ClientType
+ {
+ Local,
+ Tcp,
+ }
+
+ class CLInterface
+ {
+ public CLInterface(string[] args)
+ {
+ Environment = new NetcoreDbgTestCore.Environment();
+
+ if (args.Length == 0) {
+ NeedHelp = true;
+ return;
+ }
+
+ if (args.Length == 1 && (args[0] == "-h" || args[0] == "--help")) {
+ NeedHelp = true;
+ return;
+ }
+
+ int i = 0;
+ while (i < args.Length && !NeedHelp) {
+ switch (args[i]) {
+ case "--tcp":
+ if (i + 2 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ try {
+ ClientInfo = new TcpClientInfo(args[i + 1],
+ args[i + 2]);
+ }
+ catch {
+ NeedHelp = true;
+ break;
+ }
+ i += 3;
+
+ break;
+ case "--local":
+ if (i + 1 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ try {
+ string debuggerPath = Path.GetFullPath(args[i + 1]);
+ ClientInfo = new LocalClientInfo(debuggerPath);
+ }
+ catch {
+ NeedHelp = true;
+ break;
+ }
+ i += 2;
+
+ break;
+ case "--proto":
+ if (i + 1 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ switch (args[i + 1]) {
+ case "mi":
+ Protocol = ProtocolType.MI;
+ break;
+ case "vscode":
+ Protocol = ProtocolType.VSCode;
+ break;
+ default:
+ Protocol = ProtocolType.None;
+ break;
+ }
+ i += 2;
+
+ break;
+ case "--test":
+ if (i + 2 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ try {
+ Environment.TestName = args[i + 1];
+ }
+ catch {
+ NeedHelp = true;
+ break;
+ }
+
+ i += 2;
+
+ break;
+ case "--sources":
+ if (i + 1 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ try {
+ Environment.SourceFilesPath = Path.GetFullPath(args[i + 1]);
+ if (Environment.SourceFilesPath[Environment.SourceFilesPath.Length - 1] == ';') {
+ Environment.SourceFilesPath =
+ Environment.SourceFilesPath.Remove(Environment.SourceFilesPath.Length - 1);
+ }
+ }
+ catch {
+ NeedHelp = true;
+ break;
+ }
+
+ i += 2;
+
+ break;
+ case "--assembly":
+ if (i + 1 >= args.Length) {
+ NeedHelp = true;
+ break;
+ }
+
+ Environment.TargetAssemblyPath = args[i + 1];
+
+ i += 2;
+
+ break;
+ case "--dotnet":
+ if (i + 1 >= args.Length) {
+ NeedHelp = true;
+ }
+
+ try {
+ Path.GetFullPath(args[i + 1]);
+ }
+ catch {
+ NeedHelp = true;
+ break;
+ }
+
+ Environment.CorerunPath = args[i + 1];
+
+ i += 2;
+
+ break;
+ default:
+ NeedHelp = true;
+ break;
+ }
+ }
+
+ if (ClientInfo != null
+ && ClientInfo.Type == ClientType.Local
+ && !File.Exists(Environment.TargetAssemblyPath)) {
+ Console.Error.WriteLine("Provided assembly path is invalid");
+ throw new System.Exception();
+ }
+
+ if (NeedHelp) {
+ return;
+ }
+ }
+
+ public void PrintHelp()
+ {
+ Console.Error.WriteLine(
+@"usage: dotnet run {-h|--help|[OPTIONS] TESTS}
+options:
+ --dotnet dotnet-path Set dotnet path(default: dotnet-path=""dotnet"")
+ --proto protocol Set protocol(default: protocol=mi)
+ --tcp server port Create TCP client for debugger
+ --local debugger-path Create launch debugger locally and create client
+ --test name Test name
+ --sources path[;path] Semicolon separated paths to source files
+ --assembly path Path to target assambly file
+
+
+ ");
+ }
+
+ public bool NeedHelp = false;
+ public ProtocolType Protocol = ProtocolType.MI;
+ public NetcoreDbgTestCore.Environment Environment;
+ public ClientInfo ClientInfo;
+ }
+
+ class ClientInfo
+ {
+ public ClientType Type;
+ }
+
+ class TcpClientInfo : ClientInfo
+ {
+ public TcpClientInfo(string addr, string port)
+ {
+ Type = ClientType.Tcp;
+
+ if (!IsIpAddressValid(addr)) {
+ Console.Error.WriteLine("IP address is invalid");
+ throw new System.Exception();
+ }
+
+ Addr = addr;
+ Port = Int32.Parse(port);
+ }
+
+ private bool IsIpAddressValid(string addr)
+ {
+ try {
+ IPAddress[] hostIPs = Dns.GetHostAddresses(addr);
+ IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
+
+ foreach (IPAddress hostIP in hostIPs) {
+ if (IPAddress.IsLoopback(hostIP)) {
+ return true;
+ }
+ foreach (IPAddress localIP in localIPs) {
+ if (hostIP.Equals(localIP)) {
+ return true;
+ }
+ }
+ }
+
+ IPAddress address = IPAddress.Parse(addr);
+ return true;
+ }
+ catch {
+ }
+
+ return false;
+ }
+ public string Addr;
+ public int Port;
+ }
+
+ class LocalClientInfo : ClientInfo
+ {
+ public LocalClientInfo(string debuggerPath)
+ {
+ Type = ClientType.Local;
+ DebuggerPath = debuggerPath;
+ }
+
+ public string DebuggerPath;
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+ <ProjectReference Include="..\LocalDebugger\LocalDebugger.csproj" />
+ </ItemGroup>
+
+</Project>
--- /dev/null
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.VSCode;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+using Newtonsoft.Json;
+
+namespace NetcoreDbgTest.Script
+{
+ // Context includes methods and constants which
+ // will be move to debugger API
+ class Context
+ {
+ public static void PrepareStart()
+ {
+ InitializeRequest initializeRequest = new InitializeRequest();
+ initializeRequest.arguments.clientID = "vscode";
+ initializeRequest.arguments.clientName = "Visual Studio Code";
+ initializeRequest.arguments.adapterID = "coreclr";
+ initializeRequest.arguments.pathFormat = "path";
+ initializeRequest.arguments.linesStartAt1 = true;
+ initializeRequest.arguments.columnsStartAt1 = true;
+ initializeRequest.arguments.supportsVariableType = true;
+ initializeRequest.arguments.supportsVariablePaging = true;
+ initializeRequest.arguments.supportsRunInTerminalRequest = true;
+ initializeRequest.arguments.locale = "en-us";
+ Assert.True(VSCodeDebugger.Request(initializeRequest).Success);
+
+ LaunchRequest launchRequest = new LaunchRequest();
+ launchRequest.arguments.name = ".NET Core Launch (console) with pipeline";
+ launchRequest.arguments.type = "coreclr";
+ launchRequest.arguments.preLaunchTask = "build";
+ launchRequest.arguments.program = DebuggeeInfo.TargetAssemblyPath;
+ // NOTE this code works only with one source file
+ launchRequest.arguments.cwd = Directory.GetParent(DebuggeeInfo.SourceFilesPath).FullName;
+ launchRequest.arguments.console = "internalConsole";
+ launchRequest.arguments.stopAtEntry = true;
+ launchRequest.arguments.internalConsoleOptions = "openOnSessionStart";
+ launchRequest.arguments.__sessionId = Guid.NewGuid().ToString();
+ Assert.True(VSCodeDebugger.Request(launchRequest).Success);
+ }
+
+ public static void PrepareEnd()
+ {
+ ConfigurationDoneRequest configurationDoneRequest = new ConfigurationDoneRequest();
+ Assert.True(VSCodeDebugger.Request(configurationDoneRequest).Success);
+ }
+
+ public static void WasEntryPointHit()
+ {
+ string resJSON = VSCodeDebugger.Receive(-1);
+ Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped")
+ && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "entry"));
+
+ foreach (var Event in VSCodeDebugger.EventList) {
+ if (VSCodeDebugger.isResponseContainProperty(Event, "event", "stopped")
+ && VSCodeDebugger.isResponseContainProperty(Event, "reason", "entry")) {
+ threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(Event, "threadId"));
+ break;
+ }
+ }
+ }
+
+ public static void WasExit()
+ {
+ string resJSON = VSCodeDebugger.Receive(-1);
+ Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "terminated"));
+
+ DisconnectRequest disconnectRequest = new DisconnectRequest();
+ disconnectRequest.arguments = new DisconnectArguments();
+ disconnectRequest.arguments.restart = false;
+ Assert.True(VSCodeDebugger.Request(disconnectRequest).Success);
+ }
+
+ public static void AddBreakpoint(string bpName, string Condition = null)
+ {
+ Breakpoint bp = DebuggeeInfo.Breakpoints[bpName];
+ Assert.Equal(BreakpointType.Line, bp.Type);
+ var lbp = (LineBreakpoint)bp;
+
+ BreakpointSourceName = lbp.FileName;
+ BreakpointList.Add(new SourceBreakpoint(lbp.NumLine, Condition));
+ BreakpointLines.Add(lbp.NumLine);
+ }
+
+ public static void SetBreakpoints()
+ {
+ SetBreakpointsRequest setBreakpointsRequest = new SetBreakpointsRequest();
+ setBreakpointsRequest.arguments.source.name = BreakpointSourceName;
+ // NOTE this code works only with one source file
+ setBreakpointsRequest.arguments.source.path = DebuggeeInfo.SourceFilesPath;
+ setBreakpointsRequest.arguments.lines.AddRange(BreakpointLines);
+ setBreakpointsRequest.arguments.breakpoints.AddRange(BreakpointList);
+ setBreakpointsRequest.arguments.sourceModified = false;
+ Assert.True(VSCodeDebugger.Request(setBreakpointsRequest).Success);
+ }
+
+ public static void WasBreakpointHit(Breakpoint breakpoint)
+ {
+ string resJSON = VSCodeDebugger.Receive(-1);
+ Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped")
+ && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint"));
+
+ foreach (var Event in VSCodeDebugger.EventList) {
+ if (VSCodeDebugger.isResponseContainProperty(Event, "event", "stopped")
+ && VSCodeDebugger.isResponseContainProperty(Event, "reason", "breakpoint")) {
+ threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(Event, "threadId"));
+ }
+ }
+
+ StackTraceRequest stackTraceRequest = new StackTraceRequest();
+ stackTraceRequest.arguments.threadId = threadId;
+ stackTraceRequest.arguments.startFrame = 0;
+ stackTraceRequest.arguments.levels = 20;
+ var ret = VSCodeDebugger.Request(stackTraceRequest);
+ Assert.True(ret.Success);
+
+ Assert.Equal(BreakpointType.Line, breakpoint.Type);
+ var lbp = (LineBreakpoint)breakpoint;
+
+ StackTraceResponse stackTraceResponse =
+ JsonConvert.DeserializeObject<StackTraceResponse>(ret.ResponseStr);
+
+ foreach (var Frame in stackTraceResponse.body.stackFrames) {
+ if (Frame.line == lbp.NumLine
+ && Frame.source.name == lbp.FileName
+ // NOTE this code works only with one source file
+ && Frame.source.path == DebuggeeInfo.SourceFilesPath)
+ return;
+ }
+
+ throw new NetcoreDbgTestCore.ResultNotSuccessException();
+ }
+
+ public static void Continue()
+ {
+ ContinueRequest continueRequest = new ContinueRequest();
+ continueRequest.arguments.threadId = threadId;
+ Assert.True(VSCodeDebugger.Request(continueRequest).Success);
+ }
+
+ static VSCodeDebugger VSCodeDebugger = new VSCodeDebugger();
+ static int threadId = -1;
+ public static string BreakpointSourceName;
+ public static List<SourceBreakpoint> BreakpointList = new List<SourceBreakpoint>();
+ public static List<int> BreakpointLines = new List<int>();
+ }
+}
+
+namespace VSCodeExampleTest
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // first checkpoint (initialization) must provide "init" as id
+ Label.Checkpoint("init", "bp_test", () => {
+ Context.PrepareStart();
+ Context.AddBreakpoint("bp");
+ Context.SetBreakpoints();
+ Context.PrepareEnd();
+ Context.WasEntryPointHit();
+ Context.Continue();
+ });
+
+ Console.WriteLine("A breakpoint \"bp\" is set on this line"); Label.Breakpoint("bp");
+
+ Label.Checkpoint("bp_test", "finish", () => {
+ Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp"]);
+ Context.Continue();
+ });
+
+ // last checkpoint must provide "finish" as id or empty string ("") as next checkpoint id
+ Label.Checkpoint("finish", "", () => {
+ Context.WasExit();
+ });
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ </PropertyGroup>
+
+</Project>
--- /dev/null
+$ALL_TEST_NAMES = @(
+ "MIExampleTest"
+ "VSCodeExampleTest"
+)
+
+$TEST_NAMES = $args
+
+if ($NETCOREDBG.count -eq 0) {
+ $NETCOREDBG = "../bin/netcoredbg.exe"
+}
+
+if ($TEST_NAMES.count -eq 0) {
+ $TEST_NAMES = $ALL_TEST_NAMES
+}
+
+# Prepare
+dotnet build TestRunner
+
+$test_pass = 0
+$test_fail = 0
+$test_list = ""
+
+# Build, push and run tests
+foreach ($TEST_NAME in $TEST_NAMES) {
+ dotnet build $TEST_NAME
+
+ $SOURCE_FILE_LIST = (Get-ChildItem -Path $TEST_NAME -Recurse *.cs |
+ ? { $_.FullName -inotmatch "$TEST_NAME\\obj" }).Name
+
+ $SOURCE_FILES = ""
+ foreach ($SOURCE_FILE in $SOURCE_FILE_LIST) {
+ $SOURCE_FILES += $TEST_NAME + "/" + $SOURCE_FILE + ";"
+ }
+
+ $PROTO = "mi"
+ if ($TEST_NAME.StartsWith("VSCode")) {
+ $PROTO = "vscode"
+ }
+
+ dotnet run --project TestRunner -- `
+ --local $NETCOREDBG `
+ --proto $PROTO `
+ --test $TEST_NAME `
+ --sources $SOURCE_FILES `
+ --assembly $TEST_NAME/bin/Debug/netcoreapp2.1/$TEST_NAME.dll
+
+
+ if($?)
+ {
+ $test_pass++
+ $test_list = "$test_list$TEST_NAME ... passed`n"
+ }
+ else
+ {
+ $test_fail++
+ $test_list = "$test_list$TEST_NAME ... failed`n"
+ }
+}
+
+Write-Host ""
+Write-Host $test_list
+Write-Host "Total tests: $($test_pass + $test_fail). Passed: $test_pass. Failed: $test_fail."
--- /dev/null
+#!/bin/bash
+
+ALL_TEST_NAMES=(
+ "MIExampleTest"
+ "VSCodeExampleTest"
+)
+
+TEST_NAMES="$@"
+
+if [[ -z $NETCOREDBG ]]; then
+ NETCOREDBG="../bin/netcoredbg"
+fi
+
+if [[ -z $TEST_NAMES ]]; then
+ TEST_NAMES="${ALL_TEST_NAMES[@]}"
+fi
+
+dotnet build TestRunner
+
+test_pass=0
+test_fail=0
+test_list=""
+
+for TEST_NAME in $TEST_NAMES; do
+ dotnet build $TEST_NAME
+
+ SOURCE_FILES=$(find $TEST_NAME \! -path "$TEST_NAME/obj/*" -type f -name "*.cs" -printf '%p;')
+
+ PROTO="mi"
+ if [[ $TEST_NAME == VSCode* ]] ;
+ then
+ PROTO="vscode"
+ fi
+
+ dotnet run --project TestRunner -- \
+ --local $NETCOREDBG \
+ --proto $PROTO \
+ --test $TEST_NAME \
+ --sources "$SOURCE_FILES" \
+ --assembly $TEST_NAME/bin/Debug/netcoreapp2.1/$TEST_NAME.dll
+
+ if [ "$?" -ne "0" ]; then
+ test_fail=$(($test_fail + 1))
+ test_list="$test_list$TEST_NAME ... failed\n"
+ else
+ test_pass=$(($test_pass + 1))
+ test_list="$test_list$TEST_NAME ... passed\n"
+ fi
+done
+
+echo ""
+echo -e $test_list
+echo "Total tests: $(($test_pass + $test_fail)). Passed: $test_pass. Failed: $test_fail."
--- /dev/null
+# Please prepare sdb target with netcoredbg before start
+
+$NETCOREDBG = "/home/owner/share/tmp/sdk_tools/netcoredbg/netcoredbg"
+
+$ALL_TEST_NAMES = @(
+ "MIExampleTest"
+ "VSCodeExampleTest"
+)
+
+$TEST_NAMES = $args
+
+if ($TEST_NAMES.count -eq 0) {
+ $TEST_NAMES = $ALL_TEST_NAMES
+}
+
+# Prepare
+dotnet build TestRunner
+
+sdb root on
+sdb forward tcp:4712 tcp:4711
+
+$test_pass = 0
+$test_fail = 0
+$test_list = ""
+
+# Build, push and run tests
+foreach ($TEST_NAME in $TEST_NAMES) {
+ dotnet build $TEST_NAME
+
+ sdb push $TEST_NAME\bin\Debug\netcoreapp2.1\$TEST_NAME.dll /tmp/
+ sdb push $TEST_NAME\bin\Debug\netcoreapp2.1\$TEST_NAME.pdb /tmp/
+
+ $SOURCE_FILE_LIST = (Get-ChildItem -Path $TEST_NAME -Recurse *.cs |
+ ? { $_.FullName -inotmatch "$TEST_NAME\\obj" }).Name
+
+ $SOURCE_FILES = ""
+ foreach ($SOURCE_FILE in $SOURCE_FILE_LIST) {
+ $SOURCE_FILES += $TEST_NAME + "/" + $SOURCE_FILE + ";"
+ }
+
+ if ($TEST_NAME.StartsWith("VSCode")) {
+ $PROTO = "vscode"
+
+ # change $HOME to /tmp in order to prevent /root/nohup.out creation
+ sdb shell HOME=/tmp nohup $NETCOREDBG --server --interpreter=$PROTO -- `
+ /usr/bin/dotnet-launcher /tmp/$TEST_NAME.dll
+
+ dotnet run --project TestRunner -- `
+ --tcp localhost 4712 `
+ --proto $PROTO `
+ --test $TEST_NAME `
+ --sources $SOURCE_FILES
+ } else {
+ $PROTO = "mi"
+
+ # change $HOME to /tmp in order to prevent /root/nohup.out creation
+ sdb shell HOME=/tmp nohup $NETCOREDBG --server --interpreter=$PROTO
+
+ dotnet run --project TestRunner -- `
+ --tcp localhost 4712 `
+ --dotnet /usr/bin/dotnet-launcher `
+ --proto $PROTO `
+ --test $TEST_NAME `
+ --sources $SOURCE_FILES `
+ --assembly /tmp/$TEST_NAME.dll
+ }
+
+
+ if($?)
+ {
+ $test_pass++
+ $test_list = "$test_list$TEST_NAME ... passed`n"
+ }
+ else
+ {
+ $test_fail++
+ $test_list = "$test_list$TEST_NAME ... failed`n"
+ }
+}
+
+# Leave
+sdb root off
+
+Write-Host ""
+Write-Host $test_list
+Write-Host "Total tests: $($test_pass + $test_fail). Passed: $test_pass. Failed: $test_fail."
--- /dev/null
+#!/bin/bash
+
+ALL_TEST_NAMES=(
+ "MIExampleTest"
+ "VSCodeExampleTest"
+)
+
+TEST_NAMES="$@"
+
+if [[ -z $TEST_NAMES ]]; then
+ TEST_NAMES="${ALL_TEST_NAMES[@]}"
+fi
+
+
+SDB=${SDB:-sdb}
+
+GBSROOT=$HOME/GBS-ROOT
+TOOLS_ABS_PATH=/home/owner/share/tmp/sdk_tools
+
+SCRIPTDIR=$(dirname $(readlink -f $0))
+
+# Detect target arch
+
+if $SDB shell lscpu | grep -q armv7l; then ARCH=armv7l;
+elif $SDB shell lscpu | grep -q i686; then ARCH=i686;
+else echo "Unknown target architecture"; exit 1; fi
+
+# The following command assumes that GBS build was performed on a clean system (or in Docker),
+# which means only one such file exists.
+RPMFILE=$(find $GBSROOT/local/repos/ -type f -name netcoredbg-[0-9]\*$ARCH.rpm -print -quit)
+
+# Repackage RPM file as TGZ
+
+if [ ! -f "$RPMFILE" ]; then echo "Debugger RPM not found"; exit 1; fi
+PKGNAME=`rpm -q --qf "%{n}" -p $RPMFILE`
+PKGVERSION=`rpm -q --qf "%{v}" -p $RPMFILE`
+PKGARCH=`rpm -q --qf "%{arch}" -p $RPMFILE`
+TARGZNAME=$PKGNAME-$PKGVERSION-$PKGARCH.tar.gz
+if [ -d "$SCRIPTDIR/unpacked" ]; then rm -rf "$SCRIPTDIR/unpacked"; fi
+mkdir "$SCRIPTDIR/unpacked" && cd "$SCRIPTDIR/unpacked"
+rpm2cpio "$RPMFILE" | cpio -idmv
+touch .$TOOLS_ABS_PATH/$PKGNAME/version-$PKGVERSION
+tar cfz ../$TARGZNAME --owner=root --group=root -C .$TOOLS_ABS_PATH .
+cd ..
+
+# Upload TGZ to target and unpack
+
+REMOTETESTDIR=$TOOLS_ABS_PATH/netcoredbg-tests
+
+$SDB root on
+$SDB shell rm -rf "$TOOLS_ABS_PATH/netcoredbg"
+$SDB shell mkdir -p $TOOLS_ABS_PATH/on-demand
+$SDB push $TARGZNAME $TOOLS_ABS_PATH/on-demand
+$SDB shell "cd $TOOLS_ABS_PATH && tar xf $TOOLS_ABS_PATH/on-demand/$(basename $TARGZNAME)"
+$SDB shell rm -rf "$REMOTETESTDIR"
+$SDB shell mkdir $REMOTETESTDIR
+
+NETCOREDBG=$TOOLS_ABS_PATH/netcoredbg/netcoredbg
+
+# Prepare
+dotnet build $SCRIPTDIR/TestRunner
+sdb forward tcp:4712 tcp:4711
+
+test_pass=0
+test_fail=0
+test_list=""
+
+# Build, push and run tests
+for TEST_NAME in $TEST_NAMES; do
+ HOSTTESTDIR=$SCRIPTDIR/$TEST_NAME
+ dotnet build $HOSTTESTDIR
+ sdb push $HOSTTESTDIR/bin/Debug/netcoreapp2.1/$TEST_NAME.{dll,pdb} $REMOTETESTDIR
+
+ SOURCE_FILES=$(find $HOSTTESTDIR \! -path "$HOSTTESTDIR/obj/*" -type f -name "*.cs" -printf '%p;')
+
+ if [[ $TEST_NAME == VSCode* ]] ;
+ then
+ PROTO="vscode"
+
+ # change $HOME to $REMOTETESTDIR in order to prevent /root/nohup.out creation
+ sdb shell HOME=$REMOTETESTDIR nohup $NETCOREDBG --server --interpreter=$PROTO -- \
+ /usr/bin/dotnet-launcher $REMOTETESTDIR/$TEST_NAME.dll
+
+ dotnet run --project TestRunner -- \
+ --tcp localhost 4712 \
+ --proto $PROTO \
+ --test $TEST_NAME \
+ --sources $SOURCE_FILES
+ else
+ PROTO="mi"
+
+ # change $HOME to /tmp in order to prevent /root/nohup.out creation
+ sdb shell HOME=$REMOTETESTDIR nohup $NETCOREDBG --server --interpreter=$PROTO
+
+ dotnet run --project TestRunner -- \
+ --tcp localhost 4712 \
+ --dotnet /usr/bin/dotnet-launcher \
+ --proto $PROTO \
+ --test $TEST_NAME \
+ --sources "$SOURCE_FILES" \
+ --assembly $REMOTETESTDIR/$TEST_NAME.dll
+ fi
+
+
+ if [ "$?" -ne "0" ]; then
+ test_fail=$(($test_fail + 1))
+ test_list="$test_list$TEST_NAME ... failed\n"
+ else
+ test_pass=$(($test_pass + 1))
+ test_list="$test_list$TEST_NAME ... passed\n"
+ fi
+done
+
+# Leave
+sdb root off
+
+echo ""
+echo -e $test_list
+echo "Total tests: $(($test_pass + $test_fail)). Passed: $test_pass. Failed: $test_fail."
--- /dev/null
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetcoreDbgTest", "NetcoreDbgTest\NetcoreDbgTest.csproj", "{D6915FE2-2E22-4924-8A98-AC7C7C697691}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunner", "TestRunner\TestRunner.csproj", "{851C3938-D4E2-46A3-B3F0-80F9E1A52B78}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MIExampleTest", "MIExampleTest\MIExampleTest.csproj", "{F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VSCodeExampleTest", "VSCodeExampleTest\VSCodeExampleTest.csproj", "{337B41D3-6674-4628-B309-7723DAFE119D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x64.Build.0 = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x86.Build.0 = Debug|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x64.ActiveCfg = Release|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x64.Build.0 = Release|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x86.ActiveCfg = Release|Any CPU
+ {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x86.Build.0 = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x64.Build.0 = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x86.Build.0 = Debug|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x64.ActiveCfg = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x64.Build.0 = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x86.ActiveCfg = Release|Any CPU
+ {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x86.Build.0 = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x64.Build.0 = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x86.Build.0 = Debug|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x64.ActiveCfg = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x64.Build.0 = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x86.ActiveCfg = Release|Any CPU
+ {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x86.Build.0 = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x64.Build.0 = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x86.Build.0 = Debug|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x64.ActiveCfg = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x64.Build.0 = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x86.ActiveCfg = Release|Any CPU
+ {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal