Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add debug info support #298

Merged
merged 16 commits into from
Jul 2, 2020
133 changes: 133 additions & 0 deletions src/Neo.Compiler.MSIL/DebugExport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Neo.Compiler
{
public static class DebugExport
{
private static MyJson.JsonNode_Array GetSequencePoints(IEnumerable<NeoCode> codes, IReadOnlyDictionary<string, int> docMap, IReadOnlyDictionary<int, int> addrConvTable)
{
var points = codes
.Where(code => code.sequencePoint != null)
.Select(code => (code.addr, code.sequencePoint));

var outjson = new MyJson.JsonNode_Array();

foreach (var (address, sequencePoint) in points)
{
var value = string.Format("{0}[{1}]{2}:{3}-{4}:{5}",
addrConvTable.TryGetValue(address, out var newAddress) ? newAddress : address,
docMap[sequencePoint.Document.Url],
sequencePoint.StartLine,
sequencePoint.StartColumn,
sequencePoint.EndLine,
sequencePoint.EndColumn);
outjson.Add(new MyJson.JsonNode_ValueString(value));
}

return outjson;
}

private static MyJson.JsonNode_Array ConvertParamList(IEnumerable<NeoParam> @params)
{
@params ??= Enumerable.Empty<NeoParam>();
var paramsJson = new MyJson.JsonNode_Array();
foreach (var param in @params)
{
var value = string.Format("{0},{1}", param.name, FuncExport.ConvType(param.type));
paramsJson.Add(new MyJson.JsonNode_ValueString(value));
}

return paramsJson;
}

private static MyJson.JsonNode_Array GetMethods(NeoModule module, IReadOnlyDictionary<string, int> docMap, IReadOnlyDictionary<int, int> addrConvTable)
{
var outjson = new MyJson.JsonNode_Array();

foreach (var method in module.mapMethods.Values)
{
if (method.body_Codes.Values.Count == 0)
{
continue;
}

var name = string.Format("{0},{1}",
method._namespace, method.displayName);

var range = string.Format("{0}-{1}",
method.body_Codes.Values.First().addr,
method.body_Codes.Values.Last().addr);

var methodJson = new MyJson.JsonNode_Object();
methodJson.SetDictValue("id", method.name);
methodJson.SetDictValue("name", name);
methodJson.SetDictValue("range", range);
methodJson.SetDictValue("params", ConvertParamList(method.paramtypes));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

params field does not follow the specification. Either it should be parameters or specification should be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this @AnnaShaleva. The specification is correct. I will update the NEON code and I've filed neo-project/neo-debugger#64 to track fixing the neo 3 preview 2 debugger

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, turns out the spec was out of sync w/ the implementation of NEON 2, Neo Smart Contract Debugger for Neo 2 and neo-boa for neo 2. params and return are the correct property names. Sorry for the confusion! Spec update in PR: https://github.com/ngdseattle/design-notes/pull/7

methodJson.SetDictValue("return", FuncExport.ConvType(method.returntype));
methodJson.SetDictValue("variables", ConvertParamList(method.method?.body_Variables));
methodJson.SetDictValue("sequence-points", GetSequencePoints(method.body_Codes.Values, docMap, addrConvTable));
devhawk marked this conversation as resolved.
Show resolved Hide resolved
outjson.Add(methodJson);
}
return outjson;
}

private static MyJson.JsonNode_Array GetEvents(NeoModule module)
{
var outjson = new MyJson.JsonNode_Array();
foreach (var @event in module.mapEvents.Values)
{
var name = string.Format("{0},{1}",
@event._namespace, @event.displayName);

var eventJson = new MyJson.JsonNode_Object();
eventJson.SetDictValue("id", @event.name);
eventJson.SetDictValue("name", name);
eventJson.SetDictValue("params", ConvertParamList(@event.paramtypes));
devhawk marked this conversation as resolved.
Show resolved Hide resolved
outjson.Add(eventJson);
}
return outjson;
}

private static IReadOnlyDictionary<string, int> GetDocumentMap(NeoModule module)
{
return module.mapMethods.Values
.SelectMany(m => m.body_Codes.Values)
.Where(code => code.sequencePoint != null)
.Select(code => code.sequencePoint.Document.Url)
.Distinct()
.Select((d, i) => (d, i))
.ToDictionary(t => t.d, t => t.i);
}

private static MyJson.JsonNode_Array GetDocuments(IReadOnlyDictionary<string, int> docMap)
{
var outjson = new MyJson.JsonNode_Array();
foreach (var doc in docMap.OrderBy(kvp => kvp.Value))
{
Debug.Assert(outjson.Count == doc.Value);
outjson.Add(new MyJson.JsonNode_ValueString(doc.Key));
}
return outjson;
}

public static MyJson.JsonNode_Object Export(NeoModule module, byte[] script, IReadOnlyDictionary<int, int> addrConvTable)
{
var docMap = GetDocumentMap(module);
addrConvTable ??= ImmutableDictionary<int, int>.Empty;

var outjson = new MyJson.JsonNode_Object();
outjson.SetDictValue("hash", FuncExport.ComputeHash(script));
// outjson.SetDictValue("entrypoint", module.mainMethod);
outjson.SetDictValue("documents", GetDocuments(docMap));
outjson.SetDictValue("methods", GetMethods(module, docMap, addrConvTable));
outjson.SetDictValue("events", GetEvents(module));
return outjson;
}
}
}
15 changes: 10 additions & 5 deletions src/Neo.Compiler.MSIL/FuncExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,28 @@ internal static string ConvType(TypeReference t)
return "Unknown:" + type;
}

public static MyJson.JsonNode_Object Export(NeoModule module, byte[] script, Dictionary<int, int> addrConvTable)
public static string ComputeHash(byte[] script)
{
var sha256 = System.Security.Cryptography.SHA256.Create();
byte[] hash256 = sha256.ComputeHash(script);
var ripemd160 = new Neo.Cryptography.RIPEMD160Managed();
var hash = ripemd160.ComputeHash(hash256);

var outjson = new MyJson.JsonNode_Object();

//hash
StringBuilder sb = new StringBuilder();
sb.Append("0x");
for (int i = hash.Length - 1; i >= 0; i--)
{
sb.Append(hash[i].ToString("x02"));
}
outjson.SetDictValue("hash", sb.ToString());
return sb.ToString();
}

public static MyJson.JsonNode_Object Export(NeoModule module, byte[] script, Dictionary<int, int> addrConvTable)
{
var outjson = new MyJson.JsonNode_Object();

//hash
outjson.SetDictValue("hash", ComputeHash(script));

//functions
var methods = new MyJson.JsonNode_Array();
Expand Down
1 change: 1 addition & 0 deletions src/Neo.Compiler.MSIL/MSIL/Conv_Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ private NeoCode Convert1by1(VM.OpCode code, OpCode src, NeoMethod to, byte[] dat
_code.debugline = src.debugline;
_code.debugILAddr = src.addr;
_code.debugILCode = src.code.ToString();
_code.sequencePoint = src.sequencePoint;
}

addr++;
Expand Down
10 changes: 7 additions & 3 deletions src/Neo.Compiler.MSIL/MSIL/ILModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public class ILTryInfo
{
public int addr_Try_Begin = -1;
public int addr_Try_End = -1;
public int addr_Try_End_F = -1;//IL try catch,try final is 2 different Block,need to process that.
public int addr_Try_End_F = -1;//IL try catch,try final is 2 different Block,need to process that.
public Dictionary<string, ILCatchInfo> catch_Infos = new Dictionary<string, ILCatchInfo>();
public int addr_Finally_Begin = -1;
public int addr_Finally_End = -1;
Expand Down Expand Up @@ -225,7 +225,9 @@ public ILMethod(ILType type, MethodDefinition method, ILogger logger = null)
{
foreach (var v in bodyNative.Variables)
{
var indexname = v.VariableType.Name + ":" + v.Index;
var indexname = method.DebugInformation.TryGetName(v, out var varname)
? varname
: v.VariableType.Name + ":" + v.Index;
this.body_Variables.Add(new NeoParam(indexname, v.VariableType));
}
}
Expand All @@ -239,10 +241,11 @@ public ILMethod(ILType type, MethodDefinition method, ILogger logger = null)
};

var sp = method.DebugInformation.GetSequencePoint(code);
if (sp != null)
if (sp != null && !sp.IsHidden)
{
c.debugcode = sp.Document.Url;
c.debugline = sp.StartLine;
c.sequencePoint = sp;
}
c.InitToken(code.Operand);
this.body_Codes.Add(c.addr, c);
Expand Down Expand Up @@ -680,6 +683,7 @@ public class OpCode
public CodeEx code;
public int debugline = -1;
public string debugcode;
public Mono.Cecil.Cil.SequencePoint sequencePoint;
public object tokenUnknown;
public int tokenAddr_Index;
//public int tokenAddr;
Expand Down
20 changes: 18 additions & 2 deletions src/Neo.Compiler.MSIL/Neo.Compiler.MSIL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
<AssemblyTitle>Neo.Compiler.MSIL</AssemblyTitle>
<Version>3.0.0-preview2</Version>
<Authors>The Neo Project</Authors>
<TargetFrameworks>netcoreapp3.1;netstandard2.1</TargetFrameworks>
<OutputType>Exe</OutputType>
<AssemblyName>neon</AssemblyName>
<PackageId>Neo.Compiler.MSIL</PackageId>
<PackageTags>NEO;Blockchain;Smart Contract;Compiler</PackageTags>
<PackageProjectUrl>https://github.com/neo-project/neo-devpack-dotnet</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -21,6 +19,24 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<!--
https://johan-v-r.github.io/2018/09/05/NET-Core-Global-Tools-Configuration/
`dotnet pack` to build .NET Core 3.1 global tool
`dotnet pack /p:SharedLibrary=true` to build .NET Standard 2.0 library package
-->

<PropertyGroup Condition="'$(SharedLibrary)' != true">
<OutputType>Exe</OutputType>
<PackageId>Neo.Neon</PackageId>
<PackAsTool>true</PackAsTool>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<PropertyGroup Condition="'$(SharedLibrary)' == true">
<PackageId>Neo.Compiler.MSIL</PackageId>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
Expand Down
1 change: 1 addition & 0 deletions src/Neo.Compiler.MSIL/NeoModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public class NeoCode
public int srcaddr;
public int[] srcaddrswitch;
public string srcfunc;
public Mono.Cecil.Cil.SequencePoint sequencePoint;

public override string ToString()
{
Expand Down
41 changes: 40 additions & 1 deletion src/Neo.Compiler.MSIL/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
Expand Down Expand Up @@ -152,6 +153,7 @@ public static int Compile(Options options)
byte[] bytes;
int bSucc = 0;
string jsonstr = null;
string debugstr = null;
NeoModule module = null;

// Convert and build
Expand Down Expand Up @@ -192,6 +194,19 @@ public static int Compile(Options options)
log.Log("gen abi Error:" + err.ToString());
return -1;
}

try
{
var outjson = DebugExport.Export(module, bytes, addrConvTable);
StringBuilder sb = new StringBuilder();
outjson.ConvertToStringWithFormat(sb, 0);
debugstr = sb.ToString();
log.Log("gen debug succ");
}
catch (Exception err)
{
log.Log("gen debug Error:" + err.ToString());
}
}
catch (Exception err)
{
Expand Down Expand Up @@ -244,6 +259,30 @@ public static int Compile(Options options)
return -1;
}

try
{
string debugname = onlyname + ".debug.json";
string debugzip = onlyname + ".nefdbgnfo";

var tempName = Path.GetTempFileName();
File.Delete(tempName);
File.WriteAllText(tempName, debugstr);

File.Delete(debugzip);
using (var archive = ZipFile.Open(debugzip, ZipArchiveMode.Create))
{
archive.CreateEntryFromFile(tempName, Path.GetFileName(debugname));
}
File.Delete(tempName);
log.Log("write:" + debugzip);
bSucc++;
}
catch (Exception err)
{
log.Log("Write debug Error:" + err.ToString());
return -1;
}

try
{
var features = module == null ? ContractFeatures.NoProperty : module.attributes
Expand Down Expand Up @@ -284,7 +323,7 @@ public static int Compile(Options options)
{
}

if (bSucc == 3)
if (bSucc == 4)
{
log.Log("SUCC");
return 0;
Expand Down
33 changes: 33 additions & 0 deletions tests/Neo.Compiler.MSIL.UnitTests/UnitTest_DebugInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Compiler.MSIL.UnitTests.Utils;
using System;
using System.Linq;

namespace Neo.Compiler.MSIL
{
[TestClass]
public class UnitTest_DebugInfo
{
[TestMethod]
public void Test_DebugInfo()
{
var testengine = new TestEngine();
testengine.AddEntryScript("./TestClasses/Contract_Event.cs");
var debugInfo = testengine.ScriptEntry.debugInfo;
Assert.IsTrue(debugInfo.HaveDictItem("hash"));
Assert.AreEqual(debugInfo["hash"].type, MyJson.JsonType.Value_String);
Assert.IsTrue(debugInfo.HaveDictItem("documents"));
Assert.AreEqual(debugInfo["documents"].type, MyJson.JsonType.Array);
Assert.IsTrue(debugInfo["documents"].AsList().Count == 1);
Assert.IsTrue(debugInfo["documents"].AsList().All(n => n.type == MyJson.JsonType.Value_String));
Assert.IsTrue(debugInfo.HaveDictItem("methods"));
Assert.AreEqual(debugInfo["methods"].type, MyJson.JsonType.Array);
Assert.AreEqual(debugInfo["methods"].AsList().Count, 1);
Assert.AreEqual(debugInfo["methods"].AsList()[0].asDict()["name"].AsString(), "Neo.Compiler.MSIL.UnitTests.TestClasses.Contract_Event,main");
Assert.IsTrue(debugInfo.HaveDictItem("events"));
Assert.AreEqual(debugInfo["events"].type, MyJson.JsonType.Array);
Assert.AreEqual(debugInfo["events"].AsList().Count, 1);
Assert.AreEqual(debugInfo["events"].AsList()[0].asDict()["name"].AsString(), "Neo.Compiler.MSIL.UnitTests.TestClasses.Contract_Event,transfer");
}
}
}
12 changes: 12 additions & 0 deletions tests/Neo.Compiler.MSIL.UnitTests/Utils/BuildScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class BuildScript
public byte[] finalNEF { get; private set; }
public MyJson.JsonNode_Object finialABI { get; private set; }
public string finalManifest { get; private set; }
public MyJson.JsonNode_Object debugInfo { get; private set; }

public BuildScript()
{
Expand Down Expand Up @@ -86,6 +87,17 @@ public void Build(Stream fs, Stream fspdb, bool optimizer)
return;
}

try
{
debugInfo = DebugExport.Export(converterIL.outModule, finalNEF, addrConvTable);
}
catch (Exception err)
{
log.Log("Gen debugInfo Error:" + err.ToString());
this.Error = err;
return;
}

try
{
var features = converterIL.outModule == null ? ContractFeatures.NoProperty : converterIL.outModule.attributes
Expand Down