public void PerformEmitting( Stream sink, EmittingArguments eArgs ) { ArgumentValidator.ValidateNotNull("Stream", sink); ArgumentValidator.ValidateNotNull("Emitting arguments", eArgs); Boolean isPE64, hasRelocations; UInt16 peOptionalHeaderSize; UInt32 numSections, iatSize; CheckEmittingArgs(eArgs, out isPE64, out hasRelocations, out numSections, out peOptionalHeaderSize, out iatSize); var fAlign = eArgs.FileAlignment; var sAlign = eArgs.SectionAlignment; var importHintName = eArgs.ImportHintName; var imageBase = eArgs.ImageBase; var moduleKind = eArgs.ModuleKind; var strongName = eArgs.StrongName; IList <CILMethodBase> allMethodDefs; CILAssemblyName an; using (var md = new MetaDataWriter(eArgs, this._context, this._module, eArgs.AssemblyMapper, out allMethodDefs, out an)) { var clrEntryPointToken = 0; if (eArgs.CLREntryPoint != null) { var listIdx = allMethodDefs.IndexOf(eArgs.CLREntryPoint); if (listIdx < 0) { throw new ArgumentException("Entry point method " + eArgs.CLREntryPoint + " is not from this module (" + this._module.Name + ")."); } clrEntryPointToken = TokenUtils.EncodeToken(Tables.MethodDef, listIdx + 1); } // Start emitting headers // MS-DOS header var currentArray = new Byte[HeaderFieldOffsetsAndLengths.DOS_HEADER_AND_PE_SIG.Length]; Array.Copy(HeaderFieldOffsetsAndLengths.DOS_HEADER_AND_PE_SIG, currentArray, HeaderFieldOffsetsAndLengths.DOS_HEADER_AND_PE_SIG.Length); sink.Write(currentArray); // PE file header currentArray = new Byte[HeaderFieldOffsetsAndLengths.PE_FILE_HEADER_SIZE]; var characteristics = HeaderFieldPossibleValues.IMAGE_FILE_EXECUTABLE_IMAGE | (isPE64 ? HeaderFieldPossibleValues.IMAGE_FILE_LARGE_ADDRESS_AWARE : HeaderFieldPossibleValues.IMAGE_FILE_32BIT_MACHINE); if (moduleKind.IsDLL()) { characteristics |= HeaderFieldPossibleValues.IMAGE_FILE_DLL; } var idx = 0; currentArray .WriteUInt16LEToBytes(ref idx, (UInt16)eArgs.Machine) .WriteUInt16LEToBytes(ref idx, (UInt16)numSections) .WriteInt32LEToBytes(ref idx, Convert.ToInt32(DateTime.Now.Subtract(PE_HEADER_START_TIME).TotalSeconds)) .Skip(ref idx, 8) .WriteUInt16LEToBytes(ref idx, peOptionalHeaderSize) .WriteInt16LEToBytes(ref idx, (Int16)characteristics); sink.Write(currentArray); // PE optional header + section headers + padding + IAT + CLI header + Strong signature var codeSectionVirtualOffset = sAlign; // Strong name signature var useStrongName = strongName != null; var snSize = 0u; var snRVA = 0u; var snPadding = 0u; var delaySign = eArgs.DelaySign || (!useStrongName && !an.PublicKey.IsNullOrEmpty()); RSAParameters rParams; var signingAlgorithm = AssemblyHashAlgorithm.SHA1; if (useStrongName || delaySign) { // Set appropriate module flags eArgs.ModuleFlags |= ModuleFlags.StrongNameSigned; // Check algorithm override var algoOverride = eArgs.SigningAlgorithm; var algoOverrideWasInvalid = algoOverride.HasValue && (algoOverride.Value == AssemblyHashAlgorithm.MD5 || algoOverride.Value == AssemblyHashAlgorithm.None); if (algoOverrideWasInvalid) { algoOverride = AssemblyHashAlgorithm.SHA1; } Byte[] pkToProcess; if ((useStrongName && strongName.ContainerName != null) || (!useStrongName && delaySign)) { if (an.PublicKey.IsNullOrEmpty()) { an.PublicKey = this._context.ExtractPublicKeyFromCSP(strongName.ContainerName); } pkToProcess = an.PublicKey; } else { // Get public key from BLOB pkToProcess = strongName.KeyPair.ToArray(); } // Create RSA parameters and process public key so that it will have proper, full format. Byte[] pk; rParams = CryptoUtils.CreateSigningInformationFromKeyBLOB(pkToProcess, algoOverride, out pk, out signingAlgorithm); an.PublicKey = pk; snSize = (UInt32)rParams.Modulus.Length; } else { rParams = default(RSAParameters); } if (useStrongName || delaySign) { snRVA = codeSectionVirtualOffset + iatSize + HeaderFieldOffsetsAndLengths.CLI_HEADER_SIZE; if (snSize <= 32) { // The "Standard Public Key", ECMA-335 p. 116 // It is replaced by the runtime with 128 bytes key snSize = 128; } snPadding = BitUtils.MultipleOf4(snSize) - snSize; } var revisitableOffset = HeaderFieldOffsetsAndLengths.DOS_HEADER_AND_PE_SIG.Length + currentArray.Length; var revisitableArraySize = fAlign + iatSize + HeaderFieldOffsetsAndLengths.CLI_HEADER_SIZE - revisitableOffset; // Cheat a bit - skip now, and re-visit it after all other emitting is done sink.Seek(revisitableArraySize + snSize + snPadding, SeekOrigin.Current); // First section // Start with method ILs // Current offset within section var currentOffset = iatSize + snSize + snPadding + HeaderFieldOffsetsAndLengths.CLI_HEADER_SIZE; var methodRVAs = new Dictionary <CILMethodBase, UInt32>(allMethodDefs.Count); foreach (var method in allMethodDefs) { if (method.HasILMethodBody()) { Boolean isTiny; var array = new MethodILWriter(this._context, md, method, eArgs.AssemblyMapper) .PerformEmitting(currentOffset, out isTiny); if (!isTiny) { sink.SkipToNextAlignment(ref currentOffset, 4); } methodRVAs.Add(method, codeSectionVirtualOffset + currentOffset); sink.Write(array); currentOffset += (UInt32)array.Length; } } // Write padding sink.SkipToNextAlignment(ref currentOffset, isPE64 ? 0x10u : 0x04u); // Write manifest resources here var mRes = this._module.ManifestResources; var mResInfo = new Dictionary <String, UInt32>(); var mResRVA = mRes.Values.Any(mr => mr is EmbeddedManifestResource) ? codeSectionVirtualOffset + currentOffset : 0u; var mResSize = 0u; if (mResRVA > 0u) { var tmpArray = new Byte[4]; foreach (var kvp in mRes) { if (kvp.Value is EmbeddedManifestResource) { var data = ((EmbeddedManifestResource)kvp.Value).Data; if (data != null && data.Length > 0) { mResInfo.Add(kvp.Key, mResSize); tmpArray.WriteInt32LEToBytesNoRef(0, data.Length); sink.Write(tmpArray); sink.Write(data); mResSize += 4 + (UInt32)data.Length; } } } // Write padding currentOffset += mResSize; sink.SkipToNextAlignment(ref currentOffset, isPE64 ? 0x10u : 0x04u); } // Finalize & write metadata var mdRVA = codeSectionVirtualOffset + currentOffset; UInt32 addedToOffsetBeforeMD; var mdSize = md.WriteMetaData(sink, mdRVA, eArgs, methodRVAs, mResInfo, out addedToOffsetBeforeMD); mdRVA += addedToOffsetBeforeMD; currentOffset += mdSize + addedToOffsetBeforeMD; // Pad sink.SkipToNextAlignment(ref currentOffset, 0x4); // Write debug header if present var dbgInfo = eArgs.DebugInformation; var dbgRVA = 0u; if (dbgInfo != null) { dbgRVA = codeSectionVirtualOffset + currentOffset; var dbgData = dbgInfo.DebugData; currentArray = new Byte[MetaDataConstants.DEBUG_DD_SIZE + dbgData.Length]; idx = 0; currentArray .WriteInt32LEToBytes(ref idx, dbgInfo.Characteristics) .WriteInt32LEToBytes(ref idx, dbgInfo.Timestamp) .WriteInt16LEToBytes(ref idx, dbgInfo.VersionMajor) .WriteInt16LEToBytes(ref idx, dbgInfo.VersionMinor) .WriteInt32LEToBytes(ref idx, MetaDataConstants.CODE_VIEW_DEBUG_TYPE) .WriteInt32LEToBytes(ref idx, dbgData.Length) .WriteUInt32LEToBytes(ref idx, dbgRVA + MetaDataConstants.DEBUG_DD_SIZE) .WriteUInt32LEToBytes(ref idx, fAlign + currentOffset + (UInt32)idx + 4) // Pointer to data, end Debug Data Directory .BlockCopyFrom(ref idx, dbgData); sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; sink.SkipToNextAlignment(ref currentOffset, 0x4); } var entryPointCodeRVA = 0u; var importDirectoryRVA = 0u; var importDirectorySize = 0u; var hnRVA = 0u; if (hasRelocations) { // TODO write all of these in a single array // Import Directory // First, the table importDirectoryRVA = codeSectionVirtualOffset + currentOffset; importDirectorySize = HeaderFieldOffsetsAndLengths.IMPORT_DIRECTORY_SIZE; currentArray = new Byte[importDirectorySize]; idx = 0; currentArray .WriteUInt32LEToBytes(ref idx, codeSectionVirtualOffset + currentOffset + (UInt32)currentArray.Length) // RVA of the ILT .WriteInt32LEToBytes(ref idx, 0) // DateTimeStamp .WriteInt32LEToBytes(ref idx, 0) // ForwarderChain .WriteUInt32LEToBytes(ref idx, codeSectionVirtualOffset + currentOffset + (UInt32)currentArray.Length + HeaderFieldOffsetsAndLengths.ILT_SIZE + HeaderFieldOffsetsAndLengths.HINT_NAME_MIN_SIZE + (UInt32)importHintName.Length + 1) // RVA of Import Directory name (mscoree.dll) .WriteUInt32LEToBytes(ref idx, codeSectionVirtualOffset); // RVA of Import Address Table // The rest are zeroes sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; // ILT currentArray = new Byte[HeaderFieldOffsetsAndLengths.ILT_SIZE]; idx = 0; currentArray .WriteUInt32LEToBytes(ref idx, codeSectionVirtualOffset + currentOffset + (UInt32)currentArray.Length); // RVA of the hint/name table // The rest are zeroes sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; // Hint/Name table currentArray = new Byte[HeaderFieldOffsetsAndLengths.HINT_NAME_MIN_SIZE + importHintName.Length + 1]; hnRVA = currentOffset + codeSectionVirtualOffset; // Skip first two bytes idx = HeaderFieldOffsetsAndLengths.HINT_NAME_MIN_SIZE; currentArray.WriteASCIIString(ref idx, importHintName, true); sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; // Import DirectoryName foreach (var chr in eArgs.ImportDirectoryName) { sink.WriteByte((Byte)chr); // TODO properly ASCII-encoded string ++currentOffset; } sink.WriteByte(0); // String-terminating null ++currentOffset; // Then, a zero int // TODO investigate if this is really needed... sink.SeekFromCurrent(sizeof(Int32)); currentOffset += sizeof(Int32); // Then, a PE entrypoint entryPointCodeRVA = currentOffset + codeSectionVirtualOffset; currentArray = new Byte[sizeof(Int16) + sizeof(Int32)]; idx = 0; currentArray .WriteInt16LEToBytes(ref idx, eArgs.EntryPointInstruction) .WriteUInt32LEToBytes(ref idx, (UInt32)imageBase + codeSectionVirtualOffset); sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; } // TODO Win32 resources section var hasResourceSection = false; var textSectionInfo = new SectionInfo(sink, null, currentOffset, sAlign, fAlign, !hasRelocations && !hasResourceSection); var prevSectionInfo = textSectionInfo; // TODO Win32 resources section var rsrcSectionInfo = new SectionInfo(); // Final section - relocation section var relocSectionInfo = new SectionInfo(); if (hasRelocations) { // Need to build relocation fixup for the argument of the entry point currentOffset = 0; var relocRVA = entryPointCodeRVA + 2; var pageRVA = relocRVA & ~(RELOCATION_PAGE_SIZE - 1); currentArray = new Byte[HeaderFieldOffsetsAndLengths.RELOC_ARRAY_BASE_SIZE]; idx = 0; currentArray .WriteUInt32LEToBytes(ref idx, pageRVA) .WriteUInt32LEToBytes(ref idx, HeaderFieldOffsetsAndLengths.RELOC_ARRAY_BASE_SIZE) // Block size .WriteUInt32LEToBytes(ref idx, (RELOCATION_FIXUP_TYPE << 12) + relocRVA - pageRVA); // Type (high 4 bits) + Offset (lower 12 bits) + dummy entry (16 bits) sink.Write(currentArray); currentOffset += (UInt32)currentArray.Length; relocSectionInfo = new SectionInfo(sink, prevSectionInfo, currentOffset, sAlign, fAlign, true); prevSectionInfo = relocSectionInfo; } // Revisit PE optional header + section headers + padding + IAT + CLI header currentArray = new Byte[revisitableArraySize]; idx = 0; // PE optional header, ECMA-335 pp. 279-281 // Standard fields currentArray .WriteInt16LEToBytes(ref idx, isPE64 ? HeaderFieldPossibleValues.PE64 : HeaderFieldPossibleValues.PE32) // Magic .WriteByteToBytes(ref idx, eArgs.LinkerMajor) // Linker major version .WriteByteToBytes(ref idx, eArgs.LinkerMinor) // Linker minor version .WriteUInt32LEToBytes(ref idx, textSectionInfo.rawSize) // Code size .WriteUInt32LEToBytes(ref idx, relocSectionInfo.rawSize + rsrcSectionInfo.rawSize) // Initialized data size .WriteUInt32LEToBytes(ref idx, 0) // Unitialized data size .WriteUInt32LEToBytes(ref idx, entryPointCodeRVA) // Entry point RVA .WriteUInt32LEToBytes(ref idx, textSectionInfo.virtualAddress); // Base of code if (!isPE64) { currentArray.WriteUInt32LEToBytes(ref idx, hasResourceSection ? rsrcSectionInfo.virtualAddress : relocSectionInfo.virtualAddress); // Base of data } // WinNT-specific fields var dllFlags = DLLFlags.TerminalServerAware | DLLFlags.NXCompatible | DLLFlags.NoSEH | DLLFlags.DynamicBase; if (eArgs.HighEntropyVA) { dllFlags |= DLLFlags.HighEntropyVA; } (isPE64 ? currentArray.WriteUInt64LEToBytes(ref idx, imageBase) : currentArray.WriteUInt32LEToBytes(ref idx, (UInt32)imageBase)) .WriteUInt32LEToBytes(ref idx, sAlign) // Section alignment .WriteUInt32LEToBytes(ref idx, fAlign) // File alignment .WriteUInt16LEToBytes(ref idx, eArgs.OSMajor) // OS Major .WriteUInt16LEToBytes(ref idx, eArgs.OSMinor) // OS Minor .WriteUInt16LEToBytes(ref idx, eArgs.UserMajor) // User Major .WriteUInt16LEToBytes(ref idx, eArgs.UserMinor) // User Minor .WriteUInt16LEToBytes(ref idx, eArgs.SubSysMajor) // SubSys Major .WriteUInt16LEToBytes(ref idx, eArgs.SubSysMinor) // SubSys Minor .WriteUInt32LEToBytes(ref idx, 0) // Reserved .WriteUInt32LEToBytes(ref idx, prevSectionInfo.virtualAddress + BitUtils.MultipleOf(sAlign, prevSectionInfo.virtualSize)) // Image Size .WriteUInt32LEToBytes(ref idx, textSectionInfo.rawPointer) // Header Size .WriteUInt32LEToBytes(ref idx, 0) // File Checksum .WriteUInt16LEToBytes(ref idx, GetSubSystem(moduleKind)) // SubSystem .WriteUInt16LEToBytes(ref idx, (UInt16)dllFlags); // DLL Characteristics if (isPE64) { currentArray .WriteUInt64LEToBytes(ref idx, eArgs.StackReserve) // Stack Reserve Size .WriteUInt64LEToBytes(ref idx, eArgs.StackCommit) // Stack Commit Size .WriteUInt64LEToBytes(ref idx, eArgs.HeapReserve) // Heap Reserve Size .WriteUInt64LEToBytes(ref idx, eArgs.HeapCommit); // Heap Commit Size } else { currentArray .WriteUInt32LEToBytes(ref idx, (UInt32)eArgs.StackReserve) // Stack Reserve Size .WriteUInt32LEToBytes(ref idx, (UInt32)eArgs.StackCommit) // Stack Commit Size .WriteUInt32LEToBytes(ref idx, (UInt32)eArgs.HeapReserve) // Heap Reserve Size .WriteUInt32LEToBytes(ref idx, (UInt32)eArgs.HeapCommit); // Heap Commit Size } currentArray .WriteUInt32LEToBytes(ref idx, 0) // Loader Flags .WriteUInt32LEToBytes(ref idx, HeaderFieldOffsetsAndLengths.NUMBER_OF_DATA_DIRS) // Data Directories .WriteZeroDataDirectory(ref idx) // Export Table .WriteDataDirectory(ref idx, importDirectoryRVA, importDirectorySize) // Import Table .WriteDataDirectory(ref idx, rsrcSectionInfo.virtualAddress, rsrcSectionInfo.virtualSize) // Resource Table .WriteZeroDataDirectory(ref idx) // Exception Table .WriteZeroDataDirectory(ref idx) // Certificate Table .WriteDataDirectory(ref idx, relocSectionInfo.virtualAddress, relocSectionInfo.virtualSize) // BaseRelocationTable .WriteDataDirectory(ref idx, dbgRVA > 0u ? dbgRVA : 0u, dbgRVA > 0u ? MetaDataConstants.DEBUG_DD_SIZE : 0u) // Debug Table .WriteZeroDataDirectory(ref idx) // Copyright Table .WriteZeroDataDirectory(ref idx) // Global Ptr .WriteZeroDataDirectory(ref idx) // TLS Table .WriteZeroDataDirectory(ref idx) // Load Config Table .WriteZeroDataDirectory(ref idx) // Bound Import .WriteDataDirectory(ref idx, iatSize == 0 ? 0 : codeSectionVirtualOffset, iatSize == 0 ? 0 : iatSize) // IAT .WriteZeroDataDirectory(ref idx) // Delay Import Descriptor .WriteDataDirectory(ref idx, codeSectionVirtualOffset + iatSize, HeaderFieldOffsetsAndLengths.CLI_HEADER_SIZE) // CLI Header .WriteZeroDataDirectory(ref idx) // Reserved // Section headers .WriteSectionInfo(ref idx, textSectionInfo, CODE_SECTION_NAME, HeaderFieldPossibleValues.MEM_READ | HeaderFieldPossibleValues.MEM_EXECUTE | HeaderFieldPossibleValues.CONTAINS_CODE) .WriteSectionInfo(ref idx, rsrcSectionInfo, RESOURCE_SECTION_NAME, HeaderFieldPossibleValues.MEM_READ | HeaderFieldPossibleValues.CONTAINS_INITIALIZED_DATA) .WriteSectionInfo(ref idx, relocSectionInfo, RELOCATION_SECTION_NAME, HeaderFieldPossibleValues.MEM_READ | HeaderFieldPossibleValues.MEM_DISCARDABLE | HeaderFieldPossibleValues.CONTAINS_INITIALIZED_DATA); var headersSize = (UInt32)(revisitableOffset + idx); // Skip to beginning of .text section currentArray.Skip(ref idx, (Int32)(fAlign - (UInt32)revisitableOffset - idx)); // Write IAT if needed if (hasRelocations) { currentArray .WriteUInt32LEToBytes(ref idx, hnRVA) .WriteUInt32LEToBytes(ref idx, 0); } // CLI Header, ECMA-335, p. 283 // At the moment, the 32BitRequired flag must be specified as well, if 32BitPreferred flag is specified. // This is for backwards compatibility. // Actually, since CorFlags lets specify Preferred32Bit separately, allow this to do too. var moduleFlags = eArgs.ModuleFlags; //if ( moduleFlags.HasFlag( ModuleFlags.Preferred32Bit ) ) //{ // moduleFlags |= ModuleFlags.Required32Bit; //} currentArray .WriteUInt32LEToBytes(ref idx, HeaderFieldOffsetsAndLengths.CLI_HEADER_SIZE) // Cb .WriteUInt16LEToBytes(ref idx, eArgs.CLIMajor) // MajorRuntimeVersion .WriteUInt16LEToBytes(ref idx, eArgs.CLIMinor) // MinorRuntimeVersion .WriteDataDirectory(ref idx, mdRVA, mdSize) // MetaData .WriteInt32LEToBytes(ref idx, (Int32)moduleFlags) // Flags .WriteInt32LEToBytes(ref idx, clrEntryPointToken) // EntryPointToken .WriteDataDirectory(ref idx, mResRVA, mResSize); // Resources var snDataDirOffset = revisitableOffset + idx; currentArray .WriteDataDirectory(ref idx, snRVA, snSize) // StrongNameSignature .WriteZeroDataDirectory(ref idx) // CodeManagerTable .WriteZeroDataDirectory(ref idx) // VTableFixups .WriteZeroDataDirectory(ref idx) // ExportAddressTableJumps .WriteZeroDataDirectory(ref idx); // ManagedNativeHeader #if DEBUG if (idx != currentArray.Length) { throw new Exception("Something went wrong when emitting file headers. Emitted " + idx + " bytes, but was supposed to emit " + currentArray.Length + " bytes."); } #endif sink.Seek(revisitableOffset, SeekOrigin.Begin); sink.Write(currentArray); if (useStrongName || delaySign) { if (!delaySign) { // Try create RSA first var rsaArgs = strongName.ContainerName == null ? new RSACreationEventArgs(rParams) : new RSACreationEventArgs(strongName.ContainerName); this._context.LaunchRSACreationEvent(rsaArgs); using (var rsa = rsaArgs.RSA) { var buffer = new Byte[MetaDataConstants.STREAM_COPY_BUFFER_SIZE]; Func <Stream> hashStream; Func <Byte[]> hashGetter; IDisposable transform; this._context.LaunchHashStreamEvent(signingAlgorithm, out hashStream, out hashGetter, out transform); RSASignatureCreationEventArgs sigArgs; using (var tf = transform) { using (var cryptoStream = hashStream()) { // Calculate hash of required parts of file (ECMA-335, p.117) sink.Seek(0, SeekOrigin.Begin); sink.CopyStreamPart(cryptoStream, buffer, headersSize); sink.Seek(fAlign, SeekOrigin.Begin); sink.CopyStreamPart(cryptoStream, buffer, snRVA - codeSectionVirtualOffset); sink.Seek(snSize + snPadding, SeekOrigin.Current); sink.CopyStream(cryptoStream, buffer); } sigArgs = new RSASignatureCreationEventArgs(rsa, signingAlgorithm, hashGetter()); } this._context.LaunchRSASignatureCreationEvent(sigArgs); var strongNameArray = sigArgs.Signature; if (snSize != strongNameArray.Length) { throw new CryptographicException("Calculated and actual strong name size differ (calculated: " + snSize + ", actual: " + strongNameArray.Length + ")."); } Array.Reverse(strongNameArray); // Write strong name sink.Seek(snRVA - codeSectionVirtualOffset + fAlign, SeekOrigin.Begin); sink.Write(strongNameArray); } } currentArray = new Byte[8]; idx = 0; currentArray.WriteDataDirectory(ref idx, snRVA, snSize); sink.Seek(snDataDirOffset, SeekOrigin.Begin); sink.Write(currentArray); } } }
internal Byte[] PerformEmitting(UInt32 currentOffset, out Boolean isTiny) { if (this._methodIL._labelOffsets.Any(offset => offset == MethodILImpl.NO_OFFSET)) { throw new InvalidOperationException("Not all labels have been marked."); } if (this._methodIL._currentExceptionBlocks.Any()) { throw new InvalidOperationException("Not all exception blocks have been completed."); } // Remember that inner exception blocks must precede outer ones var allExceptionBlocksCorrectlyOrdered = this._methodIL._allExceptionBlocks.ToArray(); Array.Sort( allExceptionBlocksCorrectlyOrdered, (item1, item2) => { // Return -1 if item1 is inner block of item2, 0 if they are same, 1 if item1 is not inner block of item2 return(Object.ReferenceEquals(item1, item2) ? 0 : (item1._tryOffset >= item2._handlerOffset + item2._handlerLength || (item1._tryOffset <= item2._tryOffset && item1._handlerOffset + item1._handlerLength > item2._handlerOffset + item2._handlerLength) ? 1 : -1)); }); // Setup stack sizes based on exception blocks foreach (var block in allExceptionBlocksCorrectlyOrdered) { switch (block._blockType) { case ExceptionBlockType.Exception: this._stackSizes[block._handlerOffset] = 1; break; case ExceptionBlockType.Filter: this._stackSizes[block._handlerOffset] = 1; this._stackSizes[block._filterOffset] = 1; break; } } // Emit opcodes and arguments foreach (var info in this._methodIL._opCodes) { info.EmitOpCode(this); ++this._methodILOffset; } // Mark label targets for (var i = 0; i < this._labelInfoIndex; ++i) { var thisOffset = this._labelInfos[i].byteOffset; var startCountOffset = this._labelInfos[i].startCountOffset; var amountToJump = this._opCodeInfoOffsets[this._methodIL._labelOffsets[this._labelInfos[i].labelIdx]] - (thisOffset + startCountOffset); if (startCountOffset == 1) { if (amountToJump >= SByte.MinValue && amountToJump <= SByte.MaxValue) { this._ilCode.WriteSByteToBytes(ref thisOffset, amountToJump); } else { throw new InvalidOperationException("Tried to use one-byte branch instruction for offset of amount " + amountToJump); } } else { this._ilCode.WriteInt32LEToBytes(ref thisOffset, amountToJump); } } // Create exception blocks with byte offsets byte[][] exceptionBlocks = new byte[allExceptionBlocksCorrectlyOrdered.Length][]; Boolean[] exceptionFormats = new Boolean[exceptionBlocks.Length]; // TODO PEVerify doesn't like mixed small and fat blocks at all (however, at least Cecil understands that kind of situation) // TODO Apparently, PEVerify doesn't like multiple small blocks either (Cecil still loads code fine) // Also, because of exception block ordering, it is easier to do this way. var allAreSmall = allExceptionBlocksCorrectlyOrdered.Length <= MAX_SMALL_EXC_HANDLERS_IN_ONE_SECTION && allExceptionBlocksCorrectlyOrdered.All(excBlock => { var tryOffset = this._opCodeInfoOffsets[excBlock._tryOffset]; var tryLength = this._opCodeInfoOffsets[excBlock._tryOffset + excBlock._tryLength] - tryOffset; var handlerOffset = this._opCodeInfoOffsets[excBlock._handlerOffset]; var handlerLength = this._opCodeInfoOffsets[excBlock._handlerOffset + excBlock._handlerLength] - handlerOffset; return(tryLength <= Byte.MaxValue && handlerLength <= Byte.MaxValue && tryOffset <= UInt16.MaxValue && handlerOffset <= UInt16.MaxValue); }); for (var i = 0; i < exceptionBlocks.Length; ++i) { // ECMA-335, pp. 286-287 var block = allExceptionBlocksCorrectlyOrdered[i]; Int32 idx = 0; Byte[] array; var tryOffset = this._opCodeInfoOffsets[block._tryOffset]; var tryLength = this._opCodeInfoOffsets[block._tryOffset + block._tryLength] - tryOffset; var handlerOffset = this._opCodeInfoOffsets[block._handlerOffset]; var handlerLength = this._opCodeInfoOffsets[block._handlerOffset + block._handlerLength] - handlerOffset; var useSmallFormat = allAreSmall && tryLength <= Byte.MaxValue && handlerLength <= Byte.MaxValue && tryOffset <= UInt16.MaxValue && handlerOffset <= UInt16.MaxValue; exceptionFormats[i] = useSmallFormat; if (useSmallFormat) { array = new Byte[12]; array.WriteInt16LEToBytes(ref idx, (Int16)block._blockType) .WriteUInt16LEToBytes(ref idx, (UInt16)tryOffset) .WriteByteToBytes(ref idx, (Byte)tryLength) .WriteUInt16LEToBytes(ref idx, (UInt16)handlerOffset) .WriteByteToBytes(ref idx, (Byte)handlerLength); } else { array = new Byte[24]; array.WriteInt32LEToBytes(ref idx, (Int32)block._blockType) .WriteInt32LEToBytes(ref idx, tryOffset) .WriteInt32LEToBytes(ref idx, tryLength) .WriteInt32LEToBytes(ref idx, handlerOffset) .WriteInt32LEToBytes(ref idx, handlerLength); } if (ExceptionBlockType.Exception == block._blockType) { array.WriteInt32LEToBytes(ref idx, this._metaData.GetTokenFor(this._assemblyMapper == null ? block._exceptionType : this._assemblyMapper.MapTypeBase(block._exceptionType), false)); } else if (ExceptionBlockType.Filter == block._blockType) { array.WriteInt32LEToBytes(ref idx, block._filterOffset); } exceptionBlocks[i] = array; } // Write method header, extra data sections, and IL Byte[] result; isTiny = this._ilCodeCount < 64 && exceptionBlocks.Length == 0 && this._maxStack <= 8 && this._methodIL._locals.Count == 0; var resultIndex = 0; var hasAnyExc = false; var hasSmallExc = false; var hasLargExc = false; var smallExcCount = 0; var largeExcCount = 0; var amountToNext4ByteBoundary = 0; if (isTiny) { // Can use tiny header result = new Byte[this._ilCodeCount + 1]; result[resultIndex++] = (Byte)((Int32)MethodHeaderFlags.TinyFormat | (this._ilCodeCount << 2)); } else { // Use fat header hasAnyExc = exceptionBlocks.Length > 0; hasSmallExc = hasAnyExc && exceptionFormats.Any(excFormat => excFormat); hasLargExc = hasAnyExc && exceptionFormats.Any(excFormat => !excFormat); smallExcCount = hasSmallExc ? exceptionFormats.Count(excFormat => excFormat) : 0; largeExcCount = hasLargExc ? exceptionFormats.Count(excFormat => !excFormat) : 0; var offsetAfterIL = (Int32)(BitUtils.MultipleOf4(currentOffset) + 12 + (UInt32)this._ilCodeCount); amountToNext4ByteBoundary = BitUtils.MultipleOf4(offsetAfterIL) - offsetAfterIL; result = new Byte[12 + this._ilCodeCount + (hasAnyExc ? amountToNext4ByteBoundary : 0) + (hasSmallExc ? METHOD_DATA_SECTION_SIZE : 0) + (hasLargExc ? METHOD_DATA_SECTION_SIZE : 0) + smallExcCount * 12 + (smallExcCount / MAX_SMALL_EXC_HANDLERS_IN_ONE_SECTION) * METHOD_DATA_SECTION_SIZE + // (Amount of extra section headers ) * section size largeExcCount * 24 ]; var flags = MethodHeaderFlags.FatFormat; if (hasAnyExc) { flags |= MethodHeaderFlags.MoreSections; } if (this._methodIL.InitLocals) { flags |= MethodHeaderFlags.InitLocals; } result.WriteInt16LEToBytes(ref resultIndex, (Int16)(((Int32)flags) | (3 << 12))) .WriteInt16LEToBytes(ref resultIndex, (Int16)this._maxStack) .WriteInt32LEToBytes(ref resultIndex, this._ilCodeCount) .WriteInt32LEToBytes(ref resultIndex, this._metaData.GetSignatureTokenFor(this._method, this._methodIL._locals.ToArray())); } Array.Copy(this._ilCode, 0, result, resultIndex, this._ilCodeCount); resultIndex += this._ilCodeCount; if (hasAnyExc) { var processedIndices = new HashSet <Int32>(); resultIndex += amountToNext4ByteBoundary; var flags = MethodDataFlags.ExceptionHandling; // First, write fat sections if (hasLargExc) { // TODO like with small sections, what if too many exception clauses to be fit into DataSize? flags |= MethodDataFlags.FatFormat; if (hasSmallExc) { flags |= MethodDataFlags.MoreSections; } result.WriteByteToBytes(ref resultIndex, (Byte)flags) .WriteInt32LEToBytes(ref resultIndex, largeExcCount * 24 + METHOD_DATA_SECTION_SIZE); --resultIndex; for (var i = 0; i < exceptionBlocks.Length; ++i) { if (!exceptionFormats[i] && processedIndices.Add(i)) { var length = exceptionBlocks[i].Length; Array.Copy(exceptionBlocks[i], 0, result, resultIndex, length); resultIndex += length; } } } // Then, write small sections // If exception counts * 12 + 4 are > Byte.MaxValue, have to write several sections // (Max 20 handlers per section) flags = MethodDataFlags.ExceptionHandling; if (hasSmallExc) { var curSmallIdx = 0; while (smallExcCount > 0) { var amountToBeWritten = Math.Min(smallExcCount, MAX_SMALL_EXC_HANDLERS_IN_ONE_SECTION); if (amountToBeWritten < smallExcCount) { flags |= MethodDataFlags.MoreSections; } else { flags = flags & ~(MethodDataFlags.MoreSections); } result.WriteByteToBytes(ref resultIndex, (Byte)flags) .WriteByteToBytes(ref resultIndex, (Byte)(amountToBeWritten * 12 + METHOD_DATA_SECTION_SIZE)) .WriteInt16LEToBytes(ref resultIndex, 0); var amountActuallyWritten = 0; while (curSmallIdx < exceptionBlocks.Length && amountActuallyWritten < amountToBeWritten) { if (exceptionFormats[curSmallIdx]) { var length = exceptionBlocks[curSmallIdx].Length; Array.Copy(exceptionBlocks[curSmallIdx], 0, result, resultIndex, length); resultIndex += length; ++amountActuallyWritten; } ++curSmallIdx; } smallExcCount -= amountToBeWritten; } } } #if DEBUG if (resultIndex != result.Length) { throw new Exception("Something went wrong when emitting method headers and body. Emitted " + resultIndex + " bytes, but was supposed to emit " + result.Length + " bytes."); } #endif return(result); }