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 function pointer that refers to the new code, and invoke 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 static unsafe 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
                };
 
                Buffer buffer = new();
                using (VirtualMemoryPtr codeBytesPtr =
                    new(codeBytes.Length))
                {
                    Marshal.Copy(
                        codeBytes, 0,
                        codeBytesPtr, codeBytes.Length);
                    
                    delegate*<byte*, void> method = (delegate*<byte*, void>)(IntPtr)codeBytesPtr;
                    method(&buffer[0]);
                }
                Console.Write("Processor Id: ");
                char[] chars = new char[Buffer.Length];
                Encoding.ASCII.GetChars(buffer, chars);
                Console.WriteLine(chars);
            } // unsafe
        }
        else
        {
            Console.WriteLine("This sample is only valid for Windows");
        }
        return 0;
    }
}
 
[System.Runtime.CompilerServices.InlineArrayAttribute(Length)]
public struct Buffer
{
    public const int Length = 10;
 
    private byte _element0;
}
 

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 }}
;