/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs
1 497 строк
58 KB
Cyrus Najmabadi
use collection expression
20 дек 2024, 21:35
20 дек 2024, 21:35
29cd685
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml; namespace Roslyn.Test.PdbUtilities { public static class Token2SourceLineExporter { // NOTE: this type implementation is essentially an extraction from PdbReader // located under ndp\clr\src\ToolBox\CCI2\PdbReader folder private class PdbSource { internal readonly string name; internal Guid doctype; internal Guid language; internal Guid vendor; internal PdbSource(string name, Guid doctype, Guid language, Guid vendor) { this.name = name; this.doctype = doctype; this.language = language; this.vendor = vendor; } } private class PdbTokenLine { internal readonly uint token; internal readonly uint file_id; internal readonly uint line; internal readonly uint column; internal readonly uint endLine; internal readonly uint endColumn; internal PdbSource sourceFile; internal PdbTokenLine/*?*/ nextLine; internal PdbTokenLine(uint token, uint file_id, uint line, uint column, uint endLine, uint endColumn) { this.token = token; this.file_id = file_id; this.line = line; this.column = column; this.endLine = endLine; this.endColumn = endColumn; } } private class BitAccess { internal BitAccess(int capacity) { _buffer = new byte[capacity]; } internal byte[] Buffer { get { return _buffer; } } private byte[] _buffer; internal void FillBuffer(Stream stream, int capacity) { MinCapacity(capacity); stream.Read(_buffer, 0, capacity); _offset = 0; } internal void Append(Stream stream, int count) { int newCapacity = _offset + count; if (_buffer.Length < newCapacity) { byte[] newBuffer = new byte[newCapacity]; Array.Copy(_buffer, newBuffer, _buffer.Length); _buffer = newBuffer; } stream.Read(_buffer, _offset, count); _offset += count; } internal int Position { get { return _offset; } set { _offset = value; } } private int _offset; internal void MinCapacity(int capacity) { if (_buffer.Length < capacity) { _buffer = new byte[capacity]; } _offset = 0; } internal void Align(int alignment) { while ((_offset % alignment) != 0) { _offset++; } } internal void ReadInt16(out short value) { unchecked { value = (short)((_buffer[_offset + 0] & 0xFF) | (_buffer[_offset + 1] << 8)); } _offset += 2; } internal void ReadInt8(out sbyte value) { unchecked { value = (sbyte)_buffer[_offset]; } _offset += 1; } internal void ReadInt32(out int value) { unchecked { value = (int)((_buffer[_offset + 0] & 0xFF) | (_buffer[_offset + 1] << 8) | (_buffer[_offset + 2] << 16) | (_buffer[_offset + 3] << 24)); } _offset += 4; } internal void ReadInt64(out long value) { unchecked { value = (long)(((ulong)_buffer[_offset + 0] & 0xFF) | ((ulong)_buffer[_offset + 1] << 8) | ((ulong)_buffer[_offset + 2] << 16) | ((ulong)_buffer[_offset + 3] << 24) | ((ulong)_buffer[_offset + 4] << 32) | ((ulong)_buffer[_offset + 5] << 40) | ((ulong)_buffer[_offset + 6] << 48) | ((ulong)_buffer[_offset + 7] << 56)); } _offset += 8; } internal void ReadUInt16(out ushort value) { unchecked { value = (ushort)((_buffer[_offset + 0] & 0xFF) | (_buffer[_offset + 1] << 8)); } _offset += 2; } internal void ReadUInt8(out byte value) { unchecked { value = (byte)((_buffer[_offset + 0] & 0xFF)); } _offset += 1; } internal void ReadUInt32(out uint value) { unchecked { value = (uint)((_buffer[_offset + 0] & 0xFF) | (_buffer[_offset + 1] << 8) | (_buffer[_offset + 2] << 16) | (_buffer[_offset + 3] << 24)); } _offset += 4; } internal void ReadUInt64(out ulong value) { unchecked { value = (ulong)(((ulong)_buffer[_offset + 0] & 0xFF) | ((ulong)_buffer[_offset + 1] << 8) | ((ulong)_buffer[_offset + 2] << 16) | ((ulong)_buffer[_offset + 3] << 24) | ((ulong)_buffer[_offset + 4] << 32) | ((ulong)_buffer[_offset + 5] << 40) | ((ulong)_buffer[_offset + 6] << 48) | ((ulong)_buffer[_offset + 7] << 56)); } _offset += 8; } internal void ReadInt32(int[] values) { for (int i = 0; i < values.Length; i++) { ReadInt32(out values[i]); } } internal void ReadUInt32(uint[] values) { for (int i = 0; i < values.Length; i++) { ReadUInt32(out values[i]); } } internal void ReadBytes(byte[] bytes) { for (int i = 0; i < bytes.Length; i++) { bytes[i] = _buffer[_offset++]; } } internal float ReadFloat() { float result = BitConverter.ToSingle(_buffer, _offset); _offset += 4; return result; } internal double ReadDouble() { double result = BitConverter.ToDouble(_buffer, _offset); _offset += 8; return result; } internal decimal ReadDecimal() { int[] bits = new int[4]; this.ReadInt32(bits); return new decimal(bits); } internal void ReadBString(out string value) { this.ReadUInt16(out var len); value = Encoding.UTF8.GetString(_buffer, _offset, len); _offset += len; } internal void ReadCString(out string value) { int len = 0; while (_offset + len < _buffer.Length && _buffer[_offset + len] != 0) { len++; } value = Encoding.UTF8.GetString(_buffer, _offset, len); _offset += len + 1; } internal void SkipCString(out string value) { int len = 0; while (_offset + len < _buffer.Length && _buffer[_offset + len] != 0) { len++; } _offset += len + 1; value = null; } internal void ReadGuid(out Guid guid) { ReadUInt32(out var a); ReadUInt16(out var b); ReadUInt16(out var c); ReadUInt8(out var d); ReadUInt8(out var e); ReadUInt8(out var f); ReadUInt8(out var g); ReadUInt8(out var h); ReadUInt8(out var i); ReadUInt8(out var j); ReadUInt8(out var k); guid = unchecked(new Guid((int)a, (short)b, (short)c, d, e, f, g, h, i, j, k)); } internal string ReadString() { int len = 0; while (_offset + len < _buffer.Length && _buffer[_offset + len] != 0) { len += 2; } string result = Encoding.Unicode.GetString(_buffer, _offset, len); _offset += len + 2; return result; } } private readonly struct BitSet { internal BitSet(BitAccess bits) { bits.ReadInt32(out _size); // 0..3 : Number of words _words = new uint[_size]; bits.ReadUInt32(_words); } internal bool IsSet(int index) { int word = index / 32; if (word >= _size) return false; return ((_words[word] & GetBit(index)) != 0); } private static uint GetBit(int index) { return ((uint)1 << (index % 32)); } internal bool IsEmpty { get { return _size == 0; } } private readonly int _size; private readonly uint[] _words; } private class IntHashTable { #pragma warning disable format // https://github.com/dotnet/roslyn/issues/70711 tracks removing this suppression. private static readonly int[] s_primes = [ 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369]; #pragma warning restore format private static int GetPrime(int minSize) { if (minSize < 0) { throw new ArgumentException("Arg_HTCapacityOverflow"); } for (int i = 0; i < s_primes.Length; i++) { int size = s_primes[i]; if (size >= minSize) { return size; } } throw new ArgumentException("Arg_HTCapacityOverflow"); } // Deleted entries have their key set to buckets // The hash table data. // This cannot be serialized private struct @bucket { internal int key; internal int hash_coll; // Store hash code; sign bit means there was a collision. internal Object val; } private bucket[] _buckets; // The total number of entries in the hash table. private int _count; // The total number of collision bits set in the hashtable private int _occupancy; private int _loadsize; private readonly int _loadFactorPerc; // 100 = 1.0 private int _version; // Constructs a new hashtable. The hashtable is created with an initial // capacity of zero and a load factor of 1.0. //| <include path='docs/doc[@for="IntHashTable.IntHashTable"]/*' /> internal IntHashTable() : this(0, 100) { } internal IntHashTable(int capacity, int loadFactorPerc) { if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity), "ArgumentOutOfRange_NeedNonNegNum"); if (loadFactorPerc is not (>= 10 and <= 100)) throw new ArgumentOutOfRangeException(nameof(loadFactorPerc), "ArgumentOutOfRange_IntHashTableLoadFactor"); // Based on perf work, .72 is the optimal load factor for this table. _loadFactorPerc = (loadFactorPerc * 72) / 100; int hashsize = GetPrime((int)(capacity / _loadFactorPerc)); _buckets = new bucket[hashsize]; _loadsize = (int)(_loadFactorPerc * hashsize) / 100; if (_loadsize >= hashsize) _loadsize = hashsize - 1; } private static uint InitHash(int key, int hashsize, out uint seed, out uint incr) { // Hashcode must be positive. Also, we must not use the sign bit, since // that is used for the collision bit. uint hashcode = (uint)key & 0x7FFFFFFF; seed = (uint)hashcode; // Restriction: incr MUST be between 1 and hashsize - 1, inclusive for // the modular arithmetic to work correctly. This guarantees you'll // visit every bucket in the table exactly once within hashsize // iterations. Violate this and it'll cause obscure bugs forever. // If you change this calculation for h2(key), update putEntry too! incr = (uint)(1 + (((seed >> 5) + 1) % ((uint)hashsize - 1))); return hashcode; } internal void Add(int key, Object value) { Insert(key, value, true); } internal Object this[int key] { get { if (key < 0) { throw new ArgumentException("Argument_KeyLessThanZero"); } // Take a snapshot of buckets, in case another thread does a resize bucket[] lbuckets = _buckets; uint hashcode = InitHash(key, lbuckets.Length, out var seed, out var incr); int ntry = 0; bucket b; do { int bucketNumber = (int)(seed % (uint)lbuckets.Length); b = lbuckets[bucketNumber]; if (b.val == null) { return null; } if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && key == b.key) { return b.val; } seed += incr; } while (b.hash_coll < 0 && ++ntry < lbuckets.Length); return null; } //set { // Insert(key, value, false); //} } private void expand() { rehash(GetPrime(1 + _buckets.Length * 2)); } private void rehash() { rehash(_buckets.Length); } private void rehash(int newsize) { // reset occupancy _occupancy = 0; // Don't replace any internal state until we've finished adding to the // new bucket[]. This serves two purposes: // 1) Allow concurrent readers to see valid hashtable contents // at all times // 2) Protect against an OutOfMemoryException while allocating this // new bucket[]. bucket[] newBuckets = new bucket[newsize]; // rehash table into new buckets int nb; for (nb = 0; nb < _buckets.Length; nb++) { bucket oldb = _buckets[nb]; if (oldb.val != null) { putEntry(newBuckets, oldb.key, oldb.val, oldb.hash_coll & 0x7FFFFFFF); } } // New bucket[] is good to go - replace buckets and other internal state. _version++; _buckets = newBuckets; _loadsize = (int)(_loadFactorPerc * newsize) / 100; if (_loadsize >= newsize) { _loadsize = newsize - 1; } return; } private void Insert(int key, Object nvalue, bool add) { if (key < 0) { throw new ArgumentException("Argument_KeyLessThanZero"); } if (nvalue == null) { throw new ArgumentNullException(nameof(nvalue), "ArgumentNull_Value"); } if (_count >= _loadsize) { expand(); } else if (_occupancy > _loadsize && _count > 100) { rehash(); } // Assume we only have one thread writing concurrently. Modify // buckets to contain new data, as long as we insert in the right order. uint hashcode = InitHash(key, _buckets.Length, out var seed, out var incr); int ntry = 0; int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots // create by remove that have the collision bit set over using up new slots. do { int bucketNumber = (int)(seed % (uint)_buckets.Length); // Set emptySlot number to current bucket if it is the first available bucket that we have seen // that once contained an entry and also has had a collision. // We need to search this entire collision chain because we have to ensure that there are no // duplicate entries in the table. // Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry // OR // This bucket once contained an entry but there has never been a collision if (_buckets[bucketNumber].val == null) { // If we have found an available bucket that has never had a collision, but we've seen an available // bucket in the past that has the collision bit set, use the previous bucket instead if (emptySlotNumber != -1) { // Reuse slot bucketNumber = emptySlotNumber; } // We pretty much have to insert in this order. Don't set hash // code until the value & key are set appropriately. _buckets[bucketNumber].val = nvalue; _buckets[bucketNumber].key = key; _buckets[bucketNumber].hash_coll |= (int)hashcode; _count++; _version++; return; } // The current bucket is in use // OR // it is available, but has had the collision bit set and we have already found an available bucket if (((_buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) && key == _buckets[bucketNumber].key) { if (add) { throw new ArgumentException("Argument_AddingDuplicate__" + _buckets[bucketNumber].key); } _buckets[bucketNumber].val = nvalue; _version++; return; } // The current bucket is full, and we have therefore collided. We need to set the collision bit // UNLESS // we have remembered an available slot previously. if (emptySlotNumber == -1) {// We don't need to set the collision bit here since we already have an empty slot if (_buckets[bucketNumber].hash_coll >= 0) { _buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000); _occupancy++; } } seed += incr; } while (++ntry < _buckets.Length); // This code is here if and only if there were no buckets without a collision bit set in the entire table if (emptySlotNumber != -1) { // We pretty much have to insert in this order. Don't set hash // code until the value & key are set appropriately. _buckets[emptySlotNumber].val = nvalue; _buckets[emptySlotNumber].key = key; _buckets[emptySlotNumber].hash_coll |= (int)hashcode; _count++; _version++; return; } // If you see this assert, make sure load factor & count are reasonable. // Then verify that our double hash function (h2, described at top of file) // meets the requirements described above. You should never see this assert. throw new InvalidOperationException("InvalidOperation_HashInsertFailed"); } private void putEntry(bucket[] newBuckets, int key, Object nvalue, int hashcode) { uint seed = (uint)hashcode; uint incr = (uint)(1 + (((seed >> 5) + 1) % ((uint)newBuckets.Length - 1))); do { int bucketNumber = (int)(seed % (uint)newBuckets.Length); if ((newBuckets[bucketNumber].val == null)) { newBuckets[bucketNumber].val = nvalue; newBuckets[bucketNumber].key = key; newBuckets[bucketNumber].hash_coll |= hashcode; return; } if (newBuckets[bucketNumber].hash_coll >= 0) { newBuckets[bucketNumber].hash_coll |= unchecked((int)0x80000000); _occupancy++; } seed += incr; } while (true); } } private readonly struct DbiSecCon { internal DbiSecCon(BitAccess bits) { bits.ReadInt16(out section); bits.ReadInt16(out pad1); bits.ReadInt32(out offset); bits.ReadInt32(out size); bits.ReadUInt32(out flags); bits.ReadInt16(out module); bits.ReadInt16(out pad2); bits.ReadUInt32(out dataCrc); bits.ReadUInt32(out relocCrc); } internal readonly short section; // 0..1 internal readonly short pad1; // 2..3 internal readonly int offset; // 4..7 internal readonly int size; // 8..11 internal readonly uint flags; // 12..15 internal readonly short module; // 16..17 internal readonly short pad2; // 18..19 internal readonly uint dataCrc; // 20..23 internal readonly uint relocCrc; // 24..27 } private class DbiModuleInfo { internal DbiModuleInfo(BitAccess bits, bool readStrings) { bits.ReadInt32(out opened); new DbiSecCon(bits); bits.ReadUInt16(out flags); bits.ReadInt16(out stream); bits.ReadInt32(out cbSyms); bits.ReadInt32(out cbOldLines); bits.ReadInt32(out cbLines); bits.ReadInt16(out files); bits.ReadInt16(out pad1); bits.ReadUInt32(out offsets); bits.ReadInt32(out niSource); bits.ReadInt32(out niCompiler); if (readStrings) { bits.ReadCString(out moduleName); bits.ReadCString(out objectName); } else { bits.SkipCString(out moduleName); bits.SkipCString(out objectName); } bits.Align(4); } internal readonly int opened; // 0..3 internal readonly ushort flags; // 32..33 internal readonly short stream; // 34..35 internal readonly int cbSyms; // 36..39 internal readonly int cbOldLines; // 40..43 internal readonly int cbLines; // 44..57 internal readonly short files; // 48..49 internal readonly short pad1; // 50..51 internal readonly uint offsets; internal readonly int niSource; internal readonly int niCompiler; internal readonly string moduleName; internal readonly string objectName; } private readonly struct DbiHeader { internal DbiHeader(BitAccess bits) { bits.ReadInt32(out sig); bits.ReadInt32(out ver); bits.ReadInt32(out age); bits.ReadInt16(out gssymStream); bits.ReadUInt16(out vers); bits.ReadInt16(out pssymStream); bits.ReadUInt16(out pdbver); bits.ReadInt16(out symrecStream); bits.ReadUInt16(out pdbver2); bits.ReadInt32(out gpmodiSize); bits.ReadInt32(out secconSize); bits.ReadInt32(out secmapSize); bits.ReadInt32(out filinfSize); bits.ReadInt32(out tsmapSize); bits.ReadInt32(out mfcIndex); bits.ReadInt32(out dbghdrSize); bits.ReadInt32(out ecinfoSize); bits.ReadUInt16(out flags); bits.ReadUInt16(out machine); bits.ReadInt32(out reserved); } internal readonly int sig; // 0..3 internal readonly int ver; // 4..7 internal readonly int age; // 8..11 internal readonly short gssymStream; // 12..13 internal readonly ushort vers; // 14..15 internal readonly short pssymStream; // 16..17 internal readonly ushort pdbver; // 18..19 internal readonly short symrecStream; // 20..21 internal readonly ushort pdbver2; // 22..23 internal readonly int gpmodiSize; // 24..27 internal readonly int secconSize; // 28..31 internal readonly int secmapSize; // 32..35 internal readonly int filinfSize; // 36..39 internal readonly int tsmapSize; // 40..43 internal readonly int mfcIndex; // 44..47 internal readonly int dbghdrSize; // 48..51 internal readonly int ecinfoSize; // 52..55 internal readonly ushort flags; // 56..57 internal readonly ushort machine; // 58..59 internal readonly int reserved; // 60..63 } private readonly struct DbiDbgHdr { internal DbiDbgHdr(BitAccess bits) { bits.ReadUInt16(out snFPO); bits.ReadUInt16(out snException); bits.ReadUInt16(out snFixup); bits.ReadUInt16(out snOmapToSrc); bits.ReadUInt16(out snOmapFromSrc); bits.ReadUInt16(out snSectionHdr); bits.ReadUInt16(out snTokenRidMap); bits.ReadUInt16(out snXdata); bits.ReadUInt16(out snPdata); bits.ReadUInt16(out snNewFPO); bits.ReadUInt16(out snSectionHdrOrig); } internal readonly ushort snFPO; // 0..1 internal readonly ushort snException; // 2..3 (deprecated) internal readonly ushort snFixup; // 4..5 internal readonly ushort snOmapToSrc; // 6..7 internal readonly ushort snOmapFromSrc; // 8..9 internal readonly ushort snSectionHdr; // 10..11 internal readonly ushort snTokenRidMap; // 12..13 internal readonly ushort snXdata; // 14..15 internal readonly ushort snPdata; // 16..17 internal readonly ushort snNewFPO; // 18..19 internal readonly ushort snSectionHdrOrig; // 20..21 } private class PdbFileHeader { internal PdbFileHeader(Stream reader, BitAccess bits) { bits.MinCapacity(56); reader.Seek(0, SeekOrigin.Begin); bits.FillBuffer(reader, 52); this.magic = new byte[32]; bits.ReadBytes(this.magic); // 0..31 bits.ReadInt32(out this.pageSize); // 32..35 bits.ReadInt32(out this.freePageMap); // 36..39 bits.ReadInt32(out this.pagesUsed); // 40..43 bits.ReadInt32(out this.directorySize); // 44..47 bits.ReadInt32(out this.zero); // 48..51 int directoryPages = ((((directorySize + pageSize - 1) / pageSize) * 4) + pageSize - 1) / pageSize; this.directoryRoot = new int[directoryPages]; bits.FillBuffer(reader, directoryPages * 4); bits.ReadInt32(this.directoryRoot); } internal readonly byte[] magic; internal readonly int pageSize; internal readonly int freePageMap; internal readonly int pagesUsed; internal readonly int directorySize; internal readonly int zero; internal readonly int[] directoryRoot; } private class PdbReader { internal PdbReader(Stream reader, int pageSize) { this.pageSize = pageSize; this.reader = reader; } internal void Seek(int page, int offset) { reader.Seek(page * pageSize + offset, SeekOrigin.Begin); } internal void Read(byte[] bytes, int offset, int count) { reader.Read(bytes, offset, count); } internal int PagesFromSize(int size) { return (size + pageSize - 1) / (pageSize); } internal readonly int pageSize; internal readonly Stream reader; } private class DataStream { internal DataStream() { } internal DataStream(int contentSize, BitAccess bits, int count) { this.contentSize = contentSize; if (count > 0) { this.pages = new int[count]; bits.ReadInt32(this.pages); } } internal void Read(PdbReader reader, BitAccess bits) { bits.MinCapacity(contentSize); Read(reader, 0, bits.Buffer, 0, contentSize); } internal void Read(PdbReader reader, int position, byte[] bytes, int offset, int data) { if (position + data > contentSize) { throw new Exception( string.Format( "DataStream can't read off end of stream. (pos={0},siz={1})", position, data)); } if (position == contentSize) { return; } int left = data; int page = position / reader.pageSize; int rema = position % reader.pageSize; // First get remained of first page. if (rema != 0) { int todo = reader.pageSize - rema; if (todo > left) { todo = left; } reader.Seek(pages[page], rema); reader.Read(bytes, offset, todo); offset += todo; left -= todo; page++; } // Now get the remaining pages. while (left > 0) { int todo = reader.pageSize; if (todo > left) { todo = left; } reader.Seek(pages[page], 0); reader.Read(bytes, offset, todo); offset += todo; left -= todo; page++; } } internal int Length { get { return contentSize; } } internal readonly int contentSize; internal readonly int[] pages; } private class MsfDirectory { internal MsfDirectory(PdbReader reader, PdbFileHeader head, BitAccess bits) { int pages = reader.PagesFromSize(head.directorySize); // 0..n in page of directory pages. bits.MinCapacity(head.directorySize); int directoryRootPages = head.directoryRoot.Length; int pagesPerPage = head.pageSize / 4; int pagesToGo = pages; for (int i = 0; i < directoryRootPages; i++) { int pagesInThisPage = pagesToGo <= pagesPerPage ? pagesToGo : pagesPerPage; reader.Seek(head.directoryRoot[i], 0); bits.Append(reader.reader, pagesInThisPage * 4); pagesToGo -= pagesInThisPage; } bits.Position = 0; DataStream stream = new DataStream(head.directorySize, bits, pages); bits.MinCapacity(head.directorySize); stream.Read(reader, bits); // 0..3 in directory pages bits.ReadInt32(out var count); // 4..n int[] sizes = new int[count]; bits.ReadInt32(sizes); // n..m streams = new DataStream[count]; for (int i = 0; i < count; i++) { if (sizes[i] <= 0) { streams[i] = new DataStream(); } else { streams[i] = new DataStream(sizes[i], bits, reader.PagesFromSize(sizes[i])); } } } internal readonly DataStream[] streams; } private struct CV_FileCheckSum { internal uint name; // Index of name in name table. internal byte len; // Hash length internal byte type; // Hash type } private enum SYM { S_END = 0x0006, // Block, procedure, "with" or thunk end S_OEM = 0x0404, // OEM defined symbol S_REGISTER_ST = 0x1001, // Register variable S_CONSTANT_ST = 0x1002, // constant symbol S_UDT_ST = 0x1003, // User defined type S_COBOLUDT_ST = 0x1004, // special UDT for cobol that does not symbol pack S_MANYREG_ST = 0x1005, // multiple register variable S_BPREL32_ST = 0x1006, // BP-relative S_LDATA32_ST = 0x1007, // Module-local symbol S_GDATA32_ST = 0x1008, // Global data symbol S_PUB32_ST = 0x1009, // a internal symbol (CV internal reserved) S_LPROC32_ST = 0x100a, // Local procedure start S_GPROC32_ST = 0x100b, // Global procedure start S_VFTABLE32 = 0x100c, // address of virtual function table S_REGREL32_ST = 0x100d, // register relative address S_LTHREAD32_ST = 0x100e, // local thread storage S_GTHREAD32_ST = 0x100f, // global thread storage S_LPROCMIPS_ST = 0x1010, // Local procedure start S_GPROCMIPS_ST = 0x1011, // Global procedure start S_FRAMEPROC = 0x1012, // extra frame and proc information S_COMPILE2_ST = 0x1013, // extended compile flags and info S_MANYREG2_ST = 0x1014, // multiple register variable S_LPROCIA64_ST = 0x1015, // Local procedure start (IA64) S_GPROCIA64_ST = 0x1016, // Global procedure start (IA64) S_LOCALSLOT_ST = 0x1017, // local IL sym with field for local slot index S_PARAMSLOT_ST = 0x1018, // local IL sym with field for parameter slot index S_ANNOTATION = 0x1019, // Annotation string literals S_GMANPROC_ST = 0x101a, // Global proc S_LMANPROC_ST = 0x101b, // Local proc S_RESERVED1 = 0x101c, // reserved S_RESERVED2 = 0x101d, // reserved S_RESERVED3 = 0x101e, // reserved S_RESERVED4 = 0x101f, // reserved S_LMANDATA_ST = 0x1020, S_GMANDATA_ST = 0x1021, S_MANFRAMEREL_ST = 0x1022, S_MANREGISTER_ST = 0x1023, S_MANSLOT_ST = 0x1024, S_MANMANYREG_ST = 0x1025, S_MANREGREL_ST = 0x1026, S_MANMANYREG2_ST = 0x1027, S_MANTYPREF = 0x1028, // Index for type referenced by name from metadata S_UNAMESPACE_ST = 0x1029, // Using namespace S_ST_MAX = 0x1100, // starting point for SZ name symbols S_OBJNAME = 0x1101, // path to object file name S_THUNK32 = 0x1102, // Thunk Start S_BLOCK32 = 0x1103, // block start S_WITH32 = 0x1104, // with start S_LABEL32 = 0x1105, // code label S_REGISTER = 0x1106, // Register variable S_CONSTANT = 0x1107, // constant symbol S_UDT = 0x1108, // User defined type S_COBOLUDT = 0x1109, // special UDT for cobol that does not symbol pack S_MANYREG = 0x110a, // multiple register variable S_BPREL32 = 0x110b, // BP-relative S_LDATA32 = 0x110c, // Module-local symbol S_GDATA32 = 0x110d, // Global data symbol S_PUB32 = 0x110e, // a internal symbol (CV internal reserved) S_LPROC32 = 0x110f, // Local procedure start S_GPROC32 = 0x1110, // Global procedure start S_REGREL32 = 0x1111, // register relative address S_LTHREAD32 = 0x1112, // local thread storage S_GTHREAD32 = 0x1113, // global thread storage S_LPROCMIPS = 0x1114, // Local procedure start S_GPROCMIPS = 0x1115, // Global procedure start S_COMPILE2 = 0x1116, // extended compile flags and info S_MANYREG2 = 0x1117, // multiple register variable S_LPROCIA64 = 0x1118, // Local procedure start (IA64) S_GPROCIA64 = 0x1119, // Global procedure start (IA64) S_LOCALSLOT = 0x111a, // local IL sym with field for local slot index S_SLOT = S_LOCALSLOT, // alias for LOCALSLOT S_PARAMSLOT = 0x111b, // local IL sym with field for parameter slot index S_LMANDATA = 0x111c, S_GMANDATA = 0x111d, S_MANFRAMEREL = 0x111e, S_MANREGISTER = 0x111f, S_MANSLOT = 0x1120, S_MANMANYREG = 0x1121, S_MANREGREL = 0x1122, S_MANMANYREG2 = 0x1123, S_UNAMESPACE = 0x1124, // Using namespace S_PROCREF = 0x1125, // Reference to a procedure S_DATAREF = 0x1126, // Reference to data S_LPROCREF = 0x1127, // Local Reference to a procedure S_ANNOTATIONREF = 0x1128, // Reference to an S_ANNOTATION symbol S_TOKENREF = 0x1129, // Reference to one of the many MANPROCSYM's S_GMANPROC = 0x112a, // Global proc S_LMANPROC = 0x112b, // Local proc S_TRAMPOLINE = 0x112c, // trampoline thunks S_MANCONSTANT = 0x112d, // constants with metadata type info S_ATTR_FRAMEREL = 0x112e, // relative to virtual frame ptr S_ATTR_REGISTER = 0x112f, // stored in a register S_ATTR_REGREL = 0x1130, // relative to register (alternate frame ptr) S_ATTR_MANYREG = 0x1131, // stored in >1 register S_SEPCODE = 0x1132, S_LOCAL = 0x1133, // defines a local symbol in optimized code S_DEFRANGE = 0x1134, // defines a single range of addresses in which symbol can be evaluated S_DEFRANGE2 = 0x1135, // defines ranges of addresses in which symbol can be evaluated S_SECTION = 0x1136, // A COFF section in a PE executable S_COFFGROUP = 0x1137, // A COFF group S_EXPORT = 0x1138, // A export S_CALLSITEINFO = 0x1139, // Indirect call site information S_FRAMECOOKIE = 0x113a, // Security cookie information S_DISCARDED = 0x113b, // Discarded by LINK /OPT:REF (experimental, see richards) S_RECTYPE_MAX, // one greater than last S_RECTYPE_LAST = S_RECTYPE_MAX - 1, }; private enum DEBUG_S_SUBSECTION { SYMBOLS = 0xF1, LINES = 0xF2, STRINGTABLE = 0xF3, FILECHKSMS = 0xF4, FRAMEDATA = 0xF5, } private struct OemSymbol { internal Guid idOem; // an oem ID (GUID) internal uint typind; // (type index) Type index //internal byte[] rgl; // user data, force 4-byte alignment }; private static readonly XmlWriterSettings s_xmlWriterSettings = new XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true, IndentChars = " ", NewLineChars = "\r\n", }; public static string TokenToSourceMap2Xml(Stream read, bool maskToken = false) { var builder = new StringBuilder(); using (var writer = XmlWriter.Create(builder, s_xmlWriterSettings)) { writer.WriteStartElement("token-map"); List<PdbTokenLine> list = [.. LoadTokenToSourceMapping(read).Values]; list.Sort( (x, y) => { int result = x.line.CompareTo(y.line); if (result != 0) return result; result = x.column.CompareTo(y.column); if (result != 0) return result; result = x.endLine.CompareTo(y.endLine); if (result != 0) return result; result = x.endColumn.CompareTo(y.endColumn); if (result != 0) return result; return x.token.CompareTo(y.token); }); foreach (var rec in list) { writer.WriteStartElement("token-location"); writer.WriteAttributeString("token", Token2String(rec.token, maskToken)); writer.WriteAttributeString("file", rec.sourceFile.name); writer.WriteAttributeString("start-line", rec.line.ToString()); writer.WriteAttributeString("start-column", rec.column.ToString()); writer.WriteAttributeString("end-line", rec.endLine.ToString()); writer.WriteAttributeString("end-column", rec.endColumn.ToString()); writer.WriteEndElement(); // "token-location"; } writer.WriteEndElement(); // "token-map"; } return builder.ToString(); } private static string Token2String(uint token, bool maskToken) { string result = token.ToString("X8"); if (maskToken) result = result[..2] + "xxxxxx"; return "0x" + result; } private static Dictionary<uint, PdbTokenLine> LoadTokenToSourceMapping(Stream read) { var tokenToSourceMapping = new Dictionary<uint, PdbTokenLine>(); BitAccess bits = new BitAccess(512 * 1024); PdbFileHeader head = new PdbFileHeader(read, bits); PdbReader reader = new PdbReader(read, head.pageSize); MsfDirectory dir = new MsfDirectory(reader, head, bits); dir.streams[1].Read(reader, bits); Dictionary<string, int> nameIndex = LoadNameIndex(bits); if (!nameIndex.TryGetValue("/NAMES", out var nameStream)) { throw new Exception("No `name' stream"); } dir.streams[nameStream].Read(reader, bits); IntHashTable names = LoadNameStream(bits); dir.streams[3].Read(reader, bits); LoadDbiStream(bits, out var modules, out var header, true); if (modules != null) { for (int m = 0; m < modules.Length; m++) { var module = modules[m]; if (module.stream > 0) { dir.streams[module.stream].Read(reader, bits); if (module.moduleName == "TokenSourceLineInfo") { LoadTokenToSourceInfo(bits, module, names, tokenToSourceMapping); } } } } return tokenToSourceMapping; } private static Dictionary<string, int> LoadNameIndex(BitAccess bits) { Dictionary<string, int> result = []; bits.ReadInt32(out var ver); // 0..3 Version bits.ReadInt32(out var sig); // 4..7 Signature bits.ReadInt32(out var age); // 8..11 Age bits.ReadGuid(out var guid); // 12..27 GUID // Read string buffer. bits.ReadInt32(out var buf); // 28..31 Bytes of Strings int beg = bits.Position; int nxt = bits.Position + buf; bits.Position = nxt; // n+4..7 maximum ni. bits.ReadInt32(out var cnt); bits.ReadInt32(out var max); BitSet present = new BitSet(bits); BitSet deleted = new BitSet(bits); if (!deleted.IsEmpty) { throw new Exception("Unsupported PDB deleted bitset is not empty."); } int j = 0; for (int i = 0; i < max; i++) { if (present.IsSet(i)) { bits.ReadInt32(out var ns); bits.ReadInt32(out var ni); int saved = bits.Position; bits.Position = beg + ns; bits.ReadCString(out var name); bits.Position = saved; result.Add(name.ToUpperInvariant(), ni); j++; } } if (j != cnt) { throw new Exception(string.Format("Count mismatch. ({0} != {1})", j, cnt)); } return result; } private static readonly Guid s_msilMetaData = new Guid(unchecked((int)0xc6ea3fc9), 0x59b3, 0x49d6, 0xbc, 0x25, 0x09, 0x02, 0xbb, 0xab, 0xb4, 0x60); private static void LoadTokenToSourceInfo( BitAccess bits, DbiModuleInfo module, IntHashTable names, Dictionary<uint, PdbTokenLine> tokenToSourceMapping) { bits.Position = 0; bits.ReadInt32(out var sig); if (sig != 4) { throw new Exception(string.Format("Invalid signature. (sig={0})", sig)); } bits.Position = 4; while (bits.Position < module.cbSyms) { bits.ReadUInt16(out var siz); int star = bits.Position; int stop = bits.Position + siz; bits.Position = star; bits.ReadUInt16(out var rec); switch ((SYM)rec) { case SYM.S_OEM: OemSymbol oem; bits.ReadGuid(out oem.idOem); bits.ReadUInt32(out oem.typind); // internal byte[] rgl; // user data, force 4-byte alignment if (oem.idOem == s_msilMetaData) { string name = bits.ReadString(); if (name == "TSLI") { bits.ReadUInt32(out var token); bits.ReadUInt32(out var file_id); bits.ReadUInt32(out var line); bits.ReadUInt32(out var column); bits.ReadUInt32(out var endLine); bits.ReadUInt32(out var endColumn); if (!tokenToSourceMapping.TryGetValue(token, out var tokenLine)) tokenToSourceMapping.Add(token, new PdbTokenLine(token, file_id, line, column, endLine, endColumn)); else { while (tokenLine.nextLine != null) tokenLine = tokenLine.nextLine; tokenLine.nextLine = new PdbTokenLine(token, file_id, line, column, endLine, endColumn); } } bits.Position = stop; break; } else { throw new Exception(string.Format("OEM section: guid={0} ti={1}", oem.idOem, oem.typind)); } case SYM.S_END: bits.Position = stop; break; default: bits.Position = stop; break; } } bits.Position = module.cbSyms + module.cbOldLines; int limit = module.cbSyms + module.cbOldLines + module.cbLines; IntHashTable sourceFiles = ReadSourceFileInfo(bits, (uint)limit, names); foreach (var tokenLine in tokenToSourceMapping.Values) { tokenLine.sourceFile = (PdbSource)sourceFiles[(int)tokenLine.file_id]; } } private static readonly Guid s_symDocumentTypeGuid = new Guid("{5a869d0b-6611-11d3-bd2a-0000f80849bd}"); private static IntHashTable ReadSourceFileInfo(BitAccess bits, uint limit, IntHashTable names) { IntHashTable checks = new IntHashTable(); int begin = bits.Position; while (bits.Position < limit) { bits.ReadInt32(out var sig); bits.ReadInt32(out var siz); int place = bits.Position; int endSym = bits.Position + siz; switch ((DEBUG_S_SUBSECTION)sig) { case DEBUG_S_SUBSECTION.FILECHKSMS: while (bits.Position < endSym) { CV_FileCheckSum chk; int ni = bits.Position - place; bits.ReadUInt32(out chk.name); bits.ReadUInt8(out chk.len); bits.ReadUInt8(out chk.type); PdbSource src = new PdbSource(/*(uint)ni,*/ (string)names[(int)chk.name], s_symDocumentTypeGuid, Guid.Empty, Guid.Empty); checks.Add(ni, src); bits.Position += chk.len; bits.Align(4); } bits.Position = endSym; break; default: bits.Position = endSym; break; } } return checks; } private static IntHashTable LoadNameStream(BitAccess bits) { IntHashTable ht = new IntHashTable(); bits.ReadUInt32(out var sig); // 0..3 Signature bits.ReadInt32(out var ver); // 4..7 Version // Read (or skip) string buffer. bits.ReadInt32(out var buf); // 8..11 Bytes of Strings if (sig != 0xeffeeffe || ver != 1) { throw new Exception(string.Format("Unsupported Name Stream version. (sig={0:x8}, ver={1})", sig, ver)); } int beg = bits.Position; int nxt = bits.Position + buf; bits.Position = nxt; // Read hash table. bits.ReadInt32(out var siz); // n+0..3 Number of hash buckets. nxt = bits.Position; for (int i = 0; i < siz; i++) { bits.ReadInt32(out var ni); if (ni != 0) { int saved = bits.Position; bits.Position = beg + ni; bits.ReadCString(out var name); bits.Position = saved; ht.Add(ni, name); } } bits.Position = nxt; return ht; } private static void LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, out DbiDbgHdr header, bool readStrings) { DbiHeader dh = new DbiHeader(bits); header = new DbiDbgHdr(); // Read gpmod section. var modList = new List<DbiModuleInfo>(); int end = bits.Position + dh.gpmodiSize; while (bits.Position < end) { DbiModuleInfo mod = new DbiModuleInfo(bits, readStrings); modList.Add(mod); } if (bits.Position != end) { throw new Exception(string.Format("Error reading DBI stream, pos={0} != {1}", bits.Position, end)); } if (modList.Count > 0) { modules = [.. modList]; } else { modules = null; } // Skip the Section Contribution substream. bits.Position += dh.secconSize; // Skip the Section Map substream. bits.Position += dh.secmapSize; // Skip the File Info substream. bits.Position += dh.filinfSize; // Skip the TSM substream. bits.Position += dh.tsmapSize; // Skip the EC substream. bits.Position += dh.ecinfoSize; // Read the optional header. end = bits.Position + dh.dbghdrSize; if (dh.dbghdrSize > 0) { header = new DbiDbgHdr(bits); } bits.Position = end; } } }