-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathModuleInfoHelper.cs
More file actions
145 lines (134 loc) · 10.9 KB
/
ModuleInfoHelper.cs
File metadata and controls
145 lines (134 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
public static class ModuleInfoHelper {
private static readonly Regex rgxPDBName = new(@"^(?<pdb>.+)(\.pdb)$", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
private static readonly Regex rgxFileName = new(@"^(?<module>.+)\.(dll|exe)$", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
/// <summary>
/// Parse the input and return a set of resolved Symbol objects
/// </summary>
public async static Task<Dictionary<string, Symbol>> ParseModuleInfoAsync(List<StackDetails> listOfCallStacks, CancellationTokenSource cts) {
var syms = new Dictionary<string, Symbol>();
bool anyTaskFailed = false;
await Task.Run(() => Parallel.ForEach(listOfCallStacks.Where(c => c.Callstack.Contains(",")).Select(c => c.CallstackFrames), lines => {
if (cts.IsCancellationRequested) return;
Contract.Requires(lines.Count > 0);
foreach (var line in lines) {
if (cts.IsCancellationRequested) return;
string moduleName = null, pdbName = null;
// foreach line, split into comma-delimited fields
var fields = line.Split(',');
// only attempt to process further if this line does look like it has delimited fields
if (fields.Length >= 3) {
Guid pdbGuid = Guid.Empty;
foreach (var field in fields.Select(f => f.Trim().TrimEnd('"').TrimStart('"'))) {
// for each field, attempt using regexes to detect file name and GUIDs
if (Guid.TryParse(field, out Guid tmpGuid)) pdbGuid = tmpGuid;
if (string.IsNullOrEmpty(moduleName)) {
var matchFilename = rgxFileName.Match(field);
if (matchFilename.Success) moduleName = matchFilename.Groups["module"].Value;
}
if (string.IsNullOrEmpty(pdbName)) {
var matchPDBName = rgxPDBName.Match(field);
if (matchPDBName.Success) pdbName = matchPDBName.Groups["pdb"].Value;
}
}
_ = int.TryParse(fields[fields.Length - 1], out int pdbAge); // assumption is that last field is pdbAge
pdbName = string.IsNullOrEmpty(pdbName) ? moduleName : pdbName; // fall back to module name as PDB name
// check if we have all 3 details
if (!string.IsNullOrEmpty(pdbName) && pdbAge != int.MinValue && pdbGuid != Guid.Empty) {
lock (syms) {
if (!syms.TryGetValue(moduleName, out Symbol existingSym)) syms.Add(moduleName, new Symbol() { ModuleName = moduleName, PDBName = pdbName + ".pdb", PDBAge = pdbAge, PDBGuid = pdbGuid.ToString("N") });
else if (!(existingSym.ModuleName == moduleName && existingSym.PDBName == pdbName + ".pdb" && existingSym.PDBAge == pdbAge && existingSym.PDBGuid == pdbGuid.ToString("N"))) {
anyTaskFailed = true;
return;
}
}
}
}
}
}));
return cts.IsCancellationRequested ? new Dictionary<string, Symbol>() : (anyTaskFailed ? null : syms);
}
public async static Task<(Dictionary<string, Symbol>, List<StackDetails>)> ParseModuleInfoXMLAsync(List<StackDetails> listOfCallStacks, CancellationTokenSource cts) {
var syms = new Dictionary<string, Symbol>();
bool anyTaskFailed = false;
await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
var latestMappedModuleNames = new Dictionary<string, string>();
if (cts.IsCancellationRequested) return;
var outCallstack = new StringBuilder();
// sniff test to allow for quick exit if input has no XML at all
if (currItem.Callstack.Contains("<frame")) {
// use a multi-line regex replace to reassemble XML fragments which were split across lines
currItem.Callstack = Regex.Replace(currItem.Callstack, @"(?<prefix>\<frame[^\/\>]*?)(?<newline>(\r\n|\n))(?<suffix>.*?\/\>\s*?$)", @"${prefix}${suffix}", RegexOptions.Multiline);
if (cts.IsCancellationRequested) return;
// ensure that each <frame> element starts on a newline
currItem.Callstack = Regex.Replace(currItem.Callstack, @"/\>\s*\<frame", "/>\r\n<frame");
if (cts.IsCancellationRequested) return;
// next, replace any pre-post stuff from the XML frame lines
currItem.Callstack = Regex.Replace(currItem.Callstack, @"(?<prefix>.*?)(?<retain>\<frame.+\/\>)(?<suffix>.*?)", "${retain}");
if (cts.IsCancellationRequested) return;
using var reader = new StringReader(currItem.Callstack);
string line;
while ((line = reader.ReadLine()) != null) {
if (cts.IsCancellationRequested) return;
if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("<frame")) { // only attempt further formal XML parsing if a simple text check works
try {
using var sreader = new StringReader(line);
using var xmlReader = XmlReader.Create(sreader, new XmlReaderSettings() { XmlResolver = null });
if (xmlReader.Read()) {
// seems to be XML; start reading attributes
var moduleNameAttributeVal = xmlReader.GetAttribute("module");
if (string.IsNullOrEmpty(moduleNameAttributeVal)) moduleNameAttributeVal = xmlReader.GetAttribute("name");
var moduleName = Path.GetFileNameWithoutExtension(moduleNameAttributeVal);
var addressAttributeVal = xmlReader.GetAttribute("address");
ulong addressIfPresent = string.IsNullOrEmpty(addressAttributeVal) ? ulong.MinValue : Convert.ToUInt64(addressAttributeVal, 16);
var rvaAttributeVal = xmlReader.GetAttribute("rva");
ulong rvaIfPresent = string.IsNullOrEmpty(rvaAttributeVal) ? ulong.MinValue : Convert.ToUInt64(rvaAttributeVal, 16);
ulong calcBaseAddress = ulong.MinValue;
if (rvaIfPresent != ulong.MinValue && addressIfPresent != ulong.MinValue) calcBaseAddress = addressIfPresent - rvaIfPresent;
var pdbGuid = xmlReader.GetAttribute("guid");
var pdbAge = xmlReader.GetAttribute("age");
string uniqueModuleName;
// 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
if (pdbGuid != null && pdbAge != null) {
// PDB GUID and age are valid
uniqueModuleName = $"{pdbGuid.Replace("-", string.Empty).ToUpper()}{pdbAge}";
if (latestMappedModuleNames.ContainsKey(moduleName)) latestMappedModuleNames[moduleName] = uniqueModuleName;
else latestMappedModuleNames.Add(moduleName, uniqueModuleName);
} else {
// This frame / line is incomplete to the extent that we don't even have the PDB GUID and / or the PDB age - return the input as-is
if (!latestMappedModuleNames.TryGetValue(moduleName, out uniqueModuleName)) {
outCallstack.AppendLine(line);
continue;
}
}
lock (syms) {
if (syms.TryGetValue(uniqueModuleName, out var existingEntry)) {
if (ulong.MinValue == existingEntry.CalculatedModuleBaseAddress) existingEntry.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;
else if (ulong.MinValue != addressIfPresent && ulong.MinValue != syms[uniqueModuleName].CalculatedModuleBaseAddress)
rvaAsIsOrDerived = "0x" + (addressIfPresent - syms[uniqueModuleName].CalculatedModuleBaseAddress).ToString("X");
if (string.IsNullOrEmpty(rvaAsIsOrDerived)) { throw new NullReferenceException(); }
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;
}
} catch (Exception ex) {
if (!(ex is ArgumentNullException || ex is NullReferenceException || ex is XmlException || ex is FormatException)) { throw; }
}
}
// pass-through this line as it is either non-XML, 0-length or whitespace-only
outCallstack.AppendLine(line);
}
currItem.Callstack = outCallstack.ToString().Trim();
}
}));
return cts.IsCancellationRequested ? (new Dictionary<string, Symbol>(), new List<StackDetails>()) : (anyTaskFailed ? null : syms, listOfCallStacks);
}
}
}