Last active
May 10, 2018 18:29
-
-
Save kinchungwong/9ce34edea81012b428e302e3e64bdd67 to your computer and use it in GitHub Desktop.
C# program for parsing DUMPBIN text output with disassembly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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