C#与ASM绿色盲模拟项目触发System.AccessViolationException问题排查
解决C#与ASM互操作中的System.AccessViolationException异常
问题概述
开发C#与ASM结合的绿色盲模拟项目时,调用ASM生成的DLL处理图像数据时触发System.AccessViolationException,错误提示:
尝试读取或写入受保护的内存。这通常指示其他内存已损坏。
项目细节:
- 主应用:C#(.NET Framework x64)
- 图像处理模块:x64汇编
- 功能:加载图像,通过ASM/C#模拟绿色盲效果并保存
错误根源分析
从提供的ASM和C#代码来看,异常由以下多个问题共同导致:
1. 调用约定与参数映射错误
C#的[DllImport]默认使用StdCall调用约定,但x64汇编遵循微软FastCall约定,且你的ASM代码完全搞混了参数传递的寄存器顺序:
- 微软x64约定中,前4个参数依次存入
rcx、rdx、r8、r9,剩余参数入栈 - 你错误地使用
rdi等非参数寄存器接收参数,直接导致后续逻辑使用错误的内存地址
2. 寄存器操作逻辑混乱
ASM代码中存在寄存器覆盖、参数值丢失的问题:
- 初始化阶段就覆盖了传入的参数寄存器,导致后续计算使用无效值
- 处理像素时错误覆盖
rdx寄存器(原本指向处理后图像的指针),引发写入地址错误
3. 忽略图像Stride导致内存越界
C#传递了stride参数,但ASM完全未使用:
- 图像内存并非严格按
宽度*3字节排列,而是按stride(对齐后的行字节数)存储 - 直接按
+3递增指针会跳过行对齐的填充字节,最终访问到受保护内存区域
4. 像素格式与通道顺序错误
ASM默认按RGB顺序处理3字节像素,但C#的Bitmap默认是BGR通道顺序,且未处理32位RGBA格式的情况,直接导致内存访问偏移错误
5. 并行处理逻辑未实现
ASM代码仅写了并行计算的初始化代码,但实际还是单线程循环,线程数参数未被正确使用
修复方案
步骤1:修正ASM的调用约定与参数映射
修改ASM代码,严格遵循微软x64调用约定,同时修正通道顺序和计算逻辑:
.code DeuteranopiaAsm proc ; 微软x64调用约定参数顺序: ; rcx = originalImage (原始图像指针) ; rdx = processedImage (处理后图像指针) ; r8d = pixelCount (总像素数) ; r9d = stride (图像行字节数) ; [rbp+32] = threadCount (第5个参数,栈上) push rbp mov rbp, rsp sub rsp, 40 ; 分配栈空间,保持16字节对齐 ; 保存非易失性寄存器(x64约定需保存rbx, rsi, rdi, r12-r15) push rbx push rsi push rdi push r12 push r13 push r14 push r15 ; 读取参数 mov rsi, rcx ; rsi = 原始图像指针 mov rdi, rdx ; rdi = 处理后图像指针 mov r12d, r8d ; r12d = 总像素数 mov r13d, r9d ; r13d = stride mov r14d, [rbp+32] ; r14d = threadCount ; 计算单线程处理像素数(先实现单线程,再扩展并行) xor rax, rax mov eax, r12d xor edx, edx div r14d mov r15d, eax ; r15d = 每个线程处理像素数 ; 单线程处理逻辑 mov rbx, 0 ; rbx = 当前处理像素索引 ProcessLoop: cmp rbx, r12 jge Done ; 计算当前像素的内存偏移:行偏移 + 像素偏移 mov eax, ebx xor edx, edx div r13d ; eax = 行索引,edx = 行内字节偏移(像素索引*3) imul rax, rax, r13 ; rax = 行起始偏移 add rax, rdx ; rax = 总偏移 ; 读取原始像素(BGR顺序,对应C# Bitmap默认格式) movzx edx, byte ptr [rsi+rax] ; B movzx ecx, byte ptr [rsi+rax+1] ; G movzx eax, byte ptr [rsi+rax+2] ; R ; 绿色盲转换计算(与C#逻辑一致) ; 处理R通道:R*0.625 + G*0.375 imul eax, eax, 625 imul r8d, ecx, 375 add eax, r8d shr eax, 10 ; 钳位到0-255 cmp eax, 255 cmovg eax, dword ptr [rel max_val] cmp eax, 0 cmovl eax, dword ptr [rel min_val] ; 处理G通道:G*0.7 imul ecx, ecx, 7 shr ecx, 3 cmp ecx, 255 cmovg ecx, dword ptr [rel max_val] cmp ecx, 0 cmovl ecx, dword ptr [rel min_val] ; 处理B通道:B*0.8 imul edx, edx, 4 mov r9d, edx shr r9d, 2 sub edx, r9d ; B*4/5 ≈ B*0.8 cmp edx, 255 cmovg edx, dword ptr [rel max_val] cmp edx, 0 cmovl edx, dword ptr [rel min_val] ; 写入处理后图像(保持BGR顺序) mov byte ptr [rdi+rax], dl ; B通道 mov byte ptr [rdi+rax+1], cl ; G通道 mov byte ptr [rdi+rax+2], al ; R通道 inc rbx ; 处理下一个像素 jmp ProcessLoop Done: ; 恢复非易失性寄存器 pop r15 pop r14 pop r13 pop r12 pop rdi pop rsi pop rbx add rsp, 40 pop rbp ret ; 钳位常量 max_val dword 255 min_val dword 0 DeuteranopiaAsm endp end
步骤2:修正C#的DllImport调用约定
显式指定调用约定,避免匹配错误:
[DllImport(@"C:\Users\kacpe\Desktop\home\Programing\studia\ASM-SEM-5\Projekt\x64\Debug\ModuleAsm.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void DeuteranopiaAsm(IntPtr originalImage, IntPtr processedImage, int pixelCount, int stride, int threadCount);
步骤3:添加像素格式检查与转换
在C#调用ASM前,统一图像格式为24位BGR,避免格式不匹配:
if (_originalImage.PixelFormat != PixelFormat.Format24bppRgb) { _originalImage = _originalImage.Clone(new Rectangle(0,0,_originalImage.Width,_originalImage.Height), PixelFormat.Format24bppRgb); } _processedImage = new Bitmap(_originalImage.Width, _originalImage.Height, PixelFormat.Format24bppRgb);
步骤4:实现并行处理(可选)
若需多线程处理,建议在C#中拆分图像区域,再调用ASM处理子区域,避免在ASM中编写复杂的线程管理逻辑:
// 示例:C#拆分区域后调用ASM Parallel.For(0, threadCount, threadIndex => { int startPixel = threadIndex * pixelCount / threadCount; int endPixel = (threadIndex == threadCount-1) ? pixelCount : (threadIndex+1)*pixelCount/threadCount; // 调用ASM处理startPixel到endPixel的区域(需修改ASM支持区域处理) });
调试建议
- 使用Visual Studio混合模式调试,同时调试C#和ASM,设置断点观察寄存器值与内存访问情况
- 检查
stride值是否正确,验证行偏移计算逻辑 - 打印像素格式信息,确认通道顺序与ASM处理逻辑匹配
内容的提问来源于stack exchange,提问作者Kacper Baryłowicz
相关产品推荐
相关产品推荐

