How it Works - nvpatch
nvpatch is a command line utility to patch Windows x64 applications to enable NVidia and AMD discreet graphics GPUs on low power devices.
nvpatch
is a command line utility that patches Windows x64 .exe files to include the export symbols required for some machines to enable their discreet GPU.
Although called "nvpatch
" it works for both NVidia and AMD GPUs with appropriate drivers installed. It was originally written to be used with Sector's Edge, a game on which my son Mitch is the lead developer.
This article explains how it works. If you just want to get it and use it, then see here for instructions.
Background
NVidia Optimus and AMD Enduro are technologies that switch GPU behaviour between low power and high performance modes. One of the ways the drivers determine which mode to run is to look for special symbols exported from the main .exe of the process.
Typically graphics intensive applications like games will have these symbols included in their .exe files to enabling switching to the high performance GPU mode.
The NVidia drivers look for an export symbol named NvOptimusEnablement
while the AMD drivers look for AmdPowerXpressRequestHighPerformance
. In both cases the exported symbol refers to a 32-bit integer where a value of 1 indicates high performance mode should be enabled.
While these symbols are trivial to add to C and C++ program, for other languages it's more difficult. In C# for example, it's not possible to export native symbols. One solution is described here by Lucas Magder where he decompiles the exe IL, patches the IL to make an export symbol, recompiles and then patches the exported data at runtime.
That approach works in previous versions of .NET but with .NET 5 the executable is actually a stub program that launches the .NET runtime and then loads your program from an associated assembly dll - and the export symbols need to be on the .exe and not the .dll. In other words the .exe is produced by the Microsoft toolchain and your program has no influence over its content.
There's a couple of options here:
- Petition Microsoft (and perhaps other language vendors) to add support for this in their stub .exe files.
- Petition NVidia and AMD to provide alternative mechanisms to enable these features
- Work out how to build the .NET stub executable and add those symbols
- Get creative and patch the exe to add these symbols
I'm not going to hold my breath for options 1 and 2. Option 3 is the most technically correct but seems fragile and probably complicated to setup a build environment for such a trivial change.
Option 4 sounds complicated but it's actually not that hard and a fun dive in the PE .exe file format.
PE File Format Overview
Windows .exe files are in the Portable Executable (aka "PE") file format. While the Microsoft documentation on the PE Format is a very long document, we only need to understand a few basic concepts and one section in detail in order to patch in these new symbols.
The basic format of a PE file is simply a bunch of headers followed by a bunch of sections. The headers provide important information for the Windows loader (as well as pointers to significant data in the sections), while the sections contain the actual data and code that's loaded into memory by the loader.
In order to add new symbols we're interested in the Export Table. While the documentation suggests that the export table lives in a special ".edata" section, in practice it can reside in any section and will often be found in the ".rdata" section along with other initialized read-only data. To find the export table, we can't just look for the ".edata" section. Instead, in among the headers is a data directory entry that points to the export table - in whatever section in happens to live.
How It Works
So this is how nvpatch does its tricks:
- Loads the .exe and locates all the various headers and sections
- Finds the export table (if it exists) and parses it into its own set of data structures so it can be manipulated
- Adds the new symbols to the parsed export table
- Re-writes the modified export table to a new section at the end of the file called ".nvpatch" along with the
0x00000001
data constants that the symbols point to - Inserts a new section header in the headers area that points to the new .nvpatch section
- Updates the Export Table data directory entry (in the header area) to point to the new export table (in the .nvpatch section)
- Updates various sizes and counts in the headers so everything checks out
- Rewrites all the changes as new .exe file
Note that if the exe had an existing Export Table (many exe files don't) it's left in the file - but nothing points to it so it's ignored. The reason the existing table isn't updated is there's no guarantee there will be room to extend the existing table without overwriting other important data. It's simpler and safer to just rewrite the table to a new section.
Note too, that by good luck there is almost always room at the end of the existing section header table to insert a new section header. That's because the section headers are the last of the headers and the actual section data that follows is typically aligned to 512 byte boundaries meaning there's usually room there. If by bad luck there isn't room, nvpatch will fail.
About the Code
nvpatch is written in C#. It could have been written in any language really, but C# means it can be easily packaged up as a dotnet tool for publishing.
The code is not at all shy about using unsafe code and pointers. In fact much of the code looks more like C.
- PEFile - the main class the reads and writes the .exe files and provides helpers for adding new sections
- PEExportTable - the class responsible for reading and writing the Export Table
- PESectionBuilder - helper class for adding new sections
- PEStructs.cs - C# definitions of various structures used in the PE format
- Utils.cs - various utilities functions
- Program.cs - logic to actually patch the exe
Just to explain the PEFile class a little more, it works like this:
- Reads the entire .exe into a
byte
array - Pins the array and gets a fixed pointer to its content
- The various header addresses are calculated and available as direct pointers into the loaded byte array as
PEFile
properties. These pointers are used to directly read and manipulate data in the headers. - The
AddSection
method creates a newPESectionBuilder
instance that has aMemoryStream
into which the new section content can be written. - When the file is rewritten, the originally loaded byte array (which has now be modified) is written first, followed the contents the memory stream of the
PESectionBuilder
of any new sections.
Limitations
In the interest of expediency (aka laziness) I've taken a few shortcuts that introduce a couple of limitations:
- Only the PE32+ file format (as used by x64) is supported. The PE32 (without the plus) format typically used by x86 executables isn't supported and the tool will fail with an error message. It should be reasonably easy to add support for this, but I just haven't bothered.
- If there's no room for a new section header it will fail with an error message.
- The export table reading doesn't handle forwarding exports. It doesn't even try to detect nor warn about them.
That's it!