diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 01e92782aeef4d..6ea704e40403b6 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -90,6 +90,17 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown return CDAC{cdacLib, handle, target, legacyImpl}; } +void CDAC::CreateInstance(void* iid, ICLRDataTarget* pLegacyTarget, void** iface) +{ + HMODULE cdacLib; + if (!TryLoadCDACLibrary(&cdacLib)) + return; + + decltype(&cdac_reader_create_instance) create_instance = reinterpret_cast(::GetProcAddress(cdacLib, "cdac_reader_create_instance")); + + create_instance(iid, pLegacyTarget, iface); +} + CDAC::CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target, IUnknown* legacyImpl) : m_module{module} , m_cdac_handle{handle} diff --git a/src/coreclr/debug/daccess/cdac.h b/src/coreclr/debug/daccess/cdac.h index f6d1cdcbe5035b..15e43a34c99512 100644 --- a/src/coreclr/debug/daccess/cdac.h +++ b/src/coreclr/debug/daccess/cdac.h @@ -9,6 +9,8 @@ class CDAC final public: // static static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget, IUnknown* legacyImpl); + static void CreateInstance(void* iid, ICLRDataTarget* pLegacyTarget, void** iface); + public: CDAC() = default; diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 8af160fd73d064..486c45ead50f9f 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -25,6 +25,7 @@ #include "dbgutil.h" #include "cdac.h" #include +#include #ifdef USE_DAC_TABLE_RVA #include @@ -7009,6 +7010,19 @@ CLRDataCreateInstance(REFIID iid, } *iface = NULL; + + + CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC"); + if (enable.IsSet()) + { + DWORD val; + if (enable.TryAsInteger(10, val) && val == 1) + { + CDAC::CreateInstance((void*)&iid, pLegacyTarget, iface); + return S_OK; + } + } + ClrDataAccess * pClrDataAccess; HRESULT hr = CLRDataAccessCreateInstance(pLegacyTarget, &pClrDataAccess); if (hr != S_OK) @@ -7021,38 +7035,6 @@ CLRDataCreateInstance(REFIID iid, // TODO: [cdac] Remove when cDAC deploys with SOS - https://github.com/dotnet/runtime/issues/108720 NonVMComHolder cdacInterface = nullptr; -#ifdef CAN_USE_CDAC - CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC"); - if (enable.IsSet()) - { - DWORD val; - if (enable.TryAsInteger(10, val) && val == 1) - { - // TODO: [cdac] TryGetSymbol is only implemented for Linux, OSX, and Windows. - uint64_t contractDescriptorAddr = 0; - if (TryGetSymbol(pClrDataAccess->m_pTarget, pClrDataAccess->m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr)) - { - IUnknown* thisImpl; - HRESULT qiRes = pClrDataAccess->QueryInterface(IID_IUnknown, (void**)&thisImpl); - _ASSERTE(SUCCEEDED(qiRes)); - CDAC& cdac = pClrDataAccess->m_cdac; - cdac = CDAC::Create(contractDescriptorAddr, pClrDataAccess->m_pTarget, thisImpl); - if (cdac.IsValid()) - { - // Get SOS interfaces from the cDAC if available. - cdac.CreateSosInterface(&cdacInterface); - _ASSERTE(cdacInterface != nullptr); - - // Lifetime is now managed by cDAC implementation of SOS interfaces - pClrDataAccess->Release(); - } - - // Release the AddRef from the QI. - pClrDataAccess->Release(); - } - } - } -#endif if (cdacInterface != nullptr) { hr = cdacInterface->QueryInterface(iid, iface); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index e2885d60f74b05..f7a422a0781b4f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -60,7 +60,7 @@ public static bool TryCreate( ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, GetTargetPlatformDelegate getTargetPlatform, - out ContractDescriptorTarget? target) + [NotNullWhen(true)] out ContractDescriptorTarget? target) { Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); if (TryReadContractDescriptor( diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 9bf5ddb409090e..7c0284a1268508 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -34,6 +34,8 @@ int cdac_reader_free(intptr_t handle); // obj: returned SOS interface that can be QI'd to ISOSDacInterface* int cdac_reader_create_sos_interface(intptr_t handle, IUnknown* legacyImpl, IUnknown** obj); +int cdac_reader_create_instance(void* iid, IUnknown* pLegacyTarget, void** iface); + #ifdef __cplusplus } #endif diff --git a/src/native/managed/cdacreader/src/Decoder/BinaryReaderExtensions.cs b/src/native/managed/cdacreader/src/Decoder/BinaryReaderExtensions.cs new file mode 100644 index 00000000000000..977340d13a4a26 --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/BinaryReaderExtensions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Text; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal static class BinaryReaderExtensions +{ + /// + /// Reads a C-style, zero-terminated string from memory. + /// + public static string ReadZString(this BinaryReader reader) + { + var sb = new StringBuilder(); + byte nextByte = reader.ReadByte(); + while (nextByte != 0) + { + sb.Append((char)nextByte); + nextByte = reader.ReadByte(); + } + return sb.ToString(); + } + + public static unsafe T Read(this BinaryReader reader) + where T : unmanaged, IBinaryInteger, IMinMaxValue + { + T value = default; + Span buffer = stackalloc byte[sizeof(T)]; + + if (reader.Read(buffer) != buffer.Length) + { + throw new IOException(); + } + if (!T.TryReadLittleEndian(buffer, !IsSigned(), out value)) + { + throw new InvalidOperationException("Unable to convert to type."); + } + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSigned() where T : struct, INumberBase, IMinMaxValue + { + return T.IsNegative(T.MinValue); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/DataTargetStream.cs b/src/native/managed/cdacreader/src/Decoder/DataTargetStream.cs new file mode 100644 index 00000000000000..ed5813cc5e613d --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/DataTargetStream.cs @@ -0,0 +1,64 @@ +// 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.IO; +using Microsoft.Diagnostics.DataContractReader.Legacy; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal class DataTargetStream(ICLRDataTarget dataTarget, ulong startPosition) : Stream +{ + private readonly ulong _startPosition = startPosition; + private long _offset; + private readonly ICLRDataTarget _dataTarget = dataTarget; + + private ulong GlobalPosition => _startPosition + (ulong)_offset; + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => 0x10000; + + public override long Position { get => _offset; set => _offset = value; } + + public override unsafe int Read(byte[] buffer, int offset, int count) + { + Span span = buffer; + return Read(span.Slice(start: offset, length: count)); + } + public override unsafe int Read(Span buffer) + { + fixed (byte* bufferPtr = buffer) + { + uint bytesRead; + int hr = _dataTarget.ReadVirtual(GlobalPosition, bufferPtr, (uint)buffer.Length, &bytesRead); + _offset += bytesRead; + if (hr != 0) + throw new InvalidOperationException($"ReadVirtual failed with hr={hr}"); + return (int)bytesRead; + } + } + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + _offset = offset; + break; + case SeekOrigin.Current: + _offset += offset; + break; + case SeekOrigin.End: + throw new NotSupportedException(); + } + return _offset; + } + + public override void Flush() => throw new NotImplementedException(); + public override void SetLength(long value) => throw new NotImplementedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); +} diff --git a/src/native/managed/cdacreader/src/Decoder/ELFDecoder.cs b/src/native/managed/cdacreader/src/Decoder/ELFDecoder.cs new file mode 100644 index 00000000000000..beb9d3a45d6dac --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/ELFDecoder.cs @@ -0,0 +1,175 @@ +// 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.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal sealed class ELFDecoder : IDisposable +{ + private readonly BinaryReader _reader; + private readonly ulong _baseAddress; + + private bool _is64Bit; + private GnuHashTable? _gnuHashTable; + + private bool _disposedValue; + + public bool IsValid => _gnuHashTable is not null; + + /// + /// Create ELFReader with stream beginning at the base address of the module. + /// + public ELFDecoder(Stream stream, ulong baseAddress) + { + _reader = new(stream, Encoding.UTF8); + _baseAddress = baseAddress; + + Initialize(); + } + + private void Initialize() + { + _reader.BaseStream.Seek(0, SeekOrigin.Begin); + + uint elfMagic = _reader.ReadUInt32(); + if (elfMagic != 0x464C457F) // 0x7F followed by "ELF" + return; + + _is64Bit = _reader.ReadByte() != 1; + + if (_is64Bit) + { + Initialize(); + } + else + { + Initialize(); + } + } + + /// + /// Initializes the ElfDecoder with specific type. + /// Supports ELF32 with uint and ELF64 with ulong. + /// + private void Initialize() + where T : unmanaged, IBinaryInteger, IMinMaxValue, IConvertible + { + // read the full Elf_Ehdr + _reader.BaseStream.Seek(0, SeekOrigin.Begin); + Elf_Ehdr elfHeader = new(_reader); + + // read the list of Elf_Phdr starting at e_phoff + _reader.BaseStream.Seek(Convert.ToInt64(elfHeader.e_phoff), SeekOrigin.Begin); + List> programHeaders = []; + for (int i = 0; i < elfHeader.e_phnum; i++) + { + programHeaders.Add(new Elf_Phdr(_reader)); + } + + // Calculate the load bias from the PT_LOAD program headers. + // PT_LOAD program headers map the executable file regions to virtual memory regions. + // p_offset: offset into the executable file for first byte of segment + // p_vaddr: virtual address of first byte of segment in memory + // p_filesz: number of bytes in the file image of the segment + // p_memsz: number of bytes in the virtual memory region of the segment + // + // For a given PT_LOAD header, it maps a virtual memory region at the requested p_vaddr + // to the data beginning at p_offset in the file. While the OS can move the virtual memory segments, + // Within an executable, all of the mapped segments must maintain their relative spacing. + // We compute this "load bias" to correctly map RVAs to memory. + // + // Since the Elf Header is always at the beginning of the executable file it is mapped in the PT_LOAD, `firstLoad`, with p_offset = 0. + // We read the ElfHeader at memory _baseAddress, therefore the load bias is `_baseAddress - (firstLoad.p_vaddr)` + // Because we are working relative to _baseAddress load bias is stored as a just `firstLoad.p_vaddr` then subtracted. + ulong relativeLoadBias = 0; + foreach (Elf_Phdr programHeader in programHeaders) + { + if (programHeader.Type == HeaderType.PT_LOAD && + Convert.ToUInt64(programHeader.p_offset) == 0) + { + relativeLoadBias = Convert.ToUInt64(programHeader.p_vaddr); + break; + } + } + ulong loadBias = _baseAddress - relativeLoadBias; + + long dynamicOffset = 0; + foreach (Elf_Phdr programHeader in programHeaders) + { + if (programHeader.Type == HeaderType.PT_DYNAMIC) + { + dynamicOffset = (long)(Convert.ToUInt64(programHeader.p_vaddr) - relativeLoadBias); + break; + } + } + if (dynamicOffset == 0) return; + + long gnuHashTableOffset = 0; + long symbolTableOffset = 0; + long stringTableOffset = 0; + int stringTableSize = 0; + _reader.BaseStream.Seek(dynamicOffset, SeekOrigin.Begin); + while (true) + { + Elf_Dyn dynamicEntry = new Elf_Dyn(_reader); + + if (dynamicEntry.Type == DynamicType.DT_NULL) + break; + if (dynamicEntry.Type == DynamicType.DT_GNU_HASH) + gnuHashTableOffset = (long)(Convert.ToUInt64(dynamicEntry.d_val) - loadBias); + if (dynamicEntry.Type == DynamicType.DT_SYMTAB) + symbolTableOffset = (long)(Convert.ToUInt64(dynamicEntry.d_val) - loadBias); + if (dynamicEntry.Type == DynamicType.DT_STRTAB) + stringTableOffset = (long)(Convert.ToUInt64(dynamicEntry.d_val) - loadBias); + if (dynamicEntry.Type == DynamicType.DT_STRSZ) + stringTableSize = Convert.ToInt32(dynamicEntry.d_val); + } + + if (gnuHashTableOffset == 0) return; + if (symbolTableOffset == 0) return; + if (stringTableOffset == 0) return; + + _gnuHashTable = new GnuHashTable( + _reader.BaseStream, + gnuHashTableOffset, + symbolTableOffset, + stringTableOffset, + stringTableSize, + _is64Bit, + leaveStreamOpen: true); + } + + public bool TryGetRelativeSymbolAddress(string symbol, out ulong address) + { + address = 0; + + if (_gnuHashTable is not GnuHashTable hashTable) + return false; + + return hashTable.TryLookupRelativeSymbolAddress(symbol, out address); + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _gnuHashTable?.Dispose(); + _reader.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/ELFTypes.cs b/src/native/managed/cdacreader/src/Decoder/ELFTypes.cs new file mode 100644 index 00000000000000..dc1a8cf48b22bc --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/ELFTypes.cs @@ -0,0 +1,205 @@ +// 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.IO; +using System.Numerics; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal enum HeaderType : uint +{ + PT_NULL = 0, // Unused segment. + PT_LOAD = 1, // Loadable segment. + PT_DYNAMIC = 2, // Dynamic linking information. + PT_INTERP = 3, // Interpreter pathname. + PT_NOTE = 4, // Auxiliary information. + PT_SHLIB = 5, // Reserved. + PT_PHDR = 6, // The program header table itself. + PT_TLS = 7, // The thread-local storage template. + PT_LOOS = 0x60000000, // Lowest operating system-specific pt entry type. + PT_HIOS = 0x6fffffff, // Highest operating system-specific pt entry type. + PT_LOPROC = 0x70000000, // Lowest processor-specific program hdr entry type. + PT_HIPROC = 0x7fffffff, // Highest processor-specific program hdr entry type. +} + +internal enum DynamicType : uint +{ + DT_NULL = 0, // Marks end of dynamic array. + DT_NEEDED = 1, // String table offset of needed library. + DT_PLTRELSZ = 2, // Size of relocation entries in PLT. + DT_PLTGOT = 3, // Address associated with linkage table. + DT_HASH = 4, // Address of symbolic hash table. + DT_STRTAB = 5, // Address of dynamic string table. + DT_SYMTAB = 6, // Address of dynamic symbol table. + DT_RELA = 7, // Address of relocation table (Rela entries). + DT_RELASZ = 8, // Size of Rela relocation table. + DT_RELAENT = 9, // Size of a Rela relocation entry. + DT_STRSZ = 10, // Total size of the string table. + DT_SYMENT = 11, // Size of a symbol table entry. + DT_INIT = 12, // Address of initialization function. + DT_FINI = 13, // Address of termination function. + DT_SONAME = 14, // String table offset of a shared objects name. + DT_RPATH = 15, // String table offset of library search path. + DT_SYMBOLIC = 16, // Changes symbol resolution algorithm. + DT_REL = 17, // Address of relocation table (Rel entries). + DT_RELSZ = 18, // Size of Rel relocation table. + DT_RELENT = 19, // Size of a Rel relocation entry. + DT_PLTREL = 20, // Type of relocation entry used for linking. + DT_DEBUG = 21, // Reserved for debugger. + DT_TEXTREL = 22, // Relocations exist for non-writable segments. + DT_JMPREL = 23, // Address of relocations associated with PLT. + DT_BIND_NOW = 24, // Process all relocations before execution. + DT_INIT_ARRAY = 25, // Pointer to array of initialization functions. + DT_FINI_ARRAY = 26, // Pointer to array of termination functions. + DT_INIT_ARRAYSZ = 27, // Size of DT_INIT_ARRAY. + DT_FINI_ARRAYSZ = 28, // Size of DT_FINI_ARRAY. + DT_RUNPATH = 29, // String table offset of lib search path. + DT_FLAGS = 30, // Flags. + DT_ENCODING = 32, // Values from here to DT_LOOS follow the rules for the interpretation of the d_un union. + DT_PREINIT_ARRAY = 32, // Pointer to array of preinit functions. + DT_PREINIT_ARRAYSZ = 33, // Size of the DT_PREINIT_ARRAY array. + + DT_LOOS = 0x60000000, // Start of environment specific tags. + DT_HIOS = 0x6FFFFFFF, // End of environment specific tags. + DT_LOPROC = 0x70000000, // Start of processor specific tags. + DT_HIPROC = 0x7FFFFFFF, // End of processor specific tags. + + DT_GNU_HASH = 0x6FFFFEF5, // Reference to the GNU hash table. +}; + +internal struct Elf_Ehdr + where T : unmanaged, IBinaryInteger, IMinMaxValue +{ + public byte[] e_ident; + public ushort e_type; + public ushort e_machine; + public uint e_version; + public T e_entry; + public T e_phoff; + public T e_shoff; + public uint e_flags; + public ushort e_ehsize; + public ushort e_phentsize; + public ushort e_phnum; + public ushort e_shentsize; + public ushort e_shnum; + public ushort e_shstrndx; + + public Elf_Ehdr(BinaryReader reader) + { + e_ident = reader.ReadBytes(16); + e_type = reader.ReadUInt16(); + e_machine = reader.ReadUInt16(); + e_version = reader.ReadUInt32(); + e_entry = reader.Read(); + e_phoff = reader.Read(); + e_shoff = reader.Read(); + e_flags = reader.ReadUInt32(); + e_ehsize = reader.ReadUInt16(); + e_phentsize = reader.ReadUInt16(); + e_phnum = reader.ReadUInt16(); + e_shentsize = reader.ReadUInt16(); + e_shnum = reader.ReadUInt16(); + e_shstrndx = reader.ReadUInt16(); + } +} + +internal struct Elf_Phdr + where T : unmanaged, IBinaryInteger, IMinMaxValue +{ + public uint p_type; + public uint p_flags; + public T p_offset; + public T p_vaddr; + public T p_paddr; + public T p_filesz; + public T p_memsz; + public T p_align; + + public readonly HeaderType Type { get => (HeaderType)p_type; } + + public Elf_Phdr(BinaryReader reader) + { + T t = default; + p_type = reader.Read(); + if (t.GetByteCount() == 8) + { + // on 64 bit platforms, p_flags is the second element + p_flags = reader.ReadUInt32(); + } + p_offset = reader.Read(); + p_vaddr = reader.Read(); + p_paddr = reader.Read(); + p_filesz = reader.Read(); + p_memsz = reader.Read(); + if (t.GetByteCount() == 4) + { + // on 32 bit platforms, p_flags is located after p_memsz + p_flags = reader.ReadUInt32(); + } + p_align = reader.Read(); + } +} + +internal struct Elf_Dyn + where T : unmanaged, IBinaryInteger, IMinMaxValue +{ + public T d_tag; + public T d_val; + + public readonly DynamicType Type { get => (DynamicType)Convert.ToUInt32(d_tag); } + + public Elf_Dyn(BinaryReader reader) + { + d_tag = reader.Read(); + d_val = reader.Read(); + } +} + +internal struct Elf_Sym + where T : unmanaged, IBinaryInteger, IMinMaxValue +{ + public uint st_name; + public T st_value; + public T st_size; + public char st_info; + public char st_other; + public ushort st_shndx; + + public Elf_Sym(BinaryReader reader) + { + T t = default; + // field ordering is different on 32/64 bit platforms. + if (t.GetByteCount() == 4) + { + st_name = reader.ReadUInt32(); + st_value = reader.Read(); + st_size = reader.Read(); + st_info = reader.ReadChar(); + st_other = reader.ReadChar(); + st_shndx = reader.ReadUInt16(); + } + else if (t.GetByteCount() == 8) + { + st_name = reader.ReadUInt32(); + st_info = reader.ReadChar(); + st_other = reader.ReadChar(); + st_shndx = reader.ReadUInt16(); + st_value = reader.Read(); + st_size = reader.Read(); + } + else + { + throw new InvalidOperationException(); + } + } + + public static int GetPackedSize() + { + T t = default; + if (t.GetByteCount() == 4) return 16; + if (t.GetByteCount() == 8) return 24; + throw new InvalidOperationException(); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/GnuHashTable.cs b/src/native/managed/cdacreader/src/Decoder/GnuHashTable.cs new file mode 100644 index 00000000000000..d0bf8fb317e331 --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/GnuHashTable.cs @@ -0,0 +1,164 @@ +// 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.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal sealed class GnuHashTable : IDisposable +{ + private readonly BinaryReader _reader; + private readonly bool _is64Bit; + + private readonly long _symbolTableOffset; + private readonly long _stringTableOffset; + private readonly int _stringTableSize; + + private readonly int[] _hashBuckets; + private readonly ulong _chainsOffset; + private readonly int _symbolOffset; + + private bool _disposedValue; + + public GnuHashTable( + Stream stream, + long gnuHashTableOffset, + long symbolTableOffset, + long stringTableOffset, + int stringTableSize, + bool is64Bit, + bool leaveStreamOpen) + { + _reader = new(stream, Encoding.UTF8, leaveStreamOpen); + + _symbolTableOffset = symbolTableOffset; + _stringTableOffset = stringTableOffset; + _stringTableSize = stringTableSize; + _is64Bit = is64Bit; + + _reader.BaseStream.Seek(gnuHashTableOffset, SeekOrigin.Begin); + + int bucketCount = _reader.ReadInt32(); + _symbolOffset = _reader.ReadInt32(); + int bloomSize = _reader.ReadInt32(); + int _ /*bloomShift*/ = _reader.ReadInt32(); + + // skip bloom filter + _reader.BaseStream.Seek(bloomSize * (is64Bit ? 8 : 4), SeekOrigin.Current); + + // populate hash buckets + _hashBuckets = new int[bucketCount]; + for (int i = 0; i < bucketCount; i++) + { + _hashBuckets[i] = _reader.ReadInt32(); + } + + // chains begin at end of hash buckets + _chainsOffset = (ulong)_reader.BaseStream.Position; + } + + public bool TryLookupRelativeSymbolAddress(string symbol, out ulong address) + { + address = 0; + + uint hash = GnuHash(symbol); + + List symbolIndexes = GetPossibleSymbolIndex(hash); + + foreach (int possibleLocation in symbolIndexes) + { + string possibleString = _is64Bit ? GetSymbolName(possibleLocation) : GetSymbolName(possibleLocation); + + if (symbol == possibleString) + { + address = _is64Bit ? GetSymbolValue(possibleLocation) : GetSymbolValue(possibleLocation); + return true; + } + } + return false; + } + + private T GetSymbolValue(int index) + where T : unmanaged, IBinaryInteger, IMinMaxValue, IConvertible + { + _reader.BaseStream.Seek(_symbolTableOffset + Elf_Sym.GetPackedSize() * index, SeekOrigin.Begin); + Elf_Sym elfSymbol = new(_reader); + return elfSymbol.st_value; + } + + private string GetSymbolName(int index) + where T : unmanaged, IBinaryInteger, IMinMaxValue, IConvertible + { + _reader.BaseStream.Seek(_symbolTableOffset + Elf_Sym.GetPackedSize() * index, SeekOrigin.Begin); + Elf_Sym elfSymbol = new(_reader); + return GetStringAtIndex(elfSymbol.st_name); + } + + private List GetPossibleSymbolIndex(uint hash) + { + List symbolIndexes = []; + + int i = _hashBuckets[(int)(hash % _hashBuckets.Length)] - _symbolOffset; + while (true) + { + _reader.BaseStream.Seek((long)_chainsOffset + i * sizeof(int), SeekOrigin.Begin); + int chainVal = _reader.ReadInt32(); + + // LSB denotes end of chain. Compare hash with chain value ignoring LSB. + if ((chainVal & 0xfffffffe) == (hash & 0xfffffffe)) + { + symbolIndexes.Add(i + _symbolOffset); + } + // If LSB is set, this is the last element in the chain. + if ((chainVal & 0x1) == 0x1) + { + break; + } + i++; + } + return symbolIndexes; + } + + private string GetStringAtIndex(uint index) + { + if (index > _stringTableSize) + { + throw new InvalidOperationException("String table index out of bounds."); + } + + _reader.BaseStream.Seek(_stringTableOffset + index, SeekOrigin.Begin); + return _reader.ReadZString(); + } + + private static uint GnuHash(string symbolName) + { + uint h = 5381; + foreach (char c in symbolName) + { + h = (h << 5) + h + c; + } + return h; + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _reader.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/MachODecoder.cs b/src/native/managed/cdacreader/src/Decoder/MachODecoder.cs new file mode 100644 index 00000000000000..19abd63d0d8475 --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/MachODecoder.cs @@ -0,0 +1,177 @@ +// 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.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +/// +/// Only supports 64-bit MachO binaries. +/// +internal sealed class MachODecoder : IDisposable +{ + private readonly BinaryReader _reader; + private readonly ulong _baseAddress; + + private bool _isLittleEndian; + private Mach64SymTabCommand _symTabCommand; + private Mach64DySymTabCommand _dySymTabCommand; + private List _segments = []; + private ulong _loadBias; + + private List _symbols = []; + private long _strTabOffset; + + private bool _disposedValue; + + public bool IsValid { get; init; } + + /// + /// Create MachODecoder with stream beginning at the base address of the module. + /// + public MachODecoder(Stream stream, ulong baseAddress) + { + _reader = new(stream, Encoding.UTF8); + _baseAddress = baseAddress; + + IsValid = Initialize(); + } + + private bool Initialize() + { + _reader.BaseStream.Seek(0, SeekOrigin.Begin); + Mach64Header header = new(_reader); + + // if the magic number is not correct, this is not a MachO file + if (header.magic is not Mach64Header.LE_MAGIC and not Mach64Header.BE_MAGIC) + return false; + + _isLittleEndian = header.magic == Mach64Header.LE_MAGIC; + + // load commands + long commandOffset = _reader.BaseStream.Position; + for (int i = 0; i < header.nCmds; i++) + { + _reader.BaseStream.Seek(commandOffset, SeekOrigin.Begin); + Mach64LoadCommand command = new(_reader); + _reader.BaseStream.Seek(commandOffset, SeekOrigin.Begin); + switch (command.cmd) + { + case (uint)Mach64LoadCommand.Type.LC_SYMTAB: + { + _symTabCommand = new(_reader); + Console.WriteLine($"LC_SYMTAB: {command.cmdSize} {commandOffset} {_symTabCommand.nsyms} {_symTabCommand.strsize}"); + break; + } + case (uint)Mach64LoadCommand.Type.LC_DYNSYM: + { + _dySymTabCommand = new(_reader); + Console.WriteLine($"LC_DYNSYM: {command.cmdSize} {commandOffset} {_dySymTabCommand.iextdefsym} {_dySymTabCommand.nextdefsym}"); + break; + } + case (uint)Mach64LoadCommand.Type.LC_SEGMENT_64: + { + Mach64SegmentCommand segment = new(_reader); + if (segment.segname == Mach64SegmentCommand.SEG_TEXT) + { + _loadBias = segment.vmaddr; + } + _segments.Add(segment); + Console.WriteLine($"Segment: {segment.segname} {segment.vmaddr} {segment.vmsize} {segment.fileoff} {segment.filesize}"); + break; + } + } + + commandOffset += command.cmdSize; + } + + // read symbol table + long symbolStreamOffset = GetStreamOffsetFromFileOffset(_symTabCommand.symoff); + _reader.BaseStream.Seek(symbolStreamOffset, SeekOrigin.Begin); + for (int i = 0; i < _symTabCommand.nsyms; i++) + { + NList64 symbol = new(_reader); + _symbols.Add(symbol); + Console.WriteLine($"Symbol: {symbol.n_strx} {symbol.n_type} {symbol.n_sect} {symbol.n_desc} {symbol.n_value}"); + } + _strTabOffset = GetStreamOffsetFromFileOffset(_symTabCommand.stroff); + + return true; + } + + private long GetStreamOffsetFromFileOffset(uint offset) + { + foreach (Mach64SegmentCommand segment in _segments) + { + if (offset >= segment.fileoff && offset < segment.fileoff + segment.filesize) + { + return (long)(offset - segment.fileoff + segment.vmaddr - _loadBias); + } + } + return (long)(offset - _loadBias); + } + + private string GetSymbolName(uint index) + { + if (index >= _symbols.Count) + return string.Empty; + NList64 symbol = _symbols[(int)index]; + _reader.BaseStream.Seek(_strTabOffset + symbol.n_strx, SeekOrigin.Begin); + + // read 0-terminated string + string symbolName = _reader.ReadZString(); + + // trim leading underscore to match Linux externs + if (symbolName.Length > 0 && symbolName[0] == '_') + symbolName = symbolName.Substring(1); + return symbolName; + } + + public bool TryLookupSymbol(uint start, uint nSymbols, string symbolName, out ulong address) + { + address = 0; + for (uint i = 0; i < nSymbols; i++) + { + string name = GetSymbolName(start + i); + Console.WriteLine(name); + if (name == symbolName) + { + NList64 symbol = _symbols[(int)(start + i)]; + address = symbol.n_value - _loadBias; + return true; + } + } + if (_symbols.Count == 0) + Console.WriteLine(symbolName); + return false; + } + + public bool TryGetRelativeSymbolAddress(string symbol, out ulong address) + { + Console.WriteLine($"{_dySymTabCommand.iextdefsym} {_dySymTabCommand.nextdefsym}"); + + return TryLookupSymbol(_dySymTabCommand.iextdefsym, _dySymTabCommand.nextdefsym, symbol, out address); + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _reader.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/MachOTypes.cs b/src/native/managed/cdacreader/src/Decoder/MachOTypes.cs new file mode 100644 index 00000000000000..01dfd355421bab --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/MachOTypes.cs @@ -0,0 +1,173 @@ +// 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.IO; +using System.Numerics; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal struct Mach64Header +{ + public const uint LE_MAGIC = 0xfeedfacf; // 64-bit Mach-O file + public const uint BE_MAGIC = 0xcffaedfe; // 64-bit reversed Mach-O file + + public uint magic; // mach magic number identifier + public uint cpuType; // cpu specifier + public uint cpuSubType; // machine specifier + public uint fileType; // type of file + public uint nCmds; // number of load commands + public uint sizeOfCmds; // the size of all the load commands + public uint flags; // flags + public uint reserved; // reserved + + public Mach64Header(BinaryReader reader) + { + magic = reader.ReadUInt32(); + cpuType = reader.ReadUInt32(); + cpuSubType = reader.ReadUInt32(); + fileType = reader.ReadUInt32(); + nCmds = reader.ReadUInt32(); + sizeOfCmds = reader.ReadUInt32(); + flags = reader.ReadUInt32(); + reserved = reader.ReadUInt32(); + } +} + +internal struct Mach64LoadCommand +{ + public enum Type + { + LC_SYMTAB = 0x2, + LC_DYNSYM = 0xb, + LC_SEGMENT_64 = 0x19, + } + + public uint cmd; // type of load command + public uint cmdSize; // total size of command in bytes + + public Mach64LoadCommand(BinaryReader reader) + { + cmd = reader.ReadUInt32(); + cmdSize = reader.ReadUInt32(); + } +} + +internal struct Mach64SymTabCommand +{ + public uint cmd; // type of load command + public uint cmdSize; // total size of command in bytes + public uint symoff; // symbol table offset + public uint nsyms; // number of symbol table entries + public uint stroff; // string table offset + public uint strsize; // string table size + + public Mach64SymTabCommand(BinaryReader reader) + { + cmd = reader.ReadUInt32(); + cmdSize = reader.ReadUInt32(); + symoff = reader.ReadUInt32(); + nsyms = reader.ReadUInt32(); + stroff = reader.ReadUInt32(); + strsize = reader.ReadUInt32(); + } +} + +internal struct Mach64DySymTabCommand +{ + public uint cmd; // LC_DYSYMTAB + public uint cmdsize; // sizeof(struct dysymtab_command) + public uint ilocalsym; // index to local symbols + public uint nlocalsym; // number of local symbols + + public uint iextdefsym; // index to externally defined symbols + public uint nextdefsym; // number of externally defined symbols + + public uint iundefsym; // index to undefined symbols + public uint nundefsym; // number of undefined symbols + public uint tocoff; // file offset to table of contents + public uint ntoc; // number of entries in table of contents + public uint modtaboff; // file offset to module table + public uint nmodtab; // number of module table entries + public uint extrefsymoff; // offset to referenced symbol table + public uint nextrefsyms; // number of referenced symbol table entries + public uint indirectsymoff; // file offset to the indirect symbol table + public uint nindirectsyms; // number of indirect symbol table entries + public uint extreloff; // offset to external relocation entries + public uint nextrel; // number of external relocation entries + public uint locreloff; // offset to local relocation entries + public uint nlocrel; // number of local relocation entries + + public Mach64DySymTabCommand(BinaryReader reader) + { + cmd = reader.ReadUInt32(); + cmdsize = reader.ReadUInt32(); + ilocalsym = reader.ReadUInt32(); + nlocalsym = reader.ReadUInt32(); + iextdefsym = reader.ReadUInt32(); + nextdefsym = reader.ReadUInt32(); + iundefsym = reader.ReadUInt32(); + nundefsym = reader.ReadUInt32(); + tocoff = reader.ReadUInt32(); + ntoc = reader.ReadUInt32(); + modtaboff = reader.ReadUInt32(); + nmodtab = reader.ReadUInt32(); + extrefsymoff = reader.ReadUInt32(); + nextrefsyms = reader.ReadUInt32(); + indirectsymoff = reader.ReadUInt32(); + nindirectsyms = reader.ReadUInt32(); + extreloff = reader.ReadUInt32(); + nextrel = reader.ReadUInt32(); + locreloff = reader.ReadUInt32(); + nlocrel = reader.ReadUInt32(); + } +} + +internal struct Mach64SegmentCommand +{ + public const string SEG_TEXT = "__TEXT"; + public uint cmd; // LC_SEGMENT_64 + public uint cmdsize; // includes sizeof section_64 structs + public string segname; // segment name + public ulong vmaddr; // memory address of this segment + public ulong vmsize; // memory size of this segment + public ulong fileoff; // file offset of this segment + public ulong filesize; // amount to map from the file + public uint maxprot; // maximum VM protection + public uint initprot; // initial VM protection + public uint nsects; // number of sections in segment + public uint flags; // flags + + public Mach64SegmentCommand(BinaryReader reader) + { + cmd = reader.ReadUInt32(); + cmdsize = reader.ReadUInt32(); + segname = new string(reader.ReadChars(16)).TrimEnd('\0'); + vmaddr = reader.ReadUInt64(); + vmsize = reader.ReadUInt64(); + fileoff = reader.ReadUInt64(); + filesize = reader.ReadUInt64(); + maxprot = reader.ReadUInt32(); + initprot = reader.ReadUInt32(); + nsects = reader.ReadUInt32(); + flags = reader.ReadUInt32(); + } +} + +internal struct NList64 +{ + public uint n_strx; // index into the string table + public byte n_type; // type flag, see below + public byte n_sect; // section number or NO_SECT + public ushort n_desc; // see + public ulong n_value; // value of this symbol (or stab offset) + + public NList64(BinaryReader reader) + { + n_strx = reader.ReadUInt32(); + n_type = reader.ReadByte(); + n_sect = reader.ReadByte(); + n_desc = reader.ReadUInt16(); + n_value = reader.ReadUInt64(); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/PEDecoder.cs b/src/native/managed/cdacreader/src/Decoder/PEDecoder.cs new file mode 100644 index 00000000000000..073ca4a757d27f --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/PEDecoder.cs @@ -0,0 +1,129 @@ +// 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.IO; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; +internal sealed class PEDecoder : IDisposable +{ + private readonly Stream _stream; + private uint _peSigOffset; + private ushort _optHeaderMagic; + private IMAGE_EXPORT_DIRECTORY _exportDir; + private bool _disposedValue; + + public bool IsValid { get; init; } + + /// + /// Create PEDecoder with stream beginning at the base address of the module. + /// + public PEDecoder(Stream stream) + { + _stream = stream; + + IsValid = Initialize(); + } + + private bool Initialize() + { + using BinaryReader reader = new(_stream, Encoding.UTF8, leaveOpen: true); + + ushort dosMagic = reader.ReadUInt16(); + if (dosMagic != 0x5A4D) // "MZ" + return false; + + // PE Header offset is at 0x3C in DOS header + reader.BaseStream.Seek(0x3C, SeekOrigin.Begin); + _peSigOffset = reader.ReadUInt32(); + + // Read PE signature + reader.BaseStream.Seek(_peSigOffset, SeekOrigin.Begin); + uint peSig = reader.ReadUInt32(); + if (peSig != 0x00004550) // "PE00" + return false; + + // Seek to beginning of opt header and read magic + reader.BaseStream.Seek(_peSigOffset + 0x18, SeekOrigin.Begin); + _optHeaderMagic = reader.ReadUInt16(); + + // Seek back to beginning of opt header and parse + reader.BaseStream.Seek(_peSigOffset + 0x18, SeekOrigin.Begin); + uint rva; + switch (_optHeaderMagic) + { + case 0x10B: // PE32 + IMAGE_OPTIONAL_HEADER32 optHeader32 = new(reader); + rva = optHeader32.DataDirectory[0].VirtualAddress; + break; + case 0x20B: // PE32+ + IMAGE_OPTIONAL_HEADER64 optHeader64 = new(reader); + rva = optHeader64.DataDirectory[0].VirtualAddress; + break; + // unknown type, invalid + default: + return false; + } + + // Seek to export directory and parse + reader.BaseStream.Seek(rva, SeekOrigin.Begin); + _exportDir = new IMAGE_EXPORT_DIRECTORY(reader); + + return true; + } + + public bool TryGetRelativeSymbolAddress(string symbol, out ulong address) + { + address = 0; + if (!IsValid) + return false; + + using BinaryReader reader = new(_stream, Encoding.UTF8, leaveOpen: true); + + for (int nameIndex = 0; nameIndex < _exportDir.NumberOfNames; nameIndex++) + { + // Seek to address of names + reader.BaseStream.Seek(_exportDir.AddressOfNames + sizeof(uint) * nameIndex, SeekOrigin.Begin); + uint namePointerRVA = reader.ReadUInt32(); + + // Seek to name RVA and read name + reader.BaseStream.Seek(namePointerRVA, SeekOrigin.Begin); + string name = reader.ReadZString(); + if (name == symbol) + { + // If the name matches, we should be able to get the ordinal using the ordinal + // table with the same index. This table contains 16-bit values. + reader.BaseStream.Seek(_exportDir.AddressOfNameOrdinals + sizeof(ushort) * nameIndex, SeekOrigin.Begin); + ushort ordinalForNamedExport = reader.ReadUInt16(); + + // Use the ordinal to index into the address table. This table contains 32-bit RVA values. + reader.BaseStream.Seek(_exportDir.AddressOfFunctions + sizeof(uint) * ordinalForNamedExport, SeekOrigin.Begin); + uint symbolRVA = reader.ReadUInt32(); + + address = symbolRVA; + return true; + } + } + + return false; + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _stream.Close(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + } +} diff --git a/src/native/managed/cdacreader/src/Decoder/PETypes.cs b/src/native/managed/cdacreader/src/Decoder/PETypes.cs new file mode 100644 index 00000000000000..799e8c89d53984 --- /dev/null +++ b/src/native/managed/cdacreader/src/Decoder/PETypes.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace Microsoft.Diagnostics.DataContractReader.Decoder; + +internal struct IMAGE_OPTIONAL_HEADER32 +{ + public ushort Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public uint BaseOfData; + public uint ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public ushort Subsystem; + public ushort DllCharacteristics; + public uint SizeOfStackReserve; + public uint SizeOfStackCommit; + public uint SizeOfHeapReserve; + public uint SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + public IMAGE_DATA_DIRECTORY[] DataDirectory; + + public IMAGE_OPTIONAL_HEADER32(BinaryReader reader) + { + Magic = reader.ReadUInt16(); + MajorLinkerVersion = reader.ReadByte(); + MinorLinkerVersion = reader.ReadByte(); + SizeOfCode = reader.ReadUInt32(); + SizeOfInitializedData = reader.ReadUInt32(); + SizeOfUninitializedData = reader.ReadUInt32(); + AddressOfEntryPoint = reader.ReadUInt32(); + BaseOfCode = reader.ReadUInt32(); + BaseOfData = reader.ReadUInt32(); + ImageBase = reader.ReadUInt32(); + SectionAlignment = reader.ReadUInt32(); + FileAlignment = reader.ReadUInt32(); + MajorOperatingSystemVersion = reader.ReadUInt16(); + MinorOperatingSystemVersion = reader.ReadUInt16(); + MajorImageVersion = reader.ReadUInt16(); + MinorImageVersion = reader.ReadUInt16(); + MajorSubsystemVersion = reader.ReadUInt16(); + MinorSubsystemVersion = reader.ReadUInt16(); + Win32VersionValue = reader.ReadUInt32(); + SizeOfImage = reader.ReadUInt32(); + SizeOfHeaders = reader.ReadUInt32(); + CheckSum = reader.ReadUInt32(); + Subsystem = reader.ReadUInt16(); + DllCharacteristics = reader.ReadUInt16(); + SizeOfStackReserve = reader.ReadUInt32(); + SizeOfStackCommit = reader.ReadUInt32(); + SizeOfHeapReserve = reader.ReadUInt32(); + SizeOfHeapCommit = reader.ReadUInt32(); + LoaderFlags = reader.ReadUInt32(); + NumberOfRvaAndSizes = reader.ReadUInt32(); + + DataDirectory = new IMAGE_DATA_DIRECTORY[NumberOfRvaAndSizes]; + for (int i = 0; i < NumberOfRvaAndSizes; i++) + DataDirectory[i] = new IMAGE_DATA_DIRECTORY(reader); + } +} + +internal struct IMAGE_OPTIONAL_HEADER64 +{ + public ushort Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public ulong ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public ushort Subsystem; + public ushort DllCharacteristics; + public ulong SizeOfStackReserve; + public ulong SizeOfStackCommit; + public ulong SizeOfHeapReserve; + public ulong SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + public IMAGE_DATA_DIRECTORY[] DataDirectory; + + public IMAGE_OPTIONAL_HEADER64(BinaryReader reader) + { + Magic = reader.ReadUInt16(); + MajorLinkerVersion = reader.ReadByte(); + MinorLinkerVersion = reader.ReadByte(); + SizeOfCode = reader.ReadUInt32(); + SizeOfInitializedData = reader.ReadUInt32(); + SizeOfUninitializedData = reader.ReadUInt32(); + AddressOfEntryPoint = reader.ReadUInt32(); + BaseOfCode = reader.ReadUInt32(); + ImageBase = reader.ReadUInt64(); + SectionAlignment = reader.ReadUInt32(); + FileAlignment = reader.ReadUInt32(); + MajorOperatingSystemVersion = reader.ReadUInt16(); + MinorOperatingSystemVersion = reader.ReadUInt16(); + MajorImageVersion = reader.ReadUInt16(); + MinorImageVersion = reader.ReadUInt16(); + MajorSubsystemVersion = reader.ReadUInt16(); + MinorSubsystemVersion = reader.ReadUInt16(); + Win32VersionValue = reader.ReadUInt32(); + SizeOfImage = reader.ReadUInt32(); + SizeOfHeaders = reader.ReadUInt32(); + CheckSum = reader.ReadUInt32(); + Subsystem = reader.ReadUInt16(); + DllCharacteristics = reader.ReadUInt16(); + SizeOfStackReserve = reader.ReadUInt64(); + SizeOfStackCommit = reader.ReadUInt64(); + SizeOfHeapReserve = reader.ReadUInt64(); + SizeOfHeapCommit = reader.ReadUInt64(); + LoaderFlags = reader.ReadUInt32(); + NumberOfRvaAndSizes = reader.ReadUInt32(); + + DataDirectory = new IMAGE_DATA_DIRECTORY[NumberOfRvaAndSizes]; + for (int i = 0; i < NumberOfRvaAndSizes; i++) + DataDirectory[i] = new IMAGE_DATA_DIRECTORY(reader); + } +} + +internal struct IMAGE_DATA_DIRECTORY +{ + public uint VirtualAddress; + public uint Size; + + public IMAGE_DATA_DIRECTORY(BinaryReader reader) + { + VirtualAddress = reader.ReadUInt32(); + Size = reader.ReadUInt32(); + } +} + +internal struct IMAGE_EXPORT_DIRECTORY +{ + public uint Characteristics; + public uint TimeDateStamp; + public ushort MajorVersion; + public ushort MinorVersion; + public uint Name; + public uint Base; + public uint NumberOfFunctions; + public uint NumberOfNames; + public uint AddressOfFunctions; + public uint AddressOfNames; + public uint AddressOfNameOrdinals; + + public IMAGE_EXPORT_DIRECTORY(BinaryReader reader) + { + Characteristics = reader.ReadUInt32(); + TimeDateStamp = reader.ReadUInt32(); + MajorVersion = reader.ReadUInt16(); + MinorVersion = reader.ReadUInt16(); + Name = reader.ReadUInt32(); + Base = reader.ReadUInt32(); + NumberOfFunctions = reader.ReadUInt32(); + NumberOfNames = reader.ReadUInt32(); + AddressOfFunctions = reader.ReadUInt32(); + AddressOfNames = reader.ReadUInt32(); + AddressOfNameOrdinals = reader.ReadUInt32(); + } +} diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index 015247efc0a9c4..5495de5478305f 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Decoder; +using Microsoft.Diagnostics.DataContractReader.Legacy; namespace Microsoft.Diagnostics.DataContractReader; @@ -11,6 +14,15 @@ internal static class Entrypoints { private const string CDAC = "cdac_reader_"; + static Entrypoints() + { + StreamWriter logFileWriter = new StreamWriter("C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt", append: true); + Console.SetOut(logFileWriter); + Console.WriteLine("Creating cDAC entrypoints"); + logFileWriter.AutoFlush = true; + logFileWriter.Flush(); + } + [UnmanagedCallersOnly(EntryPoint = $"{CDAC}init")] private static unsafe int Init( ulong descriptor, @@ -83,4 +95,103 @@ private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr *obj = ptr; return 0; } + + [UnmanagedCallersOnly(EntryPoint = $"{CDAC}create_instance")] + private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface) + { + if (pLegacyTarget == IntPtr.Zero || iface == null) + return HResults.E_INVALIDARG; + + *iface = null; + + ComWrappers cw = new StrategyBasedComWrappers(); + object obj = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + ICLRDataTarget dataTarget = obj as ICLRDataTarget ?? throw new ArgumentException("Invalid ICLRDataTarget"); + ICLRRuntimeLocator? locator = obj as ICLRRuntimeLocator; + + ulong baseAddress; + if (locator is ICLRRuntimeLocator loc) + { + locator.GetRuntimeBase(&baseAddress); + } + else + { + dataTarget.GetImageBase("coreclr.dll", &baseAddress); + } + + + using PEDecoder peDecoder = new(new DataTargetStream(dataTarget, baseAddress)); + using ELFDecoder elfDecoder = new(new DataTargetStream(dataTarget, baseAddress), baseAddress); + using MachODecoder machODecoder = new(new DataTargetStream(dataTarget, baseAddress), baseAddress); + + Console.WriteLine($"PE: {peDecoder.IsValid} ELF: {elfDecoder.IsValid} MachO: {machODecoder.IsValid}"); + + Target.CorDebugPlatform targetPlatform; + ulong contractDescriptor; + if (peDecoder.IsValid) + { + targetPlatform = Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64; + if (!peDecoder.TryGetRelativeSymbolAddress("DotNetRuntimeContractDescriptor", out contractDescriptor)) + { + return -1; + } + } + else if (elfDecoder.IsValid) + { + targetPlatform = Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64; + if (!elfDecoder.TryGetRelativeSymbolAddress("DotNetRuntimeContractDescriptor", out contractDescriptor)) + { + return -1; + } + } + else if (machODecoder.IsValid) + { + targetPlatform = Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64; + if (!machODecoder.TryGetRelativeSymbolAddress("DotNetRuntimeContractDescriptor", out contractDescriptor)) + { + return -1; + } + } + else + { + return -1; + } + + if (!ContractDescriptorTarget.TryCreate( + baseAddress + contractDescriptor, + (address, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + uint bytesRead; + return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead); + } + }, + (threadId, contextFlags, contextSize, bufferToFill) => + { + fixed (byte* bufferPtr = bufferToFill) + { + return dataTarget.GetThreadContext(threadId, contextFlags, contextSize, bufferPtr); + } + }, + (out platform) => + { + platform = (int)targetPlatform; + return 0; + }, + out ContractDescriptorTarget? target)) + { + return -1; + } + + Legacy.SOSDacImpl impl = new(target, null); + nint ccw = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + Marshal.QueryInterface(ccw, *pIID, out nint ptrToIface); + *iface = (void*)ptrToIface; + + // Decrement reference count on ccw because QI increments it? + Marshal.Release(ccw); + + return 0; + } } diff --git a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs index 5b33d833beb516..cb9a1a190f1bac 100644 --- a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs +++ b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs @@ -17,3 +17,49 @@ internal unsafe partial interface ICLRDataEnumMemoryRegions [PreserveSig] int EnumMemoryRegions(/*ICLRDataEnumMemoryRegionsCallback*/ void* callback, uint miniDumpFlags, /*CLRDataEnumMemoryFlags*/ int clrFlags); } + +[GeneratedComInterface] +[Guid("3e11ccee-d08b-43e5-af01-32717a64da03")] +internal unsafe partial interface ICLRDataTarget +{ + [PreserveSig] + int GetMachineType(uint* machineType); + + [PreserveSig] + int GetPointerSize(uint* pointerSize); + + [PreserveSig] + int GetImageBase([MarshalAs(UnmanagedType.LPWStr)] string imagePath, ulong* baseAddress); + + [PreserveSig] + int ReadVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesRead); + + [PreserveSig] + int WriteVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesWritten); + + [PreserveSig] + int GetTLSValue(uint threadID, uint index, ulong* value); + + [PreserveSig] + int SetTLSValue(uint threadID, uint index, ulong value); + + [PreserveSig] + int GetCurrentThreadID(uint* threadID); + + [PreserveSig] + int GetThreadContext(uint threadID, uint contextFlags, uint contextSize, byte* context); + + [PreserveSig] + int SetThreadContext(uint threadID, uint contextSize, byte* context); + + [PreserveSig] + int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer); +} + +[GeneratedComInterface] +[Guid("b760bf44-9377-4597-8be7-58083bdc5146")] +internal unsafe partial interface ICLRRuntimeLocator +{ + [PreserveSig] + int GetRuntimeBase(ulong* baseAddress); +}