Skip to content

Instantly share code, notes, and snippets.

@imba-tjd
Last active February 22, 2023 12:31
Show Gist options
  • Save imba-tjd/44ef31ab54454ccf0d2c989d50e7414e to your computer and use it in GitHub Desktop.
Save imba-tjd/44ef31ab54454ccf0d2c989d50e7414e to your computer and use it in GitHub Desktop.
A wrapper for ngen.exe
// LICENSE: MIT
using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;
class Ngenize
{
const string NgenX86Path = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ngen.exe";
const string NgenX64Path = @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe";
static int Main(string[] files)
{
if (files.Length == 0)
{
Console.Error.WriteLine(
"Ngenize runs ngen.exe with corresponding architecture.\n" +
"Usage: ngenize a.exe b.dll\n" +
"Globbing will not be handled by this program.");
return 0;
}
if (!new System.Security.Principal.WindowsPrincipal(
System.Security.Principal.WindowsIdentity.GetCurrent()
).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
{
Console.Error.WriteLine("Ngenize must be run under Admin privilege.");
return 1;
}
int retcode = 0;
foreach (string f in files)
{
if (!File.Exists(f))
{
Console.Error.WriteLine("Ngenize: File not found: " + f);
retcode = 2;
continue;
}
string f_quoted = "\"" + f + "\"";
var flags = CorFlagsReader.ReadAssemblyMetadata(f);
// X64 file or anycpu under x64 OS. https://stackoverflow.com/questions/28341629/which-ngen-to-use-for-x86-app-on-x64-system
StartNgen(flags.ProcessorArchitecture == ProcessorArchitecture.Amd64 ||
flags.ProcessorArchitecture == ProcessorArchitecture.MSIL && !flags.Is32BitReq && Environment.Is64BitOperatingSystem
, f_quoted);
}
return retcode;
}
static void StartNgen(bool isX64, string filename)
{
Console.WriteLine($"Ngenize: Compiling " + filename + " as " + (isX64 ? "x64" : "x86") + " image.");
Process.Start(new ProcessStartInfo(isX64 ? NgenX64Path : NgenX86Path, "install " + filename) { UseShellExecute = false }).WaitForExit();
}
}
// Assembly.LoadFrom(name).ManifestModule.GetPEKind(out PortableExecutableKinds pekind, out var _); fails on loading x64 assembly from x86 process (and vice versa).
// https://stackoverflow.com/questions/58025730/how-to-determine-if-a-net-assembly-was-built-with-platform-target-anycpu-anycp/
public class CorFlagsReader
{
/// <summary>
/// Gets the processor architecture required for the assembly or
/// </summary>
/// <returns>Possible return values: X86, Amd64, MSIL </returns>
public ProcessorArchitecture ProcessorArchitecture { get; }
/// <summary>
/// If true the PE files does not contain any unmanaged parts. Otherwise it is a managed C++ Target.
/// </summary>
public bool IsPureIL { get; }
public bool Is32BitReq { get; }
public bool Is32BitPref { get; }
private enum PEFormat : ushort
{
PE32 = 0x10b,
PE32Plus = 0x20b
}
[Flags]
private enum CorFlags : uint
{
ILOnly = 0x00000001,
Requires32Bit = 0x00000002,
ILLibrary = 0x00000004,
StrongNameSigned = 0x00000008,
NativeEntryPoint = 0x00000010,
TrackDebugData = 0x00010000,
Prefers32Bit = 0x00020000,
}
private class Section
{
public uint VirtualAddress;
public uint VirtualSize;
public uint Pointer;
}
private CorFlagsReader(CorFlags corflags, PEFormat peFormat)
{
IsPureIL = (corflags & CorFlags.ILOnly) == CorFlags.ILOnly;
Is32BitReq = (corflags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit;
Is32BitPref = (corflags & CorFlags.Prefers32Bit) == CorFlags.Prefers32Bit;
ProcessorArchitecture = peFormat == PEFormat.PE32Plus
? ProcessorArchitecture.Amd64
: (corflags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit || !IsPureIL
? ProcessorArchitecture.X86
: ProcessorArchitecture.MSIL;
}
public static CorFlagsReader ReadAssemblyMetadata(string fileName)
{
using (var fStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
return ReadAssemblyMetadata(fStream);
}
public static CorFlagsReader ReadAssemblyMetadata(Stream stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
long length = stream.Length;
if (length < 0x40)
return null;
using (var reader = new BinaryReader(stream, System.Text.Encoding.UTF8, true))
{
// Read the pointer to the PE header.
stream.Position = 0x3c;
uint peHeaderPtr = reader.ReadUInt32();
if (peHeaderPtr == 0)
peHeaderPtr = 0x80;
// Ensure there is at least enough room for the following structures:
// 24 byte PE Signature & Header
// 28 byte Standard Fields (24 bytes for PE32+)
// 68 byte NT Fields (88 bytes for PE32+)
// >= 128 byte Data Dictionary Table
if (peHeaderPtr > length - 256)
return null;
// Check the PE signature. Should equal 'PE\0\0'.
stream.Position = peHeaderPtr;
var peSignature = reader.ReadUInt32();
if (peSignature != 0x00004550)
return null;
// Read PE header fields.
var machine = reader.ReadUInt16();
var numberOfSections = reader.ReadUInt16();
var timeStamp = reader.ReadUInt32();
var symbolTablePtr = reader.ReadUInt32();
var numberOfSymbols = reader.ReadUInt32();
var optionalHeaderSize = reader.ReadUInt16();
var characteristics = reader.ReadUInt16();
// Read PE magic number from Standard Fields to determine format.
PEFormat peFormat = (PEFormat)reader.ReadUInt16();
if (peFormat != PEFormat.PE32 && peFormat != PEFormat.PE32Plus)
return null;
// Read the 15th Data Dictionary RVA field which contains the CLI header RVA.
// When this is non-zero then the file contains CLI data otherwise not.
stream.Position = peHeaderPtr + (peFormat == PEFormat.PE32 ? 232 : 248);
var cliHeaderRva = reader.ReadUInt32();
if (cliHeaderRva == 0)
return new CorFlagsReader(0, peFormat);
// Read section headers. Each one is 40 bytes.
// 8 byte Name
// 4 byte Virtual Size
// 4 byte Virtual Address
// 4 byte Data Size
// 4 byte Data Pointer
// ... total of 40 bytes
var sectionTablePtr = peHeaderPtr + 24 + optionalHeaderSize;
var sections = new Section[numberOfSections];
for (int i = 0; i < numberOfSections; i++)
{
stream.Position = sectionTablePtr + i * 40 + 8;
Section section = new Section { VirtualSize = reader.ReadUInt32(), VirtualAddress = reader.ReadUInt32() };
reader.ReadUInt32();
section.Pointer = reader.ReadUInt32();
sections[i] = section;
}
// Read parts of the CLI header.
var cliHeaderPtr = ResolveRva(sections, cliHeaderRva);
if (cliHeaderPtr == 0)
return null;
stream.Position = cliHeaderPtr + 4;
var majorRuntimeVersion = reader.ReadUInt16();
var minorRuntimeVersion = reader.ReadUInt16();
var metadataRva = reader.ReadUInt32();
var metadataSize = reader.ReadUInt32();
CorFlags corflags = (CorFlags)reader.ReadUInt32();
// Done.
return new CorFlagsReader(corflags, peFormat);
}
}
private static uint ResolveRva(Section[] sections, uint rva)
{
foreach (var section in sections)
if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
return rva - section.VirtualAddress + section.Pointer;
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment