From 22163befb876c7d4522990003c038f906285dfa7 Mon Sep 17 00:00:00 2001 From: Hecate2 <2474101468@qq.com> Date: Tue, 14 May 2024 12:12:34 +0800 Subject: [PATCH] devpack 3.7.1 support --- Fairy.Debugger.Breakpoint.cs | 20 +++---- Fairy.Debugger.DebugInfo.cs | 112 +++++++++++++---------------------- Fairy.Debugger.cs | 34 +++++++---- 3 files changed, 71 insertions(+), 95 deletions(-) diff --git a/Fairy.Debugger.Breakpoint.cs b/Fairy.Debugger.Breakpoint.cs index febfb00..2d170a4 100644 --- a/Fairy.Debugger.Breakpoint.cs +++ b/Fairy.Debugger.Breakpoint.cs @@ -13,7 +13,7 @@ public partial class Fairy protected virtual JToken SetAssemblyBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToInstructionPointerToOpCode.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); @@ -41,18 +41,14 @@ protected virtual JToken SetAssemblyBreakpoints(JArray _params) protected virtual JToken ListAssemblyBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToInstructionPointerToOpCode.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); } List assemblyBreakpoints = contractScriptHashToAssemblyBreakpoints[scriptHash].ToList(); assemblyBreakpoints.Sort(); - JArray breakpointList = new(); - foreach (uint breakpointInstructionPointer in assemblyBreakpoints) - { - breakpointList.Add(breakpointInstructionPointer); - } + JArray breakpointList = [.. assemblyBreakpoints]; return breakpointList; } @@ -60,7 +56,7 @@ protected virtual JToken ListAssemblyBreakpoints(JArray _params) protected virtual JToken DeleteAssemblyBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToInstructionPointerToOpCode.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); @@ -95,7 +91,7 @@ protected virtual JToken DeleteAssemblyBreakpoints(JArray _params) protected virtual JToken SetSourceCodeBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToAllSourceLineNums.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); @@ -116,7 +112,7 @@ protected virtual JToken SetSourceCodeBreakpoints(JArray _params) i++; JObject json = new(); SourceFilenameAndLineNum breakpoint = new SourceFilenameAndLineNum { sourceFilename = sourceCodeFilename, lineNum = sourceCodeBreakpointLineNum }; - if (contractScriptHashToSourceLineNums[scriptHash].Contains(breakpoint)) + if (contractScriptHashToAllSourceLineNums[scriptHash].Contains(breakpoint)) { sourceCodeBreakpoints.Add(breakpoint); json["filename"] = sourceCodeFilename; @@ -135,7 +131,7 @@ protected virtual JToken SetSourceCodeBreakpoints(JArray _params) protected virtual JToken ListSourceCodeBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToAllSourceLineNums.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); @@ -156,7 +152,7 @@ protected virtual JToken ListSourceCodeBreakpoints(JArray _params) protected virtual JToken DeleteSourceCodeBreakpoints(JArray _params) { UInt160 scriptHash = UInt160.Parse(_params[0]!.AsString()); - if (!contractScriptHashToInstructionPointerToSourceLineNum.ContainsKey(scriptHash)) + if (!contractScriptHashToAllSourceLineNums.ContainsKey(scriptHash)) { string? contractName = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash)?.Manifest.Name; throw new ArgumentException($"Scripthash {scriptHash} {contractName} not registered for debugging. Call SetDebugInfo(scriptHash, nefDbgNfo, dumpNef) first"); diff --git a/Fairy.Debugger.DebugInfo.cs b/Fairy.Debugger.DebugInfo.cs index 72f381b..6111196 100644 --- a/Fairy.Debugger.DebugInfo.cs +++ b/Fairy.Debugger.DebugInfo.cs @@ -10,10 +10,21 @@ namespace Neo.Plugins { public partial class Fairy { - public struct SourceFilenameAndLineNum { public string sourceFilename; public uint lineNum; public string sourceContent; } - public readonly ConcurrentDictionary> contractScriptHashToSourceLineNums = new(); - public readonly ConcurrentDictionary> contractScriptHashToInstructionPointerToSourceLineNum = new(); // stores only assembly instructions which are the beginning of source code lines. Used for setting source code breakpoints; DO NOT FILL sourceContent! - public readonly ConcurrentDictionary> contractScriptHashToAllInstructionPointerToSourceLineNum = new(); // stores a mapping of all instructions to corresponding source code lines. Used only for finding the source code from assembly. + public struct SourceFilenameAndLineNum + { + public string sourceFilename; + public uint lineNum; + public string? sourceContent; // We do not consider sourceContent for equality + public override readonly bool Equals(object? obj) => obj is SourceFilenameAndLineNum other && this.Equals(other); + public readonly bool Equals(SourceFilenameAndLineNum p) => sourceFilename == p.sourceFilename && lineNum == p.lineNum; + public override readonly int GetHashCode() => (sourceFilename, lineNum).GetHashCode(); + public static bool operator ==(SourceFilenameAndLineNum lhs, SourceFilenameAndLineNum rhs) => lhs.Equals(rhs); + public static bool operator !=(SourceFilenameAndLineNum lhs, SourceFilenameAndLineNum rhs) => !(lhs == rhs); + } + public static readonly SourceFilenameAndLineNum defaultSource = new SourceFilenameAndLineNum { sourceFilename = "", lineNum = 0, sourceContent = "" }; + public readonly ConcurrentDictionary> contractScriptHashToAllSourceLineNums = new(); + // stores a mapping of all instructions to corresponding source code lines. Used only for finding the source code from assembly. + public readonly ConcurrentDictionary> contractScriptHashToAllInstructionPointerToSourceLineNum = new(); public readonly ConcurrentDictionary> contractScriptHashToSourceLineFilenames = new(); public readonly ConcurrentDictionary> contractScriptHashToInstructionPointerToOpCode = new(); public readonly ConcurrentDictionary> contractScriptHashToInstructionPointerToCoverage = new(); @@ -54,18 +65,18 @@ protected virtual JToken SetDebugInfo(JArray _params) // give me the base64encode(content) of .nefdbgnfo file JObject nefDbgNfo = (JObject)JObject.Parse(Unzip(Convert.FromBase64String(_params[1]!.AsString())))!; contractScriptHashToNefDbgNfo[scriptHash] = nefDbgNfo; - // https://github.com/devhawk/DumpNef + // https://github.com/devhawk/DumpNef (Neo < 3.5.*) + // https://github.com/Hecate2/DumpNef (Neo >= 3.6) + // https://github.com/neo-project/neo-devpack-dotnet/blob/b65b43f7d39687549ee22d33c1898c809d281ea9/src/Neo.Compiler.CSharp/Program.cs#L297 // dumpnef contract.nef > contract.nef.txt // give me the content of that txt file! string dumpNef = _params[2]!.AsString(); string[] lines = dumpNef.Replace("\r", "").Split("\n", StringSplitOptions.RemoveEmptyEntries); - HashSet sourceFilenameAndLineNums = new(); - contractScriptHashToSourceLineNums[scriptHash] = sourceFilenameAndLineNums; - Dictionary InstructionPointerToSourceLineNum = new(); - contractScriptHashToInstructionPointerToSourceLineNum[scriptHash] = InstructionPointerToSourceLineNum; - Dictionary AllInstructionPointerToSouceLineNum = new(); - contractScriptHashToAllInstructionPointerToSourceLineNum[scriptHash] = AllInstructionPointerToSouceLineNum; + HashSet allSourceFilenameAndLineNums = new(); + contractScriptHashToAllSourceLineNums[scriptHash] = allSourceFilenameAndLineNums; + Dictionary AllInstructionPointerToSourceLineNum = new(); + contractScriptHashToAllInstructionPointerToSourceLineNum[scriptHash] = AllInstructionPointerToSourceLineNum; Dictionary instructionPointerToOpCode = new(); contractScriptHashToInstructionPointerToOpCode[scriptHash] = instructionPointerToOpCode; Dictionary instructionPointerToCoverage = new(); @@ -74,75 +85,36 @@ protected virtual JToken SetDebugInfo(JArray _params) contractScriptHashToSourceLineFilenames[scriptHash] = filenames; uint lineNum; + Match sourceCodeMatch, opCodeMatch; + SourceFilenameAndLineNum sourceFilenameAndLineNum = defaultSource; for (lineNum = 0; lineNum < lines.Length; ++lineNum) { // foreach (var field in typeof(DumpNefPatterns).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) // ConsoleHelper.Info($"{field.Name}: {field.GetValue(dumpNefPatterns)}"); - Match match; - match = dumpNefPatterns.sourceCodeRegex.Match(lines[lineNum]); - if (match.Success) + sourceCodeMatch = dumpNefPatterns.sourceCodeRegex.Match(lines[lineNum]); + if (sourceCodeMatch.Success) // Current line is a line of source code { - GroupCollection sourceCodeGroups = match.Groups; + GroupCollection sourceCodeGroups = sourceCodeMatch.Groups; + string filename = sourceCodeGroups[1].ToString(); uint sourceCodeLineNum = uint.Parse(sourceCodeGroups[2].ToString()); - match = dumpNefPatterns.opCodeRegex.Match(lines[lineNum + 1]); - if (match.Success) - { - GroupCollection opcodeGroups = match.Groups; - uint instructionPointer = uint.Parse(opcodeGroups[1].ToString()); - string filename = sourceCodeGroups[1].ToString(); - filenames.Add(filename); - SourceFilenameAndLineNum sourceFilenameAndLineNum = new SourceFilenameAndLineNum { sourceFilename = filename, lineNum = sourceCodeLineNum };// , sourceContent = sourceCodeGroups[3].ToString() }; - InstructionPointerToSourceLineNum[instructionPointer] = sourceFilenameAndLineNum; - sourceFilenameAndLineNums.Add(sourceFilenameAndLineNum); - } + string sourceContent = sourceCodeGroups[3].ToString(); + filenames.Add(filename); + sourceFilenameAndLineNum = new SourceFilenameAndLineNum { sourceFilename = filename, lineNum = sourceCodeLineNum, sourceContent = sourceContent }; continue; } - match = dumpNefPatterns.opCodeRegex.Match(lines[lineNum]); - if (match.Success) + opCodeMatch = dumpNefPatterns.opCodeRegex.Match(lines[lineNum]); + if (opCodeMatch.Success) { - GroupCollection opcodeGroups = match.Groups; + GroupCollection opcodeGroups = opCodeMatch.Groups; uint instructionPointer = uint.Parse(opcodeGroups[1].ToString()); + AllInstructionPointerToSourceLineNum[instructionPointer] = sourceFilenameAndLineNum; + allSourceFilenameAndLineNums.Add(sourceFilenameAndLineNum); string[] opcodeAndOperand = opcodeGroups[2].ToString().Split(); instructionPointerToOpCode[instructionPointer] = (OpCode)Enum.Parse(typeof(OpCode), opcodeAndOperand[0]); instructionPointerToCoverage[instructionPointer] = false; continue; } } - SourceFilenameAndLineNum parseState = new SourceFilenameAndLineNum { sourceFilename = "Undefined", lineNum = 0, sourceContent = "Undefined" }; - for (lineNum = 0; lineNum < lines.Length; ++lineNum) - { - Match match; - match = dumpNefPatterns.methodStartRegex.Match(lines[lineNum]); - if (match.Success) - { - parseState.sourceFilename = match.Groups[1].ToString(); - parseState.lineNum = 0; - parseState.sourceContent = match.Groups[1].ToString(); - continue; - } - match = dumpNefPatterns.methodEndRegex.Match(lines[lineNum]); - if (match.Success) - { - parseState.sourceFilename = "Undefined"; - parseState.lineNum = 0; - parseState.sourceContent = "Undefined"; - continue; - } - match = dumpNefPatterns.sourceCodeRegex.Match(lines[lineNum]); - if (match.Success) - { - parseState.sourceFilename = match.Groups[1].ToString(); - parseState.lineNum = uint.Parse(match.Groups[2].ToString()); - parseState.sourceContent = match.Groups[3].ToString(); - continue; - } - match = dumpNefPatterns.opCodeRegex.Match(lines[lineNum]); - if (match.Success) - { - AllInstructionPointerToSouceLineNum[uint.Parse(match.Groups[1].ToString())] = parseState; - continue; - } - } JObject json = new(); json[param0] = true; return json; @@ -152,7 +124,7 @@ protected virtual JToken SetDebugInfo(JArray _params) protected virtual JToken ListDebugInfo(JArray _params) { JArray scriptHashes = new JArray(); - foreach (UInt160 s in contractScriptHashToInstructionPointerToSourceLineNum.Keys) + foreach (UInt160 s in contractScriptHashToAllInstructionPointerToSourceLineNum.Keys) { scriptHashes.Add(s.ToString()); } @@ -166,11 +138,7 @@ protected virtual JToken ListFilenamesOfContract(JArray _params) UInt160 scriptHash = UInt160.Parse(scriptHashStr); List filenameList = contractScriptHashToSourceLineFilenames[scriptHash].ToList(); filenameList.Sort(); - JArray filenames = new JArray(); - foreach (string filename in filenameList) - { - filenames.Add(filename); - } + JArray filenames = [.. filenameList]; return filenames; } @@ -182,8 +150,8 @@ protected virtual JToken DeleteDebugInfo(JArray _params) { string str = s!.AsString(); UInt160 scriptHash = UInt160.Parse(str); - contractScriptHashToSourceLineNums.Remove(scriptHash, out _); - contractScriptHashToInstructionPointerToSourceLineNum.Remove(scriptHash, out _); + contractScriptHashToAllSourceLineNums.Remove(scriptHash, out _); + contractScriptHashToAllInstructionPointerToSourceLineNum.Remove(scriptHash, out _); contractScriptHashToSourceLineFilenames.Remove(scriptHash, out _); contractScriptHashToInstructionPointerToOpCode.Remove(scriptHash, out _); contractScriptHashToInstructionPointerToCoverage.Remove(scriptHash, out _); diff --git a/Fairy.Debugger.cs b/Fairy.Debugger.cs index d382932..8d24fb9 100644 --- a/Fairy.Debugger.cs +++ b/Fairy.Debugger.cs @@ -179,15 +179,25 @@ private FairyEngine ExecuteAndCheck(FairyEngine engine, out BreakReason actualBr actualBreakReason |= BreakReason.Return; return engine; } - uint currentInstructionPointer = (uint)engine.CurrentContext.InstructionPointer; - UInt160 currentScripthash = engine.CurrentScriptHash; + uint prevInstructionPointer = (uint)engine.CurrentContext.InstructionPointer; + UInt160 prevScriptHash = engine.CurrentScriptHash; + SourceFilenameAndLineNum prevSource = defaultSource; + if (contractScriptHashToAllSourceLineNums.ContainsKey(prevScriptHash) + && contractScriptHashToAllInstructionPointerToSourceLineNum[prevScriptHash].ContainsKey(prevInstructionPointer) + && contractScriptHashToAllSourceLineNums[prevScriptHash] + .Contains(contractScriptHashToAllInstructionPointerToSourceLineNum[prevScriptHash][prevInstructionPointer])) + prevSource = contractScriptHashToAllInstructionPointerToSourceLineNum[prevScriptHash][prevInstructionPointer]; engine.ExecuteNext(); - if (contractScriptHashToInstructionPointerToCoverage.ContainsKey(currentScripthash) && contractScriptHashToInstructionPointerToCoverage[currentScripthash].ContainsKey(currentInstructionPointer)) - contractScriptHashToInstructionPointerToCoverage[currentScripthash][currentInstructionPointer] = true; + // Set coverage for the previous instruction + if (contractScriptHashToInstructionPointerToCoverage.ContainsKey(prevScriptHash) + && contractScriptHashToInstructionPointerToCoverage[prevScriptHash] + .ContainsKey(prevInstructionPointer)) + contractScriptHashToInstructionPointerToCoverage[prevScriptHash][prevInstructionPointer] = true; if (engine.State == VMState.HALT || engine.State == VMState.FAULT) return engine; + // Handle the current instruction UInt160 currentScriptHash = engine.CurrentScriptHash; - currentInstructionPointer = (uint)engine.CurrentContext.InstructionPointer; + uint currentInstructionPointer = (uint)engine.CurrentContext.InstructionPointer; if ((requiredBreakReason & BreakReason.AssemblyBreakpoint) > 0) { if (contractScriptHashToAssemblyBreakpoints.ContainsKey(currentScriptHash) @@ -202,9 +212,10 @@ private FairyEngine ExecuteAndCheck(FairyEngine engine, out BreakReason actualBr if ((requiredBreakReason & BreakReason.SourceCodeBreakpoint) > 0) { if (contractScriptHashToSourceCodeBreakpoints.ContainsKey(currentScriptHash) - && contractScriptHashToInstructionPointerToSourceLineNum[currentScriptHash].ContainsKey(currentInstructionPointer) + && contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash].ContainsKey(currentInstructionPointer) && contractScriptHashToSourceCodeBreakpoints[currentScriptHash] - .Contains(contractScriptHashToInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer])) + .Contains(contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer]) + && prevSource != contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer]) { engine.State = VMState.BREAK; actualBreakReason |= BreakReason.SourceCodeBreakpoint; @@ -213,10 +224,11 @@ private FairyEngine ExecuteAndCheck(FairyEngine engine, out BreakReason actualBr } if ((requiredBreakReason & BreakReason.SourceCode) > 0) { - if (contractScriptHashToSourceLineNums.ContainsKey(currentScriptHash) - && contractScriptHashToInstructionPointerToSourceLineNum[currentScriptHash].ContainsKey(currentInstructionPointer) - && contractScriptHashToSourceLineNums[currentScriptHash] - .Contains(contractScriptHashToInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer])) + if (contractScriptHashToAllSourceLineNums.ContainsKey(currentScriptHash) + && contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash].ContainsKey(currentInstructionPointer) + && contractScriptHashToAllSourceLineNums[currentScriptHash] + .Contains(contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer]) + && prevSource != contractScriptHashToAllInstructionPointerToSourceLineNum[currentScriptHash][currentInstructionPointer]) { engine.State = VMState.BREAK; actualBreakReason |= BreakReason.SourceCode;