obfuz/Plugins/dnlib/DotNet/Writer/ManagedExportsWriter.cs

532 lines
19 KiB
C#

// dnlib: See LICENSE.txt for more info
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using dnlib.IO;
using dnlib.PE;
namespace dnlib.DotNet.Writer {
sealed class ManagedExportsWriter {
const uint DEFAULT_VTBL_FIXUPS_ALIGNMENT = 4;
const uint DEFAULT_SDATA_ALIGNMENT = 8;
const StubType stubType = StubType.Export;
readonly string moduleName;
readonly Machine machine;
readonly RelocDirectory relocDirectory;
readonly Metadata metadata;
readonly PEHeaders peHeaders;
readonly Action<string, object[]> logError;
readonly VtableFixupsChunk vtableFixups;
readonly StubsChunk stubsChunk;
readonly SdataChunk sdataChunk;
readonly ExportDir exportDir;
readonly List<VTableInfo> vtables;
readonly List<MethodInfo> allMethodInfos;
readonly List<MethodInfo> sortedOrdinalMethodInfos;
readonly List<MethodInfo> sortedNameMethodInfos;
readonly CpuArch cpuArch;
uint exportDirOffset;
bool Is64Bit => machine.Is64Bit();
FileOffset ExportDirOffset => sdataChunk.FileOffset + exportDirOffset;
RVA ExportDirRVA => sdataChunk.RVA + exportDirOffset;
uint ExportDirSize => 0x28;
internal bool HasExports => vtables.Count != 0;
sealed class ExportDir : IChunk {
readonly ManagedExportsWriter owner;
public FileOffset FileOffset => owner.ExportDirOffset;
public RVA RVA => owner.ExportDirRVA;
public ExportDir(ManagedExportsWriter owner) => this.owner = owner;
void IChunk.SetOffset(FileOffset offset, RVA rva) => throw new NotSupportedException();
public uint GetFileLength() => owner.ExportDirSize;
public uint GetVirtualSize() => GetFileLength();
public uint CalculateAlignment() => 0;
void IChunk.WriteTo(DataWriter writer) => throw new NotSupportedException();
}
sealed class VtableFixupsChunk : IChunk {
readonly ManagedExportsWriter owner;
FileOffset offset;
RVA rva;
internal uint length;
public FileOffset FileOffset => offset;
public RVA RVA => rva;
public VtableFixupsChunk(ManagedExportsWriter owner) => this.owner = owner;
public void SetOffset(FileOffset offset, RVA rva) {
this.offset = offset;
this.rva = rva;
}
public uint GetFileLength() => length;
public uint GetVirtualSize() => GetFileLength();
public uint CalculateAlignment() => 0;
public void WriteTo(DataWriter writer) => owner.WriteVtableFixups(writer);
}
sealed class StubsChunk : IChunk {
readonly ManagedExportsWriter owner;
FileOffset offset;
RVA rva;
internal uint length;
public FileOffset FileOffset => offset;
public RVA RVA => rva;
public StubsChunk(ManagedExportsWriter owner) => this.owner = owner;
public void SetOffset(FileOffset offset, RVA rva) {
this.offset = offset;
this.rva = rva;
}
public uint GetFileLength() => length;
public uint GetVirtualSize() => GetFileLength();
public uint CalculateAlignment() => 0;
public void WriteTo(DataWriter writer) => owner.WriteStubs(writer);
}
sealed class SdataChunk : IChunk {
readonly ManagedExportsWriter owner;
FileOffset offset;
RVA rva;
internal uint length;
public FileOffset FileOffset => offset;
public RVA RVA => rva;
public SdataChunk(ManagedExportsWriter owner) => this.owner = owner;
public void SetOffset(FileOffset offset, RVA rva) {
this.offset = offset;
this.rva = rva;
}
public uint GetFileLength() => length;
public uint GetVirtualSize() => GetFileLength();
public uint CalculateAlignment() => 0;
public void WriteTo(DataWriter writer) => owner.WriteSdata(writer);
}
public ManagedExportsWriter(string moduleName, Machine machine, RelocDirectory relocDirectory, Metadata metadata, PEHeaders peHeaders, Action<string, object[]> logError) {
this.moduleName = moduleName;
this.machine = machine;
this.relocDirectory = relocDirectory;
this.metadata = metadata;
this.peHeaders = peHeaders;
this.logError = logError;
vtableFixups = new VtableFixupsChunk(this);
stubsChunk = new StubsChunk(this);
sdataChunk = new SdataChunk(this);
exportDir = new ExportDir(this);
vtables = new List<VTableInfo>();
allMethodInfos = new List<MethodInfo>();
sortedOrdinalMethodInfos = new List<MethodInfo>();
sortedNameMethodInfos = new List<MethodInfo>();
// The error is reported later when we know that there's at least one exported method
CpuArch.TryGetCpuArch(machine, out cpuArch);
}
internal void AddTextChunks(PESection textSection) {
textSection.Add(vtableFixups, DEFAULT_VTBL_FIXUPS_ALIGNMENT);
if (cpuArch is not null)
textSection.Add(stubsChunk, cpuArch.GetStubAlignment(stubType));
}
internal void AddSdataChunks(PESection sdataSection) => sdataSection.Add(sdataChunk, DEFAULT_SDATA_ALIGNMENT);
internal void InitializeChunkProperties() {
if (allMethodInfos.Count == 0)
return;
peHeaders.ExportDirectory = exportDir;
peHeaders.ImageCor20Header.VtableFixups = vtableFixups;
}
internal void AddExportedMethods(List<MethodDef> methods, uint timestamp) {
if (methods.Count == 0)
return;
// Only check for an unsupported machine when we know there's at least one exported method
if (cpuArch is null) {
logError("The module has exported methods but the CPU architecture isn't supported: {0} (0x{1:X4})", new object[] { machine, (ushort)machine });
return;
}
if (methods.Count > 0x10000) {
logError("Too many methods have been exported. No more than 2^16 methods can be exported. Number of exported methods: {0}", new object[] { methods.Count });
return;
}
Initialize(methods, timestamp);
}
sealed class MethodInfo {
public readonly MethodDef Method;
public readonly uint StubChunkOffset;
public int FunctionIndex;
public uint ManagedVtblOffset;
public uint NameOffset;
public int NameIndex;
public byte[] NameBytes;
public MethodInfo(MethodDef method, uint stubChunkOffset) {
Method = method;
StubChunkOffset = stubChunkOffset;
}
}
sealed class VTableInfo {
public uint SdataChunkOffset { get; set; }
public readonly VTableFlags Flags;
public readonly List<MethodInfo> Methods;
public VTableInfo(VTableFlags flags) {
Flags = flags;
Methods = new List<MethodInfo>();
}
}
void Initialize(List<MethodDef> methods, uint timestamp) {
var dict = new Dictionary<int, List<VTableInfo>>();
var baseFlags = Is64Bit ? VTableFlags.Bit64 : VTableFlags.Bit32;
uint stubOffset = 0;
uint stubAlignment = cpuArch.GetStubAlignment(stubType);
uint stubCodeOffset = cpuArch.GetStubCodeOffset(stubType);
uint stubSize = cpuArch.GetStubSize(stubType);
foreach (var method in methods) {
var exportInfo = method.ExportInfo;
Debug.Assert(exportInfo is not null);
if (exportInfo is null)
continue;
var flags = baseFlags;
if ((exportInfo.Options & MethodExportInfoOptions.FromUnmanaged) != 0)
flags |= VTableFlags.FromUnmanaged;
if ((exportInfo.Options & MethodExportInfoOptions.FromUnmanagedRetainAppDomain) != 0)
flags |= VTableFlags.FromUnmanagedRetainAppDomain;
if ((exportInfo.Options & MethodExportInfoOptions.CallMostDerived) != 0)
flags |= VTableFlags.CallMostDerived;
if (!dict.TryGetValue((int)flags, out var list))
dict.Add((int)flags, list = new List<VTableInfo>());
if (list.Count == 0 || list[list.Count - 1].Methods.Count >= ushort.MaxValue)
list.Add(new VTableInfo(flags));
var info = new MethodInfo(method, stubOffset + stubCodeOffset);
allMethodInfos.Add(info);
list[list.Count - 1].Methods.Add(info);
stubOffset = (stubOffset + stubSize + stubAlignment - 1) & ~(stubAlignment - 1);
}
foreach (var kv in dict)
vtables.AddRange(kv.Value);
WriteSdataBlob(timestamp);
vtableFixups.length = (uint)vtables.Count * 8;
stubsChunk.length = stubOffset;
sdataChunk.length = (uint)sdataBytesInfo.Data.Length;
uint expectedOffset = 0;
foreach (var info in allMethodInfos) {
uint currentOffset = info.StubChunkOffset - stubCodeOffset;
if (expectedOffset != currentOffset)
throw new InvalidOperationException();
cpuArch.WriteStubRelocs(stubType, relocDirectory, stubsChunk, currentOffset);
expectedOffset = (currentOffset + stubSize + stubAlignment - 1) & ~(stubAlignment - 1);
}
if (expectedOffset != stubOffset)
throw new InvalidOperationException();
}
struct NamesBlob {
readonly Dictionary<string, NameInfo> nameOffsets;
readonly List<byte[]> names;
readonly List<uint> methodNameOffsets;
uint currentOffset;
int methodNamesCount;
bool methodNamesIsFrozen;
public int MethodNamesCount => methodNamesCount;
readonly struct NameInfo {
public readonly uint Offset;
public readonly byte[] Bytes;
public NameInfo(uint offset, byte[] bytes) {
Offset = offset;
Bytes = bytes;
}
}
public NamesBlob(bool dummy) {
nameOffsets = new Dictionary<string, NameInfo>(StringComparer.Ordinal);
names = new List<byte[]>();
methodNameOffsets = new List<uint>();
currentOffset = 0;
methodNamesCount = 0;
methodNamesIsFrozen = false;
}
public uint GetMethodNameOffset(string name, out byte[] bytes) {
if (methodNamesIsFrozen)
throw new InvalidOperationException();
methodNamesCount++;
uint offset = GetOffset(name, out bytes);
methodNameOffsets.Add(offset);
return offset;
}
public uint GetOtherNameOffset(string name) {
methodNamesIsFrozen = true;
return GetOffset(name, out var bytes);
}
uint GetOffset(string name, out byte[] bytes) {
if (nameOffsets.TryGetValue(name, out var nameInfo)) {
bytes = nameInfo.Bytes;
return nameInfo.Offset;
}
bytes = GetNameASCIIZ(name);
names.Add(bytes);
uint offset = currentOffset;
nameOffsets.Add(name, new NameInfo(offset, bytes));
currentOffset += (uint)bytes.Length;
return offset;
}
// If this method gets updated, also update the reader (MethodExportInfoProvider)
static byte[] GetNameASCIIZ(string name) {
Debug.Assert(name is not null);
int size = Encoding.UTF8.GetByteCount(name);
var bytes = new byte[size + 1];
Encoding.UTF8.GetBytes(name, 0, name.Length, bytes, 0);
if (bytes[bytes.Length - 1] != 0)
throw new ModuleWriterException();
return bytes;
}
public void Write(DataWriter writer) {
foreach (var name in names)
writer.WriteBytes(name);
}
public uint[] GetMethodNameOffsets() => methodNameOffsets.ToArray();
}
struct SdataBytesInfo {
public byte[] Data;
public uint namesBlobStreamOffset;
public uint moduleNameOffset;
public uint exportDirModuleNameStreamOffset;
public uint exportDirAddressOfFunctionsStreamOffset;
public uint addressOfFunctionsStreamOffset;
public uint addressOfNamesStreamOffset;
public uint addressOfNameOrdinalsStreamOffset;
public uint[] MethodNameOffsets;
}
SdataBytesInfo sdataBytesInfo;
/// <summary>
/// Writes the .sdata blob. We could write the data in any order, but we write the data in the same order as ILASM
/// </summary>
/// <param name="timestamp">PE timestamp</param>
void WriteSdataBlob(uint timestamp) {
var stream = new MemoryStream();
var writer = new DataWriter(stream);
// Write all vtables (referenced from the .text section)
Debug.Assert((writer.Position & 7) == 0);
foreach (var vtbl in vtables) {
vtbl.SdataChunkOffset = (uint)writer.Position;
foreach (var info in vtbl.Methods) {
info.ManagedVtblOffset = (uint)writer.Position;
writer.WriteUInt32(0x06000000 + metadata.GetRid(info.Method));
if ((vtbl.Flags & VTableFlags.Bit64) != 0)
writer.WriteUInt32(0);
}
}
var namesBlob = new NamesBlob(1 == 2);
int nameIndex = 0;
bool error = false;
foreach (var info in allMethodInfos) {
var exportInfo = info.Method.ExportInfo;
var name = exportInfo.Name;
if (name is null) {
if (exportInfo.Ordinal is not null) {
sortedOrdinalMethodInfos.Add(info);
continue;
}
name = info.Method.Name;
}
if (string.IsNullOrEmpty(name)) {
error = true;
logError("Exported method name is null or empty, method: {0} (0x{1:X8})", new object[] { info.Method, info.Method.MDToken.Raw });
continue;
}
info.NameOffset = namesBlob.GetMethodNameOffset(name, out info.NameBytes);
info.NameIndex = nameIndex++;
sortedNameMethodInfos.Add(info);
}
Debug.Assert(error || sortedOrdinalMethodInfos.Count + sortedNameMethodInfos.Count == allMethodInfos.Count);
sdataBytesInfo.MethodNameOffsets = namesBlob.GetMethodNameOffsets();
Debug.Assert(sortedNameMethodInfos.Count == sdataBytesInfo.MethodNameOffsets.Length);
sdataBytesInfo.moduleNameOffset = namesBlob.GetOtherNameOffset(moduleName);
sortedOrdinalMethodInfos.Sort((a, b) => a.Method.ExportInfo.Ordinal.Value.CompareTo(b.Method.ExportInfo.Ordinal.Value));
sortedNameMethodInfos.Sort((a, b) => CompareTo(a.NameBytes, b.NameBytes));
int ordinalBase, nextFreeOrdinal;
if (sortedOrdinalMethodInfos.Count == 0) {
ordinalBase = 0;
nextFreeOrdinal = 0;
}
else {
ordinalBase = sortedOrdinalMethodInfos[0].Method.ExportInfo.Ordinal.Value;
nextFreeOrdinal = sortedOrdinalMethodInfos[sortedOrdinalMethodInfos.Count - 1].Method.ExportInfo.Ordinal.Value + 1;
}
int nameFuncBaseIndex = nextFreeOrdinal - ordinalBase;
int lastFuncIndex = 0;
for (int i = 0; i < sortedOrdinalMethodInfos.Count; i++) {
int index = sortedOrdinalMethodInfos[i].Method.ExportInfo.Ordinal.Value - ordinalBase;
sortedOrdinalMethodInfos[i].FunctionIndex = index;
lastFuncIndex = index;
}
for (int i = 0; i < sortedNameMethodInfos.Count; i++) {
lastFuncIndex = nameFuncBaseIndex + i;
sortedNameMethodInfos[i].FunctionIndex = lastFuncIndex;
}
int funcSize = lastFuncIndex + 1;
if (funcSize > 0x10000) {
logError("Exported function array is too big", Array2.Empty<object>());
return;
}
// Write IMAGE_EXPORT_DIRECTORY
Debug.Assert((writer.Position & 3) == 0);
exportDirOffset = (uint)writer.Position;
writer.WriteUInt32(0); // Characteristics
writer.WriteUInt32(timestamp);
writer.WriteUInt32(0); // MajorVersion, MinorVersion
sdataBytesInfo.exportDirModuleNameStreamOffset = (uint)writer.Position;
writer.WriteUInt32(0); // Name
writer.WriteInt32(ordinalBase); // Base
writer.WriteUInt32((uint)funcSize); // NumberOfFunctions
writer.WriteInt32(sdataBytesInfo.MethodNameOffsets.Length); // NumberOfNames
sdataBytesInfo.exportDirAddressOfFunctionsStreamOffset = (uint)writer.Position;
writer.WriteUInt32(0); // AddressOfFunctions
writer.WriteUInt32(0); // AddressOfNames
writer.WriteUInt32(0); // AddressOfNameOrdinals
sdataBytesInfo.addressOfFunctionsStreamOffset = (uint)writer.Position;
writer.WriteZeroes(funcSize * 4);
sdataBytesInfo.addressOfNamesStreamOffset = (uint)writer.Position;
writer.WriteZeroes(sdataBytesInfo.MethodNameOffsets.Length * 4);
sdataBytesInfo.addressOfNameOrdinalsStreamOffset = (uint)writer.Position;
writer.WriteZeroes(sdataBytesInfo.MethodNameOffsets.Length * 2);
sdataBytesInfo.namesBlobStreamOffset = (uint)writer.Position;
namesBlob.Write(writer);
sdataBytesInfo.Data = stream.ToArray();
}
void WriteSdata(DataWriter writer) {
if (sdataBytesInfo.Data is null)
return;
PatchSdataBytesBlob();
writer.WriteBytes(sdataBytesInfo.Data);
}
void PatchSdataBytesBlob() {
uint rva = (uint)sdataChunk.RVA;
uint namesBaseOffset = rva + sdataBytesInfo.namesBlobStreamOffset;
var writer = new DataWriter(new MemoryStream(sdataBytesInfo.Data));
writer.Position = sdataBytesInfo.exportDirModuleNameStreamOffset;
writer.WriteUInt32(namesBaseOffset + sdataBytesInfo.moduleNameOffset);
writer.Position = sdataBytesInfo.exportDirAddressOfFunctionsStreamOffset;
writer.WriteUInt32(rva + sdataBytesInfo.addressOfFunctionsStreamOffset); // AddressOfFunctions
if (sdataBytesInfo.MethodNameOffsets.Length != 0) {
writer.WriteUInt32(rva + sdataBytesInfo.addressOfNamesStreamOffset); // AddressOfNames
writer.WriteUInt32(rva + sdataBytesInfo.addressOfNameOrdinalsStreamOffset); // AddressOfNameOrdinals
}
uint funcBaseRva = (uint)stubsChunk.RVA;
writer.Position = sdataBytesInfo.addressOfFunctionsStreamOffset;
int currentFuncIndex = 0;
foreach (var info in sortedOrdinalMethodInfos) {
int zeroes = info.FunctionIndex - currentFuncIndex;
if (zeroes < 0)
throw new InvalidOperationException();
while (zeroes-- > 0)
writer.WriteInt32(0);
writer.WriteUInt32(funcBaseRva + info.StubChunkOffset);
currentFuncIndex = info.FunctionIndex + 1;
}
foreach (var info in sortedNameMethodInfos) {
if (info.FunctionIndex != currentFuncIndex++)
throw new InvalidOperationException();
writer.WriteUInt32(funcBaseRva + info.StubChunkOffset);
}
var nameOffsets = sdataBytesInfo.MethodNameOffsets;
if (nameOffsets.Length != 0) {
writer.Position = sdataBytesInfo.addressOfNamesStreamOffset;
foreach (var info in sortedNameMethodInfos)
writer.WriteUInt32(namesBaseOffset + nameOffsets[info.NameIndex]);
writer.Position = sdataBytesInfo.addressOfNameOrdinalsStreamOffset;
foreach (var info in sortedNameMethodInfos)
writer.WriteUInt16((ushort)info.FunctionIndex);
}
}
void WriteVtableFixups(DataWriter writer) {
if (vtables.Count == 0)
return;
foreach (var vtbl in vtables) {
Debug.Assert(vtbl.Methods.Count <= ushort.MaxValue);
writer.WriteUInt32((uint)sdataChunk.RVA + vtbl.SdataChunkOffset);
writer.WriteUInt16((ushort)vtbl.Methods.Count);
writer.WriteUInt16((ushort)vtbl.Flags);
}
}
void WriteStubs(DataWriter writer) {
if (vtables.Count == 0)
return;
if (cpuArch is null)
return;
ulong imageBase = peHeaders.ImageBase;
uint stubsBaseRva = (uint)stubsChunk.RVA;
uint vtblBaseRva = (uint)sdataChunk.RVA;
uint expectedOffset = 0;
uint stubCodeOffset = cpuArch.GetStubCodeOffset(stubType);
uint stubSize = cpuArch.GetStubSize(stubType);
uint stubAlignment = cpuArch.GetStubAlignment(stubType);
int zeroes = (int)((stubSize + stubAlignment - 1 & ~(stubAlignment - 1)) - stubSize);
foreach (var info in allMethodInfos) {
uint currentOffset = info.StubChunkOffset - stubCodeOffset;
if (expectedOffset != currentOffset)
throw new InvalidOperationException();
var pos = writer.Position;
cpuArch.WriteStub(stubType, writer, imageBase, stubsBaseRva + currentOffset, vtblBaseRva + info.ManagedVtblOffset);
Debug.Assert(pos + stubSize == writer.Position, "The full stub wasn't written");
if (pos + stubSize != writer.Position)
throw new InvalidOperationException();
if (zeroes != 0)
writer.WriteZeroes(zeroes);
expectedOffset = (currentOffset + stubSize + stubAlignment - 1) & ~(stubAlignment - 1);
}
if (expectedOffset != stubsChunk.length)
throw new InvalidOperationException();
}
static int CompareTo(byte[] a, byte[] b) {
if (a == b)
return 0;
int max = Math.Min(a.Length, b.Length);
for (int i = 0; i < max; i++) {
int c = a[i] - b[i];
if (c != 0)
return c;
}
return a.Length - b.Length;
}
}
}