diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index c484361..58eb125 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -64,6 +64,16 @@
+
+
+
+ symsrv.dll
+ PreserveNewest
+ False
+
+
+
+
+
+
diff --git a/src/SizeBench.AnalysisEngine/DIAInterop/DIAAdapter.cs b/src/SizeBench.AnalysisEngine/DIAInterop/DIAAdapter.cs
index ace6de1..d656c3f 100644
--- a/src/SizeBench.AnalysisEngine/DIAInterop/DIAAdapter.cs
+++ b/src/SizeBench.AnalysisEngine/DIAInterop/DIAAdapter.cs
@@ -56,6 +56,13 @@ internal sealed class DIAAdapter : IDIAAdapter, IDisposable
private uint _sectionAlignment;
private readonly int _affinitizedThreadId;
+ ///
+ /// The path DIA actually loaded the PDB from. For explicit-PDB sessions this echoes the path we passed
+ /// to loadDataFromPdb. For symbol-server sessions this is the cached/downloaded location DIA
+ /// resolved via loadDataForExe.
+ ///
+ internal string? LoadedPdbPath { get; private set; }
+
private static readonly string[] debugFastlinkSwitchNames = ["/debug:fastlink"];
[ThreadStatic]
@@ -124,6 +131,16 @@ private PEFile PEFile
#region Construction, opening, all the startup-y things
internal DIAAdapter(Session session, string pdbPath)
+ : this(session, source => ((IDiaDataSourceEx2)source).loadDataFromPdbEx(pdbPath, fPdbPrefetching: 1), pdbPath)
+ {
+ }
+
+ internal DIAAdapter(Session session, string binaryPath, string symbolSearchPath)
+ : this(session, source => source.loadDataForExe(binaryPath, symbolSearchPath, pCallback: null), binaryPath)
+ {
+ }
+
+ private DIAAdapter(Session session, Action loadFromSource, string sourceDescriptionForErrors)
{
this._session = session;
this._cache = session.DataCache;
@@ -135,7 +152,7 @@ internal DIAAdapter(Session session, string pdbPath)
{
this._diaDataSource = CoClassLoaderRegFree.CreateInstance(_diaLibraryModule, Dia140Clsid);
- this._diaDataSource.loadDataFromPdbEx(pdbPath, fPdbPrefetching: 1);
+ loadFromSource(this._diaDataSource);
this._diaDataSource.openSession(out var diaSession);
this._diaSession = (IDiaSessionEx)diaSession;
@@ -149,7 +166,7 @@ internal DIAAdapter(Session session, string pdbPath)
{
if (((uint)comException.HResult) == Convert.ToUInt32(diaHRESULTValues.GetValue(i), CultureInfo.InvariantCulture.NumberFormat))
{
- throw new PDBNotSuitableForAnalysisException($"Unable to open PDB from '{session.PdbPath}'" + Environment.NewLine +
+ throw new PDBNotSuitableForAnalysisException($"Unable to open PDB from '{sourceDescriptionForErrors}'" + Environment.NewLine +
$"DIA returned this error: {Enum.GetName((DIAHRESULTs)comException.HResult)}", comException);
}
@@ -158,6 +175,16 @@ internal DIAAdapter(Session session, string pdbPath)
}
this._globalScope = this._diaSession.globalScope;
+
+ try
+ {
+ this.LoadedPdbPath = this._globalScope.symbolsFileName;
+ }
+ catch (COMException)
+ {
+ // Leave LoadedPdbPath null if DIA can't report it for this PDB type
+ }
+
var machineType = this._globalScope.machineType;
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx
diff --git a/src/SizeBench.AnalysisEngine/Session.cs b/src/SizeBench.AnalysisEngine/Session.cs
index 4598eb4..7d9463c 100644
--- a/src/SizeBench.AnalysisEngine/Session.cs
+++ b/src/SizeBench.AnalysisEngine/Session.cs
@@ -20,7 +20,9 @@ public sealed class Session : ISession
{
private readonly string _originalPDBPathMayBeRemote;
private GuaranteedLocalFile? _guaranteedLocalPDBFile;
- public string PdbPath => this._guaranteedLocalPDBFile?.OriginalPath ?? "No pdb opened yet";
+ public string PdbPath => this._guaranteedLocalPDBFile?.OriginalPath
+ ?? this._diaAdapter?.LoadedPdbPath
+ ?? (String.IsNullOrEmpty(this._originalPDBPathMayBeRemote) ? "No pdb opened yet" : this._originalPDBPathMayBeRemote);
private readonly string _originalBinaryPathMayBeRemote;
public string BinaryPath => this._peFile?.GuaranteedLocalCopyOfBinary.OriginalPath ?? "No binary opened yet";
@@ -141,8 +143,14 @@ internal Session(string binaryPath, string pdbPath, SessionOptions options, ILog
this.SessionOptions = options;
this.DataCache = new SessionDataCache(options.SymbolSourcesSupported);
- Debug.Assert(File.Exists(pdbPath));
- this._originalPDBPathMayBeRemote = pdbPath;
+ var hasExplicitPdbPath = !String.IsNullOrEmpty(pdbPath);
+ var hasSymbolServer = !String.IsNullOrWhiteSpace(options.SymbolServerSearchPath);
+ if (!hasExplicitPdbPath && !hasSymbolServer)
+ {
+ throw new ArgumentException("Either a PDB path or a SymbolServerSearchPath in SessionOptions must be supplied.", nameof(pdbPath));
+ }
+ Debug.Assert(!hasExplicitPdbPath || File.Exists(pdbPath));
+ this._originalPDBPathMayBeRemote = pdbPath ?? String.Empty;
Debug.Assert(File.Exists(binaryPath));
this._originalBinaryPathMayBeRemote = binaryPath;
@@ -160,10 +168,22 @@ private void InitializeDIAThread()
using var initializeDiaThreadLog = this._logger.StartTaskLog($"Setting up initial data needed to open the session (symbol sources: {this.SessionOptions.SymbolSourcesSupported})");
this._diaManagedThreadId = Environment.CurrentManagedThreadId;
- this.ProgressReporter?.Report(new SessionTaskProgress("Copying PDB file locally if necessary.", 0, null));
- this._guaranteedLocalPDBFile = new GuaranteedLocalFile(this._originalPDBPathMayBeRemote, initializeDiaThreadLog);
+ var hasExplicitPdb = !String.IsNullOrEmpty(this._originalPDBPathMayBeRemote);
+ if (hasExplicitPdb)
+ {
+ this.ProgressReporter?.Report(new SessionTaskProgress("Copying PDB file locally if necessary.", 0, null));
+ this._guaranteedLocalPDBFile = new GuaranteedLocalFile(this._originalPDBPathMayBeRemote, initializeDiaThreadLog);
+
+ this._diaAdapter = new DIAAdapter(this, this._guaranteedLocalPDBFile.GuaranteedLocalPath);
+ }
+ else
+ {
+ var searchPath = this.SessionOptions.SymbolServerSearchPath!;
+ initializeDiaThreadLog.Log($"No explicit PDB supplied - using symbol server search path: {searchPath}");
+ this.ProgressReporter?.Report(new SessionTaskProgress("Downloading PDB from symbol server if necessary.", 0, null));
- this._diaAdapter = new DIAAdapter(this, this._guaranteedLocalPDBFile.GuaranteedLocalPath);
+ this._diaAdapter = new DIAAdapter(this, this._originalBinaryPathMayBeRemote, searchPath);
+ }
this._taskParameters = new SessionTaskParameters(this, this._diaAdapter, this.DataCache);
this._peFile = new PEFile(this._originalBinaryPathMayBeRemote, this.SessionOptions.SymbolSourcesSupported, initializeDiaThreadLog);
diff --git a/src/SizeBench.AnalysisEngine/SessionOptions.cs b/src/SizeBench.AnalysisEngine/SessionOptions.cs
index 5abb953..2a9ea21 100644
--- a/src/SizeBench.AnalysisEngine/SessionOptions.cs
+++ b/src/SizeBench.AnalysisEngine/SessionOptions.cs
@@ -3,4 +3,18 @@
public sealed record class SessionOptions
{
public SymbolSourcesSupported SymbolSourcesSupported { get; init; } = SymbolSourcesSupported.All;
+
+ ///
+ /// Optional symsrv-style symbol search path used to locate (and download) PDBs when an explicit
+ /// PDB path is not supplied. The string is passed verbatim to DIA's loadDataForExe, so it follows
+ /// the standard format used by dbghelp / symsrv. Examples:
+ ///
+ /// - srv*https://msdl.microsoft.com/download/symbols
+ /// - srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
+ /// - C:\local\symbols;srv*C:\cache*https://internal.symbol.server
+ ///
+ /// When this is non-empty and the PDB path passed to
+ /// is empty, the symbol server will be used to locate the PDB for the binary.
+ ///
+ public string? SymbolServerSearchPath { get; init; }
}
diff --git a/src/SizeBench.AnalysisEngine/SizeBench.AnalysisEngine.csproj b/src/SizeBench.AnalysisEngine/SizeBench.AnalysisEngine.csproj
index 23e6d44..107e26f 100644
--- a/src/SizeBench.AnalysisEngine/SizeBench.AnalysisEngine.csproj
+++ b/src/SizeBench.AnalysisEngine/SizeBench.AnalysisEngine.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/SizeBench.GUI.Tests/Windows/OpenSingleBinaryWindowViewModelTests.cs b/src/SizeBench.GUI.Tests/Windows/OpenSingleBinaryWindowViewModelTests.cs
index a822c9f..bd5ba9b 100644
--- a/src/SizeBench.GUI.Tests/Windows/OpenSingleBinaryWindowViewModelTests.cs
+++ b/src/SizeBench.GUI.Tests/Windows/OpenSingleBinaryWindowViewModelTests.cs
@@ -1,4 +1,5 @@
-using SizeBench.LocalBuild;
+using System.IO;
+using SizeBench.LocalBuild;
using SizeBench.PathLocators;
namespace SizeBench.GUI.Windows.Tests;
@@ -27,4 +28,39 @@ public void OKButtonRemainsDisabledWithJustBinaryPathSet()
Assert.IsFalse(vm.OKEnabled);
Assert.IsEmpty(propertiesChanged);
}
+
+ [TestMethod]
+ public void OKButtonEnabledWhenBinaryExistsAndSymbolServerConfigured()
+ {
+ // Pick an existing binary that we know does not have a matching PDB next to it on disk.
+ // notepad.exe is always present in System32 on test machines and won't have a sibling .pdb.
+ var existingBinaryPath = Path.Combine(Environment.SystemDirectory, "notepad.exe");
+ if (!File.Exists(existingBinaryPath))
+ {
+ Assert.Inconclusive($"Cannot run test - expected sentinel binary at {existingBinaryPath}");
+ }
+
+ var sso = new SelectSessionOptionsControlViewModel();
+ var vm = new OpenSingleBinaryWindowViewModel(new SelectSingleBinaryAndPDBControlViewModel(new IBinaryLocator[] { new LocalBuildPathLocator() }),
+ sso);
+
+ // Just the binary, no PDB, no symbol server -> still disabled
+ vm.SelectSingleBinaryAndPDBControlViewModel.BinaryPath = existingBinaryPath;
+ // Defensive: blow away any inferred PDB so the symbol-server branch is the only way to enable OK
+ vm.SelectSingleBinaryAndPDBControlViewModel.PDBPath = String.Empty;
+ Assert.IsFalse(vm.OKEnabled);
+
+ // Enabling the symbol server checkbox without any paths -> still disabled
+ sso.UseSymbolServer = true;
+ sso.SymbolServerPathsText = String.Empty;
+ Assert.IsFalse(vm.OKEnabled);
+
+ // Once a path is provided, OK becomes enabled even without a local PDB
+ sso.SymbolServerPathsText = "srv*https://msdl.microsoft.com/download/symbols";
+ Assert.IsTrue(vm.OKEnabled);
+
+ // Disabling the symbol server toggle should drop the OK enablement again
+ sso.UseSymbolServer = false;
+ Assert.IsFalse(vm.OKEnabled);
+ }
}
diff --git a/src/SizeBench.GUI.Tests/Windows/SelectSessionOptionsControlViewModelTests.cs b/src/SizeBench.GUI.Tests/Windows/SelectSessionOptionsControlViewModelTests.cs
index f0cffcf..a660f7f 100644
--- a/src/SizeBench.GUI.Tests/Windows/SelectSessionOptionsControlViewModelTests.cs
+++ b/src/SizeBench.GUI.Tests/Windows/SelectSessionOptionsControlViewModelTests.cs
@@ -91,4 +91,36 @@ public void ChangingIndividualSymbolTypesUpdatesSymbolSourcesSupported()
Assert.HasCount(4, propertiesChanged);
Assert.AreSequenceEqual(new[] { "", "", "", "" }, propertiesChanged, Microsoft.VisualStudio.TestTools.UnitTesting.SequenceOrder.InAnyOrder);
}
+
+ [TestMethod]
+ public void SymbolServerToggleFlowsThroughToSessionOptions()
+ {
+ var vm = new SelectSessionOptionsControlViewModel();
+
+ // Default - no symbol server
+ Assert.IsFalse(vm.UseSymbolServer);
+ Assert.IsNull(vm.SessionOptions.SymbolServerSearchPath);
+ Assert.IsFalse(vm.HasAnySymbolServerPaths);
+
+ // Setting the text only does not enable usage; SessionOptions still has null search path
+ vm.SymbolServerPathsText = "srv*https://contoso/symbols";
+ Assert.IsTrue(vm.HasAnySymbolServerPaths);
+ Assert.IsNull(vm.SessionOptions.SymbolServerSearchPath);
+
+ // Enabling the checkbox surfaces the search path to SessionOptions
+ vm.UseSymbolServer = true;
+ Assert.AreEqual("srv*https://contoso/symbols", vm.SessionOptions.SymbolServerSearchPath);
+
+ // Multiple lines are joined with ';' for symsrv
+ vm.SymbolServerPathsText = "C:\\local\r\nsrv*C:\\cache*https://msdl";
+ Assert.AreEqual("C:\\local;srv*C:\\cache*https://msdl", vm.SessionOptions.SymbolServerSearchPath);
+
+ // Blank lines are ignored
+ vm.SymbolServerPathsText = "\r\n\r\nC:\\local\r\n\r\n";
+ Assert.AreEqual("C:\\local", vm.SessionOptions.SymbolServerSearchPath);
+
+ // Disabling the toggle removes the search path even if text remains
+ vm.UseSymbolServer = false;
+ Assert.IsNull(vm.SessionOptions.SymbolServerSearchPath);
+ }
}
diff --git a/src/SizeBench.GUI/Settings/AppSettings.cs b/src/SizeBench.GUI/Settings/AppSettings.cs
new file mode 100644
index 0000000..71eb64f
--- /dev/null
+++ b/src/SizeBench.GUI/Settings/AppSettings.cs
@@ -0,0 +1,115 @@
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace SizeBench.GUI.Settings;
+
+internal sealed class AppSettings : IAppSettings
+{
+ private const string DefaultPublicMsftSymbolServer = "srv*https://msdl.microsoft.com/download/symbols";
+
+ private readonly string _filePath;
+ private readonly PersistedShape _data;
+
+ public AppSettings() : this(DefaultSettingsFilePath()) { }
+
+ internal AppSettings(string filePath)
+ {
+ this._filePath = filePath;
+ this._data = Load(filePath);
+ }
+
+ public bool UseSymbolServer
+ {
+ get => this._data.UseSymbolServer;
+ set
+ {
+ if (this._data.UseSymbolServer != value)
+ {
+ this._data.UseSymbolServer = value;
+ Save();
+ }
+ }
+ }
+
+ public IList SymbolServerPaths => this._data.SymbolServerPaths;
+
+ public void SetSymbolServerPaths(IEnumerable paths)
+ {
+ ArgumentNullException.ThrowIfNull(paths);
+
+ this._data.SymbolServerPaths = paths.Where(p => !String.IsNullOrWhiteSpace(p))
+ .Select(p => p.Trim())
+ .ToList();
+ Save();
+ }
+
+ public string BuildSymbolSearchPath()
+ => String.Join(";", this._data.SymbolServerPaths.Where(p => !String.IsNullOrWhiteSpace(p)));
+
+ private void Save()
+ {
+ try
+ {
+ var dir = Path.GetDirectoryName(this._filePath);
+ if (!String.IsNullOrEmpty(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+ var json = JsonSerializer.Serialize(this._data, SerializerOptions);
+ File.WriteAllText(this._filePath, json);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types - settings persistence failures should not crash the app
+ catch
+ {
+ // Best-effort persistence: if we can't save settings we still want the app to keep working.
+ }
+#pragma warning restore CA1031
+ }
+
+ internal static string DefaultSettingsFilePath()
+ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "SizeBench",
+ "settings.json");
+
+ private static PersistedShape Load(string filePath)
+ {
+ try
+ {
+ if (File.Exists(filePath))
+ {
+ var json = File.ReadAllText(filePath);
+ var loaded = JsonSerializer.Deserialize(json, SerializerOptions);
+ if (loaded != null)
+ {
+ loaded.SymbolServerPaths ??= new List();
+ return loaded;
+ }
+ }
+ }
+#pragma warning disable CA1031 // Do not catch general exception types - corrupt settings should fall back to defaults
+ catch
+ {
+ // Fall through to defaults on any read/parse failure
+ }
+#pragma warning restore CA1031
+
+ return new PersistedShape
+ {
+ UseSymbolServer = false,
+ SymbolServerPaths = new List { DefaultPublicMsftSymbolServer },
+ };
+ }
+
+ private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
+
+ internal sealed class PersistedShape
+ {
+ public bool UseSymbolServer { get; set; }
+ public List SymbolServerPaths { get; set; } = new();
+ }
+}
diff --git a/src/SizeBench.GUI/Settings/IAppSettings.cs b/src/SizeBench.GUI/Settings/IAppSettings.cs
new file mode 100644
index 0000000..b38a82b
--- /dev/null
+++ b/src/SizeBench.GUI/Settings/IAppSettings.cs
@@ -0,0 +1,10 @@
+namespace SizeBench.GUI.Settings;
+
+public interface IAppSettings
+{
+ bool UseSymbolServer { get; set; }
+
+ IList SymbolServerPaths { get; }
+
+ void SetSymbolServerPaths(IEnumerable paths);
+}
diff --git a/src/SizeBench.GUI/SizeBench.GUI.csproj b/src/SizeBench.GUI/SizeBench.GUI.csproj
index 954980f..1732fa7 100644
--- a/src/SizeBench.GUI/SizeBench.GUI.csproj
+++ b/src/SizeBench.GUI/SizeBench.GUI.csproj
@@ -27,7 +27,10 @@
+
+
+
diff --git a/src/SizeBench.GUI/Windows/OpenSingleBinaryWindowViewModel.cs b/src/SizeBench.GUI/Windows/OpenSingleBinaryWindowViewModel.cs
index 1310fff..ed2e9cd 100644
--- a/src/SizeBench.GUI/Windows/OpenSingleBinaryWindowViewModel.cs
+++ b/src/SizeBench.GUI/Windows/OpenSingleBinaryWindowViewModel.cs
@@ -13,6 +13,7 @@ public OpenSingleBinaryWindowViewModel(SelectSingleBinaryAndPDBControlViewModel
this.SelectSingleBinaryAndPDBControlViewModel.PropertyChanged += (s, e) => EnableOKButtonIfReady();
this.SelectSessionOptionsControlViewModel = ssoViewModel;
+ this.SelectSessionOptionsControlViewModel.PropertyChanged += (s, e) => EnableOKButtonIfReady();
}
public SelectSingleBinaryAndPDBControlViewModel SelectSingleBinaryAndPDBControlViewModel { get; }
@@ -44,8 +45,22 @@ public bool OKEnabled
private void EnableOKButtonIfReady()
{
- this.OKEnabled = File.Exists(this.SelectSingleBinaryAndPDBControlViewModel.PDBPath) &&
- File.Exists(this.SelectSingleBinaryAndPDBControlViewModel.BinaryPath);
+ if (!File.Exists(this.SelectSingleBinaryAndPDBControlViewModel.BinaryPath))
+ {
+ this.OKEnabled = false;
+ return;
+ }
+
+ if (File.Exists(this.SelectSingleBinaryAndPDBControlViewModel.PDBPath))
+ {
+ this.OKEnabled = true;
+ return;
+ }
+
+ // No explicit PDB on disk - allow proceeding only when the symbol server fallback is configured
+ // with at least one path, so that DIA has somewhere to look for the matching PDB.
+ this.OKEnabled = this.SelectSessionOptionsControlViewModel.UseSymbolServer &&
+ this.SelectSessionOptionsControlViewModel.HasAnySymbolServerPaths;
}
}
diff --git a/src/SizeBench.GUI/Windows/SelectSessionOptionsControl.xaml b/src/SizeBench.GUI/Windows/SelectSessionOptionsControl.xaml
index e09fa34..22fa656 100644
--- a/src/SizeBench.GUI/Windows/SelectSessionOptionsControl.xaml
+++ b/src/SizeBench.GUI/Windows/SelectSessionOptionsControl.xaml
@@ -7,31 +7,59 @@
d:DesignWidth="500" d:Background="White"
MaxWidth="600">
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- Some sources of symbols (like PDATA and XDATA) can take a long time to parse out of large binaries, so
- you can disable them to open a binary up much faster if you're only looking for certain things like code.
- Hover over any of the checkboxes to see a description of this type of symbol.
-
-
- Note: if some symbol sources are disabled, then SizeBench will show a subset of its normal data.
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Some sources of symbols (like PDATA and XDATA) can take a long time to parse out of large binaries, so
+ you can disable them to open a binary up much faster if you're only looking for certain things like code.
+ Hover over any of the checkboxes to see a description of this type of symbol.
+
+
+ Note: if some symbol sources are disabled, then SizeBench will show a subset of its normal data.
+
+
+
+
+
+
+
+
+
+ One symbol server path per line. Each line is a standard symsrv-style entry, for example:
+
+ srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
+
+ The list below is saved across sessions.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/SizeBench.GUI/Windows/SelectSessionOptionsControlViewModel.cs b/src/SizeBench.GUI/Windows/SelectSessionOptionsControlViewModel.cs
index 6a1cf5c..c7d03bb 100644
--- a/src/SizeBench.GUI/Windows/SelectSessionOptionsControlViewModel.cs
+++ b/src/SizeBench.GUI/Windows/SelectSessionOptionsControlViewModel.cs
@@ -1,14 +1,34 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using SizeBench.AnalysisEngine;
+using SizeBench.GUI.Settings;
namespace SizeBench.GUI.Windows;
public sealed class SelectSessionOptionsControlViewModel : INotifyPropertyChanged
{
+ private readonly IAppSettings? _appSettings;
+
+ public SelectSessionOptionsControlViewModel() : this(null) { }
+
+ public SelectSessionOptionsControlViewModel(IAppSettings? appSettings)
+ {
+ this._appSettings = appSettings;
+
+ if (appSettings != null)
+ {
+ this._useSymbolServer = appSettings.UseSymbolServer;
+ this._symbolServerPathsText = String.Join(Environment.NewLine, appSettings.SymbolServerPaths);
+ }
+ }
+
public SymbolSourcesSupported SymbolSourcesSupported { get; private set; } = SymbolSourcesSupported.All;
- public SessionOptions SessionOptions => new SessionOptions() { SymbolSourcesSupported = this.SymbolSourcesSupported };
+ public SessionOptions SessionOptions => new SessionOptions()
+ {
+ SymbolSourcesSupported = this.SymbolSourcesSupported,
+ SymbolServerSearchPath = this.UseSymbolServer ? BuildSymbolSearchPath() : null,
+ };
public bool CodeSymbolsSupported
{
@@ -46,6 +66,48 @@ public bool OtherPESymbolsSupported
set => SetFlagOnSymbolsSupported(SymbolSourcesSupported.OtherPESymbols, value);
}
+ private bool _useSymbolServer;
+ public bool UseSymbolServer
+ {
+ get => this._useSymbolServer;
+ set
+ {
+ if (this._useSymbolServer != value)
+ {
+ this._useSymbolServer = value;
+ this._appSettings?.UseSymbolServer = value;
+ RaiseOnPropertyChanged(String.Empty);
+ }
+ }
+ }
+
+ private string _symbolServerPathsText = String.Empty;
+ public string SymbolServerPathsText
+ {
+ get => this._symbolServerPathsText;
+ set
+ {
+ value ??= String.Empty;
+ if (this._symbolServerPathsText != value)
+ {
+ this._symbolServerPathsText = value;
+ this._appSettings?.SetSymbolServerPaths(ParseLines(value));
+ RaiseOnPropertyChanged(String.Empty);
+ }
+ }
+ }
+
+ public bool HasAnySymbolServerPaths => ParseLines(this._symbolServerPathsText).Any();
+
+ private string BuildSymbolSearchPath()
+ => String.Join(";", ParseLines(this._symbolServerPathsText));
+
+ private static IEnumerable ParseLines(string text)
+ => (text ?? String.Empty)
+ .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
+ .Select(p => p.Trim())
+ .Where(p => p.Length > 0);
+
private void SetFlagOnSymbolsSupported(SymbolSourcesSupported flag, bool flagValue)
{
if (flagValue)
diff --git a/src/SizeBench.GUI/WindsorInstaller.cs b/src/SizeBench.GUI/WindsorInstaller.cs
index 1ad78a9..3e88387 100644
--- a/src/SizeBench.GUI/WindsorInstaller.cs
+++ b/src/SizeBench.GUI/WindsorInstaller.cs
@@ -52,6 +52,10 @@ public void Install(IWindsorContainer container, IConfigurationStore store)
.Instance(new SessionFactory())
.LifestyleSingleton());
+ container.Register(Component.For()
+ .ImplementedBy()
+ .LifestyleSingleton());
+
container.Register(Classes.FromAssembly(typeof(MainWindow).Assembly)
.IncludeNonPublicTypes()
.Where(c => c.Name.EndsWith("ViewModel", StringComparison.Ordinal) &&