Skip to content

Commit f6ec171

Browse files
Handle very large inputs (#153)
- Switch to using List<string> instead of string[] to avoid exceeding array dimension limits when parsing large inputs. - Operate on individual lines when returning the CallstackFrames property, to avoid having to hit string.Split() array limits. - Update other method signatures to use List<string> accordingly. These changes fix #149.
1 parent 5701787 commit f6ec171

File tree

4 files changed

+38
-32
lines changed

4 files changed

+38
-32
lines changed

Engine/DLLOrdinalHelper.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ internal void Initialize() {
1313
}
1414

1515
/// This function loads DLLs from a specified path, so that we can then build the DLL export's ordinal / address map
16-
internal string[] LoadDllsIfApplicable(string[] callstackFrames, bool recurse, List<string> dllPaths) {
16+
internal List<string> LoadDllsIfApplicable(List<string> callstackFrames, bool recurse, List<string> dllPaths) {
1717
if (dllPaths == null) return callstackFrames;
18-
19-
var processedFrames = new string[callstackFrames.Length];
20-
for (var idx = 0; idx < callstackFrames.Length; idx++) {
21-
var callstack = callstackFrames[idx];
18+
var processedFrames = new List<string>(callstackFrames.Count);
19+
foreach (var callstack in callstackFrames) {
2220
// first we seek out distinct module names in this call stack
2321
// 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
2422
// sample frames are given below
@@ -47,7 +45,7 @@ internal string[] LoadDllsIfApplicable(string[] callstackFrames, bool recurse, L
4745

4846
// finally do a pattern based replace the replace method calls a delegate (ReplaceOrdinalWithRealOffset) which figures
4947
// out the start address of the ordinal and then computes the actual offset
50-
processedFrames[idx] = fullpattern.Replace(callstack, ReplaceOrdinalWithRealOffset);
48+
processedFrames.Add(fullpattern.Replace(callstack, ReplaceOrdinalWithRealOffset));
5149
}
5250
return processedFrames;
5351
}

Engine/ModuleInfoHelper.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public async static Task<Dictionary<string, Symbol>> ParseModuleInfoAsync(List<S
1313
bool anyTaskFailed = false;
1414
await Task.Run(() => Parallel.ForEach(listOfCallStacks.Where(c => c.Callstack.Contains(",")).Select(c => c.CallstackFrames), lines => {
1515
if (cts.IsCancellationRequested) return;
16-
Contract.Requires(lines.Length > 0);
16+
Contract.Requires(lines.Count > 0);
1717
foreach (var line in lines) {
1818
if (cts.IsCancellationRequested) return;
1919
string moduleName = null, pdbName = null;
@@ -75,26 +75,27 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
7575
// next, replace any pre-post stuff from the XML frame lines
7676
currItem.Callstack = Regex.Replace(currItem.Callstack, @"(?<prefix>.*?)(?<retain>\<frame.+\/\>)(?<suffix>.*?)", "${retain}");
7777
if (cts.IsCancellationRequested) return;
78-
79-
foreach (var line in currItem.Callstack.Split('\n')) {
78+
using var reader = new StringReader(currItem.Callstack);
79+
string line;
80+
while ((line = reader.ReadLine()) != null) {
8081
if (cts.IsCancellationRequested) return;
8182
if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("<frame")) { // only attempt further formal XML parsing if a simple text check works
8283
try {
8384
using var sreader = new StringReader(line);
84-
using var reader = XmlReader.Create(sreader, new XmlReaderSettings() { XmlResolver = null });
85-
if (reader.Read()) {
85+
using var xmlReader = XmlReader.Create(sreader, new XmlReaderSettings() { XmlResolver = null });
86+
if (xmlReader.Read()) {
8687
// seems to be XML; start reading attributes
87-
var moduleNameAttributeVal = reader.GetAttribute("module");
88-
if (string.IsNullOrEmpty(moduleNameAttributeVal)) moduleNameAttributeVal = reader.GetAttribute("name");
88+
var moduleNameAttributeVal = xmlReader.GetAttribute("module");
89+
if (string.IsNullOrEmpty(moduleNameAttributeVal)) moduleNameAttributeVal = xmlReader.GetAttribute("name");
8990
var moduleName = Path.GetFileNameWithoutExtension(moduleNameAttributeVal);
90-
var addressAttributeVal = reader.GetAttribute("address");
91+
var addressAttributeVal = xmlReader.GetAttribute("address");
9192
ulong addressIfPresent = string.IsNullOrEmpty(addressAttributeVal) ? ulong.MinValue : Convert.ToUInt64(addressAttributeVal, 16);
92-
var rvaAttributeVal = reader.GetAttribute("rva");
93+
var rvaAttributeVal = xmlReader.GetAttribute("rva");
9394
ulong rvaIfPresent = string.IsNullOrEmpty(rvaAttributeVal) ? ulong.MinValue : Convert.ToUInt64(rvaAttributeVal, 16);
9495
ulong calcBaseAddress = ulong.MinValue;
9596
if (rvaIfPresent != ulong.MinValue && addressIfPresent != ulong.MinValue) calcBaseAddress = addressIfPresent - rvaIfPresent;
96-
var pdbGuid = reader.GetAttribute("guid");
97-
var pdbAge = reader.GetAttribute("age");
97+
var pdbGuid = xmlReader.GetAttribute("guid");
98+
var pdbAge = xmlReader.GetAttribute("age");
9899
string uniqueModuleName;
99100
// Create a map of the last mapped module names to handle future cases when the frame is "truncated" and the above PDB details are not available
100101
if (pdbGuid != null && pdbAge != null) {
@@ -112,7 +113,7 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
112113
lock (syms) {
113114
if (syms.TryGetValue(uniqueModuleName, out var existingEntry)) {
114115
if (ulong.MinValue == existingEntry.CalculatedModuleBaseAddress) existingEntry.CalculatedModuleBaseAddress = calcBaseAddress;
115-
} 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 });
116+
} 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 });
116117
}
117118
string rvaAsIsOrDerived = null;
118119
if (ulong.MinValue != rvaIfPresent) rvaAsIsOrDerived = rvaAttributeVal;
@@ -121,7 +122,7 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
121122

122123
if (string.IsNullOrEmpty(rvaAsIsOrDerived)) { throw new NullReferenceException(); }
123124

124-
var frameNumHex = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0:x2}", int.Parse(reader.GetAttribute("id")));
125+
var frameNumHex = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0:x2}", int.Parse(xmlReader.GetAttribute("id")));
125126
// transform the XML into a simple module+offset notation
126127
outCallstack.AppendFormat($"{frameNumHex} {uniqueModuleName}+{rvaAsIsOrDerived}{Environment.NewLine}");
127128
continue;

Engine/StackDetails.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,21 @@ public string Callstack {
2424
set { this._callStack = value; }
2525
}
2626

27-
public string[] CallstackFrames {
27+
public List<string> CallstackFrames {
2828
get {
2929
// sometimes we see call stacks which are arranged horizontally (this typically is seen when copy-pasting directly
3030
// from the SSMS XEvent window (copying the callstack field without opening it in its own viewer)
3131
// in that case, space is a valid delimiter, and we need to support that as an option
3232
var delims = this._framesOnSingleLine ? new char[] { '\t', '\n' } : new char[] { '\n' };
3333
if (!this._relookupSource && this._framesOnSingleLine) delims = delims.Append(' ').ToArray();
34-
return this._callStack.Replace("\r", string.Empty).Split(delims);
34+
var result = new List<string>();
35+
using (var reader = new StringReader(this._callStack.Replace("\r", string.Empty))) {
36+
string line;
37+
while ((line = reader.ReadLine()) != null) {
38+
result.AddRange(line.Split(delims, StringSplitOptions.RemoveEmptyEntries));
39+
}
40+
}
41+
return result;
3542
}
3643
}
3744
public string Resolvedstack {

Engine/StackResolver.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,22 @@ public async Task<Tuple<int, string>> ExtractFromXELAsync(string[] xelFiles, boo
3939
}
4040

4141
/// Convert virtual-address only type frames to their module+offset format
42-
private string[] PreProcessVAs(string[] callStackLines, CancellationTokenSource cts) {
43-
string[] retval = new string[callStackLines.Length];
44-
int frameNum = 0;
42+
private List<string> PreProcessVAs(List<string> callStackLines, CancellationTokenSource cts) {
43+
var retval = new List<string>(callStackLines.Count);
4544
foreach (var currentFrame in callStackLines) {
4645
if (cts.IsCancellationRequested) return callStackLines;
47-
// let's see if this is an VA-only address
46+
// Check if the frame is a VA-only address
4847
var matchVA = rgxVAOnly.Match(currentFrame);
4948
if (matchVA.Success) {
5049
ulong virtAddress = Convert.ToUInt64(matchVA.Groups["vaddress"].Value, 16);
51-
retval[frameNum] = TryObtainModuleOffset(virtAddress, out string moduleName, out uint offset)
52-
? string.Format(CultureInfo.CurrentCulture, "{0}+0x{1:X}", moduleName, offset)
53-
: currentFrame.Trim();
50+
if (TryObtainModuleOffset(virtAddress, out string moduleName, out uint offset)) {
51+
retval.Add(string.Format(CultureInfo.CurrentCulture, "{0}+0x{1:X}", moduleName, offset));
52+
} else {
53+
retval.Add(currentFrame.Trim());
54+
}
55+
} else {
56+
retval.Add(currentFrame.Trim());
5457
}
55-
else retval[frameNum] = currentFrame.Trim();
56-
57-
frameNum++;
5858
}
5959
return retval;
6060
}
@@ -83,7 +83,7 @@ public bool IsInputVAOnly(string text) {
8383
}
8484

8585
/// Runs through each of the frames in a call stack and looks up symbols for each
86-
private string ResolveSymbols(Dictionary<string, DiaUtil> _diautils, Dictionary<string, string> moduleNamesMap, string[] callStackLines, string userSuppliedSymPath, string symSrvSymPath, bool searchPDBsRecursively, bool cachePDB, bool includeSourceInfo, bool relookupSource, bool includeOffsets, bool showInlineFrames, List<string> modulesToIgnore, CancellationTokenSource cts) {
86+
private string ResolveSymbols(Dictionary<string, DiaUtil> _diautils, Dictionary<string, string> moduleNamesMap, List<string> callStackLines, string userSuppliedSymPath, string symSrvSymPath, bool searchPDBsRecursively, bool cachePDB, bool includeSourceInfo, bool relookupSource, bool includeOffsets, bool showInlineFrames, List<string> modulesToIgnore, CancellationTokenSource cts) {
8787
var finalCallstack = new StringBuilder();
8888
int runningFrameNum = int.MinValue;
8989
foreach (var iterFrame in callStackLines) {

0 commit comments

Comments
 (0)