From 1e0c106b8f687200cfcaa1d5de53fce86002901e Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Fri, 17 Sep 2021 17:21:57 -0700 Subject: [PATCH] Improve SymbolService.ParseSymbolPath support for Watson. Issue https://github.com/dotnet/diagnostics/issues/2512. Can now handle the various symbol paths that Watson can throw at us. Doesn't support actually calling the symbol server dll like in the symsrv*symaudit.dll*\\server\share syntax. The dll is ignored. --- dotnet.sh | 0 .../SymbolService.cs | 115 ++++++++++------- src/SOS/SOS.Extensions/HostServices.cs | 2 +- .../SOS.Hosting/SymbolServiceExtensions.cs | 18 +++ src/SOS/SOS.Hosting/SymbolServiceWrapper.cs | 8 +- .../SymbolServiceTests.cs | 116 ++++++++++++++++++ 6 files changed, 210 insertions(+), 49 deletions(-) mode change 100644 => 100755 dotnet.sh create mode 100644 src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs diff --git a/dotnet.sh b/dotnet.sh old mode 100644 new mode 100755 diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs index fffb98fca3..f014c23fc2 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs @@ -8,6 +8,7 @@ using Microsoft.SymbolStore.SymbolStores; using SOS; using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; @@ -27,8 +28,8 @@ public class SymbolService : ISymbolService /// /// Symbol server URLs /// - const string MsdlSymbolServer = "http://msdl.microsoft.com/download/symbols/"; - const string SymwebSymbolServer = "http://symweb.corp.microsoft.com/"; + public const string MsdlSymbolServer = "http://msdl.microsoft.com/download/symbols/"; + public const string SymwebSymbolServer = "http://symweb.corp.microsoft.com/"; private readonly IHost _host; private string _defaultSymbolCache; @@ -96,77 +97,103 @@ public bool ParseSymbolPath(string symbolPath) foreach (string path in paths.Reverse()) { - string[] parts = path.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries); - - // UNC or directory paths are ignored (paths not prefixed with srv* or cache*). + string[] parts = path.Split(new char[] { '*' }, StringSplitOptions.None); if (parts.Length > 0) { - string symbolServerPath = null; - string symbolCachePath = null; + List symbolCachePaths = new(); string symbolDirectoryPath = null; - bool msdl = false; + string symbolServerPath = null; + + void ParseServer(int start) + { + symbolServerPath = MsdlSymbolServer; + for (int i = start; i < parts.Length; i++) + { + if (string.IsNullOrEmpty(parts[i])) + { + // srv** means use default cache + if (i != (parts.Length - 1)) + { + symbolCachePaths.Add(DefaultSymbolCache); + } + } + else if (i < (parts.Length - 1)) + { + symbolCachePaths.Add(parts[i]); + } + else + { + symbolServerPath = parts[i]; + } + } + } switch (parts[0].ToLowerInvariant()) { + case "symsrv": + if (parts.Length <= 2) + { + return false; + } + // ignore symsrv.dll or other server dlls in parts[2] + ParseServer(2); + break; + case "srv": - switch (parts.Length) - { - case 1: - msdl = true; - symbolCachePath = DefaultSymbolCache; - break; - case 2: - symbolServerPath = parts[1]; - break; - case 3: - symbolCachePath = parts[1]; - symbolServerPath = parts[2]; - break; - default: - return false; + if (parts.Length <= 1) + { + return false; } + ParseServer(1); break; case "cache": - switch (parts.Length) - { - case 1: - symbolCachePath = DefaultSymbolCache; - break; - case 2: - symbolCachePath = parts[1]; - break; - default: - return false; + if (parts.Length <= 1) + { + return false; + } + else + { + for (int i = 1; i < parts.Length; i++) + { + if (string.IsNullOrEmpty(parts[i])) + { + if (i == 1) + { + symbolCachePaths.Add(DefaultSymbolCache); + } + } + else + { + symbolCachePaths.Add(parts[i]); + } + } } break; default: // Directory path search - switch (parts.Length) + if (parts.Length != 1) { - case 1: - symbolDirectoryPath = parts[0]; - break; - default: - return false; + return false; } + symbolDirectoryPath = parts[0]; break; } - if (msdl || symbolServerPath != null) + if (symbolServerPath != null) { - if (!AddSymbolServer(msdl, symweb: false, symbolServerPath, authToken: null, timeoutInMinutes: 0)) + if (!AddSymbolServer(msdl: false, symweb: false, symbolServerPath.Trim(), authToken: null, timeoutInMinutes: 0)) { return false; } } - if (symbolCachePath != null) + foreach (string symbolCachePath in symbolCachePaths.Reverse()) { - AddCachePath(symbolCachePath); + AddCachePath(symbolCachePath.Trim()); } if (symbolDirectoryPath != null) { - AddDirectoryPath(symbolDirectoryPath); + AddDirectoryPath(symbolDirectoryPath.Trim()); } } } diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 7424a7ae7d..fb5e4ab587 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -236,7 +236,7 @@ private HResult RegisterDebuggerServices( hr = DebuggerServices.GetSymbolPath(out string symbolPath); if (hr == HResult.S_OK) { - if (!_symbolService.ParseSymbolPath(symbolPath)) + if (!_symbolService.ParseSymbolPathFixDefault(symbolPath)) { Trace.TraceError("ParseSymbolPath FAILED: {0}", symbolPath); } diff --git a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs index 21b4a629b1..285b1cace5 100644 --- a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs +++ b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs @@ -21,6 +21,24 @@ public static class SymbolServiceExtensions // HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) const int E_INSUFFICIENT_BUFFER = unchecked((int)0x8007007a); + /// + /// Set the windows symbol path converting the default "srv*" to the cached public symbol server URL. + /// + /// The windows symbol path to translate and set + /// if false, error parsing symbol path + public static bool ParseSymbolPathFixDefault( + this ISymbolService symbolService, + string symbolPath) + { + // Translate dbgeng's default .sympath to what the public version actually does. Normally "srv*" + // means no caching and the server path depends on whether dbgeng is internal or public. + if (symbolPath.ToLowerInvariant() == "srv*") + { + symbolPath = "cache*;SRV*https://msdl.microsoft.com/download/symbols"; + } + return symbolService.ParseSymbolPath(symbolPath); + } + /// /// Metadata locator helper for the DAC. /// diff --git a/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs b/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs index a5cee29318..ea90410b6a 100644 --- a/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs +++ b/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs @@ -157,16 +157,16 @@ private bool InitializeSymbolStore( /// /// Parse the Windows sympath format /// - /// windows symbol path + /// windows symbol path /// if false, failure private bool ParseSymbolPath( IntPtr self, - string windowsSymbolPath) + string symbolPath) { - if (windowsSymbolPath == null) { + if (string.IsNullOrWhiteSpace(symbolPath)) { return false; } - return _symbolService.ParseSymbolPath(windowsSymbolPath); + return _symbolService.ParseSymbolPathFixDefault(symbolPath); } /// diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs new file mode 100644 index 0000000000..ab083dcbbd --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs @@ -0,0 +1,116 @@ +// 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. + +using Microsoft.Diagnostics.DebugServices.Implementation; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Diagnostics.DebugServices.UnitTests +{ + /// + /// Test the service event implementation + /// + public class SymbolServiceTests : IHost + { + public SymbolServiceTests() + { + } + + [Fact] + public void SymbolPathTests() + { + var symbolService = new SymbolService(this); + Assert.False(symbolService.ParseSymbolPath("srv")); + Assert.False(symbolService.ParseSymbolPath("cache")); + Assert.False(symbolService.ParseSymbolPath("symsrv")); + + string defaultServer = $"Server: {SymbolService.MsdlSymbolServer}"; + string defaultPath = $"Cache: {symbolService.DefaultSymbolCache} {defaultServer}"; + string localSymbolCache = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "c:\\localsymbolcache" : "/home/foo/localsymbolcache"; + + Assert.True(symbolService.ParseSymbolPath("srv*")); + Assert.Equal(defaultServer, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath("srv**")); + Assert.Equal(defaultPath, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath("symsrv*symsrv.dll*")); + Assert.Equal(defaultServer, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath("cache*;srv*")); + Assert.Equal(defaultPath, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath("srv*http://msdl.microsoft.com/download/symbols/")); + Assert.Equal(defaultServer, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath($"srv**{SymbolService.MsdlSymbolServer}")); + Assert.Equal(defaultPath, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath($"srv*{localSymbolCache}*{SymbolService.SymwebSymbolServer}")); + string testpath1 = $"Cache: {localSymbolCache} Server: {SymbolService.SymwebSymbolServer}"; + Assert.Equal(testpath1, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath($"cache*{localSymbolCache};srv*")); + string testpath2 = $"Cache: {localSymbolCache} Server: {SymbolService.MsdlSymbolServer}"; + Assert.Equal(testpath2, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath($"srv**{localSymbolCache}*http://msdl.microsoft.com/download/symbols/")); + Assert.Equal($"Cache: {symbolService.DefaultSymbolCache} Cache: {localSymbolCache} Server: http://msdl.microsoft.com/download/symbols/", symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.True(symbolService.ParseSymbolPath($"symsrv*symsrv.dll*{localSymbolCache}*\\\\server\\share")); + Assert.Equal($"Cache: {localSymbolCache} Cache: \\\\server\\share", symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + Assert.True(symbolService.ParseSymbolPath("symsrv*symsrv.dll*d:\\data\\SYM\\symcache*\\\\aw0eus0symcache.file.core.windows.net\\Symbols*http://localhost/remote200/30e07e1454924e55901d7f693f7eddf1/0/x64/4542784547/remote")); + Assert.Equal("Cache: d:\\data\\SYM\\symcache Cache: \\\\aw0eus0symcache.file.core.windows.net\\Symbols Server: http://localhost/remote200/30e07e1454924e55901d7f693f7eddf1/0/x64/4542784547/remote/", symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + } + + string symbolDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "c:\\symbols\\" : "/home/foo/symbols/"; + Assert.True(symbolService.ParseSymbolPath(symbolDirectory)); + Assert.Equal($"Directory: {symbolDirectory}", symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + + string symbolDirectory2 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "c:\\foo\\bar" : "/home/foo/bar"; + Assert.True(symbolService.ParseSymbolPath($"{symbolDirectory};{symbolDirectory2};srv*")); + Assert.Equal($"Directory: {symbolDirectory} Directory: {symbolDirectory2} " + defaultServer, symbolService.FormatSymbolStores()); + symbolService.DisableSymbolStore(); + } + + #region IHost + + public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent(); + + HostType IHost.HostType => HostType.DotnetDump; + + IServiceProvider IHost.Services => throw new NotImplementedException(); + + IEnumerable IHost.EnumerateTargets() => throw new NotImplementedException(); + + void IHost.DestroyTarget(ITarget target) => throw new NotImplementedException(); + + #endregion + } + public static class SymbolServiceExtensions + { + public static string FormatSymbolStores( + this ISymbolService symbolService) + { + return symbolService.ToString().Replace(Environment.NewLine, " ").TrimEnd(); + } + } +}