Last active
February 22, 2023 12:31
-
-
Save imba-tjd/44ef31ab54454ccf0d2c989d50e7414e to your computer and use it in GitHub Desktop.
A wrapper for ngen.exe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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