2 Copyright (C) 2008-2011 Jeroen Frijters
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
25 using System.Collections.Generic;
26 using System.Diagnostics;
28 using System.Security.Cryptography;
29 using IKVM.Reflection.Emit;
30 using IKVM.Reflection.Impl;
31 using IKVM.Reflection.Metadata;
33 namespace IKVM.Reflection.Writer
35 static class ModuleWriter
37 internal static void WriteModule(StrongNameKeyPair keyPair, byte[] publicKey, ModuleBuilder moduleBuilder,
38 PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine,
39 ResourceSection resources, int entryPointToken)
41 WriteModule(keyPair, publicKey, moduleBuilder, fileKind, portableExecutableKind, imageFileMachine, resources, entryPointToken, null);
44 internal static void WriteModule(StrongNameKeyPair keyPair, byte[] publicKey, ModuleBuilder moduleBuilder,
45 PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine,
46 ResourceSection resources, int entryPointToken, Stream stream)
50 string fileName = moduleBuilder.FullyQualifiedName;
51 bool mono = System.Type.GetType("Mono.Runtime") != null;
56 // Mono mmaps the file, so unlink the previous version since it may be in use
57 File.Delete(fileName);
61 using (FileStream fs = new FileStream(fileName, FileMode.Create))
63 WriteModuleImpl(keyPair, publicKey, moduleBuilder, fileKind, portableExecutableKind, imageFileMachine, resources, entryPointToken, fs);
65 // if we're running on Mono, mark the module as executable by using a Mono private API extension
68 File.SetAttributes(fileName, (FileAttributes)(unchecked((int)0x80000000)));
73 WriteModuleImpl(keyPair, publicKey, moduleBuilder, fileKind, portableExecutableKind, imageFileMachine, resources, entryPointToken, stream);
77 private static void WriteModuleImpl(StrongNameKeyPair keyPair, byte[] publicKey, ModuleBuilder moduleBuilder,
78 PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine,
79 ResourceSection resources, int entryPointToken, Stream stream)
81 moduleBuilder.ApplyUnmanagedExports(imageFileMachine);
82 moduleBuilder.FixupMethodBodyTokens();
84 moduleBuilder.ModuleTable.Add(0, moduleBuilder.Strings.Add(moduleBuilder.moduleName), moduleBuilder.Guids.Add(moduleBuilder.ModuleVersionId), 0, 0);
86 if (moduleBuilder.UserStrings.IsEmpty)
88 // for compat with Ref.Emit, if there aren't any user strings, we add one
89 moduleBuilder.UserStrings.Add(" ");
92 if (resources != null)
97 PEWriter writer = new PEWriter(stream);
98 writer.Headers.OptionalHeader.FileAlignment = (uint)moduleBuilder.__FileAlignment;
99 switch (imageFileMachine)
101 case ImageFileMachine.I386:
102 writer.Headers.FileHeader.Machine = IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE_I386;
103 writer.Headers.FileHeader.Characteristics |= IMAGE_FILE_HEADER.IMAGE_FILE_32BIT_MACHINE;
104 writer.Headers.OptionalHeader.SizeOfStackReserve = moduleBuilder.GetStackReserve(0x100000);
106 case ImageFileMachine.ARM:
107 writer.Headers.FileHeader.Machine = IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE_ARM;
108 writer.Headers.FileHeader.Characteristics |= IMAGE_FILE_HEADER.IMAGE_FILE_32BIT_MACHINE;
109 writer.Headers.OptionalHeader.SizeOfStackReserve = moduleBuilder.GetStackReserve(0x100000);
111 case ImageFileMachine.AMD64:
112 writer.Headers.FileHeader.Machine = IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE_AMD64;
113 writer.Headers.FileHeader.Characteristics |= IMAGE_FILE_HEADER.IMAGE_FILE_LARGE_ADDRESS_AWARE;
114 writer.Headers.FileHeader.SizeOfOptionalHeader = 0xF0;
115 writer.Headers.OptionalHeader.Magic = IMAGE_OPTIONAL_HEADER.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
116 writer.Headers.OptionalHeader.SizeOfStackReserve = moduleBuilder.GetStackReserve(0x400000);
117 writer.Headers.OptionalHeader.SizeOfStackCommit = 0x4000;
118 writer.Headers.OptionalHeader.SizeOfHeapCommit = 0x2000;
120 case ImageFileMachine.IA64:
121 writer.Headers.FileHeader.Machine = IMAGE_FILE_HEADER.IMAGE_FILE_MACHINE_IA64;
122 writer.Headers.FileHeader.Characteristics |= IMAGE_FILE_HEADER.IMAGE_FILE_LARGE_ADDRESS_AWARE;
123 writer.Headers.FileHeader.SizeOfOptionalHeader = 0xF0;
124 writer.Headers.OptionalHeader.Magic = IMAGE_OPTIONAL_HEADER.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
125 writer.Headers.OptionalHeader.SizeOfStackReserve = moduleBuilder.GetStackReserve(0x400000);
126 writer.Headers.OptionalHeader.SizeOfStackCommit = 0x4000;
127 writer.Headers.OptionalHeader.SizeOfHeapCommit = 0x2000;
130 throw new ArgumentOutOfRangeException("imageFileMachine");
132 if (fileKind == PEFileKinds.Dll)
134 writer.Headers.FileHeader.Characteristics |= IMAGE_FILE_HEADER.IMAGE_FILE_DLL;
139 case PEFileKinds.WindowApplication:
140 writer.Headers.OptionalHeader.Subsystem = IMAGE_OPTIONAL_HEADER.IMAGE_SUBSYSTEM_WINDOWS_GUI;
143 writer.Headers.OptionalHeader.Subsystem = IMAGE_OPTIONAL_HEADER.IMAGE_SUBSYSTEM_WINDOWS_CUI;
146 writer.Headers.OptionalHeader.DllCharacteristics = (ushort)moduleBuilder.__DllCharacteristics;
148 CliHeader cliHeader = new CliHeader();
150 cliHeader.MajorRuntimeVersion = 2;
151 cliHeader.MinorRuntimeVersion = moduleBuilder.MDStreamVersion < 0x20000 ? (ushort)0 : (ushort)5;
152 if ((portableExecutableKind & PortableExecutableKinds.ILOnly) != 0)
154 cliHeader.Flags |= CliHeader.COMIMAGE_FLAGS_ILONLY;
156 if ((portableExecutableKind & PortableExecutableKinds.Required32Bit) != 0)
158 cliHeader.Flags |= CliHeader.COMIMAGE_FLAGS_32BITREQUIRED;
160 if ((portableExecutableKind & PortableExecutableKinds.Preferred32Bit) != 0)
162 cliHeader.Flags |= CliHeader.COMIMAGE_FLAGS_32BITREQUIRED | CliHeader.COMIMAGE_FLAGS_32BITPREFERRED;
166 cliHeader.Flags |= CliHeader.COMIMAGE_FLAGS_STRONGNAMESIGNED;
168 if (moduleBuilder.IsPseudoToken(entryPointToken))
170 entryPointToken = moduleBuilder.ResolvePseudoToken(entryPointToken);
172 cliHeader.EntryPointToken = (uint)entryPointToken;
174 moduleBuilder.Strings.Freeze();
175 moduleBuilder.UserStrings.Freeze();
176 moduleBuilder.Guids.Freeze();
177 moduleBuilder.Blobs.Freeze();
178 MetadataWriter mw = new MetadataWriter(moduleBuilder, stream);
179 moduleBuilder.Tables.Freeze(mw);
180 TextSection code = new TextSection(writer, cliHeader, moduleBuilder, ComputeStrongNameSignatureLength(publicKey));
183 if (code.ExportDirectoryLength != 0)
185 writer.Headers.OptionalHeader.DataDirectory[0].VirtualAddress = code.ExportDirectoryRVA;
186 writer.Headers.OptionalHeader.DataDirectory[0].Size = code.ExportDirectoryLength;
190 if (code.ImportDirectoryLength != 0)
192 writer.Headers.OptionalHeader.DataDirectory[1].VirtualAddress = code.ImportDirectoryRVA;
193 writer.Headers.OptionalHeader.DataDirectory[1].Size = code.ImportDirectoryLength;
196 // Import Address Table Directory
197 if (code.ImportAddressTableLength != 0)
199 writer.Headers.OptionalHeader.DataDirectory[12].VirtualAddress = code.ImportAddressTableRVA;
200 writer.Headers.OptionalHeader.DataDirectory[12].Size = code.ImportAddressTableLength;
203 // COM Descriptor Directory
204 writer.Headers.OptionalHeader.DataDirectory[14].VirtualAddress = code.ComDescriptorRVA;
205 writer.Headers.OptionalHeader.DataDirectory[14].Size = code.ComDescriptorLength;
208 if (code.DebugDirectoryLength != 0)
210 writer.Headers.OptionalHeader.DataDirectory[6].VirtualAddress = code.DebugDirectoryRVA;
211 writer.Headers.OptionalHeader.DataDirectory[6].Size = code.DebugDirectoryLength;
214 // we need to start by computing the number of sections, because code.PointerToRawData depends on that
215 writer.Headers.FileHeader.NumberOfSections = 1;
217 if (moduleBuilder.initializedData.Length != 0)
220 writer.Headers.FileHeader.NumberOfSections++;
223 if (resources != null)
226 writer.Headers.FileHeader.NumberOfSections++;
229 if (imageFileMachine != ImageFileMachine.ARM)
232 writer.Headers.FileHeader.NumberOfSections++;
235 SectionHeader text = new SectionHeader();
237 text.VirtualAddress = code.BaseRVA;
238 text.VirtualSize = (uint)code.Length;
239 text.PointerToRawData = code.PointerToRawData;
240 text.SizeOfRawData = writer.ToFileAlignment((uint)code.Length);
241 text.Characteristics = SectionHeader.IMAGE_SCN_CNT_CODE | SectionHeader.IMAGE_SCN_MEM_EXECUTE | SectionHeader.IMAGE_SCN_MEM_READ;
243 SectionHeader sdata = new SectionHeader();
244 sdata.Name = ".sdata";
245 sdata.VirtualAddress = text.VirtualAddress + writer.ToSectionAlignment(text.VirtualSize);
246 sdata.VirtualSize = (uint)moduleBuilder.initializedData.Length;
247 sdata.PointerToRawData = text.PointerToRawData + text.SizeOfRawData;
248 sdata.SizeOfRawData = writer.ToFileAlignment((uint)moduleBuilder.initializedData.Length);
249 sdata.Characteristics = SectionHeader.IMAGE_SCN_CNT_INITIALIZED_DATA | SectionHeader.IMAGE_SCN_MEM_READ | SectionHeader.IMAGE_SCN_MEM_WRITE;
251 SectionHeader rsrc = new SectionHeader();
253 rsrc.VirtualAddress = sdata.VirtualAddress + writer.ToSectionAlignment(sdata.VirtualSize);
254 rsrc.PointerToRawData = sdata.PointerToRawData + sdata.SizeOfRawData;
255 rsrc.VirtualSize = resources == null ? 0 : (uint)resources.Length;
256 rsrc.SizeOfRawData = writer.ToFileAlignment(rsrc.VirtualSize);
257 rsrc.Characteristics = SectionHeader.IMAGE_SCN_MEM_READ | SectionHeader.IMAGE_SCN_CNT_INITIALIZED_DATA;
259 if (rsrc.SizeOfRawData != 0)
261 // Resource Directory
262 writer.Headers.OptionalHeader.DataDirectory[2].VirtualAddress = rsrc.VirtualAddress;
263 writer.Headers.OptionalHeader.DataDirectory[2].Size = rsrc.VirtualSize;
266 SectionHeader reloc = new SectionHeader();
267 reloc.Name = ".reloc";
268 reloc.VirtualAddress = rsrc.VirtualAddress + writer.ToSectionAlignment(rsrc.VirtualSize);
269 if (imageFileMachine != ImageFileMachine.ARM)
271 reloc.VirtualSize = ((uint)moduleBuilder.unmanagedExports.Count + 1) * 12;
273 reloc.PointerToRawData = rsrc.PointerToRawData + rsrc.SizeOfRawData;
274 reloc.SizeOfRawData = writer.ToFileAlignment(reloc.VirtualSize);
275 reloc.Characteristics = SectionHeader.IMAGE_SCN_MEM_READ | SectionHeader.IMAGE_SCN_CNT_INITIALIZED_DATA | SectionHeader.IMAGE_SCN_MEM_DISCARDABLE;
277 if (reloc.SizeOfRawData != 0)
279 // Base Relocation Directory
280 writer.Headers.OptionalHeader.DataDirectory[5].VirtualAddress = reloc.VirtualAddress;
281 writer.Headers.OptionalHeader.DataDirectory[5].Size = reloc.VirtualSize;
284 writer.Headers.OptionalHeader.SizeOfCode = text.SizeOfRawData;
285 writer.Headers.OptionalHeader.SizeOfInitializedData = sdata.SizeOfRawData + rsrc.SizeOfRawData + reloc.SizeOfRawData;
286 writer.Headers.OptionalHeader.SizeOfUninitializedData = 0;
287 writer.Headers.OptionalHeader.SizeOfImage = reloc.VirtualAddress + writer.ToSectionAlignment(reloc.VirtualSize);
288 writer.Headers.OptionalHeader.SizeOfHeaders = text.PointerToRawData;
289 writer.Headers.OptionalHeader.BaseOfCode = code.BaseRVA;
290 writer.Headers.OptionalHeader.BaseOfData = sdata.VirtualAddress;
291 writer.Headers.OptionalHeader.ImageBase = (ulong)moduleBuilder.__ImageBase;
293 if (imageFileMachine == ImageFileMachine.IA64)
295 // apparently for IA64 AddressOfEntryPoint points to the address of the entry point
296 // (i.e. there is an additional layer of indirection), so we add the offset to the pointer
297 writer.Headers.OptionalHeader.AddressOfEntryPoint = code.StartupStubRVA + 0x20;
299 else if (imageFileMachine != ImageFileMachine.ARM)
301 writer.Headers.OptionalHeader.AddressOfEntryPoint = code.StartupStubRVA;
304 writer.WritePEHeaders();
305 writer.WriteSectionHeader(text);
306 if (sdata.SizeOfRawData != 0)
308 writer.WriteSectionHeader(sdata);
310 if (rsrc.SizeOfRawData != 0)
312 writer.WriteSectionHeader(rsrc);
314 if (reloc.SizeOfRawData != 0)
316 writer.WriteSectionHeader(reloc);
319 stream.Seek(text.PointerToRawData, SeekOrigin.Begin);
320 code.Write(mw, sdata.VirtualAddress);
322 if (sdata.SizeOfRawData != 0)
324 stream.Seek(sdata.PointerToRawData, SeekOrigin.Begin);
325 mw.Write(moduleBuilder.initializedData);
328 if (rsrc.SizeOfRawData != 0)
330 stream.Seek(rsrc.PointerToRawData, SeekOrigin.Begin);
331 resources.Write(mw, rsrc.VirtualAddress);
334 if (reloc.SizeOfRawData != 0)
336 stream.Seek(reloc.PointerToRawData, SeekOrigin.Begin);
337 code.WriteRelocations(mw);
341 stream.SetLength(reloc.PointerToRawData + reloc.SizeOfRawData);
343 // do the strong naming
346 StrongName(stream, keyPair, writer.HeaderSize, text.PointerToRawData, code.StrongNameSignatureRVA - text.VirtualAddress + text.PointerToRawData, code.StrongNameSignatureLength);
349 if (moduleBuilder.symbolWriter != null)
351 moduleBuilder.WriteSymbolTokenMap();
352 moduleBuilder.symbolWriter.Close();
356 private static int ComputeStrongNameSignatureLength(byte[] publicKey)
358 if (publicKey == null)
362 else if (publicKey.Length == 16)
364 // it must be the ECMA pseudo public key, we don't know the key size of the real key, but currently both Mono and Microsoft use a 1024 bit key size
369 // for the supported strong naming algorithms, the signature size is the same as the key size
370 // (we have to subtract 32 for the header)
371 return publicKey.Length - 32;
375 private static void StrongName(Stream stream, StrongNameKeyPair keyPair, uint headerLength, uint textSectionFileOffset, uint strongNameSignatureFileOffset, uint strongNameSignatureLength)
377 SHA1Managed hash = new SHA1Managed();
378 using (CryptoStream cs = new CryptoStream(Stream.Null, hash, CryptoStreamMode.Write))
380 stream.Seek(0, SeekOrigin.Begin);
381 byte[] buf = new byte[8192];
382 HashChunk(stream, cs, buf, (int)headerLength);
383 stream.Seek(textSectionFileOffset, SeekOrigin.Begin);
384 HashChunk(stream, cs, buf, (int)(strongNameSignatureFileOffset - textSectionFileOffset));
385 stream.Seek(strongNameSignatureLength, SeekOrigin.Current);
386 HashChunk(stream, cs, buf, (int)(stream.Length - (strongNameSignatureFileOffset + strongNameSignatureLength)));
388 using (RSA rsa = keyPair.CreateRSA())
390 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter(rsa);
391 byte[] signature = sign.CreateSignature(hash);
392 Array.Reverse(signature);
393 if (signature.Length != strongNameSignatureLength)
395 throw new InvalidOperationException("Signature length mismatch");
397 stream.Seek(strongNameSignatureFileOffset, SeekOrigin.Begin);
398 stream.Write(signature, 0, signature.Length);
401 // compute the PE checksum
402 stream.Seek(0, SeekOrigin.Begin);
403 int count = (int)stream.Length / 4;
404 BinaryReader br = new BinaryReader(stream);
406 for (int i = 0; i < count; i++)
408 sum += br.ReadUInt32();
409 int carry = (int)(sum >> 32);
413 while ((sum >> 16) != 0)
415 sum = (sum & 0xFFFF) + (sum >> 16);
417 sum += stream.Length;
419 // write the PE checksum, note that it is always at offset 0xD8 in the file
420 ByteBuffer bb = new ByteBuffer(4);
422 stream.Seek(0xD8, SeekOrigin.Begin);
426 internal static void HashChunk(Stream stream, CryptoStream cs, byte[] buf, int length)
430 int read = stream.Read(buf, 0, Math.Min(buf.Length, length));
431 cs.Write(buf, 0, read);