File: Compiler\ObjectWriter\CoffObjectWriter.Aot.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysisFramework;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.TypesDebugInfo;
using static ILCompiler.DependencyAnalysis.RelocType;
using static ILCompiler.ObjectWriter.CoffObjectWriter.CoffRelocationType;

namespace ILCompiler.ObjectWriter
{
    /// <summary>
    /// COFF object file format writer for Windows targets.
    /// </summary>
    /// <remarks>
    /// The PE/COFF object format is described in the official specifciation at
    /// https://learn.microsoft.com/windows/win32/debug/pe-format. However,
    /// numerous extensions are missing in the specification. The most notable
    /// ones are listed below.
    ///
    /// Object files with more than 65279 sections use an extended big object
    /// file format that is recognized by the Microsoft linker. Many of the
    /// internal file structures are different. The code below denotes it by
    /// "BigObj" in parameters and variables.
    ///
    /// Section names longer than 8 bytes need to be written indirectly in the
    /// string table. The PE/COFF specification describes the /NNNNNNN syntax
    /// for referencing them. However, if the string table gets big enough the
    /// syntax no longer works. There's an undocumented //BBBBBB syntax where
    /// base64 offset is used instead.
    ///
    /// CodeView debugging format uses 16-bit section index relocations. Once
    /// the number of sections exceeds 2^16 the same file format is still used.
    /// The linker treats the CodeView relocations symbolically.
    /// </remarks>
    internal partial class CoffObjectWriter : ObjectWriter
    {
        // Debugging
        private SectionWriter _debugTypesSectionWriter;
        private SectionWriter _debugSymbolSectionWriter;
        private CodeViewFileTableBuilder _debugFileTableBuilder;
        private CodeViewSymbolsBuilder _debugSymbolsBuilder;
        private CodeViewTypesBuilder _debugTypesBuilder;

        // Exception handling
        private static readonly ObjectNodeSection XDataSection = new ObjectNodeSection("xdata", SectionType.ReadOnly);
        private static readonly ObjectNodeSection PDataSection = new ObjectNodeSection("pdata", SectionType.UnwindData);
        private SectionWriter _pdataSectionWriter;

        private protected override void CreateEhSections()
        {
            // Create .pdata
            _pdataSectionWriter = GetOrCreateSection(PDataSection);
        }

        private protected override void EmitUnwindInfo(
            SectionWriter sectionWriter,
            INodeWithCodeInfo nodeWithCodeInfo,
            Utf8String currentSymbolName)
        {
            if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos &&
                nodeWithCodeInfo is ISymbolDefinitionNode)
            {
                SectionWriter xdataSectionWriter;
                SectionWriter pdataSectionWriter;
                bool shareSymbol = ShouldShareSymbol((ObjectNode)nodeWithCodeInfo);

                Span<byte> i_str = stackalloc byte[16];

                for (int i = 0; i < frameInfos.Length; i++)
                {
                    FrameInfo frameInfo = frameInfos[i];

                    int start = frameInfo.StartOffset;
                    int end = frameInfo.EndOffset;
                    byte[] blob = frameInfo.BlobData;

                    _utf8StringBuilder.Clear()
                        .Append("_unwind"u8)
                        .Append(FormatUtf8Int(i_str, i))
                        .Append(currentSymbolName);
                    Utf8String unwindSymbolName = _utf8StringBuilder.ToUtf8String();

                    if (shareSymbol)
                    {
                        // Produce an associative COMDAT symbol.
                        xdataSectionWriter = GetOrCreateSection(XDataSection, currentSymbolName, unwindSymbolName);
                        pdataSectionWriter = GetOrCreateSection(PDataSection, currentSymbolName, default);
                    }
                    else
                    {
                        // Produce a COMDAT section for each unwind symbol and let linker
                        // do the deduplication across the ones with identical content.
                        xdataSectionWriter = GetOrCreateSection(XDataSection, unwindSymbolName, unwindSymbolName);
                        pdataSectionWriter = _pdataSectionWriter;
                    }

                    // Need to emit the UNWIND_INFO at 4-byte alignment to ensure that the
                    // pointer has the lower two bits in .pdata section set to zero. On ARM64
                    // non-zero bits would mean a compact encoding.
                    xdataSectionWriter.EmitAlignment(4);

                    xdataSectionWriter.EmitSymbolDefinition(unwindSymbolName);

                    // Emit UNWIND_INFO
                    xdataSectionWriter.Write(blob);

                    FrameInfoFlags flags = frameInfo.Flags;

                    if (i != 0)
                    {
                        xdataSectionWriter.WriteByte((byte)flags);
                    }
                    else
                    {
                        MethodExceptionHandlingInfoNode ehInfo = nodeWithCodeInfo.EHInfo;
                        ISymbolNode associatedDataNode = nodeWithCodeInfo.GetAssociatedDataNode(_nodeFactory) as ISymbolNode;

                        flags |= ehInfo is not null ? FrameInfoFlags.HasEHInfo : 0;
                        flags |= associatedDataNode is not null ? FrameInfoFlags.HasAssociatedData : 0;

                        xdataSectionWriter.WriteByte((byte)flags);

                        if (associatedDataNode is not null)
                        {
                            xdataSectionWriter.EmitSymbolReference(
                                IMAGE_REL_BASED_ADDR32NB,
                                GetMangledName(associatedDataNode));
                        }

                        if (ehInfo is not null)
                        {
                            xdataSectionWriter.EmitSymbolReference(
                                IMAGE_REL_BASED_ADDR32NB,
                                GetMangledName(ehInfo));
                        }

                        if (nodeWithCodeInfo.GCInfo is not null)
                        {
                            xdataSectionWriter.Write(nodeWithCodeInfo.GCInfo);
                        }
                    }

                    // Emit RUNTIME_FUNCTION
                    pdataSectionWriter.EmitAlignment(4);
                    pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, currentSymbolName, start);
                    // Only x86/x64 has the End symbol
                    if (_machine is Machine.I386 or Machine.Amd64)
                    {
                        pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, currentSymbolName, end);
                    }
                    // Unwind info pointer
                    pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, unwindSymbolName, 0);
                }
            }
        }

        private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder()
        {
            _debugFileTableBuilder = new CodeViewFileTableBuilder();

            _debugSymbolSectionWriter = GetOrCreateSection(DebugSymbolSection);
            _debugSymbolSectionWriter.EmitAlignment(4);
            _debugSymbolsBuilder = new CodeViewSymbolsBuilder(
                _nodeFactory.Target.Architecture,
                _debugSymbolSectionWriter);

            _debugTypesSectionWriter = GetOrCreateSection(DebugTypesSection);
            _debugTypesSectionWriter.EmitAlignment(4);
            _debugTypesBuilder = new CodeViewTypesBuilder(
                _nodeFactory.NameMangler, _nodeFactory.Target.PointerSize,
                _debugTypesSectionWriter);
            return _debugTypesBuilder;
        }

        private protected override void EmitDebugFunctionInfo(
            uint methodTypeIndex,
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode,
            bool hasSequencePoints)
        {
            DebugEHClauseInfo[] clauses = null;
            CodeViewSymbolsBuilder debugSymbolsBuilder;

            if (debugNode is INodeWithCodeInfo nodeWithCodeInfo)
            {
                clauses = nodeWithCodeInfo.DebugEHClauseInfos;
            }

            if (ShouldShareSymbol((ObjectNode)debugNode))
            {
                // If the method is emitted in COMDAT section then we need to create an
                // associated COMDAT section for the debugging symbols.
                var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, default);
                debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter);
            }
            else
            {
                debugSymbolsBuilder = _debugSymbolsBuilder;
            }

            debugSymbolsBuilder.EmitSubprogramInfo(
                methodName,
                methodSymbol.Size,
                methodTypeIndex,
                debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))),
                clauses ?? Array.Empty<DebugEHClauseInfo>());

            if (hasSequencePoints)
            {
                debugSymbolsBuilder.EmitLineInfo(
                    _debugFileTableBuilder,
                    methodName,
                    methodSymbol.Size,
                    debugNode.GetNativeSequencePoints());
            }
        }

        private protected override void EmitDebugThunkInfo(
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode)
        {
            if (!debugNode.GetNativeSequencePoints().Any())
                return;

            CodeViewSymbolsBuilder debugSymbolsBuilder;

            if (ShouldShareSymbol((ObjectNode)debugNode))
            {
                // If the method is emitted in COMDAT section then we need to create an
                // associated COMDAT section for the debugging symbols.
                var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, default);
                debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter);
            }
            else
            {
                debugSymbolsBuilder = _debugSymbolsBuilder;
            }

            debugSymbolsBuilder.EmitLineInfo(
                _debugFileTableBuilder,
                methodName,
                methodSymbol.Size,
                debugNode.GetNativeSequencePoints());
        }

        private protected override void EmitDebugSections(IDictionary<Utf8String, SymbolDefinition> definedSymbols)
        {
            _debugSymbolsBuilder.WriteUserDefinedTypes(_debugTypesBuilder.UserDefinedTypes);
            _debugFileTableBuilder.Write(_debugSymbolSectionWriter);
        }
    }
}