diff --git a/Engine/DLLOrdinalHelper.cs b/Engine/DLLOrdinalHelper.cs index b82d952..66e41a1 100644 --- a/Engine/DLLOrdinalHelper.cs +++ b/Engine/DLLOrdinalHelper.cs @@ -13,12 +13,10 @@ internal void Initialize() { } /// This function loads DLLs from a specified path, so that we can then build the DLL export's ordinal / address map - internal string[] LoadDllsIfApplicable(string[] callstackFrames, bool recurse, List dllPaths) { + internal List LoadDllsIfApplicable(List callstackFrames, bool recurse, List dllPaths) { if (dllPaths == null) return callstackFrames; - - var processedFrames = new string[callstackFrames.Length]; - for (var idx = 0; idx < callstackFrames.Length; idx++) { - var callstack = callstackFrames[idx]; + var processedFrames = new List(callstackFrames.Count); + foreach (var callstack in callstackFrames) { // first we seek out distinct module names in this call stack // note that such frames will only be seen in the call stack when trace flag 3656 is enabled, but there were no PDBs in the BINN folder // sample frames are given below @@ -47,7 +45,7 @@ internal string[] LoadDllsIfApplicable(string[] callstackFrames, bool recurse, L // finally do a pattern based replace the replace method calls a delegate (ReplaceOrdinalWithRealOffset) which figures // out the start address of the ordinal and then computes the actual offset - processedFrames[idx] = fullpattern.Replace(callstack, ReplaceOrdinalWithRealOffset); + processedFrames.Add(fullpattern.Replace(callstack, ReplaceOrdinalWithRealOffset)); } return processedFrames; } diff --git a/Engine/ModuleInfoHelper.cs b/Engine/ModuleInfoHelper.cs index 96ff1ae..76fe714 100644 --- a/Engine/ModuleInfoHelper.cs +++ b/Engine/ModuleInfoHelper.cs @@ -13,7 +13,7 @@ public async static Task> ParseModuleInfoAsync(List Parallel.ForEach(listOfCallStacks.Where(c => c.Callstack.Contains(",")).Select(c => c.CallstackFrames), lines => { if (cts.IsCancellationRequested) return; - Contract.Requires(lines.Length > 0); + Contract.Requires(lines.Count > 0); foreach (var line in lines) { if (cts.IsCancellationRequested) return; string moduleName = null, pdbName = null; @@ -75,26 +75,27 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => { // next, replace any pre-post stuff from the XML frame lines currItem.Callstack = Regex.Replace(currItem.Callstack, @"(?.*?)(?\)(?.*?)", "${retain}"); if (cts.IsCancellationRequested) return; - - foreach (var line in currItem.Callstack.Split('\n')) { + using var reader = new StringReader(currItem.Callstack); + string line; + while ((line = reader.ReadLine()) != null) { if (cts.IsCancellationRequested) return; if (!string.IsNullOrWhiteSpace(line) && line.StartsWith(" Parallel.ForEach(listOfCallStacks, currItem => { lock (syms) { if (syms.TryGetValue(uniqueModuleName, out var existingEntry)) { if (ulong.MinValue == existingEntry.CalculatedModuleBaseAddress) existingEntry.CalculatedModuleBaseAddress = calcBaseAddress; - } else syms.Add(uniqueModuleName, new Symbol() { PDBName = reader.GetAttribute("pdb").ToLower(), ModuleName = moduleName, PDBAge = int.Parse(pdbAge), PDBGuid = Guid.Parse(pdbGuid).ToString("N").ToUpper(), CalculatedModuleBaseAddress = calcBaseAddress }); + } else syms.Add(uniqueModuleName, new Symbol() { PDBName = xmlReader.GetAttribute("pdb").ToLower(), ModuleName = moduleName, PDBAge = int.Parse(pdbAge), PDBGuid = Guid.Parse(pdbGuid).ToString("N").ToUpper(), CalculatedModuleBaseAddress = calcBaseAddress }); } string rvaAsIsOrDerived = null; if (ulong.MinValue != rvaIfPresent) rvaAsIsOrDerived = rvaAttributeVal; @@ -121,7 +122,7 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => { if (string.IsNullOrEmpty(rvaAsIsOrDerived)) { throw new NullReferenceException(); } - var frameNumHex = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0:x2}", int.Parse(reader.GetAttribute("id"))); + var frameNumHex = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0:x2}", int.Parse(xmlReader.GetAttribute("id"))); // transform the XML into a simple module+offset notation outCallstack.AppendFormat($"{frameNumHex} {uniqueModuleName}+{rvaAsIsOrDerived}{Environment.NewLine}"); continue; diff --git a/Engine/StackDetails.cs b/Engine/StackDetails.cs index 3b3ef70..614c459 100644 --- a/Engine/StackDetails.cs +++ b/Engine/StackDetails.cs @@ -24,14 +24,21 @@ public string Callstack { set { this._callStack = value; } } - public string[] CallstackFrames { + public List CallstackFrames { get { // sometimes we see call stacks which are arranged horizontally (this typically is seen when copy-pasting directly // from the SSMS XEvent window (copying the callstack field without opening it in its own viewer) // in that case, space is a valid delimiter, and we need to support that as an option var delims = this._framesOnSingleLine ? new char[] { '\t', '\n' } : new char[] { '\n' }; if (!this._relookupSource && this._framesOnSingleLine) delims = delims.Append(' ').ToArray(); - return this._callStack.Replace("\r", string.Empty).Split(delims); + var result = new List(); + using (var reader = new StringReader(this._callStack.Replace("\r", string.Empty))) { + string line; + while ((line = reader.ReadLine()) != null) { + result.AddRange(line.Split(delims, StringSplitOptions.RemoveEmptyEntries)); + } + } + return result; } } public string Resolvedstack { diff --git a/Engine/StackResolver.cs b/Engine/StackResolver.cs index a384991..902dcd6 100644 --- a/Engine/StackResolver.cs +++ b/Engine/StackResolver.cs @@ -39,22 +39,22 @@ public async Task> ExtractFromXELAsync(string[] xelFiles, boo } /// Convert virtual-address only type frames to their module+offset format - private string[] PreProcessVAs(string[] callStackLines, CancellationTokenSource cts) { - string[] retval = new string[callStackLines.Length]; - int frameNum = 0; + private List PreProcessVAs(List callStackLines, CancellationTokenSource cts) { + var retval = new List(callStackLines.Count); foreach (var currentFrame in callStackLines) { if (cts.IsCancellationRequested) return callStackLines; - // let's see if this is an VA-only address + // Check if the frame is a VA-only address var matchVA = rgxVAOnly.Match(currentFrame); if (matchVA.Success) { ulong virtAddress = Convert.ToUInt64(matchVA.Groups["vaddress"].Value, 16); - retval[frameNum] = TryObtainModuleOffset(virtAddress, out string moduleName, out uint offset) - ? string.Format(CultureInfo.CurrentCulture, "{0}+0x{1:X}", moduleName, offset) - : currentFrame.Trim(); + if (TryObtainModuleOffset(virtAddress, out string moduleName, out uint offset)) { + retval.Add(string.Format(CultureInfo.CurrentCulture, "{0}+0x{1:X}", moduleName, offset)); + } else { + retval.Add(currentFrame.Trim()); + } + } else { + retval.Add(currentFrame.Trim()); } - else retval[frameNum] = currentFrame.Trim(); - - frameNum++; } return retval; } @@ -83,7 +83,7 @@ public bool IsInputVAOnly(string text) { } /// 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) { + private string ResolveSymbols(Dictionary _diautils, Dictionary moduleNamesMap, List 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(); int runningFrameNum = int.MinValue; foreach (var iterFrame in callStackLines) {