尝试使用 il.Emit 进行 Memory.Slice 时出现未定义的行为

问题描述 投票:0回答:1

我正在尝试编写一个动态方法,它接受

ReadOnlyMemory<byte>
并处理它。但当我试图分割记忆时,我经历了非常奇怪的行为。切片(或同一上下文中的任何其他函数)的参数似乎已损坏。这是重现该问题的顶级语句示例。

using System.Reflection.Emit;

DynamicMethod m = new DynamicMethod("EMITD_arraydeser_" + typeof(int[]), null, new Type[1] { typeof(ReadOnlyMemory<byte>) }, true);
var il = m.GetILGenerator();
il.DeclareLocal(typeof(ReadOnlyMemory<byte>));


il.Emit(OpCodes.Ldarg, 0);
il.Emit(OpCodes.Ldc_I4, 0);
il.Emit(OpCodes.Ldc_I4, 4);
il.EmitWriteLine("checkSlice_1....");
il.Emit(OpCodes.Call, typeof(Helpers).GetMethod("checkSlice")); // This works as expected, with mem = ReadOnlyMemory<byte>[16], start = 0, end = 4
il.Emit(OpCodes.Pop);

il.Emit(OpCodes.Ldarg, 0);
il.Emit(OpCodes.Ldc_I4, 0);
il.Emit(OpCodes.Ldc_I4, 4);
il.EmitWriteLine("checkSlice_2....");
il.Emit(OpCodes.Call, typeof(Helpers).GetMethod("checkSlice")); // This gets corrupted, with start = 4, end = random value and mem not being readable

// Print the result
il.Emit(OpCodes.Call, typeof(ReadOnlyMemory<byte>).GetMethod("ToArray"));
il.Emit(OpCodes.Call, typeof(BitConverter).GetMethod("ToString", new Type[] { typeof(byte[]) }));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

il.Emit(OpCodes.Ldarga_S, 0);
il.Emit(OpCodes.Ldc_I4, 0);
il.Emit(OpCodes.Ldc_I4, 4);
il.EmitWriteLine("slice_1....");

il.Emit(OpCodes.Call, typeof(ReadOnlyMemory<byte>).GetMethod("Slice", new Type[2] {typeof(int), typeof(int)})); // throws ArgumentOutOfRangeException

// Print the result
il.Emit(OpCodes.Call, typeof(ReadOnlyMemory<byte>).GetMethod("ToArray"));
il.Emit(OpCodes.Call, typeof(BitConverter).GetMethod("ToString", new Type[] { typeof(byte[]) }));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

il.Emit(OpCodes.Ret);


var del = m.CreateDelegate<Action<ReadOnlyMemory<byte>>>();
var s = new byte[] {
    3,0,0,0,
    0xAA,0xBB,0,0,
    0xCC,0xDD,0,0,
    0xEE,0xFF,0,0,
};
ReadOnlyMemory<byte> buff = new(s);
del(buff);

class Helpers
{
    public static ReadOnlyMemory<byte> checkSlice(ReadOnlyMemory<byte> mem, int start, int end)
    {
        
        try
        {
            Console.WriteLine($"{start}:{end}, {mem}");
            return mem.Slice(start, end);
        }
        catch (Exception)
        {
            Console.WriteLine("Exception on slice...." + start + ":" + end);
            return new ReadOnlyMemory<byte>();
        }
        
    } 
}

这是实际输出:

checkSlice_1....
0:4, System.ReadOnlyMemory<Byte>[16]
checkSlice_2....
Exception on slice....4:1616289928
slice_1....
Unhandled exception. System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'start')
at EMITD_arraydeser_System.Int32[](ReadOnlyMemory`1)
at Program.<Main>$(String[] args) in C:\PATH\Program.cs:line 49

我怀疑它与我用来打印ReadOnlyMemory(在“

// Print the result
”注释下)的3行有关,我从切片返回(mb因为ReadOnlyMemory是一个值类型?),但我不知道了解这如何影响之前执行的函数调用。遗憾的是,据我所知,无法查看生成的实际 IL。

我对 IL 的了解非常有限,而且我发现参考文献很难理解,所以我希望比我聪明的人可以解释它是如何工作的。 :)

c# cil reflection.emit
1个回答
0
投票

因为 ReadOnlyMemory 是值类型,所以像 ToArray 这样的实例方法要求您传递 ReadOnlyMemory 实例的 address,而不是实例本身。为此,请将 StlocLdloca 操作码与您声明的局部变量结合使用:

var il = m.GetILGenerator();
var local = il.DeclareLocal(typeof(ReadOnlyMemory<byte>)); // Save the LocalBuilder

il.Emit(OpCodes.Ldarg, 0);
il.Emit(OpCodes.Ldc_I4, 0);
il.Emit(OpCodes.Ldc_I4, 4);
il.Emit(OpCodes.Call, typeof(Helpers).GetMethod("checkSlice"));
il.Emit(OpCodes.Stloc, local); // Save the ReadOnlyMemory<byte> instance to the local variable

// Print the result
il.Emit(OpCodes.Ldloca, local); // IMPORTANT: Push the address of the local variable
il.Emit(OpCodes.Call, typeof(ReadOnlyMemory<byte>).GetMethod("ToArray"));
il.Emit(OpCodes.Call, typeof(BitConverter).GetMethod("ToString", new Type[] { typeof(byte[]) }));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

il.Emit(OpCodes.Ret);

您的原始代码导致 ReadOnlyMemory.ToArray 访问无效的内存地址,从而导致未定义的行为。

© www.soinside.com 2019 - 2024. All rights reserved.