File: Compiler\ObjectWriter\Dwarf\DwarfBuilder.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.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using ILCompiler.DependencyAnalysis;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.TypesDebugInfo;
using static ILCompiler.ObjectWriter.DwarfNative;

namespace ILCompiler.ObjectWriter
{
    internal sealed class DwarfBuilder : ITypesDebugInfoWriter
    {
        private record struct SectionInfo(Utf8String SectionSymbolName, ulong Size);
        private record struct MemberFunctionTypeInfo(MemberFunctionTypeDescriptor MemberDescriptor, uint[] ArgumentTypes, bool IsStatic);
        public delegate (Utf8String SectionSymbolName, long Address) ResolveStaticVariable(Utf8String name);

        private readonly NameMangler _nameMangler;
        private readonly TargetArchitecture _architecture;
        private readonly byte _targetPointerSize;
        private readonly bool _useDwarf5;
        private readonly int _frameRegister;
        private readonly byte _minimumInstructionLength;
        private readonly RelocType _codeRelocType;

        private readonly Dictionary<int, DwarfLineSequenceWriter> _lineSequences = new();
        private readonly Dictionary<string, int> _fileNameMap = new(); // fileName -> _fileNames index (1-based)
        private readonly List<DwarfFileName> _fileNames = new();

        private readonly List<SectionInfo> _sections = new();

        private readonly List<MemberFunctionTypeInfo> _memberFunctionTypeInfos = new();
        private readonly List<DwarfMemberFunction> _memberFunctions = new();
        private readonly Dictionary<TypeFlags, uint> _primitiveDwarfTypes = new();
        private readonly Dictionary<(uint, uint), uint> _simpleArrayDwarfTypes = new(); // (elementTypeIndex, size) -> arrayTypeIndex
        private readonly List<DwarfStaticVariableInfo> _staticFields = new();

        private readonly List<DwarfInfo> _dwarfTypes = new();
        private uint[] _dwarfTypeOffsets;
        private readonly List<DwarfInfo> _dwarfSubprograms = new();

        public DwarfBuilder(
            NameMangler nameMangler,
            TargetDetails target,
            bool useDwarf5)
        {
            _nameMangler = nameMangler;
            _architecture = target.Architecture;
            _useDwarf5 = useDwarf5;
            _minimumInstructionLength = (byte)target.MinimumCodeAlignment;

            switch (target.Architecture)
            {
                case TargetArchitecture.ARM64:
                    _targetPointerSize = 8;
                    _frameRegister = 29; // FP
                    _codeRelocType = RelocType.IMAGE_REL_BASED_DIR64;
                    break;

                case TargetArchitecture.ARM:
                    _targetPointerSize = 4;
                    _frameRegister = 7; // R7
                    _codeRelocType = RelocType.IMAGE_REL_BASED_HIGHLOW;
                    break;

                case TargetArchitecture.X64:
                    _targetPointerSize = 8;
                    _frameRegister = 6; // RBP
                    _codeRelocType = RelocType.IMAGE_REL_BASED_DIR64;
                    break;

                case TargetArchitecture.X86:
                    _targetPointerSize = 4;
                    _frameRegister = 5; // EBP
                    _codeRelocType = RelocType.IMAGE_REL_BASED_HIGHLOW;
                    break;

                case TargetArchitecture.LoongArch64:
                    _targetPointerSize = 8;
                    _frameRegister = 22; // FP
                    _codeRelocType = RelocType.IMAGE_REL_BASED_DIR64;
                    break;

                case TargetArchitecture.RiscV64:
                    _targetPointerSize = 8;
                    _frameRegister = 8; // FP
                    _codeRelocType = RelocType.IMAGE_REL_BASED_DIR64;
                    break;

                default:
                    throw new NotSupportedException("Unsupported architecture");
            }
        }

        public TargetArchitecture TargetArchitecture => _architecture;
        public byte TargetPointerSize => _targetPointerSize;
        public int FrameRegister => _frameRegister;

        public uint ResolveOffset(uint typeIndex) => typeIndex == 0 ? 0u : _dwarfTypeOffsets[typeIndex - 1];

        public void Write(
            SectionWriter infoSectionWriter,
            SectionWriter stringSectionWriter,
            SectionWriter abbrevSectionWriter,
            SectionWriter locSectionWriter,
            SectionWriter rangeSectionWriter,
            SectionWriter lineSectionWriter,
            SectionWriter arangeSectionWriter,
            ResolveStaticVariable resolveStaticVariable)
        {
            WriteInfoTable(
                infoSectionWriter,
                stringSectionWriter,
                abbrevSectionWriter,
                locSectionWriter,
                rangeSectionWriter,
                resolveStaticVariable);
            WriteLineInfoTable(lineSectionWriter);
            WriteAddressRangeTable(arangeSectionWriter);
        }

        public void WriteInfoTable(
            SectionWriter infoSectionWriter,
            SectionWriter stringSectionWriter,
            SectionWriter abbrevSectionWriter,
            SectionWriter locSectionWriter,
            SectionWriter rangeSectionWriter,
            ResolveStaticVariable resolveStaticVariable)
        {
            // Length
            byte[] sizeBuffer = new byte[sizeof(uint)];
            infoSectionWriter.EmitData(sizeBuffer);
            // Version
            infoSectionWriter.WriteLittleEndian<ushort>((ushort)(_useDwarf5 ? 5u : 4u));
            if (_useDwarf5)
            {
                // Unit type, Address Size
                infoSectionWriter.Write([DW_UT_compile, _targetPointerSize]);
                // Abbrev offset
                infoSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_HIGHLOW, new Utf8String(".debug_abbrev"u8), 0);
            }
            else
            {
                // Abbrev offset
                infoSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_HIGHLOW, new Utf8String(".debug_abbrev"u8), 0);
                // Address Size
                infoSectionWriter.Write([_targetPointerSize]);
            }

            using (DwarfInfoWriter dwarfInfoWriter = new(
                infoSectionWriter,
                stringSectionWriter,
                abbrevSectionWriter,
                locSectionWriter,
                rangeSectionWriter,
                this,
                _codeRelocType))
            {
                dwarfInfoWriter.WriteStartDIE(DwarfAbbrev.CompileUnit);

                // DW_AT_producer
                dwarfInfoWriter.WriteStringReference("NetRuntime");
                // DW_AT_language
                dwarfInfoWriter.WriteUInt16(DW_LANG_C_plus_plus);
                // DW_AT_name
                dwarfInfoWriter.WriteStringReference("il.cpp");
                // DW_AT_comp_dir
                dwarfInfoWriter.WriteStringReference("/_");
                // DW_AT_low_pc
                dwarfInfoWriter.WriteAddressSize(0);
                // DW_AT_ranges
                dwarfInfoWriter.WriteStartRangeList();
                foreach (var sectionInfo in _sections)
                {
                    dwarfInfoWriter.WriteRangeListEntry(sectionInfo.SectionSymbolName, 0, (uint)sectionInfo.Size);
                }
                dwarfInfoWriter.WriteEndRangeList();
                // DW_AT_stmt_list
                dwarfInfoWriter.WriteLineReference(0);

                _dwarfTypeOffsets = new uint[_dwarfTypes.Count];

                int typeIndex = 0;
                foreach (DwarfInfo type in _dwarfTypes)
                {
                    _dwarfTypeOffsets[typeIndex] = (uint)dwarfInfoWriter.Position;
                    type.Dump(dwarfInfoWriter);
                    typeIndex++;
                }

                foreach (DwarfInfo subprogram in _dwarfSubprograms)
                {
                    subprogram.Dump(dwarfInfoWriter);
                }

                foreach (DwarfStaticVariableInfo staticField in _staticFields)
                {
                    (Utf8String sectionSymbolName, long address) = resolveStaticVariable(staticField.Name);
                    if (!sectionSymbolName.IsNull)
                    {
                        staticField.Dump(dwarfInfoWriter, sectionSymbolName, address);
                    }
                }

                dwarfInfoWriter.WriteEndDIE();
            }

            // End of compile unit
            infoSectionWriter.WriteByte(0);

            // Update the size
            BinaryPrimitives.WriteUInt32LittleEndian(sizeBuffer, (uint)(infoSectionWriter.Position - sizeof(uint)));
        }

        private void WriteLineInfoTable(SectionWriter lineSectionWriter)
        {
            using (var lineProgramTableWriter = new DwarfLineProgramTableWriter(
                lineSectionWriter,
                _fileNames,
                _targetPointerSize,
                _minimumInstructionLength,
                _codeRelocType))
            {
                foreach (DwarfLineSequenceWriter lineSequence in _lineSequences.Values)
                {
                    lineProgramTableWriter.WriteLineSequence(lineSequence);
                }
            }
        }

        private void WriteAddressRangeTable(SectionWriter arangeSectionWriter)
        {
            // Length
            var sizeBuffer = new byte[sizeof(uint)];
            arangeSectionWriter.EmitData(sizeBuffer);
            // Version
            arangeSectionWriter.WriteLittleEndian<ushort>(2);
            // Debug Info Offset
            arangeSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_HIGHLOW, new Utf8String(".debug_info"u8), 0);
            // Address size, Segment selector size
            arangeSectionWriter.Write([_targetPointerSize, 0]);
            // Ranges have to be aligned
            arangeSectionWriter.EmitAlignment(_targetPointerSize * 2);
            foreach (var sectionInfo in _sections)
            {
                arangeSectionWriter.EmitSymbolReference(_codeRelocType, sectionInfo.SectionSymbolName, 0);
                switch (_targetPointerSize)
                {
                    case 8: arangeSectionWriter.WriteLittleEndian<ulong>(sectionInfo.Size); break;
                    case 4: arangeSectionWriter.WriteLittleEndian<uint>((uint)sectionInfo.Size); break;
                    default: throw new NotSupportedException();
                }
            }
            arangeSectionWriter.WritePadding(_targetPointerSize * 2);
            // Update the size
            BinaryPrimitives.WriteUInt32LittleEndian(sizeBuffer, (uint)(arangeSectionWriter.Position - sizeof(uint)));
        }

        public uint GetPrimitiveTypeIndex(TypeDesc type)
        {
            Debug.Assert(type.IsPrimitive, "it is not a primitive type");
            return GetPrimitiveTypeIndex(type.Category);
        }

        private uint GetPrimitiveTypeIndex(TypeFlags typeFlags)
        {
            if (_primitiveDwarfTypes.TryGetValue(typeFlags, out uint index))
            {
                return index;
            }

            _dwarfTypes.Add(new DwarfPrimitiveTypeInfo(typeFlags, _targetPointerSize));
            uint typeIndex = (uint)_dwarfTypes.Count;
            _primitiveDwarfTypes.Add(typeFlags, typeIndex);
            return typeIndex;
        }

        public uint GetPointerTypeIndex(PointerTypeDescriptor pointerDescriptor)
        {
            uint voidTypeIndex = GetPrimitiveTypeIndex(TypeFlags.Void);

            // Creating a pointer to what DWARF considers Void type (DW_TAG_unspecified_type -
            // per http://eagercon.com/dwarf/issues/minutes-001017.htm) leads to unhappiness
            // since debuggers don't really know how to handle that. The Clang symbol parser
            // in LLDB only handles DW_TAG_unspecified_type if it's named
            // "nullptr_t" or "decltype(nullptr)".
            //
            // We resort to this kludge to generate the exact same debug info for void* that
            // clang would generate (pointer type with no element type specified).
            if (pointerDescriptor.ElementType == voidTypeIndex)
            {
                _dwarfTypes.Add(new DwarfVoidPtrTypeInfo());
            }
            else
            {
                _dwarfTypes.Add(new DwarfPointerTypeInfo(pointerDescriptor));
            }

            return (uint)_dwarfTypes.Count;
        }

        private uint GetSimpleArrayTypeIndex(uint elementIndex, uint size)
        {
            if (_simpleArrayDwarfTypes.TryGetValue((elementIndex, size), out uint index))
            {
                return index;
            }

            _dwarfTypes.Add(new DwarfSimpleArrayTypeInfo(elementIndex, size));
            uint typeIndex = (uint)_dwarfTypes.Count;
            _simpleArrayDwarfTypes.Add((elementIndex, size), typeIndex);

            return typeIndex;
        }

        public uint GetArrayTypeIndex(
            ClassTypeDescriptor classDescriptor,
            ArrayTypeDescriptor arrayDescriptor)
        {
            // Create corresponding class info
            ClassTypeDescriptor arrayClassDescriptor = classDescriptor;

            List<DataFieldDescriptor> fieldDescriptors = new();
            ulong fieldOffset = _targetPointerSize;

            fieldDescriptors.Add(new DataFieldDescriptor
            {
                FieldTypeIndex = GetPrimitiveTypeIndex(TypeFlags.Int32),
                Offset = fieldOffset,
                Name = new Utf8String("m_NumComponents"u8),
            });
            fieldOffset += _targetPointerSize;

            if (arrayDescriptor.IsMultiDimensional != 0)
            {
                fieldDescriptors.Add(new DataFieldDescriptor
                {
                    FieldTypeIndex = GetSimpleArrayTypeIndex(GetPrimitiveTypeIndex(TypeFlags.Int32), arrayDescriptor.Rank),
                    Offset = fieldOffset,
                    Name = new Utf8String("m_Bounds"u8),
                });
                fieldOffset += 2u * 4u * (ulong)arrayDescriptor.Rank;
            }

            fieldDescriptors.Add(new DataFieldDescriptor
            {
                FieldTypeIndex = GetSimpleArrayTypeIndex(arrayDescriptor.ElementType, 0),
                Offset = fieldOffset,
                Name = new Utf8String("m_Data"u8),
            });

            // We currently don't encode the size of the variable length data. The DWARF5
            // specification allows encoding variable length arrays through DW_AT_lower_bound,
            // DW_AT_upper_bound, and DW_AT_count expressions. There's potentially room
            // to improve the debugging information by a more substential restructuring.
            arrayClassDescriptor.InstanceSize = fieldOffset;

            ClassFieldsTypeDescriptor fieldsTypeDesc = new ClassFieldsTypeDescriptor
            {
                Size = _targetPointerSize,
                FieldsCount = arrayDescriptor.IsMultiDimensional != 0 ? 3 : 2,
            };

            return GetCompleteClassTypeIndex(arrayClassDescriptor, fieldsTypeDesc, fieldDescriptors.ToArray(), null);
        }

        public uint GetEnumTypeIndex(
            EnumTypeDescriptor typeDescriptor,
            EnumRecordTypeDescriptor[] typeRecords)
        {
            byte byteSize = ((DwarfPrimitiveTypeInfo)_dwarfTypes[(int)typeDescriptor.ElementType - 1]).ByteSize;
            _dwarfTypes.Add(new DwarfEnumTypeInfo(typeDescriptor, typeRecords, byteSize));
            return (uint)_dwarfTypes.Count;
        }

        public uint GetClassTypeIndex(ClassTypeDescriptor classDescriptor)
        {
            _dwarfTypes.Add(new DwarfClassTypeInfo(classDescriptor));
            return (uint)_dwarfTypes.Count;
        }

        public uint GetCompleteClassTypeIndex(
            ClassTypeDescriptor classTypeDescriptor,
            ClassFieldsTypeDescriptor classFieldsTypeDescriptor,
            DataFieldDescriptor[] fields,
            StaticDataFieldDescriptor[] statics)
        {
            var classInfo = new DwarfClassTypeInfo(classTypeDescriptor, classFieldsTypeDescriptor, fields, statics);
            _dwarfTypes.Add(classInfo);
            _staticFields.AddRange(classInfo.StaticVariables);
            return (uint)_dwarfTypes.Count;
        }

        public uint GetMemberFunctionTypeIndex(MemberFunctionTypeDescriptor memberDescriptor, uint[] argumentTypes)
        {
            _memberFunctionTypeInfos.Add(new MemberFunctionTypeInfo(
                memberDescriptor,
                argumentTypes,
                memberDescriptor.TypeIndexOfThisPointer == GetPrimitiveTypeIndex(TypeFlags.Void)));

            return (uint)_memberFunctionTypeInfos.Count;
        }

        public uint GetMemberFunctionId(MemberFunctionIdTypeDescriptor memberIdDescriptor)
        {
            MemberFunctionTypeInfo memberFunctionTypeInfo = _memberFunctionTypeInfos[(int)memberIdDescriptor.MemberFunction - 1];
            DwarfClassTypeInfo parentClass = (DwarfClassTypeInfo)_dwarfTypes[(int)(memberIdDescriptor.ParentClass - 1)];
            DwarfMemberFunction memberFunction = new DwarfMemberFunction(
                memberIdDescriptor.Name,
                memberFunctionTypeInfo.MemberDescriptor,
                memberFunctionTypeInfo.ArgumentTypes,
                memberFunctionTypeInfo.IsStatic);

            parentClass.AddMemberFunction(memberFunction);
            _memberFunctions.Add(memberFunction);

            return (uint)_memberFunctions.Count;
        }

        public Utf8String GetMangledName(TypeDesc type)
        {
            return _nameMangler.GetMangledTypeName(type);
        }

        public void EmitSubprogramInfo(
            Utf8String methodName,
            Utf8String sectionSymbolName,
            long methodAddress,
            int methodPCLength,
            uint methodTypeIndex,
            IEnumerable<(DebugVarInfoMetadata, uint)> debugVars,
            DebugEHClauseInfo[] debugEHClauseInfos)
        {
            if (methodTypeIndex == 0)
            {
                return;
            }

            DwarfMemberFunction memberFunction = _memberFunctions[(int)methodTypeIndex - 1];
            memberFunction.LinkageName = methodName;

            _dwarfSubprograms.Add(new DwarfSubprogramInfo(
                sectionSymbolName,
                methodAddress,
                methodPCLength,
                memberFunction,
                debugVars.ToArray(),
                debugEHClauseInfos));
        }

        public void EmitLineInfo(
            int sectionIndex,
            Utf8String sectionSymbolName,
            long methodAddress,
            IEnumerable<NativeSequencePoint> sequencePoints)
        {
            DwarfLineSequenceWriter lineSequence;

            // Create line sequence for every section so they can get the
            // base address relocated properly.
            if (!_lineSequences.TryGetValue(sectionIndex, out lineSequence))
            {
                lineSequence = new DwarfLineSequenceWriter(sectionSymbolName, _minimumInstructionLength);
                _lineSequences.Add(sectionIndex, lineSequence);
            }

            int fileNameIndex = 0;
            string lastFileName = null;

            foreach (NativeSequencePoint sequencePoint in sequencePoints)
            {
                if (lastFileName != sequencePoint.FileName)
                {
                    if (!_fileNameMap.TryGetValue(sequencePoint.FileName, out fileNameIndex))
                    {
                        var dwarfFileName = string.IsNullOrEmpty(sequencePoint.FileName) ?
                            new DwarfFileName("<stdin>", null) :
                            new DwarfFileName(Path.GetFileName(sequencePoint.FileName), Path.GetDirectoryName(sequencePoint.FileName));
                        _fileNames.Add(dwarfFileName);
                        fileNameIndex = _fileNames.Count;
                        _fileNameMap.Add(sequencePoint.FileName, fileNameIndex);
                    }
                    lastFileName = sequencePoint.FileName;
                }

                lineSequence.EmitLineInfo(fileNameIndex, methodAddress, sequencePoint);
            }
        }

        public void EmitSectionInfo(Utf8String sectionSymbolName, ulong size)
        {
            _sections.Add(new SectionInfo(sectionSymbolName, size));
        }
    }
}