Skip to content

Instantly share code, notes, and snippets.

@kinchungwong
Last active May 10, 2018 18:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kinchungwong/9ce34edea81012b428e302e3e64bdd67 to your computer and use it in GitHub Desktop.
Save kinchungwong/9ce34edea81012b428e302e3e64bdd67 to your computer and use it in GitHub Desktop.
C# program for parsing DUMPBIN text output with disassembly
// Copyright 2018 Kin-Chung (Ryan) Wong
// All rights reserved.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Immutable; // (NuGet from Microsoft - official)
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Text.RegularExpressions;
namespace DumpBinToy_20180505
{
// Note: currently not used; user passes in text output from DUMPBIN.
public class DumpBinExecutor
{
public static readonly string[] ExecSearchPaths = new string[]
{
@"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.13.26128\bin\Hostx64\x64\dumpbin.exe"
};
}
/// <summary>
/// The lines of text loaded from the text output.
/// </summary>
public class LoadedText
{
/// <summary>
/// All lines of text loaded from the text output.
/// </summary>
public IReadOnlyList<string> Lines { get; internal set; }
/// <summary>
/// Number of lines from the beginning of the file to be excluded
/// from disassembly parsing
/// </summary>
public int FileHeaderLineCount
{
get
{
return 8;
}
}
public static LoadedText FromFile(string filename)
{
return new LoadedText()
{
Lines = File.ReadAllLines(filename)
};
}
}
/// <summary>
/// Extension methods for working with multi-value dictionary that is emulated
/// on top of a dictionary of lists.
/// </summary>
public static class MultiValueDictionaryMethods
{
/// <summary>
/// <para>
/// For a dictionary which maps to a list of values, this extension method appends
/// the specified value to the list associated with the specified key.
/// </para>
/// <para>
/// If the key is not previously known to the dictionary, a new list is created
/// and initialized with the specified value, and then the list is added to the
/// dictionary associated with the specified key.
/// </para>
/// <para>
/// This extension method is typically used to emulate a multi-value dictionary.
/// </para>
/// </summary>
/// <typeparam name="TK">The dictionary's key type</typeparam>
/// <typeparam name="TV">The dictionary's mapped element type.</typeparam>
/// <param name="dict">The dictionary to be modified</param>
/// <param name="k">The key</param>
/// <param name="v">The value</param>
/// <returns>True if the dictionary already contains the key.
/// False if the key is newly added.
/// </returns>
public static bool AppendValue<TK, TV>(this Dictionary<TK, List<TV>> dict, TK k, TV v)
{
bool exists = dict.TryGetValue(k, out List<TV> lv);
if (exists)
{
lv.Add(v);
}
else
{
lv = new List<TV>{ v };
dict.Add(k, lv);
}
return exists;
}
/// <summary>
/// Converts the emulated multi-value dictionary into a read-only dictionary in which the
/// mapped list of values are also exposed in a read-only way.
/// </summary>
/// <typeparam name="TK">The dictionary's key type</typeparam>
/// <typeparam name="TV">The dictionary's mapped element type.</typeparam>
/// <param name="dict">The dictionary that has been populated that will be converted into a read-only one.</param>
/// <returns>The read-only dictionary.</returns>
public static IReadOnlyDictionary<TK, IReadOnlyList<TV>> ToReadOnly<TK, TV>(this Dictionary<TK, List<TV>> dict)
{
var temp = new Dictionary<TK, IReadOnlyList<TV>>();
foreach (var kvp in dict)
{
temp.Add(kvp.Key, kvp.Value.AsReadOnly());
}
return new ReadOnlyDictionary<TK, IReadOnlyList<TV>>(temp);
}
}
/// <summary>
/// Contains internal static data or internal code that should not be used outside this library.
/// </summary>
internal static class Internals
{
internal static readonly string CodePattern = @"^\s+(?<addr>[0-9A-Fa-f]{8,16})\:\s(?<code>.*)$";
private static Lazy<Regex> __Lazy__CodeRegex = new Lazy<Regex>(
() => new Regex(CodePattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture));
internal static Regex CodeRegex => __Lazy__CodeRegex.Value;
internal static readonly char[] CodeSplitChars = new char[] { ' ', ',', '[', '+', ']', '-' };
}
/// <summary>
/// <para>
/// Information about an occurrence of a function in the text file.
/// </para>
/// </summary>
/// <remarks>
/// It is possible for a function to appear multiple times in the text file.
/// An instance of this class (FunctionInfo) refers to a single occurrence.
/// </remarks>
public class FunctionInfo
{
/// <summary>
/// Function name
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The address of the function. This is also the address of the first
/// disassembly instruction in the function.
/// </summary>
public string Address { get; internal set; }
/// <summary>
/// The line number in the text file where the function name is seen.
/// </summary>
public int LineNumber { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
/// <param name="address"></param>
/// <param name="lineNum"></param>
public FunctionInfo(string name, string address, int lineNum)
{
Name = name;
Address = address;
LineNumber = lineNum;
}
}
/// <summary>
/// Represents a function call seen in the disassembly code.
/// </summary>
public class CallEdge : IEquatable<CallEdge>
{
/// <summary>
/// The function name of the caller.
/// </summary>
public string CallSite { get; }
/// <summary>
/// The function name of the callee.
/// </summary>
public string CallTarget { get; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="callSite"></param>
/// <param name="callTarget"></param>
public CallEdge(string callSite, string callTarget)
{
CallSite = callSite;
CallTarget = callTarget;
}
public override string ToString()
{
return CallSite + "->->" + CallTarget;
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as CallEdge);
}
public bool Equals(CallEdge other)
{
if (other is null)
{
return false;
}
return string.Equals(CallSite, other.CallSite, StringComparison.InvariantCulture) &&
string.Equals(CallTarget, other.CallTarget, StringComparison.InvariantCulture);
}
}
/// <summary>
/// The collection of information about functions parsed from the text file.
/// </summary>
public class ParsedFunctions
{
public LoadedText Text { get; internal set; }
public IReadOnlyDictionary<int, FunctionInfo> ByLineNum { get; internal set; }
public IReadOnlyDictionary<string, IReadOnlyList<FunctionInfo>> ByFuncName { get; internal set; }
public ImmutableSortedSet<string> SortedFuncNames { get; internal set; }
public ParsedFunctions(LoadedText text)
{
Text = text;
var codeRegex = Internals.CodeRegex;
int nextLineNum = 0;
var byLineNum = new Dictionary<int, FunctionInfo>();
var byFuncName = new Dictionary<string, List<FunctionInfo>>();
FunctionInfo recentFuncInfo = null;
foreach (string line in Text.Lines)
{
int lineNum = nextLineNum;
nextLineNum++;
if (lineNum < Text.FileHeaderLineCount)
{
continue;
}
if (line.Length == 0)
{
continue;
}
if (!line.EndsWith(":"))
{
if ((recentFuncInfo != null) &&
(lineNum == recentFuncInfo.LineNumber + 1))
{
// The line of code immediately following the function name
// provides the address of the function.
var match = codeRegex.Match(line);
if (match.Success && (match.Groups.Count == 3))
{
string strAddr = match.Groups[1].Value;
recentFuncInfo.Address = strAddr;
}
}
continue;
}
// Remark
// Do not reject lines starting with a space, example:
// (in DUMPBIN output, this example is seen with a leading space)
// ?? ::HA::cv::XZ::`cv::ocl::internal::ProgramEntry::operator ocl::internal::ProgramSource & __ptr64'::`1'::dtor$5:
//
string funcName = line.Substring(0, line.Length - 1);
recentFuncInfo = new FunctionInfo(funcName, string.Empty, lineNum);
byLineNum.Add(lineNum, recentFuncInfo);
byFuncName.AppendValue(funcName, recentFuncInfo);
}
ByLineNum = new ReadOnlyDictionary<int, FunctionInfo>(byLineNum);
ByFuncName = byFuncName.ToReadOnly();
SortedFuncNames = ImmutableSortedSet.CreateRange<string>(ByFuncName.Keys);
}
}
/// <summary>
/// The collection of information about calls between functions parsed from the disassembly
/// code in the text file.
/// </summary>
public class ParsedFunctionCalls
{
public LoadedText Text { get; internal set; }
public ParsedFunctions ParsedFuncs { get; internal set; }
public ImmutableHashSet<CallEdge> Calls { get; internal set; }
public IReadOnlyDictionary<string, IReadOnlyList<CallEdge>> ByCallSite { get; internal set; }
public IReadOnlyDictionary<string, IReadOnlyList<CallEdge>> ByCallTarget { get; internal set; }
public ParsedFunctionCalls(ParsedFunctions parsedFuncs)
{
Text = parsedFuncs.Text;
ParsedFuncs = parsedFuncs;
int nextLineNum = 0;
string recentFuncName = string.Empty;
var codeRegex = Internals.CodeRegex;
var codeSplitChars = Internals.CodeSplitChars;
var calls = new HashSet<CallEdge>();
foreach (string line in Text.Lines)
{
int lineNum = nextLineNum;
nextLineNum++;
if (lineNum < Text.FileHeaderLineCount)
{
continue;
}
if (ParsedFuncs.ByLineNum.TryGetValue(lineNum, out FunctionInfo funcInfo))
{
// A line that contains the symbol name (function name)
// will not contain any code. Thus, remember the function name,
// but continue parsing next lines.
recentFuncName = funcInfo.Name;
}
else
{
if (string.IsNullOrEmpty(recentFuncName))
{
continue;
}
// For each line of code, check if it is referring to a named address;
// then check if that named address is a known function name.
var match = codeRegex.Match(line);
if (match.Success && (match.Groups.Count == 3))
{
string strAddr = match.Groups[1].Value;
string code = match.Groups[2].Value;
if (code.StartsWith("CC"))
{
continue;
}
string[] codeParts = code.Split(codeSplitChars, StringSplitOptions.RemoveEmptyEntries);
foreach (var codePart in codeParts)
{
if (ParsedFuncs.ByFuncName.ContainsKey(codePart))
{
string callTarget = codePart;
calls.Add(new CallEdge(recentFuncName, callTarget));
}
}
}
}
}
Calls = ImmutableHashSet.CreateRange<CallEdge>(calls);
var byCallSite = new Dictionary<string, List<CallEdge>>();
var byCallTarget = new Dictionary<string, List<CallEdge>>();
foreach (var callEdge in Calls)
{
byCallSite.AppendValue(callEdge.CallSite, callEdge);
byCallTarget.AppendValue(callEdge.CallTarget, callEdge);
}
ByCallSite = byCallSite.ToReadOnly();
ByCallTarget = byCallTarget.ToReadOnly();
}
}
/// <summary>
/// Demo program
/// </summary>
class Program
{
static void Main(string[] args)
{
if (args.Length == 0 || string.IsNullOrEmpty(args[0]) || !File.Exists(args[0]))
{
Console.WriteLine("Help:");
return;
}
string filename = args[0];
var loadedText = LoadedText.FromFile(filename);
var parsedFuncs = new ParsedFunctions(loadedText);
var parsedCalls = new ParsedFunctionCalls(parsedFuncs);
foreach (var funcName in parsedFuncs.SortedFuncNames)
{
Console.WriteLine("FN: " + funcName);
Console.WriteLine("LN: " + ListToString(
(from funcInfo
in parsedFuncs.ByFuncName[funcName]
select funcInfo.LineNumber)));
Console.WriteLine("AD: " + ListToString(
(from funcInfo
in parsedFuncs.ByFuncName[funcName]
select funcInfo.Address)));
if (parsedCalls.ByCallSite.ContainsKey(funcName))
{
Console.WriteLine(ListToString(
(from callEdge
in parsedCalls.ByCallSite[funcName]
select ("CC: " + callEdge.CallTarget)
).ToArray(), "\n"));
}
Console.WriteLine();
}
}
internal static string ListToString<T>(IEnumerable<T> values, string sep = ",")
{
StringBuilder sb = new StringBuilder();
bool hasSep = !string.IsNullOrEmpty(sep);
bool isFirst = true;
foreach (T value in values)
{
if (!isFirst && hasSep)
{
sb.Append(sep);
}
isFirst = false;
sb.Append(value);
}
return sb.ToString();
}
}
}
FN: ?parallelForAttachNestedRegion@details@trace@utils@cv@@YAXAEBVRegion@1234@@Z
LN: 446196
AD: 00000001801B33F0
CC: ?getTraceManager@details@trace@utils@cv@@YAAEAVTraceManager@1234@XZ
CC: ??0String@cv@@QEAA@PEBD@Z
CC: ?getData@TLSDataContainer@cv@@QEBAPEAXXZ
FN: ?parallelForFinalize@details@trace@utils@cv@@YAXAEBVRegion@1234@@Z
LN: 446253
AD: 00000001801B34E0
CC: ?getTraceManager@details@trace@utils@cv@@YAAEAVTraceManager@1234@XZ
CC: ?gatherData@TLSDataContainer@cv@@IEBAXAEAV?$vector@PEAXV?$allocator@PEAX@std@@@std@@@Z
CC: ??0String@cv@@QEAA@PEBD@Z
CC: ?getData@TLSDataContainer@cv@@QEBAPEAXXZ
CC: ??3@YAXPEAX_K@Z
FN: ?parallelForSetRootRegion@details@trace@utils@cv@@YAXAEBVRegion@1234@AEBUTraceManagerThreadLocal@1234@@Z
LN: 446460
AD: 00000001801B37E0
CC: ?getData@TLSDataContainer@cv@@QEBAPEAXXZ
CC: ?getTraceManager@details@trace@utils@cv@@YAAEAVTraceManager@1234@XZ
CC: ??0String@cv@@QEAA@PEBD@Z
FN: ?patchNaNs@cv@@YAXAEBV_InputOutputArray@1@N@Z
LN: 176806
AD: 00000001800AE050
CC: ??0String@cv@@QEAA@PEBD@Z
CC: __security_check_cookie
CC: ?dims@_InputArray@cv@@QEBAHH@Z
CC: ?fastFree@cv@@YAXPEAX@Z
CC: ??ENAryMatIterator@cv@@QEAAAEAV01@XZ
CC: ?depth@_InputArray@cv@@QEBAHH@Z
CC: ?isOpenCLActivated@ocl@cv@@YA_NXZ
CC: ??0Region@details@trace@utils@cv@@QEAA@AEBULocationStaticStorage@01234@@Z
CC: ?destroy@Region@details@trace@utils@cv@@QEAAXXZ
CC: ??0NAryMatIterator@cv@@QEAA@PEAPEBVMat@1@PEAPEAEH@Z
CC: ?kind@_InputArray@cv@@QEBAHXZ
CC: ?getMat@_InputArray@cv@@QEBA?AVMat@2@H@Z
CC: ?deallocate@Mat@cv@@QEAAXXZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment