/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using dnlib.DotNet.MD;
using dnlib.PE;
namespace dnSpy.AsmEditor.Compiler.MDEditor {
sealed class MDWriter {
readonly RawModuleBytes moduleData;
readonly MetadataEditor mdEditor;
readonly MDWriterStream stream;
readonly List sections;
PESection? textSection;
long dataDirPosition;
public MetadataEditor MetadataEditor => mdEditor;
public RawModuleBytes ModuleData => moduleData;
const uint SectionAlignment = 0x2000;
const uint FileAlignment = 0x200;
static readonly byte[] dosHeader = new byte[0x80] {
0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD,
0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x54, 0x68,
0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72,
0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E, 0x6E, 0x6F,
0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E,
0x20, 0x69, 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20,
0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A,
0x24, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
};
sealed class PESection {
public byte[] Name { get; }
public uint Characteristics { get; }
public List SectionData { get; }
public uint VirtualAddress { get; set; }
public uint PointerToRawData { get; set; }
public uint VirtualSize { get; set; }
public uint SizeOfRawData { get; set; }
public PESection(string name, uint characteristics) {
var bytes = Encoding.ASCII.GetBytes(name);
if (bytes.Length != 8)
Array.Resize(ref bytes, 8);
Name = bytes;
Characteristics = characteristics;
SectionData = new List();
}
}
public MDWriter(RawModuleBytes moduleData, MetadataEditor mdEditor, MDWriterStream stream) {
this.moduleData = moduleData;
this.mdEditor = mdEditor;
this.stream = stream;
sections = new List();
}
public void Write() {
var peImage = mdEditor.RealMetadata.PEImage;
bool is32Bit = peImage.ImageNTHeaders.OptionalHeader is ImageOptionalHeader32;
sections.Add(textSection = new PESection(".text", 0x60000020));
StrongNameSignatureSectionData? snData = null;
var cor20 = mdEditor.RealMetadata.ImageCor20Header;
if ((cor20.Flags & ComImageFlags.StrongNameSigned) != 0 && cor20.StrongNameSignature.Size != 0 && cor20.StrongNameSignature.VirtualAddress != 0)
snData = new StrongNameSignatureSectionData(cor20.StrongNameSignature.Size);
if (snData is not null)
textSection.SectionData.Add(snData);
var mdData = new DotNetMetadataSectionData(mdEditor);
textSection.SectionData.Add(new ImageCor20HeaderSectionData(mdData, snData));
textSection.SectionData.Add(mdData);
// DOS MZ header
stream.Write(dosHeader);
// PE\0\0
Debug.Assert(stream.Position == BitConverter.ToUInt32(dosHeader, 0x7C));
stream.Write(0x00004550);
// IMAGE_FILE_HEADER
var ifh = peImage.ImageNTHeaders.FileHeader;
stream.Write((ushort)ifh.Machine);
Debug.Assert(sections.Count <= ushort.MaxValue);
stream.Write((ushort)sections.Count);
stream.Write(ifh.TimeDateStamp);
stream.Position += 8;// PointerToSymbolTable & NumberOfSymbols
stream.Write((ushort)(is32Bit ? 0xE0 : 0xF0));
stream.Write((ushort)ifh.Characteristics);
// IMAGE_OPTIONAL_HEADER
var optHeaderPos = stream.Position;
if (is32Bit) {
var opt = (ImageOptionalHeader32)peImage.ImageNTHeaders.OptionalHeader;
var start = stream.Position;
stream.Write(opt.Magic);
stream.Write(opt.MajorLinkerVersion);
stream.Write(opt.MinorLinkerVersion);
// SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData
// AddressOfEntryPoint, BaseOfCode, BaseOfData
stream.Position += 6 * 4;
stream.Write((uint)opt.ImageBase);
stream.Write((uint)SectionAlignment);
stream.Write((uint)FileAlignment);
stream.Write(opt.MajorOperatingSystemVersion);
stream.Write(opt.MinorOperatingSystemVersion);
stream.Write(opt.MajorImageVersion);
stream.Write(opt.MinorImageVersion);
stream.Write(opt.MajorSubsystemVersion);
stream.Write(opt.MinorSubsystemVersion);
stream.Write(opt.Win32VersionValue);
// SizeOfImage, SizeOfHeaders, CheckSum
stream.Position += 3 * 4;
stream.Write((ushort)opt.Subsystem);
stream.Write((ushort)opt.DllCharacteristics);
stream.Write((uint)opt.SizeOfStackReserve);
stream.Write((uint)opt.SizeOfStackCommit);
stream.Write((uint)opt.SizeOfHeapReserve);
stream.Write((uint)opt.SizeOfHeapCommit);
stream.Write(opt.LoaderFlags);
stream.Write(0x10);// NumberOfRvaAndSizes
Debug.Assert((stream.Position - start) == 0x60);
}
else {
var opt = (ImageOptionalHeader64)peImage.ImageNTHeaders.OptionalHeader;
var start = stream.Position;
stream.Write(opt.Magic);
stream.Write(opt.MajorLinkerVersion);
stream.Write(opt.MinorLinkerVersion);
// SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData
// AddressOfEntryPoint, BaseOfCode
stream.Position += 5 * 4;
stream.Write(opt.ImageBase);
stream.Write((uint)SectionAlignment);
stream.Write((uint)FileAlignment);
stream.Write(opt.MajorOperatingSystemVersion);
stream.Write(opt.MinorOperatingSystemVersion);
stream.Write(opt.MajorImageVersion);
stream.Write(opt.MinorImageVersion);
stream.Write(opt.MajorSubsystemVersion);
stream.Write(opt.MinorSubsystemVersion);
stream.Write(opt.Win32VersionValue);
// SizeOfImage, SizeOfHeaders, CheckSum
stream.Position += 3 * 4;
stream.Write((ushort)opt.Subsystem);
stream.Write((ushort)opt.DllCharacteristics);
stream.Write(opt.SizeOfStackReserve);
stream.Write(opt.SizeOfStackCommit);
stream.Write(opt.SizeOfHeapReserve);
stream.Write(opt.SizeOfHeapCommit);
stream.Write(opt.LoaderFlags);
stream.Write(0x10);// NumberOfRvaAndSizes
Debug.Assert((stream.Position - start) == 0x70);
}
// IMAGE_DATA_DIRECTORY
dataDirPosition = stream.Position;
stream.Position += 0x10 * 8;
// IMAGE_SECTION_HEADER
var sectionPos = stream.Position;
foreach (var section in sections) {
Debug.Assert(section.Name.Length == 8);
stream.Write(section.Name);
// VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData
// PointerToRelocations, PointerToLinenumbers, NumberOfRelocations, NumberOfLinenumbers
stream.Position += 6 * 4 + 2 * 2;
stream.Write(section.Characteristics);
}
uint headerLength = (uint)stream.Position;
AlignUp(FileAlignment);
// Write all sections
uint rva = SectionAlignment;
foreach (var section in sections) {
section.VirtualAddress = rva;
section.PointerToRawData = (uint)stream.Position;
foreach (var data in section.SectionData) {
var pos = stream.Position;
AlignUp(data.Alignment);
rva += (uint)(stream.Position - pos);
pos = stream.Position;
data.Write(this, rva, stream);
rva += (uint)(stream.Position - pos);
}
Debug.Assert(stream.Position != section.PointerToRawData);
if (stream.Position == section.PointerToRawData)
stream.Position++;
section.VirtualSize = (uint)stream.Position - section.PointerToRawData;
section.SizeOfRawData = ((uint)stream.Position - section.PointerToRawData + FileAlignment - 1) & ~(FileAlignment - 1);
rva = (rva + SectionAlignment - 1) & ~(SectionAlignment - 1);
AlignUp(FileAlignment);
}
stream.Length = stream.Position;
// Update IMAGE_SECTION_HEADER
stream.Position = sectionPos;
foreach (var section in sections) {
stream.Position += 8;
stream.Write(section.VirtualSize);
stream.Write(section.VirtualAddress);
stream.Write(section.SizeOfRawData);
stream.Write(section.PointerToRawData);
stream.Position += 16;
}
// Update IMAGE_OPTIONAL_HEADER
var sectionSizes = new SectionSizes(FileAlignment, SectionAlignment, headerLength, () => GetSectionSizeInfos());
stream.Position = optHeaderPos;
if (is32Bit) {
var opt = (ImageOptionalHeader32)peImage.ImageNTHeaders.OptionalHeader;
stream.Position += 4;
stream.Write(sectionSizes.SizeOfCode);
stream.Write(sectionSizes.SizeOfInitdData);
stream.Write(sectionSizes.SizeOfUninitdData);
stream.Position += 4;
stream.Write(sectionSizes.BaseOfCode);
stream.Write(sectionSizes.BaseOfData);
stream.Position += 0x1C;
stream.Write(sectionSizes.SizeOfImage);
stream.Write(sectionSizes.SizeOfHeaders);
}
else {
var opt = (ImageOptionalHeader64)peImage.ImageNTHeaders.OptionalHeader;
stream.Position += 4;
stream.Write(sectionSizes.SizeOfCode);
stream.Write(sectionSizes.SizeOfInitdData);
stream.Write(sectionSizes.SizeOfUninitdData);
stream.Position += 4;
stream.Write(sectionSizes.BaseOfCode);
stream.Position += 0x20;
stream.Write(sectionSizes.SizeOfImage);
stream.Write(sectionSizes.SizeOfHeaders);
}
foreach (var section in sections) {
foreach (var data in section.SectionData)
data.Finish(this, stream);
}
}
IEnumerable GetSectionSizeInfos() {
foreach (var section in sections)
yield return new SectionSizeInfo(section.VirtualSize, section.Characteristics);
}
void AlignUp(uint alignment) => stream.Position = (stream.Position + alignment - 1) & ~(alignment - 1);
public void WriteDataDirectory(int index, uint rva, uint size) {
if ((uint)index >= 0x10)
throw new ArgumentOutOfRangeException(nameof(index));
var oldPos = stream.Position;
stream.Position = dataDirPosition + index * 8;
stream.Write(rva);
stream.Write(size);
stream.Position = oldPos;
}
}
}