使用IL.Emit调用Memory.Slice时出现未定义行为的问题求助
问题:DynamicMethod调用ReadOnlyMemory.Slice时参数损坏异常
尝试编写接收ReadOnlyMemory<byte>的DynamicMethod动态方法,但调用Slice切割内存时出现参数损坏的异常行为,复现代码如下:
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")); // 正常工作,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")); // 参数损坏,start=4, end=随机值,mem不可读 // 打印结果 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)})); // 抛出ArgumentOutOfRangeException // 打印结果 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
问题成因与修正方案
1. 栈不平衡导致参数错乱
checkSlice方法返回的ReadOnlyMemory<byte>是值类型,调用方法后会将返回值压入IL栈中。第一个checkSlice调用后你用OpCodes.Pop清理了栈,但第二个调用后没有执行Pop操作,栈上残留的返回值会干扰后续指令的参数传递,导致checkSlice_2的参数被错误的栈数据覆盖,出现参数损坏的情况。
2. 错误调用ReadOnlyMemory.Slice实例方法
Slice是ReadOnlyMemory<T>的实例方法,你使用OpCodes.Ldarga_S加载参数0的地址是错误的。对于值类型的实例方法,this参数需要直接加载实例本身(用OpCodes.Ldarg 0),而非地址。Ldarga_S会将参数的地址压栈,导致方法接收到的this参数类型不匹配,进而引发ArgumentOutOfRangeException。
修正后的关键IL代码
// 修正checkSlice_2的栈平衡 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")); il.Emit(OpCodes.Pop); // 新增Pop清理返回值 // 修正Slice调用 il.Emit(OpCodes.Ldarg, 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[] { typeof(int), typeof(int) }));
修正后,IL栈会保持平衡,参数传递正确,方法可以正常执行。
内容的提问来源于stack exchange,提问作者user10619713
相关产品推荐
相关产品推荐

