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 raw bytes column to IL instruction editor #215

Open
UserUnknownFactor opened this issue Jun 28, 2023 · 4 comments
Open

Add raw bytes column to IL instruction editor #215

UserUnknownFactor opened this issue Jun 28, 2023 · 4 comments
Labels
enhancement New feature or request

Comments

@UserUnknownFactor
Copy link

UserUnknownFactor commented Jun 28, 2023

Problem Description

It's currently difficult to check how changing an IL code instruction will change its bytes after editing or what they are initially.

Proposal

It'd be nice to have a (maybe toggleable from options) column with raw bytes of the entire instruction as a feature for learning and research purposes like this (replacing the question marks with the actual data):
dnspy2023-06-26

Additional Context

I tried to implement it myself but couldn't figure out how to convert operands to their byte representations.
It would be nice to have columns with RVA and FileOffset values too.
And it would be even nicer if we could copy the bytes to clipboard as text.

@UserUnknownFactor UserUnknownFactor added the enhancement New feature or request label Jun 28, 2023
@GazziFX
Copy link

GazziFX commented Jun 28, 2023

IIRC new data cant be immediately shown as raw bytes, because it needs to assign tokens, offsets, etc which happens on writing module

@UserUnknownFactor
Copy link
Author

Maybe show old data first and ?? marks on changes then?

@ElektroKill
Copy link
Member

Hi,

This is a feature I can definitely see in dnSpyEx. I see you have already started working on implementing this yourself. If we are to implement such a feature, I suggest adding an option for this in the dnSpyEx settings. As for the ??, it is impossible to know the operand bytes before the module is written. We can however display the original operand bytes up until the moment the body is edited. These actual bytes can be obtained using IInstructionBytesReader interface created from InstructionBytesReader class. Displaying RVA or FileOffset would not really be possible as this information is too fragile and cannot be predetermined before writing the module to disk. If you are interested in implementing such a feature into dnSpyEx, go ahead and open a draft PR where we can discuss implementation details further!

@UserUnknownFactor
Copy link
Author

I doubt my code will be of any real use because I'm neither a proper C# programmer nor have a faintest clue about the codebase or motivated to learn it. I simply modified the most suitable part I found to produce the result I expected.

If you want it anyway, here's almost my entire modification of Extensions/dnSpy.AsmEditor/MethodBody/InstructionVM.cs:

public static void Revert(ref byte[] outArray)
{
	int hi_i = outArray.Length - 1;
	if (hi_i == 0)
		return;
	for (int low_i = 0; low_i < outArray.Length / 2; low_i++)
	{
		(outArray[low_i], outArray[hi_i]) = (outArray[hi_i], outArray[low_i]);
		hi_i--;
	}
}

public static byte[] TransformToBytes<T>(T num, int byteLength, bool correct = false)
{
	var res = new byte[byteLength];
	var converted = (byte[])typeof(BitConverter)
	    .GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(T) })
	    .Invoke(null, new[] { num as object });
	Array.Copy(converted, res, byteLength);
	if (correct)
		Revert(ref res);
	return res;
}

public static string FormatAsHexBytes(byte[] data)
{
	return String.Join(" ", data.Select(x => x.ToString("X2")));
}

public string Bytes {
	get {
		if (!string.IsNullOrEmpty(bytes)) return bytes;
		var inst = Code.ToOpCode();
		var inst_size = inst.Size;
		var op_size = GetSize() - inst_size;
		var inst_bytes = TransformToBytes((ulong)Code, inst_size, true);
		string? op_data = null;
		if (op_size > 0) {
			switch (inst.OperandType) {
    			//case OperandType.InlineVar:
    			case OperandType.InlineI:
    			    op_data = " " + FormatAsHexBytes(TransformToBytes<int>(InstructionOperandVM.Int32.Value, op_size));
    			    break;
    			case OperandType.InlineI8:
    			    op_data = " " + FormatAsHexBytes(TransformToBytes<long>(InstructionOperandVM.Int64.Value, op_size));
    			    break;
    			case OperandType.InlineR:
    			    op_data = " " + FormatAsHexBytes(TransformToBytes<double>(InstructionOperandVM.Double.Value, op_size));
    			    break;
    			case OperandType.ShortInlineR:
    			    op_data = " " + FormatAsHexBytes(TransformToBytes<float>(InstructionOperandVM.Single.Value, op_size));
    			    break;
    			//case OperandType.ShortInlineVar:
    			case OperandType.ShortInlineI:
    				if (Code == Code.Ldc_I4_S) {
    					op_data = " " + FormatAsHexBytes(TransformToBytes<sbyte>(InstructionOperandVM.SByte.Value, op_size));
    					break;
    				}
    				op_data = " " + FormatAsHexBytes(TransformToBytes<byte>(InstructionOperandVM.Byte.Value, op_size));
    				break;
    			default:
    				break;
    			}
		}
		if (op_size > 0 && string.IsNullOrWhiteSpace(op_data))
    			op_data = new StringBuilder(3 * op_size).Insert(0, " ??", op_size).ToString();
		bytes = FormatAsHexBytes(inst_bytes) + (op_size > 0 ? op_data : "");
		return bytes;
	}
	set {
		if (bytes != value) {
			bytes = value;
			OnPropertyChanged(nameof(Bytes));
		}
	}
}
string bytes;

Additional modifications to add the column to the editor in these files:

Extensions/dnSpy.AsmEditor/MethodBody/MethodBodyControl.xaml
Extensions/dnSpy.AsmEditor/Properties/dnSpy.AsmEditor.Resources.Designer.cs
Extensions/dnSpy.AsmEditor/Properties/dnSpy.AsmEditor.Resources.resx

are too trivial to mention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants