Search code examples
c#assemblydllcompiler-constructionaccess-violation

Trying to create basic compiler in C#, getting Access Violation Errors


I'm trying to learn a bit about compilers. I'm using NASM to compile my object files and alink to turn them into dll's. I'm using dependency walker to verify the contents of my dll. So far so good on compiling my dll in code and I can retrieve it with GetProcAddress. However, when I try to invoke it I get the following error:

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

All I do is set eax to 1 so not 100% why I get an error. I'm not sure what memory is getting corrupted, any assistance on what I can do to invoke this dll properly would be much appreciated.

Edit: I am using 32-bit assembly on Windows x64, at work probably try x64 assembly/assembler when I get home to see if it works.

Dynamically generated assembly file

global DllMain
export DllMain

global testfunc
export testfunc

section .code use32

DllMain:        ; This code is required in .dll files
mov eax,1
ret 12

testfunc:
mov eax, 1
ret

C# Code

   namespace KCompiler
    {
        public static class NativeMethods
        {
            [DllImport("kernel32.dll")]
            public static extern IntPtr LoadLibrary(string dllToLoad);

            [DllImport("kernel32.dll")]
            public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
            [DllImport("kernel32.dll")]
            public static extern bool FreeLibrary(IntPtr hModule);
        }

        class Program
        {
            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
            delegate int TestFuncDelegate();
            static int Main(string[] args)
            {
                /*
                AntlrFileStream stream = new AntlrFileStream("../../example.k");
                CLexer lexer = new CLexer(stream);
                CommonTokenStream tokens = new CommonTokenStream(lexer);
                CParser parser = new CParser(tokens);
                ParseTreeWalker tree = new ParseTreeWalker();
                CListener listener = new CListener();
                tree.Walk(listener, parser.file());
                */

                KAssembler assembler = new KAssembler();

                //assembler.PushR("ebp");
                //assembler.Mov32RR("ebp", "esp");
                assembler.Mov32RI("eax", 1);
                //assembler.PopR("ebp");
                assembler.Return();

                string RelativeDirectory = @"..\..";
                string fullAssembly = File.ReadAllText(Path.Combine(RelativeDirectory,"k_template.asm")).Replace("{ASSEMBLY}", assembler.ToString());
                Console.WriteLine(fullAssembly);
                File.WriteAllText(Path.Combine(RelativeDirectory,"k.asm"), fullAssembly);

                ProcessStartInfo nasmInfo = new ProcessStartInfo()
                {
                    UseShellExecute = false,
                    FileName = Path.Combine(RelativeDirectory,"nasm.exe"),
                    RedirectStandardOutput = true,
                    Arguments = @"-fobj ..\..\k.asm",
                };

                using (Process nasm = Process.Start(nasmInfo))
                {
                    nasm.WaitForExit();
                    Console.WriteLine($"NASM exited with code: {nasm.ExitCode}");
                    if (nasm.ExitCode != 0) return nasm.ExitCode;
                }

                ProcessStartInfo alinkInfo = new ProcessStartInfo()
                {
                    UseShellExecute = false,
                    FileName = Path.Combine(RelativeDirectory,"alink.exe"),
                    RedirectStandardOutput = true,
                    Arguments = Path.Combine(RelativeDirectory,"k.obj") + " -oPE -dll",
                };

                using (Process alink = Process.Start(alinkInfo))
                {
                    alink.WaitForExit();
                    Console.WriteLine($"alink exited with code: {alink.ExitCode}");
                    if (alink.ExitCode != 0) return alink.ExitCode;
                }

                IntPtr dll = new IntPtr(0);
                try
                {
                    dll = NativeMethods.LoadLibrary(Path.Combine(RelativeDirectory, "k.dll"));
                    Console.WriteLine(dll.ToInt32() == 0 ? "Unable to Load k.dll" : "Loaded k.dll");
                    if (dll.ToInt32() == 0) return 1;

                    IntPtr TestFunctionPtr = NativeMethods.GetProcAddress(dll, "testfunc");
                    Console.WriteLine(TestFunctionPtr.ToInt32() == 0 ? "Unable to Load 'testfunc'" : "Loaded 'testfunc'");
                    if (TestFunctionPtr.ToInt32() == 0) return 1;

                    TestFuncDelegate Test = Marshal.GetDelegateForFunctionPointer<TestFuncDelegate>(TestFunctionPtr);

                    int result = Test(); //Error right here
                    Console.WriteLine($"Test Function Returned: {result}");
                }
                finally
                {
                    if(dll.ToInt32() != 0)
                        NativeMethods.FreeLibrary(dll);
                }

                return 0;
            }
        }
    }

Solution

  • Alright, I found the solution. alink had difficulties linking the -fwin32 format to a dll so I switched to the golink linker. So now I'm using a NASM assembler with the golink linker and was able to get it to work with the same setup as before, code is provided below.

    Also NASM defaults to 16-bit mode if you don't put [bits #] at the top of your code, so had to switch that to 32 bit. If you put 64 there you'd have to write 64-bit assembly to get it to work.

    ASM Code

    [bits 32]
    global DllMain
    global testfunc
    
    export DllMain
    export testfunc
    
    section .text
    
    DllMain:        ; This code is required in .dll files
    mov eax,1
    ret 12
    
    testfunc:
    mov eax, 32
    ret
    

    C# Code

    using System;
    using Antlr4;
    using System.IO;
    using Antlr4.Runtime;
    using Antlr4.Runtime.Misc;
    using Antlr4.Runtime.Tree;
    using System.Collections.Generic;
    using KCompiler.KCore;
    using KCompiler.Assembler;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    
    namespace KCompiler
    {
        public static class NativeMethods
        {
            [DllImport("kernel32.dll")]
            public static extern IntPtr LoadLibrary(string dllToLoad);
    
            [DllImport("kernel32.dll")]
            public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
            [DllImport("kernel32.dll")]
            public static extern bool FreeLibrary(IntPtr hModule);
        }
    
        class Program
        {
            [UnmanagedFunctionPointer(CallingConvention.StdCall)]
            delegate int TestFuncDelegate();
            static int Main(string[] args)
            {
                /*
                AntlrFileStream stream = new AntlrFileStream("../../example.k");
                CLexer lexer = new CLexer(stream);
                CommonTokenStream tokens = new CommonTokenStream(lexer);
                CParser parser = new CParser(tokens);
                ParseTreeWalker tree = new ParseTreeWalker();
                CListener listener = new CListener();
                tree.Walk(listener, parser.file());
                */
    
                KAssembler assembler = new KAssembler();
                assembler.Mov32RI("eax", 32);
    
                string RelativeDirectory = @"..\..";
                string fullAssembly = File.ReadAllText(Path.Combine(RelativeDirectory,"k_template.asm")).Replace("{ASSEMBLY}", assembler.ToString());
                Console.WriteLine(fullAssembly);
                File.WriteAllText(Path.Combine(RelativeDirectory,"k.asm"), fullAssembly);
    
                ProcessStartInfo nasmInfo = new ProcessStartInfo()
                {
                    UseShellExecute = false,
                    FileName = Path.Combine(RelativeDirectory,"nasm.exe"),
                    RedirectStandardOutput = true,
                    Arguments = @"-fwin32 "+ Path.Combine(RelativeDirectory,"k.asm")
                };
    
                using (Process nasm = Process.Start(nasmInfo))
                {
                    nasm.WaitForExit();
                    Console.WriteLine($"NASM exited with code: {nasm.ExitCode}");
                    if (nasm.ExitCode != 0) return nasm.ExitCode;
                }
    
                ProcessStartInfo golinkInfo = new ProcessStartInfo()
                {
                    UseShellExecute = false,
                    FileName = Path.Combine(RelativeDirectory,"GoLink.exe"),
                    RedirectStandardOutput = true,
                    //Arguments = Path.Combine(RelativeDirectory,"k.obj") + " -c -oPE -dll -subsys windows",
                    Arguments = Path.Combine(RelativeDirectory, "k.obj") + " /dll",
                };
    
                using (Process golink = Process.Start(golinkInfo))
                {
                    golink.WaitForExit();
                    Console.WriteLine($"alink exited with code: {golink.ExitCode}");
                    if (golink.ExitCode != 0) return golink.ExitCode;
                }
    
                IntPtr dll = new IntPtr(0);
                try
                {
                    dll = NativeMethods.LoadLibrary(Path.Combine(RelativeDirectory, "k.dll"));
                    Console.WriteLine(dll.ToInt32() == 0 ? "Unable to Load k.dll" : "Loaded k.dll");
                    if (dll.ToInt32() == 0) return 1;
    
                    IntPtr TestFunctionPtr = NativeMethods.GetProcAddress(dll, "testfunc");
                    Console.WriteLine(TestFunctionPtr.ToInt32() == 0 ? "Unable to Load 'testfunc'" : "Loaded 'testfunc'");
                    if (TestFunctionPtr.ToInt32() == 0) return 1;
                    TestFuncDelegate Test = Marshal.GetDelegateForFunctionPointer<TestFuncDelegate>(TestFunctionPtr);
                    int result = Test();
                    Console.WriteLine($"Test Function Returned: {result}");
                }
                finally
                {
                    if(dll.ToInt32() != 0)
                        NativeMethods.FreeLibrary(dll);
                }
    
                return 0;
            }
        }
    }