// dnlib: See LICENSE.txt for more info
using System;
using dnlib.DotNet.Writer;
using dnlib.IO;
using dnlib.PE;
namespace dnlib.DotNet {
enum StubType {
Export,
EntryPoint,
}
abstract class CpuArch {
// To support a new CPU arch, the easiest way is to check coreclr/src/ilasm/writer.cpp or
// coreclr/src/dlls/mscorpe/stubs.h, eg. ExportStubAMD64Template, ExportStubX86Template,
// ExportStubARMTemplate, ExportStubIA64Template, or use ilasm to generate a file with
// exports and check the stub
static readonly X86CpuArch x86CpuArch = new X86CpuArch();
static readonly X64CpuArch x64CpuArch = new X64CpuArch();
static readonly ItaniumCpuArch itaniumCpuArch = new ItaniumCpuArch();
static readonly ArmCpuArch armCpuArch = new ArmCpuArch();
///
/// Gets the required alignment for the stubs, must be a power of 2
///
/// Stub type
///
public abstract uint GetStubAlignment(StubType stubType);
///
/// Gets the size of a stub, it doesn't have to be a multiple of
///
/// Stub type
///
public abstract uint GetStubSize(StubType stubType);
///
/// Gets the offset of the code (entry point) relative to the start of the stub
///
/// Stub type
///
public abstract uint GetStubCodeOffset(StubType stubType);
public static bool TryGetCpuArch(Machine machine, out CpuArch cpuArch) {
switch (machine) {
case Machine.I386:
case Machine.I386_Native_Apple:
case Machine.I386_Native_FreeBSD:
case Machine.I386_Native_Linux:
case Machine.I386_Native_NetBSD:
case Machine.I386_Native_Sun:
cpuArch = x86CpuArch;
return true;
case Machine.AMD64:
case Machine.AMD64_Native_Apple:
case Machine.AMD64_Native_FreeBSD:
case Machine.AMD64_Native_Linux:
case Machine.AMD64_Native_NetBSD:
case Machine.AMD64_Native_Sun:
cpuArch = x64CpuArch;
return true;
case Machine.IA64:
cpuArch = itaniumCpuArch;
return true;
case Machine.ARMNT:
case Machine.ARMNT_Native_Apple:
case Machine.ARMNT_Native_FreeBSD:
case Machine.ARMNT_Native_Linux:
case Machine.ARMNT_Native_NetBSD:
case Machine.ARMNT_Native_Sun:
cpuArch = armCpuArch;
return true;
case Machine.ARM64:
case Machine.ARM64_Native_Apple:
case Machine.ARM64_Native_FreeBSD:
case Machine.ARM64_Native_Linux:
case Machine.ARM64_Native_NetBSD:
case Machine.ARM64_Native_Sun:
//TODO: Support ARM64
goto default;
default:
cpuArch = null;
return false;
}
}
///
/// Gets the RVA of the func field that the stub jumps to
///
/// Reader, positioned at the stub func
/// PE image
/// Updated with RVA of func field
///
public bool TryGetExportedRvaFromStub(ref DataReader reader, IPEImage peImage, out uint funcRva) =>
TryGetExportedRvaFromStubCore(ref reader, peImage, out funcRva);
protected abstract bool TryGetExportedRvaFromStubCore(ref DataReader reader, IPEImage peImage, out uint funcRva);
///
/// Writes stub relocs, if needed
///
/// Stub type
/// Reloc directory
/// The chunk where this stub will be written to
/// Offset of this stub in
public abstract void WriteStubRelocs(StubType stubType, RelocDirectory relocDirectory, IChunk chunk, uint stubOffset);
///
/// Writes the stub that jumps to the managed function
///
/// Stub type
/// Writer
/// Image base
/// RVA of this stub
/// RVA of a pointer-sized field that contains the absolute address of the managed function
public abstract void WriteStub(StubType stubType, DataWriter writer, ulong imageBase, uint stubRva, uint managedFuncRva);
}
sealed class X86CpuArch : CpuArch {
public override uint GetStubAlignment(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 4;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubSize(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 2/*padding*/ + 6;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubCodeOffset(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 2/*padding*/;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override bool TryGetExportedRvaFromStubCore(ref DataReader reader, IPEImage peImage, out uint funcRva) {
funcRva = 0;
// FF25xxxxxxxx jmp DWORD PTR [xxxxxxxx]
if (reader.ReadUInt16() != 0x25FF)
return false;
funcRva = reader.ReadUInt32() - (uint)peImage.ImageNTHeaders.OptionalHeader.ImageBase;
return true;
}
public override void WriteStubRelocs(StubType stubType, RelocDirectory relocDirectory, IChunk chunk, uint stubOffset) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
relocDirectory.Add(chunk, stubOffset + 4);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void WriteStub(StubType stubType, DataWriter writer, ulong imageBase, uint stubRva, uint managedFuncRva) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
writer.WriteUInt16(0);// padding
writer.WriteUInt16(0x25FF);
writer.WriteUInt32((uint)imageBase + managedFuncRva);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
sealed class X64CpuArch : CpuArch {
public override uint GetStubAlignment(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 4;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubSize(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 2/*padding*/ + 12;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubCodeOffset(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 2/*padding*/;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override bool TryGetExportedRvaFromStubCore(ref DataReader reader, IPEImage peImage, out uint funcRva) {
funcRva = 0;
// 48A1xxxxxxxxxxxxxxxx movabs rax,[xxxxxxxxxxxxxxxx]
// FFE0 jmp rax
if (reader.ReadUInt16() != 0xA148)
return false;
ulong absAddr = reader.ReadUInt64();
if (reader.ReadUInt16() != 0xE0FF)
return false;
ulong rva = absAddr - peImage.ImageNTHeaders.OptionalHeader.ImageBase;
if (rva > uint.MaxValue)
return false;
funcRva = (uint)rva;
return true;
}
public override void WriteStubRelocs(StubType stubType, RelocDirectory relocDirectory, IChunk chunk, uint stubOffset) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
relocDirectory.Add(chunk, stubOffset + 4);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void WriteStub(StubType stubType, DataWriter writer, ulong imageBase, uint stubRva, uint managedFuncRva) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
writer.WriteUInt16(0);// padding
writer.WriteUInt16(0xA148);
writer.WriteUInt64(imageBase + managedFuncRva);
writer.WriteUInt16(0xE0FF);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
sealed class ItaniumCpuArch : CpuArch {
public override uint GetStubAlignment(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 16;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubSize(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 0x30;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubCodeOffset(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 0x20;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override bool TryGetExportedRvaFromStubCore(ref DataReader reader, IPEImage peImage, out uint funcRva) {
funcRva = 0;
// From ExportStubIA64Template in coreclr/src/ilasm/writer.cpp
//
// ld8 r9 = [gp] ;;
// ld8 r10 = [r9],8
// nop.i ;;
// ld8 gp = [r9]
// mov b6 = r10
// br.cond.sptk.few b6
//
// 0x0B, 0x48, 0x00, 0x02, 0x18, 0x10, 0xA0, 0x40,
// 0x24, 0x30, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00,
// 0x10, 0x08, 0x00, 0x12, 0x18, 0x10, 0x60, 0x50,
// 0x04, 0x80, 0x03, 0x00, 0x60, 0x00, 0x80, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//address of the template
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 //address of VTFixup slot
ulong addrTemplate = reader.ReadUInt64();
ulong absAddr = reader.ReadUInt64();
reader.Position = (uint)peImage.ToFileOffset((RVA)(addrTemplate - peImage.ImageNTHeaders.OptionalHeader.ImageBase));
if (reader.ReadUInt64() != 0x40A010180200480BUL)
return false;
if (reader.ReadUInt64() != 0x0004000000283024UL)
return false;
if (reader.ReadUInt64() != 0x5060101812000810UL)
return false;
if (reader.ReadUInt64() != 0x0080006000038004UL)
return false;
ulong rva = absAddr - peImage.ImageNTHeaders.OptionalHeader.ImageBase;
if (rva > uint.MaxValue)
return false;
funcRva = (uint)rva;
return true;
}
public override void WriteStubRelocs(StubType stubType, RelocDirectory relocDirectory, IChunk chunk, uint stubOffset) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
relocDirectory.Add(chunk, stubOffset + 0x20);
relocDirectory.Add(chunk, stubOffset + 0x28);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void WriteStub(StubType stubType, DataWriter writer, ulong imageBase, uint stubRva, uint managedFuncRva) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
writer.WriteUInt64(0x40A010180200480BUL);
writer.WriteUInt64(0x0004000000283024UL);
writer.WriteUInt64(0x5060101812000810UL);
writer.WriteUInt64(0x0080006000038004UL);
writer.WriteUInt64(imageBase + stubRva);
writer.WriteUInt64(imageBase + managedFuncRva);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
sealed class ArmCpuArch : CpuArch {
public override uint GetStubAlignment(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 4;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubSize(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 8;
default:
throw new ArgumentOutOfRangeException();
}
}
public override uint GetStubCodeOffset(StubType stubType) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
return 0;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override bool TryGetExportedRvaFromStubCore(ref DataReader reader, IPEImage peImage, out uint funcRva) {
funcRva = 0;
// DFF800F0 ldr.w pc,[pc]
// xxxxxxxx
if (reader.ReadUInt32() != 0xF000F8DF)
return false;
funcRva = reader.ReadUInt32() - (uint)peImage.ImageNTHeaders.OptionalHeader.ImageBase;
return true;
}
public override void WriteStubRelocs(StubType stubType, RelocDirectory relocDirectory, IChunk chunk, uint stubOffset) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
relocDirectory.Add(chunk, stubOffset + 4);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void WriteStub(StubType stubType, DataWriter writer, ulong imageBase, uint stubRva, uint managedFuncRva) {
switch (stubType) {
case StubType.Export:
case StubType.EntryPoint:
writer.WriteUInt32(0xF000F8DF);
writer.WriteUInt32((uint)imageBase + managedFuncRva);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}