diff --git a/Engine/StackResolver.cs b/Engine/StackResolver.cs index 6f6a080..2fd1d6d 100644 --- a/Engine/StackResolver.cs +++ b/Engine/StackResolver.cs @@ -25,7 +25,7 @@ public class StackResolver : IDisposable { private static readonly RegexOptions rgxOptions = RegexOptions.ExplicitCapture | RegexOptions.Compiled; private static readonly Regex rgxModuleOffsetFrame = new(@"((?[0-9a-fA-F]+)\s+)*(?\w+)(\.(dll|exe))*\s*\+\s*(0[xX])*(?[0-9a-fA-F]+)\s*", rgxOptions); - private static readonly Regex rgxVAOnly = new (@"^\s*0[xX](?[0-9a-fA-F]+)\s*$", rgxOptions); + private static readonly Regex rgxVAOnly = new (@"\s*0[xX](?[0-9a-fA-F]+)\s*", rgxOptions); private static readonly Regex rgxAlreadySymbolizedFrame = new (@"((?\d+)\s+)*(?\w+)(\.(dll|exe))*!(?.+?)\s*\+\s*(0[xX])*(?[0-9a-fA-F]+)\s*", rgxOptions); private static readonly Regex rgxmoduleaddress = new (@"^\s*(?.+)(\t+| +)(?(0x)?[0-9a-fA-F`]+)\s*$", RegexOptions.Multiline); @@ -72,6 +72,16 @@ public bool IsInputSingleLine(string text, string patternsToTreatAsMultiline) { return false; } + public bool IsInputVAOnly(string text) { + text = System.Net.WebUtility.HtmlDecode(text); // decode XML markup if present + if (Regex.Match(text, @"\ 0 || rgxModuleOffsetFrame.Matches(text).Count > 0) + return false; + + if (rgxVAOnly.Matches(text).Count > 0) return true; + + return false; + } + /// Runs through each of the frames in a call stack and looks up symbols for each private string ResolveSymbols(Dictionary _diautils, Dictionary moduleNamesMap, string[] callStackLines, string userSuppliedSymPath, string symSrvSymPath, bool searchPDBsRecursively, bool cachePDB, bool includeSourceInfo, bool relookupSource, bool includeOffsets, bool showInlineFrames, List modulesToIgnore, CancellationTokenSource cts) { var finalCallstack = new StringBuilder(); @@ -250,7 +260,10 @@ private string ProcessFrameModuleOffset(Dictionary _diautils, D /// public bool ProcessBaseAddresses(string baseAddressesString) { bool retVal = true; - if (string.IsNullOrEmpty(baseAddressesString)) return true; // not a true error condition so we are okay + LoadedModules.Clear(); // regardless of user input, we always clear the loaded modules list first + + if (string.IsNullOrEmpty(baseAddressesString)) return true; + LoadedModules.Clear(); var mcmodules = rgxmoduleaddress.Matches(baseAddressesString); if (!mcmodules.Cast().Any()) return false; // it is likely that we have malformed input, cannot ignore this so return false. diff --git a/GUI/MainForm.cs b/GUI/MainForm.cs index 93633a7..9652141 100644 --- a/GUI/MainForm.cs +++ b/GUI/MainForm.cs @@ -77,6 +77,10 @@ private bool ValidateInputs() { return false; } + if (this._resolver.IsInputVAOnly(callStackInput.Text) && string.IsNullOrEmpty(this._baseAddressesString) && DialogResult.No == MessageBox.Show(this, + "The input provided seems to be comprised entirely of virtual addresses only, but the required corresponding module information has not been provided. Do you still want to attempt to process this input?", + "Missing module base information", MessageBoxButtons.YesNo, MessageBoxIcon.Warning)) return false; + var res = this._resolver.ProcessBaseAddresses(this._baseAddressesString); if (!res) { MessageBox.Show(this, "Cannot interpret the module base address information. Make sure you just have the output of the following query (no column headers, no other columns) copied from SSMS using the Grid Results\r\n\r\nselect name, base_address from sys.dm_os_loaded_modules where name not like '%.rll'", @@ -284,7 +288,7 @@ private void SelectSQLPDB_Click(object sender, EventArgs e) { } private async void MainForm_Load(object sender, EventArgs e) { - MessageBox.Show(this, "Copyright (c) 2022 Microsoft Corporation. All rights reserved.\r\n\r\nTHIS SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\nUSAGE OF THE MICROSOFT SYMBOL SERVER IS COVERED BY THE LICENSE TERMS PUBLISHED AT https://docs.microsoft.com/legal/windows-sdk/microsoft-symbol-server-license-terms.", "SQLCallStackResolver - Legal Notice", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show(this, "Copyright (c) 2025 Microsoft Corporation. All rights reserved.\r\n\r\nTHIS SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\nUSAGE OF THE MICROSOFT SYMBOL SERVER IS COVERED BY THE LICENSE TERMS PUBLISHED AT https://docs.microsoft.com/legal/windows-sdk/microsoft-symbol-server-license-terms.", "SQLCallStackResolver - Legal Notice", MessageBoxButtons.OK, MessageBoxIcon.Information); DateTime latestReleaseDateTimeServer = DateTime.MinValue; DateTime latestReleaseDateTimeLocal = DateTime.MinValue; var latestReleaseURLs = ConfigurationManager.AppSettings["LatestReleaseURLs"].Split(';'); diff --git a/GUI/MultilineInput.resx b/GUI/MultilineInput.resx index 00abee9..d90537b 100644 --- a/GUI/MultilineInput.resx +++ b/GUI/MultilineInput.resx @@ -1,16 +1,12 @@ -text/microsoft-resx2.0System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089**** If your stacks are hexadecimal addresses only, then please replace entire contents with the output of the following query without column headers and no other columns copied from SSMS using the Grid Results +text/microsoft-resx2.0System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089README: If your stacks are hexadecimal addresses only, then +please replace entire contents with the output of the following query +without column headers from SSMS using the Grid Results: select name, base_address from sys.dm_os_loaded_modules where name not like '%.rll' -SAMPLE values shown below - again, replace ALL of the contents shown in this box with the results of the above query!! +2 SAMPLE rows shown below; replace ALL of the contents shown +in this box (including these instructions) with the results of the above query!! C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqlservr.exe 0x00007FF6191C0000 -C:\windows\SYSTEM32\ntdll.dll 0x00007FFB0C1C0000 -C:\windows\System32\KERNEL32.DLL 0x00007FFB0B4B0000 -C:\windows\System32\KERNELBASE.dll 0x00007FFB092D0000 -... C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqlmin.dll 0x00007FFAE0CB0000 -C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\SQLOS.dll 0x00007FFAE8F70000 -C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqllang.dll 0x00007FFADE700000 -... -C:\windows\System32\MSASN1.dll 0x00007FFB0867000017, 17 \ No newline at end of file +...17, 17 \ No newline at end of file diff --git a/Tests/Tests.cs b/Tests/Tests.cs index d85b02c..2a58b79 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -50,6 +50,35 @@ public class Tests { Assert.IsFalse(csr.IsInputSingleLine("Histogram 0x1\r\n0x1", PatternsToTreatAsMultiline)); } + [TestMethod][TestCategory("Unit")] + public void VAOnlyInputDetection() { + using var csr = new StackResolver(); + Assert.IsFalse(csr.IsInputVAOnly("05 sqldk!SOS_Scheduler::UpdateWaitTimeStats+789")); + Assert.IsFalse(csr.IsInputVAOnly(@"\r\n sqldk+0x40609\r\n")); + Assert.IsFalse(csr.IsInputVAOnly("<frame id=\"00\" address=\"0xf00\" pdb=\"ntdll.pdb\" age=\"1\" guid=\"C374E059-5793-9B92-6525-386A66A2D3F5\" module=\"ntdll.dll\" rva=\"0x9F7E4\" /><" + +"frame id=\"01\" address=\"0xf00\" pdb=\"kernelbase.pdb\" age=\"1\" guid=\"E77E26E7-D1C4-72BB-2C05-DD17624A9E58\" module=\"KERNELBASE.dll\" rva=\"0x38973\" /><" + +"frame id=\"02\" address=\"0xf00\" pdb=\"SqlDK.pdb\" age=\"2\" guid=\"6a193443-3512-464b-8b8e-d905ad930ee6\" module=\"sqldk.dll\" rva=\"0x40609\" />")); + Assert.IsTrue(csr.IsInputVAOnly("callstack\r\n0x00007FFEABD0D919\r\n0x00007FFEABC4D45D\r\n0x00007FFEAC0F7EE0")); + Assert.IsFalse(csr.IsInputVAOnly(@"\r\n sqldk+0x40609 sqldk+40609\r\n")); + Assert.IsFalse(csr.IsInputVAOnly(@"\r\n sqldk+0x40609 sqldk+40609\r\nsqldk+0x40609 sqldk+40609")); + Assert.IsTrue(csr.IsInputVAOnly("0x00007FFEABD0D919\r\n0x00007FFEABC4D45D\r\n0x00007FFEAC0F7EE0\r\n0x00007FFEAC0F80CF\r\n0x00007FFEAC1EE447\r\n0x00007FFEAC1EE6F5")); + Assert.IsTrue(csr.IsInputVAOnly("annotation for histogram #1 0 0x00007FFEABD0D919 0x00007FFEABC4D45D 0x00007FFEAC0F7EE0 0x00007FFEAC0F80CF 0x00007FFEAC1EE447 0x00007FFEAC1EE6F5 0x00007FFEAC1D48B0 0x00007FFEAC71475A 0x00007FFEA9A708F1 0x00007FFEA9991FB9 0x00007FFEA9993D21 0x00007FFEA99B59F1 0x00007FFEA99B5055 0x00007FFEA99B2B8F 0x00007FFEA9675AD1 0x00007FFEA9671EFB 0x00007FFEAA37D83D 0x00007FFEAA37D241 0x00007FFEAA379F98 0x00007FFEA96719CA 0x00007FFEA9672933 0x00007FFEA9672041 0x00007FFEA967A82B 0x00007FFEA9681542\r\n" + + "annotation for histogram #2 1 0x00007FFEABD0D919 0x00007FFEABC4D45D 0x00007FFEAC0F7EE0 0x00007FFEAC0F80CF 0x00007FFEAC1EE447 0x00007FFEAC1EE6F5 0x00007FFEAC1D48B0 0x00007FFEAC71475A 0x00007FFEA9A708F1 0x00007FFEA9991FB9 0x00007FFEA9993D21 0x00007FFEA99B59F1 0x00007FFEA99B5055 0x00007FFEA99B2B8F 0x00007FFEA9675AD1 0x00007FFEA9671EFB 0x00007FFEAA37D83D 0x00007FFEAA37D241 0x00007FFEAA379F98 0x00007FFEA96719CA 0x00007FFEA9672933 0x00007FFEA9672041 0x00007FFEA967A82B 0x00007FFEA9681542\r\n")); + Assert.IsFalse(csr.IsInputVAOnly("" + +"" + +"" + +"]]>" + +"" + +"]]>")); + Assert.IsFalse(csr.IsInputVAOnly("\r\n\r\n<frame id=\"00\" address=\"0xf00\" pdb=\"ntdll.pdb\" age=\"1\" guid=\"C374E059-5793-9B92-6525-386A66A2D3F5\" module=\"ntdll.dll\" rva=\"0x9F7E4\" /><" + +"frame id=\"01\" address=\"0xf00\" pdb=\"kernelbase.pdb\" age=\"1\" guid=\"E77E26E7-D1C4-72BB-2C05-DD17624A9E58\" module=\"KERNELBASE.dll\" rva=\"0x38973\" /><" + +"frame id=\"02\" address=\"0xf00\" pdb=\"SqlDK.pdb\" age=\"2\" guid=\"6a193443-3512-464b-8b8e-d905ad930ee6\" module=\"sqldk.dll\" rva=\"0x40609\" />" + +"\r\n\r\n\r\n\r\n\r\n")); + Assert.IsTrue(csr.IsInputVAOnly("Histogram\r\n0x1\r\n0x1")); + } + /// Validate that "block symbols" in a PDB are resolved correctly. [TestMethod][TestCategory("Unit")] public async Task BlockResolution() { using var csr = new StackResolver(); @@ -713,12 +742,12 @@ public async Task E2EHistogramOffsets() { var pdbPath = @"..\..\..\Tests\TestCases\sqlsyms\13.0.4001.0\x64"; var input = "0x00007FFEABD0D919 0x00007FFEABC4D45D 0x00007FFEAC0F7EE0 0x00007FFEAC0F80CF "; var ret = await csr.ResolveCallstacksAsync(await csr.GetListofCallStacksAsync(input, true, cts), pdbPath, false, null, false, false, false, true, false, false, null, cts); - var expected = "0x00007FFEABD0D919\r\nsqldk!SpinlockBase::Sleep+182\r\nsqlmin!Spinlock<143,7,1>::SpinToAcquireWithExponentialBackoff+363\r\nsqlmin!lck_lockInternal+2042\r\n"; - Assert.AreEqual(expected.Trim(), ret.Trim()); // we just expect the input text back as-is + var expected = "::SpinToAcquireWithExponentialBackoff+363\r\nsqlmin!lck_lockInternal+2042\r\n"; + Assert.AreEqual(expected.Trim(), ret.Trim()); input = "0x00007FFEABD0D919 0x00007FFEABC4D45D 0x00007FFEAC0F7EE0 0x00007FFEAC0F80CF "; ret = await csr.ResolveCallstacksAsync(await csr.GetListofCallStacksAsync(input, true, cts), pdbPath, false, null, false, false, false, true, false, false, null, cts); - expected = "0x00007FFEABD0D919\r\nsqldk!SpinlockBase::Sleep+182\r\nsqlmin!Spinlock<143,7,1>::SpinToAcquireWithExponentialBackoff+363\r\nsqlmin!lck_lockInternal+2042\r\n"; - Assert.AreEqual(expected.Trim(), ret.Trim()); // we just expect the input text back as-is + expected = "::SpinToAcquireWithExponentialBackoff+363\r\nsqlmin!lck_lockInternal+2042\r\n"; + Assert.AreEqual(expected.Trim(), ret.Trim()); } /// End-to-end test with stacks being resolved based on symbols from symsrv.