Skip to content

Commit

Permalink
Support multiple PDB versions for XML frames (#94)
Browse files Browse the repository at this point in the history
* Uniquely identify module names based on PDB GUID+age
* Return error when input has info about multiple PDB versions in CSV metadata
* Add tests for multiple PDB versions in XML frames and also in CSV metadata
* Separate user-supplied and generated sympaths
* Infer sym info for truncated frames from prior ones
* Preserve original module name for final output
  • Loading branch information
arvindshmicrosoft authored Oct 16, 2022
1 parent 6fecc09 commit 9b4228d
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 61 deletions.
32 changes: 24 additions & 8 deletions Engine/DiaUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ internal class DiaUtil {
internal readonly IDiaDataSource _IDiaDataSource;
internal readonly IDiaSession _IDiaSession;
private bool disposedValue = false;
public readonly bool HasSourceInfo = false;
internal readonly bool HasSourceInfo = false;
private static readonly object _syncRoot = new();

internal DiaUtil(string pdbName) {
Expand Down Expand Up @@ -48,26 +48,39 @@ private void ReleaseDiaObjects() {
}

/// This function builds up the PDB map, by searching for matched PDBs (based on name) and constructing the DIA session for each
internal static bool LocateandLoadPDBs(Dictionary<string, DiaUtil> _diautils, string rootPaths, bool recurse, List<string> moduleNames, bool cachePDB, List<string> modulesToIgnore) {
internal static bool LocateandLoadPDBs(Dictionary<string, DiaUtil> _diautils, string userSuppliedSymPath, string symSrvSymPath, bool recurse, Dictionary<string, string> moduleNamesMap, bool cachePDB, List<string> modulesToIgnore) {
var completeSymPath = $"{symSrvSymPath};{userSuppliedSymPath}";
var moduleNames = moduleNamesMap.Keys.ToList();
// loop through each module, trying to find matched PDB files
foreach (string currentModule in moduleNames.Where(m => !modulesToIgnore.Contains(m) && !_diautils.ContainsKey(m))) {
// we only need to search for the PDB if it does not already exist in our map
var cachedPDBFile = Path.Combine(Path.GetTempPath(), "SymCache", currentModule + ".pdb");
lock (_syncRoot) { // the lock is needed to ensure that we do not make multiple copies of PDBs when cachePDB is true
if (!File.Exists(cachedPDBFile)) {
foreach (var currPath in rootPaths.Split(';').Where(p => Directory.Exists(p))) {
var foundFiles = Directory.EnumerateFiles(currPath, currentModule + ".pdb", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
IEnumerable<string> foundFiles = new List<string>();
foreach (var currPath in completeSymPath.Split(';').Where(p => Directory.Exists(p))) {
foundFiles = Directory.EnumerateFiles(currPath, currentModule + ".pdb", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
if (!foundFiles.Any()) {
// repeat the search but with a more relaxed filter. this (somewhat hacky) consideration is required
// for modules like vcruntime140.dll where the PDB name is actually vcruntime140.amd64.pdb
foundFiles = Directory.EnumerateFiles(currPath, currentModule + ".*.pdb", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
if (foundFiles.Any()) break;
} else break;

if (currPath.EndsWith(currentModule)) { // search for subfolder with PDB GUID as the name
foundFiles = Directory.EnumerateFiles(currPath, "*.pdb", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
if (foundFiles.Any()) break;
}
}

if (foundFiles.Any()) {
if (cachePDB) File.Copy(foundFiles.First(), cachedPDBFile);
else cachedPDBFile = foundFiles.First();
break;
// if needed, make a last attempt looking for the original module name - but only amongst user-supplied symbol path folder(s)
if (!foundFiles.Any()) foreach (var currPath in userSuppliedSymPath.Split(';').Where(p => Directory.Exists(p))) {
if (!currPath.EndsWith(currentModule)) foundFiles = Directory.EnumerateFiles(currPath, moduleNamesMap[currentModule] + ".pdb", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}

if (foundFiles?.Count() == 1) { // we need to be sure there is only 1 file which matches
if (cachePDB) File.Copy(foundFiles.First(), cachedPDBFile);
else cachedPDBFile = foundFiles.First();
}
}
}
Expand Down Expand Up @@ -98,6 +111,9 @@ internal static string GetSymbolizedFrame(string moduleName, IDiaSymbol mysym, b
string offsetStr = string.Empty;
if (includeOffset) offsetStr = string.Format(CultureInfo.CurrentCulture, "+{0}", displacement);
var inlineePrefix = isInLinee ? "(Inline Function) " : string.Empty;
// replace any "unique" module names with their original values


return $"{inlineePrefix}{moduleName}!{funcname2}{offsetStr}";
}

Expand Down
46 changes: 35 additions & 11 deletions Engine/ModuleInfoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public static class ModuleInfoHelper {
/// 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 retval = new Dictionary<string, Symbol>();
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.Length > 0);
Expand Down Expand Up @@ -41,20 +42,26 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks.Where(c => c.Callstack.Co
// check if we have all 3 details
if (!string.IsNullOrEmpty(pdbName) && pdbAge != int.MinValue && pdbGuid != Guid.Empty) {
lock (retval) {
if (!retval.ContainsKey(moduleName)) retval.Add(moduleName, new Symbol() { PDBName = pdbName + ".pdb", PDBAge = pdbAge, PDBGuid = pdbGuid.ToString("N") });
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>() : retval;
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
Expand Down Expand Up @@ -86,22 +93,39 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
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 = reader.GetAttribute("guid");
var pdbAge = reader.GetAttribute("age");
string uniqueModuleName;
// createe a map of the last mapped module names to handle cases when the frame is "truncated" and the above PDB details are not available
if (pdbGuid != null && pdbAge != null) {
uniqueModuleName = $"{pdbGuid.Replace("-", string.Empty).ToUpper()}{pdbAge}";
if (latestMappedModuleNames.ContainsKey(moduleName)) latestMappedModuleNames[moduleName] = uniqueModuleName;
else latestMappedModuleNames.Add(moduleName, uniqueModuleName);
} else {
if (!latestMappedModuleNames.TryGetValue(moduleName, out uniqueModuleName)) {
anyTaskFailed = true;
return;
}
}
lock (syms) {
if (syms.TryGetValue(moduleName, out var existingEntry)) {
if (syms.TryGetValue(uniqueModuleName, out var existingEntry)) {
//if (Guid.Parse(reader.GetAttribute("guid")).ToString("N") != existingEntry.PDBGuid || int.Parse(reader.GetAttribute("age")) != existingEntry.PDBAge) {
// anyTaskFailed = true;
// return;
//}
if (ulong.MinValue == existingEntry.CalculatedModuleBaseAddress) existingEntry.CalculatedModuleBaseAddress = calcBaseAddress;
} else syms.Add(moduleName, new Symbol() { PDBName = reader.GetAttribute("pdb").ToLower(), PDBAge = int.Parse(reader.GetAttribute("age")), PDBGuid = Guid.Parse(reader.GetAttribute("guid")).ToString("N"), 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 });
}
string rvaAsIsOrDerived = null;
if (ulong.MinValue != rvaIfPresent) rvaAsIsOrDerived = rvaAttributeVal;
else if (ulong.MinValue != addressIfPresent && ulong.MinValue != syms[moduleName].CalculatedModuleBaseAddress)
rvaAsIsOrDerived = "0x" + (addressIfPresent - syms[moduleName].CalculatedModuleBaseAddress).ToString("X");
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(reader.GetAttribute("id")));
// transform the XML into a simple module+offset notation
outCallstack.AppendFormat($"{frameNumHex} {moduleName}+{rvaAsIsOrDerived}{Environment.NewLine}");
outCallstack.AppendFormat($"{frameNumHex} {uniqueModuleName}+{rvaAsIsOrDerived}{Environment.NewLine}");
continue;
}
} catch (Exception ex) {
Expand All @@ -116,7 +140,7 @@ await Task.Run(() => Parallel.ForEach(listOfCallStacks, currItem => {
}
}));

return cts.IsCancellationRequested ? (new Dictionary<string, Symbol>(), new List<StackDetails>()) : (syms, listOfCallStacks);
return cts.IsCancellationRequested ? (new Dictionary<string, Symbol>(), new List<StackDetails>()) : (anyTaskFailed ? null : syms, listOfCallStacks);
}
}
}
Loading

0 comments on commit 9b4228d

Please sign in to comment.