Skip to content
118 changes: 69 additions & 49 deletions src/Neo.CLI/CLI/MainService.Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.SmartContract;
using Neo.VM;
using Neo.Wallets;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace Neo.CLI
Expand Down Expand Up @@ -56,7 +56,8 @@ private void OnParseCommand(string value)

if (result != null)
{
Console.WriteLine($"{pair.Key,-30}\t{result}");
ConsoleHelper.Info("", "-----", pair.Key, "-----");
ConsoleHelper.Info("", result, Environment.NewLine);
any = true;
}
}
Expand Down Expand Up @@ -417,62 +418,81 @@ private static string Base64Fixed(string str)
[ParseFunction("Base64 Smart Contract Script Analysis")]
private string? ScriptsToOpCode(string base64)
{
Script script;
try
{
var scriptData = Convert.FromBase64String(base64);
script = new Script(scriptData.ToArray(), true);
var bytes = Convert.FromBase64String(base64);
var sb = new StringBuilder();
var line = 0;

foreach (var instruct in new VMInstruction(bytes))
{
if (instruct.OperandSize == 0)
sb.AppendFormat("L{0:D04}:{1:X04} {2}{3}", line, instruct.Position, instruct.OpCode, Environment.NewLine);
else
sb.AppendFormat("L{0:D04}:{1:X04} {2,-10}{3}{4}", line, instruct.Position, instruct.OpCode, DecodeOperand(instruct), Environment.NewLine);
line++;
}

return sb.ToString();
}
catch (Exception)
catch
{
return null;
}
return ScriptsToOpCode(script);
}

private string ScriptsToOpCode(Script script)
private string DecodeOperand(VMInstruction instruction)
{
//Initialize all InteropService
var dic = new Dictionary<uint, string>();
ApplicationEngine.Services.ToList().ForEach(p => dic.Add(p.Value.Hash, p.Value.Name));

//Analyzing Scripts
var ip = 0;
Instruction instruction;
var result = new List<string>();
while (ip < script.Length && (instruction = script.GetInstruction(ip)) != null)
{
ip += instruction.Size;

var op = instruction.OpCode;

if (op.ToString().StartsWith("PUSHINT"))
{
var operand = instruction.Operand.ToArray();
result.Add($"{op} {new BigInteger(operand)}");
}
else if (op == OpCode.SYSCALL)
{
var operand = instruction.Operand.ToArray();
result.Add($"{op} {dic[BitConverter.ToUInt32(operand)]}");
}
else
{
if (!instruction.Operand.IsEmpty && instruction.Operand.Length > 0)
{
var operand = instruction.Operand.ToArray();
var ascii = Encoding.Default.GetString(operand);
ascii = ascii.Any(p => p < '0' || p > 'z') ? operand.ToHexString() : ascii;

result.Add($"{op} {(operand.Length == 20 ? new UInt160(operand).ToString() : ascii)}");
}
else
{
result.Add($"{op}");
}
}
}
return Environment.NewLine + string.Join("\r\n", result.ToArray());
var operand = instruction.Operand[instruction.OperandPrefixSize..].ToArray();
var asStr = Encoding.UTF8.GetString(operand);
var readable = asStr.All(char.IsAsciiLetterOrDigit);
return instruction.OpCode switch
{
VM.OpCode.JMP or
VM.OpCode.JMPIF or
VM.OpCode.JMPIFNOT or
VM.OpCode.JMPEQ or
VM.OpCode.JMPNE or
VM.OpCode.JMPGT or
VM.OpCode.JMPLT or
VM.OpCode.CALL or
VM.OpCode.ENDTRY => $"[{checked(instruction.Position + instruction.AsToken<byte>()):X08}]",
VM.OpCode.PUSHA or
VM.OpCode.JMP_L or
VM.OpCode.JMPIF_L or
VM.OpCode.JMPIFNOT_L or
VM.OpCode.JMPEQ_L or
VM.OpCode.JMPNE_L or
VM.OpCode.JMPGT_L or
VM.OpCode.JMPLT_L or
VM.OpCode.CALL_L or
VM.OpCode.ENDTRY_L => $"[{checked(instruction.Position + instruction.AsToken<int>()):X08}]",
VM.OpCode.TRY => $"[{instruction.AsToken<byte>():X02}, {instruction.AsToken<byte>(1):X02}]",
VM.OpCode.INITSLOT => $"{instruction.AsToken<byte>()}, {instruction.AsToken<byte>(1)}",
VM.OpCode.TRY_L => $"[{checked(instruction.Position + instruction.AsToken<int>()):X08}, {checked(instruction.Position + instruction.AsToken<int>()):X08}]",
VM.OpCode.CALLT => $"[{checked(instruction.Position + instruction.AsToken<ushort>()):X08}]",
VM.OpCode.NEWARRAY_T or
VM.OpCode.ISTYPE or
VM.OpCode.CONVERT => $"{instruction.AsToken<byte>():X02}",
VM.OpCode.STLOC or
VM.OpCode.LDLOC or
VM.OpCode.LDSFLD or
VM.OpCode.STSFLD or
VM.OpCode.LDARG or
VM.OpCode.STARG or
VM.OpCode.INITSSLOT => $"{instruction.AsToken<byte>()}",
VM.OpCode.PUSHINT8 => $"{instruction.AsToken<sbyte>()}",
VM.OpCode.PUSHINT16 => $"{instruction.AsToken<short>()}",
VM.OpCode.PUSHINT32 => $"{instruction.AsToken<int>()}",
VM.OpCode.PUSHINT64 => $"{instruction.AsToken<long>()}",
VM.OpCode.PUSHINT128 or
VM.OpCode.PUSHINT256 => $"{new BigInteger(operand)}",
VM.OpCode.SYSCALL => $"[{ApplicationEngine.Services[Unsafe.As<byte, uint>(ref operand[0])].Name}]",
VM.OpCode.PUSHDATA1 or
VM.OpCode.PUSHDATA2 or
VM.OpCode.PUSHDATA4 => readable ? $"{Convert.ToHexString(operand)} // {asStr}" : Convert.ToHexString(operand),
_ => readable ? $"\"{asStr}\"" : $"{Convert.ToHexString(operand)}",
};
}

/// <summary>
Expand Down
113 changes: 113 additions & 0 deletions src/Neo.CLI/Tools/VMInstruction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// VMInstruction.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.VM;
using System;
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Neo.CLI
{
using System.Runtime.CompilerServices;

[DebuggerDisplay("OpCode={OpCode}, OperandSize={OperandSize}")]
internal sealed class VMInstruction : IEnumerable<VMInstruction>
{
private const int OpCodeSize = 1;

public int Position { get; private init; }
public OpCode OpCode { get; private init; }
public ReadOnlyMemory<byte> Operand { get; private init; }
public int OperandSize { get; private init; }
public int OperandPrefixSize { get; private init; }

private static readonly int[] s_operandSizeTable = new int[256];
private static readonly int[] s_operandSizePrefixTable = new int[256];

private readonly ReadOnlyMemory<byte> _script;

public VMInstruction(ReadOnlyMemory<byte> script, int start = 0)
{
if (script.IsEmpty)
throw new Exception("Bad Script.");

var opcode = (OpCode)script.Span[start];

if (Enum.IsDefined(opcode) == false)
throw new InvalidDataException($"Invalid opcode at Position: {start}.");

OperandPrefixSize = s_operandSizePrefixTable[(int)opcode];
OperandSize = OperandPrefixSize switch
{
0 => s_operandSizeTable[(int)opcode],
1 => script.Span[start + 1],
2 => BinaryPrimitives.ReadUInt16LittleEndian(script.Span[(start + 1)..]),
4 => unchecked((int)BinaryPrimitives.ReadUInt32LittleEndian(script.Span[(start + 1)..])),
_ => throw new InvalidDataException($"Invalid opcode prefix at Position: {start}."),
};

OperandSize += OperandPrefixSize;

if (start + OperandSize + OpCodeSize > script.Length)
throw new IndexOutOfRangeException("Operand size exceeds end of script.");

Operand = script.Slice(start + OpCodeSize, OperandSize);

_script = script;
OpCode = opcode;
Position = start;
}

static VMInstruction()
{
foreach (var field in typeof(OpCode).GetFields(BindingFlags.Public | BindingFlags.Static))
{
var attr = field.GetCustomAttribute<OperandSizeAttribute>();
if (attr == null) continue;

var index = (uint)(OpCode)field.GetValue(null)!;
s_operandSizeTable[index] = attr.Size;
s_operandSizePrefixTable[index] = attr.SizePrefix;
}
}

public IEnumerator<VMInstruction> GetEnumerator()
{
var nip = Position + OperandSize + OpCodeSize;
yield return this;

VMInstruction? instruct;
for (var ip = nip; ip < _script.Length; ip += instruct.OperandSize + OpCodeSize)
yield return instruct = new VMInstruction(_script, ip);
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();

public T AsToken<T>(uint index = 0)
where T : unmanaged
{
var size = Unsafe.SizeOf<T>();

if (size > OperandSize)
throw new ArgumentOutOfRangeException(nameof(T), $"SizeOf {typeof(T).FullName} is too big for operand. OpCode: {OpCode}.");
if (size + index > OperandSize)
throw new ArgumentOutOfRangeException(nameof(index), $"SizeOf {typeof(T).FullName} + {index} is too big for operand. OpCode: {OpCode}.");

var bytes = Operand[..OperandSize].ToArray();
return Unsafe.As<byte, T>(ref bytes[index]);
}
}
}