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
1. using System;
2. using System.Runtime.InteropServices;
3. using System.Text;
4.  
5. public class Program
6. {
7.     public static unsafe int Main()
8.     {
9.         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
10.         {
11.             unsafe
12.             {
13.                 byte[] codeBytes = new byte[] {
14.                 0x49, 0x89, 0xd8,       // mov    %rbx,%r8
15.                 0x49, 0x89, 0xc9,       // mov    %rcx,%r9
16.                 0x48, 0x31, 0xc0,       // xor    %rax,%rax
17.                 0x0f, 0xa2,             // cpuid
18.                 0x4c, 0x89, 0xc8,       // mov    %r9,%rax
19.                 0x89, 0x18,             // mov    %ebx,0x0(%rax)
20.                 0x89, 0x50, 0x04,       // mov    %edx,0x4(%rax)
21.                 0x89, 0x48, 0x08,       // mov    %ecx,0x8(%rax)
22.                 0x4c, 0x89, 0xc3,       // mov    %r8,%rbx
23.                 0xc3                    // retq
24.                 };
25.  
26.                 Buffer buffer = new();
27.                 using (VirtualMemoryPtr codeBytesPtr =
28.                     new(codeBytes.Length))
29.                 {
30.                     Marshal.Copy(
31.                         codeBytes, 0,
32.                         codeBytesPtr, codeBytes.Length);
33.                     
34.                     delegate*<byte*, void> method = (delegate*<byte*, void>)(IntPtr)codeBytesPtr;
35.                     method(&buffer[0]);
36.                 }
37.                 Console.Write("Processor Id: ");
38.                 char[] chars = new char[Buffer.Length];
39.                 Encoding.ASCII.GetChars(buffer, chars);
40.                 Console.WriteLine(chars);
41.             } // unsafe
42.         }
43.         else
44.         {
45.             Console.WriteLine("This sample is only valid for Windows");
46.         }
47.         return 0;
48.     }
49. }
50.  
51. [System.Runtime.CompilerServices.InlineArrayAttribute(Length)]
52. public struct Buffer
53. {
54.     public const int Length = 10;
55.  
56.     private byte _element0;
57. }
58.  

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