src/Microsoft.Diagnostics.Runtime/Implementation/SymbolGroup.cs (176 lines of code) (raw):

// 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.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Azure.Core; namespace Microsoft.Diagnostics.Runtime.Implementation { internal sealed class SymbolGroup : IFileLocator { private static readonly string s_defaultCacheLocation = Path.Combine(Path.GetTempPath(), "symbols"); private static volatile FileSymbolCache? s_cache; private readonly ImmutableArray<IFileLocator> _groups; public SymbolGroup(IEnumerable<IFileLocator> groups) { _groups = groups.ToImmutableArray(); } public string? FindPEImage(string fileName, int buildTimeStamp, int imageSize, bool checkProperties) { foreach (IFileLocator locator in _groups) { string? result = locator.FindPEImage(fileName, buildTimeStamp, imageSize, checkProperties); if (result != null) return result; } return null; } public string? FindPEImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildIdOrUUID, OSPlatform originalPlatform, bool checkProperties) { foreach (IFileLocator locator in _groups) { string? result = locator.FindPEImage(fileName, archivedUnder, buildIdOrUUID, originalPlatform, checkProperties); if (result != null) return result; } return null; } public string? FindElfImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildId, bool checkProperties) { foreach (IFileLocator locator in _groups) { string? result = locator.FindElfImage(fileName, archivedUnder, buildId, checkProperties); if (result != null) return result; } return null; } public string? FindMachOImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> uuid, bool checkProperties) { foreach (IFileLocator locator in _groups) { string? result = locator.FindMachOImage(fileName, archivedUnder, uuid, checkProperties); if (result != null) return result; } return null; } private static FileSymbolCache GetDefaultCache() { FileSymbolCache? cache = s_cache; if (cache != null) return cache; // We always expect to be able to create a temporary directory Directory.CreateDirectory(s_defaultCacheLocation); cache = new FileSymbolCache(s_defaultCacheLocation); Interlocked.CompareExchange(ref s_cache, cache, null); return s_cache!; } public static IFileLocator CreateFromSymbolPath(string symbolPath, bool trace, TokenCredential? credential) { FileSymbolCache defaultCache = GetDefaultCache(); List<IFileLocator> locators = new(); bool first = false; SymbolServer? single = null; foreach ((string? Cache, string[] Servers) in symbolPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(EnumerateEntries)) { if (Servers.Length == 0) continue; FileSymbolCache cache = defaultCache; if (Cache != null && !defaultCache.Location.Equals(Cache, FileSymbolCache.IsCaseInsensitiveFileSystem ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) { Directory.CreateDirectory(Cache); cache = new FileSymbolCache(Cache); // if the cache is not the default, we have to add it to the list of locators so we check there before hitting the symbol server locators.Add(cache); } foreach (string server in Servers) { if (IsUrl(server)) { SymbolServer symSvr = new(cache, server, trace, credential); locators.Add(symSvr); if (first) { single = symSvr; first = false; } else { single = null; } } else if (Directory.Exists(server)) { locators.Add(new FileSymbolCache(server)); } else { Trace.WriteLine($"Ignoring symbol part: {server}"); } } } if (single != null) return single; if (locators.Count == 0) return new SymbolServer(defaultCache, SymbolServer.Msdl, trace, null); return new SymbolGroup(locators); } internal static (string? Cache, string[] Servers) EnumerateEntries(string part) { string? cache = null; List<string> servers = []; foreach (string entry in EnumerateParts(part)) { if (cache is null && servers.Count == 0 && !IsUrl(entry)) cache = entry; else servers.Add(entry); } return (cache, servers.ToArray()); } private static IEnumerable<string> EnumerateParts(string path) { bool possiblySkipNextDll = false; int curr = 0; for (int i = 0; i < path.Length; i++) { if (path[i] == '*') { ReadOnlySpan<char> part = path.AsSpan(curr, i - curr).Trim(); curr = i + 1; if (part.Equals("cache".AsSpan(), StringComparison.OrdinalIgnoreCase) || part.Equals("svr".AsSpan(), StringComparison.OrdinalIgnoreCase) || part.Equals("srv".AsSpan(), StringComparison.OrdinalIgnoreCase)) { // Don't yield this. } else if (part.Equals("symsrv".AsSpan(), StringComparison.OrdinalIgnoreCase)) { possiblySkipNextDll = true; } else { bool skip = possiblySkipNextDll && part.EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase); possiblySkipNextDll = false; if (!skip && part.Length > 0) yield return part.ToString(); } } } if (curr < path.Length) { ReadOnlySpan<char> part = path.AsSpan(curr).Trim(); bool skip = possiblySkipNextDll && part.EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase); if (!skip && part.Length > 0) yield return part.ToString(); } } private static bool IsUrl(string path) { bool result = Uri.TryCreate(path, UriKind.Absolute, out Uri? uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); return result; } } }