IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
ModuleWriter.cs
Go to the documentation of this file.
1/*
2 Copyright (C) 2008-2015 Jeroen Frijters
3
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.
7
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:
11
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.
19
20 Jeroen Frijters
21 jeroen@frijters.net
22
23*/
24using System;
25using System.Collections.Generic;
26using System.Collections.Immutable;
27using System.Diagnostics;
28using System.IO;
29using System.Linq;
30using System.Reflection.Emit;
31using System.Reflection.Metadata;
32using System.Reflection.Metadata.Ecma335;
33using System.Reflection.PortableExecutable;
34using System.Security.Cryptography;
35using System.Text;
36
39
41{
42
43 static class ModuleWriter
44 {
45
46 const ulong DefaultExeBaseAddress32Bit = 0x00400000;
47 const ulong DefaultExeBaseAddress64Bit = 0x0000000140000000;
48
49 const ulong DefaultDllBaseAddress32Bit = 0x10000000;
50 const ulong DefaultDllBaseAddress64Bit = 0x0000000180000000;
51
52 const ulong DefaultSizeOfHeapReserve32Bit = 0x00100000;
53 const ulong DefaultSizeOfHeapReserve64Bit = 0x00400000;
54
55 const ulong DefaultSizeOfHeapCommit32Bit = 0x1000;
56 const ulong DefaultSizeOfHeapCommit64Bit = 0x2000;
57
58 const ulong DefaultSizeOfStackReserve32Bit = 0x00100000;
59 const ulong DefaultSizeOfStackReserve64Bit = 0x00400000;
60
61 const ulong DefaultSizeOfStackCommit32Bit = 0x1000;
62 const ulong DefaultSizeOfStackCommit64Bit = 0x4000;
63
64 const ushort DefaultFileAlignment32Bit = 0x200;
65 const ushort DefaultFileAlignment64Bit = 0x200; //both 32 and 64 bit binaries used this value in the native stack.
66
67 const ushort DefaultSectionAlignment = 0x2000;
68
80 internal static void WriteModule(StrongNameKeyPair keyPair, byte[] publicKey, IKVM.Reflection.Emit.ModuleBuilder module, IKVM.Reflection.Emit.PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine, ModuleResourceSectionBuilder nativeResources, MethodInfo entryPoint)
81 {
82 WriteModule(keyPair, publicKey, module, fileKind, portableExecutableKind, imageFileMachine, nativeResources, entryPoint, null, null, null, null);
83 }
84
98 internal static void WriteModule(StrongNameKeyPair keyPair, byte[] publicKey, IKVM.Reflection.Emit.ModuleBuilder module, IKVM.Reflection.Emit.PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine, ModuleResourceSectionBuilder nativeResources, MethodInfo entryPoint, string peFileName, Stream peStream, string pdbFileName, Stream pdbStream)
99 {
100 FileStream disposablePeStream = null;
101 FileStream disposePdbStream = null;
102
103 // default values for PE file output
104 peFileName ??= module.FullyQualifiedName;
105 peStream ??= disposablePeStream = new FileStream(peFileName, FileMode.Create);
106
107 // default values for PDB file output, if emitting symbols
108 if (module.GetSymWriter() is IMetadataSymbolWriter)
109 {
110 pdbFileName ??= Path.ChangeExtension(peFileName, ".pdb");
111 pdbStream ??= disposePdbStream = new FileStream(pdbFileName, FileMode.Create);
112 }
113
114 try
115 {
116 WriteModuleImpl(keyPair, publicKey, module, fileKind, portableExecutableKind, imageFileMachine, nativeResources, entryPoint, peFileName, peStream, pdbFileName, pdbStream);
117 }
118 finally
119 {
120 disposablePeStream?.Dispose();
121 disposePdbStream?.Dispose();
122 }
123 }
124
140 static void WriteModuleImpl(StrongNameKeyPair keyPair, byte[] publicKey, IKVM.Reflection.Emit.ModuleBuilder module, IKVM.Reflection.Emit.PEFileKinds fileKind, PortableExecutableKinds portableExecutableKind, ImageFileMachine imageFileMachine, ModuleResourceSectionBuilder nativeResources, MethodInfo entryPoint, string peFileName, Stream peStream, string pdbFileName, Stream pdbStream)
141 {
142 // add some empty entries at the top just to ensure they are present
143 module.Metadata.GetOrAddString("");
144 module.Metadata.GetOrAddUserString("");
145 module.Metadata.GetOrAddBlob(Array.Empty<byte>());
146 module.Metadata.GetOrAddGuid(Guid.Empty);
147
148 // write the module content
149 WriteModuleImpl(module);
150
151 // are we outputing for a 64 bit architecture?
152 var is64BitArch = imageFileMachine is ImageFileMachine.AMD64 or ImageFileMachine.ARM64 or ImageFileMachine.IA64;
153
154 // initialize PE header builder
155 var peHeaderBuilder = new PEHeaderBuilder(
156 machine: GetMachine(imageFileMachine),
157 sectionAlignment: GetSectionAlignment(module, is64BitArch),
158 fileAlignment: GetFileAlignment(module, is64BitArch),
159 imageBase: GetImageBase(module, fileKind, is64BitArch),
160 majorLinkerVersion: 0x30,
161 minorLinkerVersion: 0,
162 majorOperatingSystemVersion: 4,
163 minorOperatingSystemVersion: 0,
164 majorImageVersion: 0,
165 minorImageVersion: 0,
166 majorSubsystemVersion: GetMajorSubsystemVersion(imageFileMachine, fileKind),
167 minorSubsystemVersion: GetMinorSubsystemVersion(imageFileMachine, fileKind),
168 subsystem: GetSubsystem(fileKind),
169 dllCharacteristics: (System.Reflection.PortableExecutable.DllCharacteristics)(int)module.__DllCharacteristics,
170 imageCharacteristics: GetImageCharacteristics(imageFileMachine, fileKind),
171 sizeOfStackReserve: GetSizeOfStackReserve(module, is64BitArch),
172 sizeOfStackCommit: GetSizeOfStackCommit(module, is64BitArch),
173 sizeOfHeapReserve: GetSizeOfHeapReserve(module, is64BitArch),
174 sizeOfHeapCommit: GetSizeOfHeapCommit(module, is64BitArch));
175
176 // root builder provides access to information about the assembled type metadata
177 var rootBuilder = new MetadataRootBuilder(module.Metadata);
178 var debugBuilder = default(DebugDirectoryBuilder);
179
180 // ask the symbol writer if it supplies a debug directory
181 if (module.GetSymWriter() is IMetadataSymbolWriter msym)
182 {
183 // generate a new metadata image for the PDB
184 var pdbMetadata = new MetadataBuilder();
185 pdbMetadata.GetOrAddString("");
186 pdbMetadata.GetOrAddUserString("");
187 pdbMetadata.GetOrAddBlob(Array.Empty<byte>());
188 pdbMetadata.GetOrAddGuid(Guid.Empty);
189 msym.WriteTo(pdbMetadata, out var pdbUserEntryPoint);
190
191 // create new PDB builder around produced metadata
192 var pdbContentHash = ImmutableArray<byte>.Empty;
193 var pdb = new PortablePdbBuilder(
194 pdbMetadata,
195 rootBuilder.Sizes.RowCounts,
196 pdbUserEntryPoint != 0 ? (MethodDefinitionHandle)MetadataTokens.EntityHandle(module.ResolvePseudoToken(pdbUserEntryPoint)) : default,
197 module.Universe.Deterministic ? new Func<IEnumerable<Blob>, BlobContentId>(content => BlobContentId.FromHash(pdbContentHash = CryptographicHashProvider.ComputeHash(module.Universe.PdbChecksumAlgorithm, content))) : null);
198
199 // serialize the PDB
200 var pdbContent = new BlobBuilder();
201 var pdbContentId = pdb.Serialize(pdbContent);
202
203 // we are going to add a builder for a debug diretory
204 debugBuilder = new DebugDirectoryBuilder();
205
206 // mark symbols as reproducible
207 if (module.Universe.Deterministic)
208 debugBuilder.AddReproducibleEntry();
209
210 // a checksum hash was produced, so we add it to the debug information
211 if (pdbContentHash.IsDefaultOrEmpty == false)
212 debugBuilder.AddPdbChecksumEntry(module.Universe.PdbChecksumAlgorithm.Name, pdbContentHash);
213
214 // file name specified means we are generating an external PDB
215 if (pdbStream != null)
216 {
217 // default PDB file name
218 if (peFileName != null)
219 pdbFileName ??= Path.ChangeExtension(peFileName, ".pdb");
220
221 // we don't actually want a full path in the PDB
222 if (pdbFileName != null)
223 pdbFileName = Path.GetFileName(pdbFileName);
224
225 // add code view entry describing the external PDB
226 debugBuilder.AddCodeViewEntry(PadPdbPath(pdbFileName), pdbContentId, pdb.FormatVersion);
227
228 // output PDB to external stream
229 pdbContent.WriteContentTo(pdbStream);
230 }
231 else
232 {
233 // embed PDB into PE
234 debugBuilder.AddEmbeddedPortablePdbEntry(pdbContent, pdb.FormatVersion);
235 }
236 }
237
238 // initialize PE builder
239 var strongNameSignatureSize = ComputeStrongNameSignatureLength(publicKey);
240 var peBuilder = new ManagedPEBuilder(
241 peHeaderBuilder,
242 new MetadataRootBuilder(module.Metadata),
243 module.ILStream,
244 managedResources: module.ResourceStream,
245 nativeResources: nativeResources.Count > 0 ? nativeResources : null,
246 debugDirectoryBuilder: debugBuilder,
247 strongNameSignatureSize: strongNameSignatureSize,
248 entryPoint: entryPoint != null ? (MethodDefinitionHandle)MetadataTokens.EntityHandle(entryPoint.GetCurrentToken()) : default,
249 flags: GetCorFlags(portableExecutableKind, keyPair),
250 deterministicIdProvider: GetDeterministicIdProvider(module));
251
252 // serialize the image
253 var pe = new BlobBuilder();
254 var peContentId = peBuilder.Serialize(pe);
255
256 // fixup MVID after generation
257 var mvidId = new BlobWriter(module.GetModuleVersionIdFixup().Content);
258 mvidId.WriteGuid(peContentId.Guid);
259 Debug.Assert(mvidId.RemainingBytes == 0);
260
261 // strong name specified, sign the blobs
262 if (keyPair != null)
263 peBuilder.Sign(pe, blobs => GetSignature(keyPair, blobs, strongNameSignatureSize));
264
265 // write the final content to the output stream
266 pe.WriteContentTo(peStream);
267 }
268
277 static string PadPdbPath(string path)
278 {
279 const int MinLength = 260;
280 return path + new string('\0', Math.Max(0, MinLength - Encoding.UTF8.GetByteCount(path) - 1));
281 }
282
287 static void WriteModuleImpl(IKVM.Reflection.Emit.ModuleBuilder module)
288 {
289 // registers replacements for pseudo tokens
290 module.FixupMethodBodyTokens();
291
292 // fixup content in tables
293 module.TypeRefTable.Fixup(module);
294 module.MethodImplTable.Fixup(module);
295 module.MethodSemanticsTable.Fixup(module);
296 module.InterfaceImplTable.Fixup(module);
297 module.ResolveInterfaceImplPseudoTokens();
298 module.MemberRefTable.Fixup(module);
299 module.ConstantTable.Fixup(module);
300 module.FieldMarshalTable.Fixup(module);
301 module.DeclSecurityTable.Fixup(module);
302 module.GenericParamTable.Fixup(module);
303 module.CustomAttributeTable.Fixup(module);
304 module.FieldLayoutTable.Fixup(module);
305 module.FieldRVATable.Fixup(module);
306 module.ImplMapTable.Fixup(module);
307 module.ExportedTypeTable.Fixup(module);
308 module.ManifestResourceTable.Fixup(module);
309 module.MethodSpecTable.Fixup(module);
310 module.GenericParamConstraint.Fixup(module);
311
312 // close the symbol writer which may cause entries in the module table
313 module.GetSymWriter()?.Close();
314
315 // write to module metadata
316 module.WriteResources();
317 module.WriteMetadata();
318 }
319
326 static Machine GetMachine(ImageFileMachine imageFileMachine)
327 {
328 return imageFileMachine switch
329 {
330 ImageFileMachine.UNKNOWN => Machine.Unknown,
331 ImageFileMachine.I386 => Machine.I386,
332 ImageFileMachine.ARM => Machine.Arm,
333 ImageFileMachine.AMD64 => Machine.Amd64,
334 ImageFileMachine.IA64 => Machine.IA64,
335 ImageFileMachine.ARM64 => Machine.Arm64,
336 _ => throw new ArgumentOutOfRangeException("imageFileMachine"),
337 };
338 }
339
346 static int GetSectionAlignment(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
347 {
348 return DefaultSectionAlignment;
349 }
350
357 static int GetFileAlignment(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
358 {
359 if (module.__FileAlignment == 0)
360 return is64BitArch ? DefaultFileAlignment64Bit : DefaultFileAlignment32Bit;
361 else
362 return (int)module.__FileAlignment;
363 }
364
373 static ulong GetImageBase(IKVM.Reflection.Emit.ModuleBuilder module, IKVM.Reflection.Emit.PEFileKinds fileKind, bool is64BitArch)
374 {
375 var baseAddress = unchecked(module.__ImageBase + 0x8000) & (is64BitArch ? 0xffffffffffff0000 : 0x00000000ffff0000);
376
377 // cover values smaller than 0x8000, overflow and default value 0):
378 if (baseAddress == 0)
379 {
380 if (fileKind is IKVM.Reflection.Emit.PEFileKinds.ConsoleApplication or IKVM.Reflection.Emit.PEFileKinds.WindowApplication)
381 return is64BitArch ? DefaultExeBaseAddress64Bit : DefaultExeBaseAddress32Bit;
382 else
383 return is64BitArch ? DefaultDllBaseAddress64Bit : DefaultDllBaseAddress32Bit;
384 }
385
386 return baseAddress;
387 }
388
393 static ushort GetMajorSubsystemVersion(ImageFileMachine imageFileMachine, IKVM.Reflection.Emit.PEFileKinds fileKinds)
394 {
395 return imageFileMachine == ImageFileMachine.ARM ? (ushort)6 : (ushort)4;
396 }
397
402 static ushort GetMinorSubsystemVersion(ImageFileMachine imageFileMachine, IKVM.Reflection.Emit.PEFileKinds fileKinds)
403 {
404 return imageFileMachine == ImageFileMachine.ARM ? (ushort)2 : (ushort)0;
405 }
406
412 static Subsystem GetSubsystem(IKVM.Reflection.Emit.PEFileKinds fileKind) => fileKind switch
413 {
414 IKVM.Reflection.Emit.PEFileKinds.WindowApplication => Subsystem.WindowsGui,
415 _ => Subsystem.WindowsCui,
416 };
417
425 static Characteristics GetImageCharacteristics(ImageFileMachine imageFileMachine, IKVM.Reflection.Emit.PEFileKinds fileKind)
426 {
427 var characteristics = Characteristics.ExecutableImage;
428
429 switch (imageFileMachine)
430 {
431 case ImageFileMachine.UNKNOWN:
432 break;
433 case ImageFileMachine.I386:
434 characteristics |= Characteristics.Bit32Machine;
435 break;
436 case ImageFileMachine.ARM:
437 characteristics |= Characteristics.Bit32Machine | Characteristics.LargeAddressAware;
438 break;
439 case ImageFileMachine.AMD64:
440 case ImageFileMachine.IA64:
441 case ImageFileMachine.ARM64:
442 characteristics |= Characteristics.LargeAddressAware;
443 break;
444 default:
445 throw new ArgumentOutOfRangeException("imageFileMachine");
446 }
447
448 switch (fileKind)
449 {
450 case IKVM.Reflection.Emit.PEFileKinds.Dll:
451 characteristics |= Characteristics.Dll;
452 break;
453 }
454
455 return characteristics;
456 }
457
464 static ulong GetSizeOfStackReserve(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
465 {
466 return is64BitArch ? DefaultSizeOfStackReserve64Bit : DefaultSizeOfStackReserve32Bit;
467 }
468
475 static ulong GetSizeOfStackCommit(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
476 {
477 return is64BitArch ? DefaultSizeOfStackCommit64Bit : DefaultSizeOfStackCommit32Bit;
478 }
479
486 static ulong GetSizeOfHeapReserve(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
487 {
488 return is64BitArch ? DefaultSizeOfHeapReserve64Bit : DefaultSizeOfHeapReserve32Bit;
489 }
490
497 static ulong GetSizeOfHeapCommit(IKVM.Reflection.Emit.ModuleBuilder module, bool is64BitArch)
498 {
499 return is64BitArch ? DefaultSizeOfHeapCommit64Bit : DefaultSizeOfHeapCommit32Bit;
500 }
501
508 static CorFlags GetCorFlags(PortableExecutableKinds portableExecutableKind, StrongNameKeyPair keyPair)
509 {
510 var flags = default(CorFlags);
511
512 if ((portableExecutableKind & PortableExecutableKinds.ILOnly) != 0)
513 flags |= CorFlags.ILOnly;
514 if ((portableExecutableKind & PortableExecutableKinds.Required32Bit) != 0)
515 flags |= CorFlags.Requires32Bit;
516 if ((portableExecutableKind & PortableExecutableKinds.Preferred32Bit) != 0)
517 flags |= CorFlags.Requires32Bit | CorFlags.Prefers32Bit;
518 if (keyPair != null)
519 flags |= CorFlags.StrongNameSigned;
520
521 return flags;
522 }
523
529 static Func<IEnumerable<Blob>, BlobContentId> GetDeterministicIdProvider(IKVM.Reflection.Emit.ModuleBuilder module)
530 {
531 if (module.Universe.Deterministic)
532 return e => BlobContentId.FromHash(CryptographicHashProvider.ComputeHash(HashAlgorithmName.SHA256, e));
533 else
534 return null;
535 }
536
542 static int ComputeStrongNameSignatureLength(byte[] publicKey)
543 {
544 if (publicKey == null)
545 {
546 return 0;
547 }
548 else if (publicKey.Length == 16)
549 {
550 // 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
551 return 128;
552 }
553 else
554 {
555 // for the supported strong naming algorithms, the signature size is the same as the key size
556 // (we have to subtract 32 for the header)
557 return publicKey.Length - 32;
558 }
559 }
560
568 static byte[] GetSignature(StrongNameKeyPair keyPair, IEnumerable<Blob> blobs, int strongNameSignatureSize)
569 {
570 // sign the hash with the keypair
571 using var rsa = keyPair.CreateRSA();
572 var signature = rsa.SignHash(CryptographicHashProvider.ComputeHash(HashAlgorithmName.SHA1, blobs).ToArray(), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
573 Array.Reverse(signature);
574
575 // check that our signature length matches
576 if (signature.Length != strongNameSignatureSize)
577 throw new InvalidOperationException("Signature length mismatch.");
578
579 return signature;
580 }
581
582 }
583
584}
Implements a ResourceSectionBuilder for writing IKVM reflection resource section data.
int Count
Gets the number of entries in the root of the builder.