diff --git a/Engine/StackResolver.cs b/Engine/StackResolver.cs index 1f51290..98df754 100644 --- a/Engine/StackResolver.cs +++ b/Engine/StackResolver.cs @@ -72,7 +72,7 @@ public bool IsInputSingleLine(string text, string patternsToTreatAsMultiline) { /// Runs through each of the frames in a call stack and looks up symbols for each private string ResolveSymbols(Dictionary _diautils, string[] callStackLines, string symPath, bool searchPDBsRecursively, bool cachePDB, bool includeSourceInfo, bool relookupSource, bool includeOffsets, bool showInlineFrames, List modulesToIgnore, CancellationTokenSource cts) { var finalCallstack = new StringBuilder(); - int frameNum = int.MinValue; + int runningFrameNum = int.MinValue; foreach (var iterFrame in callStackLines) { if (cts.IsCancellationRequested) { StatusMessage = OperationCanceled; PercentComplete = 0; return OperationCanceled; } // hard-coded find-replace for XML markup - useful when importing from XML histograms @@ -127,9 +127,10 @@ private string ResolveSymbols(Dictionary _diautils, string[] ca var matchedModuleName = match.Groups["module"].Value; // maybe we have a "not well-known" module, attempt to (best effort) find PDB for it. if (!_diautils.ContainsKey(matchedModuleName)) DiaUtil.LocateandLoadPDBs(_diautils, symPath, searchPDBsRecursively, new List() { matchedModuleName }, cachePDB, modulesToIgnore); - frameNum = string.IsNullOrWhiteSpace(match.Groups["framenum"].Value) ? int.MinValue : frameNum == int.MinValue ? Convert.ToInt32(match.Groups["framenum"].Value, 16) : frameNum; + int frameNumFromInput = string.IsNullOrWhiteSpace(match.Groups["framenum"].Value) ? int.MinValue : Convert.ToInt32(match.Groups["framenum"].Value, 16); + if (frameNumFromInput != int.MinValue && runningFrameNum == int.MinValue) runningFrameNum = frameNumFromInput; if (_diautils.ContainsKey(matchedModuleName)) { - string processedFrame = ProcessFrameModuleOffset(_diautils, ref frameNum, matchedModuleName, match.Groups["offset"].Value, includeSourceInfo, includeOffsets, showInlineFrames); + string processedFrame = ProcessFrameModuleOffset(_diautils, frameNumFromInput, ref runningFrameNum, matchedModuleName, match.Groups["offset"].Value, includeSourceInfo, includeOffsets, showInlineFrames); if (!string.IsNullOrEmpty(processedFrame)) finalCallstack.AppendLine(processedFrame); // typically this is because we could not find the offset in any known function range else finalCallstack.AppendLine(currentFrame); } @@ -162,7 +163,7 @@ private bool TryObtainModuleOffset(ulong virtAddress, out string moduleName, out /// This is the most important function in this whole utility! It uses DIA to lookup the symbol based on RVA offset /// It also looks up line number information if available and then formats all of this information for returning to caller - private string ProcessFrameModuleOffset(Dictionary _diautils, ref int frameNum, string moduleName, string offset, bool includeSourceInfo, bool includeOffset, bool showInlineFrames) { + private string ProcessFrameModuleOffset(Dictionary _diautils, int frameNumFromInput, ref int frameNum, string moduleName, string offset, bool includeSourceInfo, bool includeOffset, bool showInlineFrames) { bool useUndecorateLogic = false; // the offsets in the XE output are in hex, so we convert to base-10 accordingly @@ -233,6 +234,7 @@ private string ProcessFrameModuleOffset(Dictionary _diautils, r } if (frameNum != int.MinValue) { + if (frameNumFromInput == 0) frameNum = frameNumFromInput; var withFrameNums = new StringBuilder(); var resultLines = result.Split('\n'); foreach (var line in resultLines) { diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 57099a9..be5e179 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -494,6 +494,22 @@ private string PrepareLargeXEventInput() { Assert.AreEqual(expected.Trim(), ret.Trim()); } + /// End-to-end test with stacks being resolved based on symbols from symsrv, and to check that frame numbers are preserved + [TestMethod][TestCategory("Unit")] public async Task E2ESymSrvXMLFramesWithInlineFramesAndRenumbering() { + using var csr = new StackResolver(); + using var cts = new CancellationTokenSource(); + var pdbPath = @"srv*https://msdl.microsoft.com/download/symbols"; + var input = "" + +"\r\n" + +"\r\n\r\n" + +"" + +""; + + var ret = await csr.ResolveCallstacksAsync(await csr.GetListofCallStacksAsync(input, false, cts), pdbPath, false, null, false, true, false, true, true, false, null, cts); + var expected = "00 ntdll!NtWaitForSingleObject+20\r\n01 (Inline Function) Wdf01000!Mx::MxLeaveCriticalRegion+12 (minkernel\\wdf\\framework\\shared\\inc\\primitives\\km\\MxGeneralKm.h:198)\r\n02 (Inline Function) Wdf01000!FxWaitLockInternal::ReleaseLock+62 (minkernel\\wdf\\framework\\shared\\inc\\private\\common\\FxWaitLock.hpp:305)\r\n03 (Inline Function) Wdf01000!FxEnumerationInfo::ReleaseParentPowerStateLock+62 (minkernel\\wdf\\framework\\shared\\inc\\private\\common\\FxPkgPnp.hpp:510)\r\n04 Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143 (minkernel\\wdf\\framework\\shared\\inc\\private\\common\\FxPkgPnp.hpp:4127)\r\n05 KERNELBASE!WaitForSingleObjectEx+147\r\n00 ntdll!NtWaitForSingleObject+20\r\n01 KERNELBASE!WaitForSingleObjectEx+147"; + Assert.AreEqual(expected.Trim(), ret.Trim()); + } + /// End-to-end test with stacks being resolved based on symbols from symsrv, with XML-encoded input (as is usually seen in clients like SSMS). [TestMethod][TestCategory("Unit")] public async Task E2ESymSrvXMLFramesEncoded() { using var csr = new StackResolver();