C#、asm 项目返回 System.AccessViolationException

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

我正在开发一个项目,该项目结合使用 C# 和汇编语言 (ASM) 来模拟绿色盲(一种色觉缺陷)。 ASM 代码生成一个处理图像数据的 DLL,我遇到了 System.AccessViolationException 错误,并显示以下消息:

尝试读取或写入受保护的内存。这通常表明其他内存已损坏。

项目详情:

编程语言:C# 用于主应用程序,汇编语言 (ASM) 用于图像处理。 功能:该应用程序允许用户加载图像,处理它(使用asm模块或c#模块)以模拟绿色盲,并保存处理后的图像。

以下是相关代码的简要概述:

ASM代码(ModuleAsm.asm):

.code
DeuteranopiaAsm proc

SimulateDeuteranopiaASM:
    ; Save registers
    push rbp
    mov rbp, rsp
    sub rsp, 32

    mov r9, rdx             ; pixelCount
    mov r8d, edi            ; threadCount (passed through edi)
    mov rdx, rcx            ; pointer to original image (originalImage)
    mov rcx, rdi            ; pointer to processed image (processedImage)

    ; Calculate the portion size for each thread
    xor rax, rax            ; Clear rax before division
    mov eax, r9d            ; Move pixelCount to eax (32-bit register for division)
    xor edx, edx            ; Clear edx (higher part of rax)
    div r8d                 ; Divide eax by r8d (threadCount in r8d, 32-bit register)
    mov r10d, eax           ; Store the result (portion size) in r10d

    ; Parallel processing
    mov eax, r10d
    mov r11, rdx            ; pointer to original image
    mov rdx, rcx            ; pointer to processed image

ProcessLoop:
    cmp r9d, 0
    je Done

    ; Load original pixel data using 8-bit registers
    movzx eax, byte ptr [r11]      ; Load red channel (R) into eax (extended to 32-bit register)
    movzx ecx, byte ptr [r11 + 1]  ; Load green channel (G) into ecx (extended to 32-bit register)
    movzx edx, byte ptr [r11 + 2]  ; Load blue channel (B) into edx (extended to 32-bit register)

    ; Simulate deuteranopia (green color blindness)
    imul eax, eax, 625         ; Transform red channel
    imul ecx, ecx, 375         ; Transform green channel
    add eax, ecx
    shr eax, 10                ; Divide by 1024
    mov [rdx + 2], al          ; Save transformed red channel to processed image

    imul ecx, ecx, 7           ; Further transform green channel
    shr ecx, 3                 ; Divide by 8
    mov [rdx + 1], cl          ; Save transformed green channel

    imul edx, edx, 8           ; Transform blue channel
    shr edx, 3                 ; Divide by 8
    mov [rdx], dl              ; Save transformed blue channel

    ; Move to the next pixel
    add r11, 3                 ; Move pointer in original image to the next pixel
    add rdx, 3                 ; Move pointer in processed image to the next pixel
    dec r9d                    ; Decrement pixel count
    jmp ProcessLoop            ; Repeat loop

Done:
    ; Restore registers
    add rsp, 32
    pop rbp
    ret
DeuteranopiaAsm endp
end

C# 代码(MainWindow.xaml.cs):

using System;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Projekt
{
    public partial class MainWindow : Window
    {
        // Importing the ASM function (updated with the correct path)
        [DllImport(@"C:\Users\kacpe\Desktop\home\Programing\studia\ASM-SEM-5\Projekt\x64\Debug\ModuleAsm.dll")]
        public static extern void DeuteranopiaAsm(IntPtr originalImage, IntPtr processedImage, int pixelCount, int stride, int threadCount);

        private Bitmap _originalImage;
        private Bitmap _processedImage;

        public MainWindow()
        {
            InitializeComponent();
            int processorThreads = Environment.ProcessorCount;
            threadSlider.Value = processorThreads;
            threadCount.Text = $"Selected threads: {processorThreads}";
        }

        private void Exit_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }

        private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ButtonState == MouseButtonState.Pressed)
            {
                this.DragMove();
            }
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.WindowStartupLocation = WindowStartupLocation.Manual;
            var screenWidth = SystemParameters.PrimaryScreenWidth;
            var screenHeight = SystemParameters.PrimaryScreenHeight;
            this.Left = (screenWidth - this.Width) / 2;
            this.Top = (screenHeight - this.Height) / 2;
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (threadCount != null)
            {
                int threadValue = (int)threadSlider.Value;
                threadCount.Text = $"Thread count: {threadValue}";
            }
        }

        private void ChooseImage_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "Image files (*.jpg, *.png)|*.jpg;*.png",
                Title = "Choose an image"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                imagePathTextBox.Text = openFileDialog.FileName;
                _originalImage = new Bitmap(openFileDialog.FileName);
                MessageBox.Show("Image loaded successfully.");
            }
        }

        private void ProcessImage_Click(object sender, RoutedEventArgs e)
        {
            if (_originalImage == null)
            {
                MessageBox.Show("Please select an image first.");
                return;
            }

            int threadCount = (int)threadSlider.Value;

            _processedImage = new Bitmap(_originalImage.Width, _originalImage.Height);

            if (asmRadioButton.IsChecked == true)
            {
                Rectangle rect = new Rectangle(0, 0, _originalImage.Width, _originalImage.Height);
                BitmapData originalData = _originalImage.LockBits(rect, ImageLockMode.ReadOnly, _originalImage.PixelFormat);
                BitmapData processedData = _processedImage.LockBits(rect, ImageLockMode.WriteOnly, _processedImage.PixelFormat);

                int pixelCount = _originalImage.Width * _originalImage.Height;
                int stride = originalData.Stride;

                IntPtr originalPtr = originalData.Scan0;
                IntPtr processedPtr = processedData.Scan0;

                // Call to the ASM function
                DeuteranopiaAsm(originalPtr, processedPtr, pixelCount, stride, threadCount);

                _originalImage.UnlockBits(originalData);
                _processedImage.UnlockBits(processedData);

                MessageBox.Show("Image processed in ASM.");
            }
            else if (cSharpRadioButton.IsChecked == true)
            {
                _processedImage = SimulateDeuteranopia(_originalImage, threadCount);
                MessageBox.Show("Image processed in C#.");
            }
        }

        private void SaveImage_Click(object sender, RoutedEventArgs e)
        {
            if (_processedImage == null)
            {
                MessageBox.Show("Please process the image first.");
                return;
            }

            SaveFileDialog saveFileDialog = new SaveFileDialog
            {
                Filter = "Image files (*.jpg, *.png)|*.jpg;*.png",
                Title = "Save image"
            };

            if (saveFileDialog.ShowDialog() == true)
            {
                _processedImage.Save(saveFileDialog.FileName);
                MessageBox.Show("Image saved successfully.");
            }
        }

        public static Bitmap SimulateDeuteranopia(Bitmap original, int threads)
        {
            int width = original.Width;
            int height = original.Height;
            Bitmap simulatedImage = new Bitmap(width, height, original.PixelFormat);

            Rectangle rect = new Rectangle(0, 0, original.Width, original.Height);
            BitmapData originalData = original.LockBits(rect, ImageLockMode.ReadOnly, original.PixelFormat);
            BitmapData simulatedData = simulatedImage.LockBits(rect, ImageLockMode.WriteOnly, simulatedImage.PixelFormat);

            int bytesPerPixel = Image.GetPixelFormatSize(original.PixelFormat) / 8;
            int stride = originalData.Stride;
            IntPtr originalScan0 = originalData.Scan0;
            IntPtr simulatedScan0 = simulatedData.Scan0;

            byte[] originalPixels = new byte[stride * height];
            byte[] simulatedPixels = new byte[stride * height];

            Marshal.Copy(originalScan0, originalPixels, 0, originalPixels.Length);

            Parallel.For(0, threads, threadIndex =>
            {
                int partitionSize = height / threads;
                int start = threadIndex * partitionSize;
                int end = (threadIndex == threads - 1) ? height : start + partitionSize;

                for (int y = start; y < end; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        int pixelIndex = y * stride + x * bytesPerPixel;

                        byte originalB = originalPixels[pixelIndex];
                        byte originalG = originalPixels[pixelIndex + 1];
                        byte originalR = originalPixels[pixelIndex + 2];

                        // Simulate color transformation for deuteranopia
                        int newR = (int)(originalR * 0.625 + originalG * 0.375);
                        int newG = (int)(originalG * 0.7);
                        int newB = (int)(originalB * 0.8);

                        newR = Clamp(newR, 0, 255);
                        newG = Clamp(newG, 0, 255);
                        newB = Clamp(newB, 0, 255);

                        simulatedPixels[pixelIndex] = (byte)newB;
                        simulatedPixels[pixelIndex + 1] = (byte)newG;
                        simulatedPixels[pixelIndex + 2] = (byte)newR;
                    }
                }
            });

            Marshal.Copy(simulatedPixels, 0, simulatedScan0, simulatedPixels.Length);

            original.UnlockBits(originalData);
            simulatedImage.UnlockBits(simulatedData);

            return simulatedImage;
        }

        public static int Clamp(int value, int min, int max)
        {
            if (value < min) return min;
            if (value > max) return max;
            return value;
        }
    }
}

问题描述:

当代码尝试访问不允许的内存时,通常会发生 AccessViolationException。我怀疑这可能与 C# 和 ASM 代码之间的指针和内存管理方式有关。我正确锁定了图像的位,但我想知道是否存在任何内存对齐问题或者像素操作是否可能导致问题。

附加信息:

  • 我正在使用 .NET Framework 并针对 x64 架构。
  • DLL 正在正确加载,但在图像处理步骤中发生崩溃。
  • 如果您能深入了解可能导致此异常的常见陷阱,尤其是在 C# 和 ASM 之间的互操作场景中,我将不胜感激。

我尝试过重组,但毫无结果...

c# xaml assembly 64-bit nasm
2个回答
3
投票

您丢失了指向原始图像的指针

mov rdx, rcx            ; pointer to original image (originalImage)
...
xor edx, edx            ; Clear edx (higher part of rax)
div r8d                 ; Divide eax by r8d
...
mov r11, rdx            ; pointer to original image

div r8d
指令不是“eax除以r8d”,而是组合EDX:EAX除以R8D。
只需从头开始将相关指针存储在R11中即可

; Clear edx (higher part of rax)

这显示了对 x86 寄存器的误解。 EDX是RDX寄存器的低32位。 RAX 是一个完全不同的寄存器。


mov [rdx], dl              ; Save transformed blue channel

此代码中还有另一个严重错误:由于您选择了 RDX 作为指向已处理图像的指针,因此您不能同时使用 DL 或 EDX 来保存蓝色通道。 DL、DH、DX 和 EDX 都是同一 RDX 寄存器的不同部分。我认为你最好选择例如。使用调用破坏的 R10 作为输出寄存器。

还有另外两个观察结果:

  • 在输入端,您有红色,然后是绿色,然后是蓝色,但在输出端,您颠倒了顺序,所以您有蓝色,然后是绿色,然后是红色。这是故意的吗?

  • 蓝色通道的变换是多余的!

    imul edx, edx, 8
    乘以 8,但
    shr edx, 3
    然后除以 8,撤消更改。


mov r9, rdx             ; pixelCount
mov r8d, edi            ; threadCount (passed through edi)
mov rdx, rcx            ; pointer to original image (originalImage)
mov rcx, rdi            ; pointer to processed image (processedImage)

你们的论点也存在不匹配和重叠!如果 RDI 持有指向已处理图像的指针,那么 EDI 就不可能持有该 threadCount。 EDI 根本不是一个单独的寄存器; EDI 是我们给 RDI 寄存器的最低 32 位起的名字。

DeuteranopiaAsm(originalPtr, processedPtr, pixelCount, stride, threadCount);
,我希望在RCX中找到originalPtr,在RDX中找到processedPtr,在R8中找到pixelCount,在R9中找到stride,以及在32字节影子空间上方的堆栈上找到threadCount .


0
投票

@九月罗兰 我尝试修复我的代码,这就是结果:

.code
DeuteranopiaAsm proc

SimulateDeuteranopiaASM:
    ; RCX = originalImage (pointer)
    ; RDX = processedImage (pointer)
    ; R8  = pixelCount (number of pixels)
    ; R9  = stride (bytes per row)

    mov r11, rcx              ; Save pointer to original image
    mov r10, rdx              ; Save pointer to processed image

    xor rbx, rbx              ; Use RBX as pixel counter

PixelLoop:
    cmp rbx, r8               ; Check if all pixels are processed
    jge EndLoop               ; If rbx >= pixelCount, exit

    mov rax, rbx              ; Copy pixel counter to rax
    shl rax, 1                ; rax = rbx * 2 (shift left by 1)
    add rax, rbx              ; rax = rax + rbx = rbx * 3

    ; Load color data (B, G, R)
    mov al, byte ptr [r11 + rax]     ; Load blue channel
    mov cl, byte ptr [r11 + rax + 1] ; Load green channel
    mov dl, byte ptr [r11 + rax + 2] ; Load red channel

    ; Simulate deuteranopia (adjust color channels)
    ; New Red channel
    movzx r8d, dl              ; Convert red channel to 32-bit
    imul r8d, 625              ; R = R * 0.625
    movzx r9d, cl              ; Convert green channel to 32-bit
    imul r9d, 375              ; G = G * 0.375
    add r8d, r9d               ; New Red = (R * 0.625 + G * 0.375)
    shr r8d, 8                 ; Divide by 256 to fit in byte range

    ; Clamp red channel
    cmp r8d, 255
    jle NoClampRed
    mov r8d, 255
NoClampRed:

    ; Transform green channel
    movzx r9d, cl              ; Reload green channel
    imul r9d, 7                ; G = G * 0.7
    shr r9d, 8                 ; Divide by 256

    ; Clamp green channel
    cmp r9d, 255
    jle NoClampGreen
    mov r9d, 255
NoClampGreen:

    ; Transform blue channel
    movzx rax, al              ; Convert blue to 32-bit
    imul rax, 8                ; B = B * 0.8
    shr rax, 8                 ; Divide by 256

    ; Clamp blue channel
    cmp rax, 255
    jle NoClampBlue
    mov rax, 255
NoClampBlue:

    ; Store processed pixel back into the image
    mov byte ptr [r10 + rax], al     ; Store blue channel
    mov byte ptr [r10 + rax + 1], cl ; Store green channel
    mov byte ptr [r10 + rax + 2], dl ; Store red channel

    inc rbx                   ; Increment pixel counter
    jmp PixelLoop             ; Repeat for next pixel

EndLoop:
    ret
DeuteranopiaAsm endp
end

代码可以编译,但输出图像是黑色的,左上角有奇怪的点。 输入照片

输出照片

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