Exporting data symbols in C# for NVIDIA Optimus

This is kind of a neat hack I ran across: At work were talking about if it was possible to export a native Win32 data symbol from a .NET application. The reason you would want to do this is because there is a method of controlling NVIDIA Optimus by doing so. Optimus tries to guess if you application needs the high performance GPU or not, but sometimes it guesses wrong. If you are a game developer you can force this by exporting a symbol like this from your binary (more info here):

extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}

That way when the driver loads it can check for this symbol and the value. I think the reason it's done this way is because calling in API would have to done after the driver is loaded, which is too late to choose which driver you are going to use (Intel vs. NVIDIA)...although this not an "official professional opinion". I didn't actually ask anyone at work so don't quote me. ­čÖé

The problem is you can't really do this with C# because there is no way of exporting a static integer. In fact there's no way of exporting anything. There is this NuGet package called UnmanagedExports however, it only lets you export symbols from a DLL, and only methods. Unfortunately for Optimus to detect the symbol it needs to be exported by the EXE itself. It's a bit unusual for EXEs to have exports in Win32, but not invalid.

So how does UnmanagedExports work exactly? Well it turns out in .NET you can actually roundtrip between a compiled binary and MSIL assembly pretty easily. Using the ildasm tool you can disassemble your combiled assembly into a .il file and .res file, edit them and then re-assemble them with ilasm. It also turns out that MSIL has a .export keyword you can place on methods to export them but the C# has no way of generating that opcode for some reason.

However, if we want to get creative, we can have a post-build step that disassembles our EXE, finds so specially named function (NvOptimusEnablementExporter_DontCallThis), inserts the attribute and then re-assembles the binary. UnmanagedExports adds some other stuff to make sure the calling conventions and marshalling code is correct to call the function from native code but we don't care about that since nobody is going to call this.

MSIL Before and After

MSIL Before and After

Ok, so that works. But now the problem is: we are exporting code, not data. Well in Win32 there isn't actually a difference. The export just signifies and address in the loaded binary that could be either depending on what the code expects. Our address definitely doesn't contain the right DWORD to control the Optimus state, it actually has a jmp into some code to initialize the .NET runtime. But that's OK, we can fix that.

In C# a class can have a static constructor, basically a function that runs on startup. We can create one that finds the address the symbol is pointing to and then overwrites the code the compiler has written there. This would cause a crash if anyone calls the function, but nobody does. The memory is also marked as read-only since it contains executable code, so first we need to make it writable. This all happends using native non-.NET APIs so we also need to get the native module handle of our assembly. Luckily we can assume the name of our DLL is the same as our assembly since we know we aren't running out of some run-time generated module.

class NvOptimusEnablementExporter
{
  static uint NvOptimusEnablement = 1;

  const uint PAGE_READWRITE = 0x04;
  [DllImport("kernel32.dll", SetLastError = true)]
  static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, 
   out uint lpflOldProtect);

  [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
  static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

  const uint GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x2;
  [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  static extern bool GetModuleHandleEx(uint dwFlags, string lpModuleName, out IntPtr phModule);

  static NvOptimusEnablementExporter()
  {
    Assembly thisAssembly = Assembly.GetExecutingAssembly();
    IntPtr myNativeModuleHandle = IntPtr.Zero;
    if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 
        thisAssembly.ManifestModule.Name, out myNativeModuleHandle))
    {
      IntPtr nvExportAddress = GetProcAddress(myNativeModuleHandle, "NvOptimusEnablement");
      if (nvExportAddress != IntPtr.Zero)
      {
        uint oldProtect = 0;
        //make it writable 
        if (VirtualProtect(nvExportAddress, 4, PAGE_READWRITE, out oldProtect))
        {
          unsafe
          {
            uint* dwordValuePtr = (uint*)nvExportAddress.ToPointer();
            //overwrite code that will never be called with the dword the driver is looking for
            *dwordValuePtr = NvOptimusEnablement;
          }
          VirtualProtect(nvExportAddress, 4, oldProtect, out oldProtect);
        }
      }
      else
      {
        Console.Error.WriteLine("You didn't hack the MSIL output!");
      }
    }

  }

  //This magic name is found by regex in the post-build step
  private static void NvOptimusEnablementExporter_DontCallThis()
  {

  }
}

This needs to happen before the display driver DLL is loaded and queries the symbol, but that should usually be the case since the DLL is loaded when you create D3D device or OpenGL context and not a startup.

I haven't actually tested beyond verifying the export is correct since I don't have an Optimus system on hand, but if anyone is interested the code is here: https://github.com/lmagder/OptimusEnablementNET

-Lucas

2 responses to “Exporting data symbols in C# for NVIDIA Optimus”

  1. Amit Tsur

    Hey, thanks, it’s working for me.

    How can I alter it to support both AMD and Nvidia cards? AMD have a similar way of requesting high performance:

    extern “C”
    {
    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
    }

    Do I need to add another DontCallThis, add another post build step and copy-pase the content of the external if statement with 8 instead of 4?

    Thanks.