Executing Unsafe Code via a Delegate

As promised at the beginning of this chapter, we finish up with a full working example of what is likely the most “unsafe” thing you can do in C#: obtain a pointer to a block of memory, fill it with the bytes of machine code, make a delegate that refers to the new code, and execute it. In this example, we use assembly code to determine the processor ID. If run on a Windows machine, it prints the processor ID. Listing 23.20 shows how to do it (results shown in Output 23.6).

Listing 23.20: Designating a Block for Unsafe Code
using System;
using System.Runtime.InteropServices;
using System.Text;
 
public class Program
{
    public unsafe delegate void MethodInvoker(byte* buffer);
 
    public unsafe static int Main()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            unsafe
            {
                byte[] codeBytes = new byte[] {
                0x49, 0x89, 0xd8,       // mov    %rbx,%r8
                0x49, 0x89, 0xc9,       // mov    %rcx,%r9
                0x48, 0x31, 0xc0,       // xor    %rax,%rax
                0x0f, 0xa2,             // cpuid
                0x4c, 0x89, 0xc8,       // mov    %r9,%rax
                0x89, 0x18,             // mov    %ebx,0x0(%rax)
                0x89, 0x50, 0x04,       // mov    %edx,0x4(%rax)
                0x89, 0x48, 0x08,       // mov    %ecx,0x8(%rax)
                0x4c, 0x89, 0xc3,       // mov    %r8,%rbx
                0xc3                    // retq
        };
 
                byte[] buffer = new byte[12];
 
                using (VirtualMemoryPtr codeBytesPtr =
                    new VirtualMemoryPtr(codeBytes.Length))
                {
                    Marshal.Copy(
                        codeBytes, 0,
                        codeBytesPtr, codeBytes.Length);
 
                    MethodInvoker method = Marshal.GetDelegateForFunctionPointer<MethodInvoker>(codeBytesPtr);
                    fixed (byte* newBuffer = &buffer[0])
                    {
                        method(newBuffer);
                    }
                }
                Console.Write("Processor Id: ");
                Console.WriteLine(ASCIIEncoding.ASCII.GetChars(buffer));
            } // unsafe
        }
        else
        {
            Console.WriteLine("This sample is only valid for Windows");
        }
        return 0;
    }
}

Output 23.6
Processor Id: GenuineIntel
Chapter 23: Summary

As demonstrated throughout this book, C# offers great power, flexibility, consistency, and a fantastic structure. This chapter highlighted the ability of C# programs to perform very low-level machine-code operations.

Before we end the book, Chapter 24 briefly describes the underlying execution framework and shifts the focus from the C# language to the broader context in which C# programs execute.

{{ snackbarMessage }}