SQLCallStackResolver moved to Microsoft org

This commit is contained in:
Arvind Shyamsundar 2021-06-09 20:51:56 -07:00
Родитель c1ac972baa
Коммит fe93f1f151
57 изменённых файлов: 4726 добавлений и 328 удалений

1
.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
* text=auto

36
.github/workflows/build.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,36 @@
name: Build SQLCallStackResolver
on:
- pull_request
- workflow_dispatch
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
name: Checkout Code
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
- name: Copy DIA and DbgHelp binary dependencies
run: .\downloadbinaries.ps1 '${{ secrets.DBGHELP_DIA_BIN_URL }}'
working-directory: Engine
shell: powershell
- name: Setup NuGet
uses: NuGet/setup-nuget@v1
- name: Restore NuGet Packages
run: nuget restore SQLCallStackResolver.sln
- name: Build SQLCallStackResolver
run: msbuild SQLCallStackResolver.sln /p:Configuration=Release
- name: Upload Artifact
uses: actions/upload-artifact@v1.0.0
with:
name: SQLCallStackResolver
path: Target\Release
- name: Prep for running tests
run: .\downloadsyms.ps1 '${{ secrets.SQLDKDLL_URL }}' '${{ secrets.XESPINS_ZIP_URL }}' '${{ secrets.XEWAIT_URL }}'
working-directory: Tests\TestCases
shell: powershell
- name: Run tests
run: ..\..\packages\xunit.runner.console.2.4.1\tools\net472\xunit.console.exe .\SQLCalLStackResolver.xUnit.Tests.dll -verbose
working-directory: Target\Release
shell: powershell

273
.gitignore поставляемый
Просмотреть файл

@ -1,22 +1,9 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
@ -30,41 +17,14 @@ bld/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
@ -92,11 +52,7 @@ StyleCopReport.xml
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
@ -104,247 +60,36 @@ ipch/
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
Target
/Tests/TestCases/TestOrdinal/sqldk.dll
/Tests/TestCases/TestOrdinal/sqldk.zip
/Tests/TestCases/ImportXEL/*.xel
/Tests/TestCases/ImportXEL/*.zip
/TestResults/
Engine/BinaryDependencies.zip
Engine/DIA
Engine/DbgHelp

Просмотреть файл

@ -1,9 +1,7 @@
# Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

Просмотреть файл

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
class DLLOrdinalHelper {
/// This holds the mapping of the various DLL exports for a module and the address (offset) for each such export
/// Only populated if the user provides the 'image path' to the DLLs
Dictionary<string, Dictionary<int, ExportedSymbol>> _DLLOrdinalMap;
internal void Initialize() {
_DLLOrdinalMap = new Dictionary<string, Dictionary<int, ExportedSymbol>>();
}
/// This function loads DLLs from a specified path, so that we can then build the DLL export's ordinal / address map
internal string LoadDllsIfApplicable(string callstack, bool recurse, List<string> dllPaths) {
if (dllPaths == null) {
return callstack;
}
// first we seek out distinct module names in this call stack
// 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
// sample frames are given below
// sqldk.dll!Ordinal947+0x25f
// sqldk.dll!Ordinal699 + 0x5f
// sqlmin.dll!Ordinal1634 + 0x76c
// More recent patterns which we choose not to support, because in these cases the module+offset is cleanly represented and it does symbolize nicely
// 00007FF818405E70 Module(sqlmin+0000000001555E70) (Ordinal1877 + 00000000000004B0)
// 00007FF81840226A Module(sqlmin+000000000155226A) (Ordinal1261 + 00000000000071EA)
// 00007FF81555A663 Module(sqllang+0000000000C6A663) (Ordinal1203 + 0000000000005E33)
// define a regex to identify such ordinal based frames
var rgxOrdinalNotation = new Regex(@"(?<module>\w+)(\.dll)*!Ordinal(?<ordinal>[0-9]+)\s*\+\s*(0[xX])*");
var matchednotations = rgxOrdinalNotation.Matches(callstack);
var moduleNames = new List<string>();
if (matchednotations.Count > 0) {
foreach (Match match in matchednotations) {
var currmodule = match.Groups["module"].Value;
if (!moduleNames.Contains(currmodule)) {
moduleNames.Add(currmodule);
}
}
}
// then we see if there is a matched DLL in any of the paths we have
foreach (var currmodule in moduleNames) {
foreach (var currPath in dllPaths) {
var foundFiles = Directory.EnumerateFiles(currPath, currmodule + ".dll", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
lock (_DLLOrdinalMap) {
if (!_DLLOrdinalMap.ContainsKey(currmodule) && foundFiles.Any()) {
_DLLOrdinalMap.Add(currmodule, ExportedSymbol.GetExports(foundFiles.First()));
break;
}
}
}
}
// finally do a pattern based replace the replace method calls a delegate (ReplaceOrdinalWithRealOffset) which figures
// out the start address of the ordinal and then computes the actual offset
var fullpattern = new Regex(@"(?<module>\w+)(\.dll)*!Ordinal(?<ordinal>[0-9]+)\s*\+\s*(0[xX])*(?<offset>[0-9a-fA-F]+)\s*");
return fullpattern.Replace(callstack, ReplaceOrdinalWithRealOffset);
}
/// This delegate is invoked by the Replace function and is used to compute the effective offset from module load address
/// based on ordinal start address and original offset
private string ReplaceOrdinalWithRealOffset(Match mtch) {
var moduleName = mtch.Groups["module"].Value;
if (!_DLLOrdinalMap.ContainsKey(moduleName)) {
return mtch.Value;
}
uint offsetSpecified = Convert.ToUInt32(mtch.Groups["offset"].Value, 16);
return string.Format(CultureInfo.CurrentCulture, "{0}.dll+0x{1:X}{2}", moduleName,
_DLLOrdinalMap[moduleName][int.Parse(mtch.Groups["ordinal"].Value, CultureInfo.CurrentCulture)].Address + offsetSpecified, Environment.NewLine);
}
}
}

181
Engine/DiaUtil.cs Normal file
Просмотреть файл

@ -0,0 +1,181 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Dia;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
/// Wrapper class around DIA
internal class DiaUtil {
internal IDiaDataSource _IDiaDataSource;
internal IDiaSession _IDiaSession;
private bool disposedValue = false;
public bool HasSourceInfo = false;
private static object _syncRoot = new object();
internal DiaUtil(string pdbName) {
_IDiaDataSource = new DiaSource();
_IDiaDataSource.loadDataFromPdb(pdbName);
_IDiaDataSource.openSession(out _IDiaSession);
this._IDiaSession.findChildrenEx(this._IDiaSession.globalScope, SymTagEnum.SymTagFunction, null, 0, out IDiaEnumSymbols matchedSyms);
foreach (IDiaSymbol sym in matchedSyms) {
this._IDiaSession.findLinesByRVA(sym.relativeVirtualAddress, (uint)sym.length, out IDiaEnumLineNumbers enumLineNums);
Marshal.ReleaseComObject(sym);
if (enumLineNums.count > 0) {
// this PDB has at least 1 function with source info, so end the search
HasSourceInfo = true;
break;
}
Marshal.ReleaseComObject(enumLineNums);
}
}
public void Dispose() {
this.Dispose(true);
}
protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
Marshal.FinalReleaseComObject(_IDiaSession);
Marshal.FinalReleaseComObject(_IDiaDataSource);
}
disposedValue = true;
}
}
/// This function builds up the PDB map, by searching for matched PDBs (based on name) and constructing the DIA session for each
/// It is VERY important to specify the PDB search paths correctly, because there is no 'signature' information available
/// to match the PDB in any automatic way.
internal static bool LocateandLoadPDBs(Dictionary<string, DiaUtil> _diautils, string rootPaths, bool recurse, List<string> moduleNames, bool cachePDB) {
// loop through each module, trying to find matched PDB files
var splitRootPaths = rootPaths.Split(';');
foreach (string currentModule in moduleNames) {
if (!_diautils.ContainsKey(currentModule)) {
// check if the PDB is already cached locally
var cachedPDBFile = Path.Combine(Path.GetTempPath(), "SymCache", currentModule + ".pdb");
lock (_syncRoot) {
if (!File.Exists(cachedPDBFile)) {
foreach (var currPath in splitRootPaths) {
if (Directory.Exists(currPath)) {
var 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()) {
if (cachePDB) {
File.Copy(foundFiles.First(), cachedPDBFile);
}
else {
cachedPDBFile = foundFiles.First();
}
break;
}
}
}
}
}
if (File.Exists(cachedPDBFile)) {
try {
_diautils.Add(currentModule, new DiaUtil(cachedPDBFile));
} catch (COMException) {
return false;
}
}
}
}
return true;
}
/// Internal helper function to return the symbolized frame text (not including source info)
internal static string GetSymbolizedFrame(string moduleName, IDiaSymbol mysym, bool useUndecorateLogic, bool includeOffset, int displacement) {
string funcname2;
if (!useUndecorateLogic) {
funcname2 = mysym.name;
}
else {
// refer https://msdn.microsoft.com/en-us/library/kszfk0fs.aspx
// UNDNAME_NAME_ONLY == 0x1000: Gets only the name for primary declaration; returns just [scope::]name. Expands template params.
mysym.get_undecoratedNameEx(0x1000, out funcname2);
// catch-all / fallback
if (string.IsNullOrEmpty(funcname2)) {
funcname2 = mysym.name;
}
}
string offsetStr = string.Empty;
if (includeOffset) {
offsetStr = string.Format(CultureInfo.CurrentCulture, "+{0}", displacement);
}
return string.Format(CultureInfo.CurrentCulture, "{0}!{1}{2}", moduleName, funcname2, offsetStr);
}
/// Internal helper function to obtain source information for given symbol
internal static string GetSourceInfo(IDiaEnumLineNumbers enumLineNums, bool pdbHasSourceInfo) {
var sbOutput = new StringBuilder();
// only if we found line number information should we append to output
if (enumLineNums.count > 0) {
for (uint tmpOrdinal = 0; tmpOrdinal < enumLineNums.count; tmpOrdinal++) {
if (tmpOrdinal > 0) {
sbOutput.Append(" -- WARN: multiple matches -- ");
}
sbOutput.Append(string.Format(CultureInfo.CurrentCulture,
"({0}:{1})",
enumLineNums.Item(tmpOrdinal).sourceFile.fileName,
enumLineNums.Item(tmpOrdinal).lineNumber));
Marshal.FinalReleaseComObject(enumLineNums.Item(tmpOrdinal));
}
}
else {
if (pdbHasSourceInfo) {
sbOutput.Append("-- WARN: unable to find source info --");
}
}
Marshal.FinalReleaseComObject(enumLineNums);
return sbOutput.ToString();
}
/// Internal helper function to find any inline frames at a given RVA
internal static string ProcessInlineFrames(string moduleName, bool useUndecorateLogic, bool includeOffset, bool includeSourceInfo, uint rva, IDiaSymbol parentSym, bool pdbHasSourceInfo) {
var sbInline = new StringBuilder();
try {
var inlineRVA = rva - 1;
parentSym.findInlineFramesByRVA(inlineRVA, out IDiaEnumSymbols enumInlinees);
foreach (IDiaSymbol inlineFrame in enumInlinees) {
var inlineeOffset = (int)(rva - inlineFrame.relativeVirtualAddress);
sbInline.Append("(Inline Function) ");
sbInline.Append(DiaUtil.GetSymbolizedFrame(moduleName, inlineFrame, useUndecorateLogic, includeOffset, inlineeOffset));
if (includeSourceInfo) {
inlineFrame.findInlineeLinesByRVA(inlineRVA, 0, out IDiaEnumLineNumbers enumLineNums);
sbInline.Append("\t");
sbInline.Append(DiaUtil.GetSourceInfo(enumLineNums, pdbHasSourceInfo));
}
Marshal.ReleaseComObject(inlineFrame);
sbInline.AppendLine();
}
Marshal.ReleaseComObject(enumInlinees);
} catch (COMException) {
sbInline.AppendLine(" -- WARN: Unable to process inline frames");
}
return sbInline.ToString();
}
}
}

49
Engine/ExportedSymbol.cs Normal file
Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Microsoft.Diagnostics.Runtime.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.MemoryMappedFiles;
/// Helper class which stores DLL export name and address (offset)
public class ExportedSymbol {
public string Name { get; set; }
public uint Address { get; set; }
/// Helper function to load a DLL and then lookup exported functions. For this we use CLRMD and specifically the PEHeader class
public static Dictionary<int, ExportedSymbol> GetExports(string DLLPath) {
// this is the placeholder for the final mapping of ordinal # to address map
Dictionary<int, ExportedSymbol> exports = null;
using (var dllStream = new FileStream(DLLPath, FileMode.Open, FileAccess.Read)) {
using (var dllImage = new PEImage(dllStream)) {
var dir = dllImage.PEHeader.ExportTableDirectory;
var offset = dllImage.RvaToOffset(Convert.ToInt32(dir.RelativeVirtualAddress));
using (var mmf = MemoryMappedFile.CreateFromFile(dllStream, null, 0, MemoryMappedFileAccess.Read, null, HandleInheritability.None, false)) {
using (var mmfAccessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) {
mmfAccessor.Read(offset, out ImageExportDirectory exportDirectory);
var count = exportDirectory.NumberOfFunctions;
exports = new Dictionary<int, ExportedSymbol>(count);
var namesOffset = exportDirectory.AddressOfNames != 0 ? dllImage.RvaToOffset(exportDirectory.AddressOfNames) : 0;
var ordinalOffset = exportDirectory.AddressOfOrdinals != 0 ? dllImage.RvaToOffset(exportDirectory.AddressOfOrdinals) : 0;
var functionsOffset = dllImage.RvaToOffset(exportDirectory.AddressOfFunctions);
var ordinalBase = (int)exportDirectory.Base;
for (uint funcOrdinal = 0; funcOrdinal < count; funcOrdinal++) {
// read function address
var address = mmfAccessor.ReadUInt32(functionsOffset + funcOrdinal * 4);
if (0 != address) {
exports.Add((int)(ordinalBase + funcOrdinal), new ExportedSymbol { Name = string.Format(CultureInfo.CurrentCulture, "Ordinal{0}", ordinalBase + funcOrdinal), Address = address});
}
}
}
}
}
return exports;
}
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System.Runtime.InteropServices;
/// PE header's Image Export Directory
[StructLayout(LayoutKind.Sequential)]
#pragma warning disable CA1815 // Override equals and operator equals on value types
public struct ImageExportDirectory
#pragma warning restore CA1815 // Override equals and operator equals on value types
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public int Name;
public int Base;
public int NumberOfFunctions;
public int NumberOfNames;
public int AddressOfFunctions;
public int AddressOfNames;
public int AddressOfOrdinals;
}
}

16
Engine/ModuleInfo.cs Normal file
Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
using System.Globalization;
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
/// Helper class to store module name, start and end address
public class ModuleInfo {
public string ModuleName;
public ulong BaseAddress;
public ulong EndAddress;
public override string ToString() {
return string.Format(CultureInfo.CurrentCulture, "{0} from {1:X} to {2:X}", ModuleName, BaseAddress, EndAddress);
}
}
}

Просмотреть файл

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text.RegularExpressions;
public static class ModuleInfoHelper {
private static Regex rgxPDBName = new Regex(@"^(?<pdb>.+)(\.pdb)$", RegexOptions.IgnoreCase);
private static Regex rgxFileName = new Regex(@"^(?<module>.+)\.(dll|exe)$", RegexOptions.IgnoreCase);
/// Given a set of rows each containing several comma-separated fields, return a set of resolved Symbol
/// objects each of which have PDB GUID and age details.
public static Dictionary<string, Symbol> ParseModuleInfo(string input) {
var retval = new Dictionary<string, Symbol>();
Contract.Requires(!string.IsNullOrEmpty(input));
// split into multiple lines
var lines = input.Split('\n');
foreach (var line in lines) {
Guid pdbGuid = Guid.Empty;
string moduleName = null;
string pdbName = null;
// foreach line, split into comma-delimited fields
var fields = line.Split(',');
foreach (var rawfield in fields) {
var field = rawfield.Trim().TrimEnd('"').TrimStart('"');
Guid tmpGuid = Guid.Empty;
// for each field, attempt using regexes to detect file name and GUIDs
if (Guid.TryParse(field, out 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 pdbAge = int.MinValue;
// assumption is that last field is pdbAge - TODO parameterize
_ = int.TryParse(fields[fields.Length - 1], out pdbAge);
if (string.IsNullOrEmpty(pdbName)) {
// fall back to module name as PDB name
pdbName = moduleName;
}
// check if we have all 3 details
if (!string.IsNullOrEmpty(pdbName)
&& pdbAge != int.MinValue
&& pdbGuid != Guid.Empty) {
retval.Add(moduleName, new Symbol() { PDBName = pdbName + ".pdb", PDBAge = pdbAge, PDBGuid = pdbGuid.ToString("N")});
}
}
return retval;
}
}
}

32
Engine/Preprocessors.cs Normal file
Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
internal class Preprocessors {
/// Find out distinct module names in a given stack. This is used to later load PDBs and optionally DLLs
internal static List<string> EnumModuleNames(string[] callStack) {
List<string> uniqueModuleNames = new List<string>();
var reconstructedCallstack = new StringBuilder();
foreach (var frame in callStack) {
reconstructedCallstack.AppendLine(frame);
}
// using the ".dll!0x" to locate the module names
var rgxModuleName = new Regex(@"(?<module>\w+)((\.(dll|exe))*(!(?<symbolizedfunc>.+))*)*\s*\+(0[xX])*");
var matchedModuleNames = rgxModuleName.Matches(reconstructedCallstack.ToString());
foreach (Match moduleMatch in matchedModuleNames) {
var actualModuleName = moduleMatch.Groups["module"].Value;
if (!uniqueModuleNames.Contains(actualModuleName)) {
uniqueModuleNames.Add(actualModuleName);
}
}
return uniqueModuleNames;
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SQLCallStackResolver Engine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SQLCallStackResolver")]
[assembly: AssemblyCopyright("Copyright (c) Microsoft")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("782bbd60-ee45-43cd-8a4f-0505efe4ff1a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.1.0.0")]
[assembly: AssemblyFileVersion("2.1.0.0")]

108
Engine/SQLBuildInfo.cs Normal file
Просмотреть файл

@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
public class SQLBuildInfo {
public string ProductMajorVersion = "<<ProductMajorVersion>>";
public string ProductLevel = "<<ProductLevel>>";
public string Label = "<<BuildName>>";
public string BuildNumber = "<<BuildNumber>>";
public string KBInfo = "<<KBArticle>>";
public List<Symbol> SymbolDetails;
public string MachineType = "<<x64|x86>>";
public override string ToString() {
return string.Format(CultureInfo.CurrentCulture,
$"{ProductMajorVersion} {ProductLevel} {Label} - {BuildNumber} - {MachineType} ({KBInfo})");
}
public static SortedDictionary<string, SQLBuildInfo> GetSqlBuildInfo(string jsonFile) {
var allBuilds = new SortedDictionary<string, SQLBuildInfo>();
using (var fs = new FileStream(jsonFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
using (var rdr = new StreamReader(fs)) {
using (var jsonRdr = new JsonTextReader(rdr)) {
jsonRdr.SupportMultipleContent = true;
var serializer = new JsonSerializer();
while (true) {
if (!jsonRdr.Read()) {
break;
}
var currBuildInfo = serializer.Deserialize<SQLBuildInfo>(jsonRdr);
currBuildInfo.BuildNumber = currBuildInfo.BuildNumber.Trim();
currBuildInfo.KBInfo = currBuildInfo.KBInfo.Trim();
currBuildInfo.Label = currBuildInfo.Label.Trim();
currBuildInfo.ProductLevel = currBuildInfo.ProductLevel.Trim();
currBuildInfo.ProductMajorVersion = currBuildInfo.ProductMajorVersion.Trim();
if (!allBuilds.ContainsKey(currBuildInfo.ToString())) {
allBuilds.Add(currBuildInfo.ToString(), currBuildInfo);
}
else {
allBuilds[currBuildInfo.ToString()] = currBuildInfo;
}
}
jsonRdr.Close();
}
rdr.Close();
}
}
return new SortedDictionary<string, SQLBuildInfo>(allBuilds);
}
public static void SaveSqlBuildInfo(List<SQLBuildInfo> allBuilds, string jsonFile) {
if (allBuilds != null) {
using (var fs = new FileStream(jsonFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) {
// initially, truncate the file
fs.SetLength(0);
using (var wrtr = new StreamWriter(fs)) {
foreach (var bld in allBuilds) {
wrtr.WriteLine(JsonConvert.SerializeObject(bld));
}
wrtr.Flush();
wrtr.Close();
}
}
}
}
public static string GetDownloadScriptPowerShell(SQLBuildInfo bld, bool includeMarkdown) {
Contract.Requires(bld != null);
var symcmds = new StringBuilder();
if (null != bld.SymbolDetails && bld.SymbolDetails.Where(s => s.DownloadVerified).Any()) {
if (includeMarkdown) {
symcmds.AppendLine($"# {bld}");
symcmds.AppendLine("``` powershell");
}
symcmds.AppendLine($"# {bld}");
symcmds.AppendLine($"$outputFolder = 'c:\\sqlsyms\\{bld.BuildNumber}\\{bld.MachineType}' # <<change this output folder if needed>>'");
symcmds.AppendLine($"mkdir -f $outputFolder");
foreach (var sym in bld.SymbolDetails) {
if (!sym.DownloadVerified) continue;
symcmds.AppendLine($"if (-not (Test-Path \"$outputFolder\\{sym.PDBName}.pdb\")) {{ Invoke-WebRequest -uri '{sym.DownloadURL}' -OutFile \"$outputFolder\\{sym.PDBName}.pdb\" }} # File version {sym.FileVersion}");
}
if (includeMarkdown) symcmds.AppendLine("```");
symcmds.AppendLine();
}
return symcmds.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver</RootNamespace>
<AssemblyName>SQLCallStackResolver.Engine</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\Target\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>..\Target\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Diagnostics.NETCore.Client, Version=0.2.2.37102, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Diagnostics.NETCore.Client.0.2.137102\lib\netstandard2.0\Microsoft.Diagnostics.NETCore.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Diagnostics.Runtime, Version=2.0.3.11401, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Diagnostics.Runtime.2.0.161401\lib\net461\Microsoft.Diagnostics.Runtime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.SqlServer.XEvent.XELite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.SqlServer.XEvent.XELite.2019.11.20.2\lib\net461\Microsoft.SqlServer.XEvent.XELite.dll</HintPath>
</Reference>
<Reference Include="msdia140typelib_clr0200">
<HintPath>c:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Team Tools\Performance Tools\Plugins\msdia140typelib_clr0200.dll</HintPath>
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll</HintPath>
</Reference>
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=1.2.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.7.1\lib\net461\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=1.4.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.1.8.1\lib\net461\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DLLOrdinalHelper.cs" />
<Compile Include="ModuleInfoHelper.cs" />
<Compile Include="Preprocessors.cs" />
<Compile Include="SafeNativeMethods.cs" />
<Compile Include="DiaUtil.cs" />
<Compile Include="ExportedSymbol.cs" />
<Compile Include="ImageExportDirectory.cs" />
<Compile Include="ModuleInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SQLBuildInfo.cs" />
<Compile Include="StackResolver.cs" />
<Compile Include="Symbol.cs" />
<Compile Include="StackWithCount.cs" />
<Compile Include="SymSrvHelpers.cs" />
<Compile Include="ThreadParams.cs" />
<Compile Include="XELHelper.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="app.manifest" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="DIA\msdia140.dll.manifest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>msdia140.dll.manifest</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="DIA\msdia140.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>msdia140.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="DIA\msvcp140.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>msvcp140.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="DIA\vcruntime140.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>vcruntime140.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="DIA\vcruntime140_1.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>vcruntime140_1.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ContentWithTargetPath Include="DbgHelp\dbghelp.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>dbghelp.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="DbgHelp\symsrv.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>symsrv.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
[SuppressUnmanagedCodeSecurityAttribute]
internal class SafeNativeMethods {
//Code adapted from Stack Exchange network post https://stackoverflow.com/questions/26514954/registration-free-com-interop-deactivating-activation-context-in-finalizer-thro
//Authored by https://stackoverflow.com/users/3742925/aurora
//Answered by https://stackoverflow.com/users/505088/david-heffernan
private const uint ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x008;
private const UInt16 ISOLATIONAWARE_MANIFEST_RESOURCE_ID = 2;
[DllImport("Kernel32.dll")]
private extern static IntPtr CreateActCtx(ref ACTCTX actctx);
[DllImport("Kernel32.dll")]
private extern static bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);
[DllImport("Kernel32.dll")]
private extern static bool DeactivateActCtx(uint dwFlags, IntPtr lpCookie);
[DllImport("Kernel32.dll")]
private extern static bool ReleaseActCtx(IntPtr hActCtx);
private struct ACTCTX {
public int cbSize;
public uint dwFlags;
public string lpSource;
public ushort wProcessorArchitecture;
public ushort wLangId;
public string lpAssemblyDirectory;
public UInt16 lpResourceName;
public string lpApplicationName;
public IntPtr hModule;
}
[ThreadStatic]
private static IntPtr m_cookie;
[ThreadStatic]
private static IntPtr m_hActCtx;
internal static bool DestroyActivationContext() {
if (m_cookie != IntPtr.Zero) {
if (!DeactivateActCtx(0, m_cookie))
return false;
m_cookie = IntPtr.Zero;
if (!ReleaseActCtx(m_hActCtx))
return false;
m_hActCtx = IntPtr.Zero;
}
return true;
}
internal static bool EstablishActivationContext() {
ACTCTX info = new ACTCTX {
cbSize = Marshal.SizeOf(typeof(ACTCTX)),
dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID,
lpSource = System.Reflection.Assembly.GetExecutingAssembly().Location,
lpResourceName = ISOLATIONAWARE_MANIFEST_RESOURCE_ID
};
m_hActCtx = CreateActCtx(ref info);
if (m_hActCtx == new IntPtr(-1))
return false;
m_cookie = IntPtr.Zero;
if (!ActivateActCtx(m_hActCtx, out m_cookie))
return false;
return true;
}
[DllImport("dbghelp.dll", CharSet = CharSet.Unicode)]
public static extern bool SymFindFileInPath(IntPtr hProcess,
[MarshalAs(UnmanagedType.LPWStr)] string SearchPath,
[MarshalAs(UnmanagedType.LPWStr)] string FileName,
IntPtr id,
Int32 two,
Int32 three,
Int32 flags,
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder filePath,
IntPtr callback,
IntPtr context);
[DllImport("dbghelp.dll")]
public static extern bool SymCleanup(IntPtr hProcess);
[DllImport("dbghelp.dll", CharSet = CharSet.Unicode)]
public static extern bool SymInitialize(
IntPtr hProcess,
[MarshalAs(UnmanagedType.LPWStr)] string UserSearchPath,
bool fInvadeProcess);
}
}

620
Engine/StackResolver.cs Normal file
Просмотреть файл

@ -0,0 +1,620 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Dia;
using Microsoft.Diagnostics.Runtime.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
public class StackResolver : IDisposable {
/// This is used to store module name and start / end virtual address ranges
/// Only populated if the user provides a tab-separated string corresponding to the output of the following SQL query:
/// select name, base_address from sys.dm_os_loaded_modules where name not like '%.rll'
public List<ModuleInfo> LoadedModules = new List<ModuleInfo>();
/// A cache of already resolved addresses
Dictionary<string, string> cachedSymbols = new Dictionary<string, string>();
/// R/W lock to protect the above cached symbols dictionary
ReaderWriterLockSlim rwLockCachedSymbols = new ReaderWriterLockSlim();
DLLOrdinalHelper dllMapHelper = new DLLOrdinalHelper();
/// Status message - populated during associated long-running operations
public string StatusMessage;
/// Internal counter used to implement progress reporting
internal int globalCounter = 0;
internal bool cancelRequested = false;
/// Percent completed - populated during associated long-running operations
public int PercentComplete;
public void CancelRunningTasks() {
this.cancelRequested = true;
}
/// Public method which to help import XEL files
public Tuple<int, string> ExtractFromXEL(string[] xelFiles, bool bucketize) {
return XELHelper.ExtractFromXEL(this, xelFiles, bucketize);
}
/// Convert virtual-address only type frames to their module+offset format
private string[] PreProcessVAs(string[] callStackLines) {
var rgxVAOnly = new Regex(@"^\s*0[xX](?<vaddress>[0-9a-fA-F]+)\s*$");
string[] retval = new string[callStackLines.Length];
int frameNum = 0;
foreach (var currentFrame in callStackLines) {
// let's see if this is an VA-only address
var matchVA = rgxVAOnly.Match(currentFrame);
if (matchVA.Success) {
ulong virtAddress = Convert.ToUInt64(matchVA.Groups["vaddress"].Value, 16);
if (TryObtainModuleOffset(virtAddress, out string moduleName, out uint offset)) {
// finalCallstack.AppendLine(ProcessFrameModuleOffset(moduleName, offset.ToString()));
retval[frameNum] = string.Format(CultureInfo.CurrentCulture, "{0}+0x{1:X}", moduleName, offset);
}
else {
retval[frameNum] = currentFrame.Trim();
}
}
else {
retval[frameNum] = currentFrame.Trim();
}
frameNum++;
}
return retval;
}
/// Runs through each of the frames in a call stack and looks up symbols for each
private string ResolveSymbols(Dictionary<string, DiaUtil> _diautils, string[] callStackLines, bool includeSourceInfo, bool relookupSource, bool includeOffsets, bool showInlineFrames) {
var finalCallstack = new StringBuilder();
var rgxModuleName = new Regex(@"(?<module>\w+)(\.(dll|exe))*\s*\+\s*(0[xX])*(?<offset>[0-9a-fA-F]+)\s*");
var rgxAlreadySymbolizedFrame = new Regex(@"(?<module>\w+)(\.(dll|exe))*!(?<symbolizedfunc>.+?)\s*\+\s*(0[xX])*(?<offset>[0-9a-fA-F]+)\s*");
foreach (var iterFrame in callStackLines) {
// hard-coded find-replace for XML markup - useful when importing from XML histograms
var currentFrame = iterFrame.Replace("&lt;", "<").Replace("&gt;", ">");
if (relookupSource && includeSourceInfo) {
// This is a rare case. Sometimes we get frames which are already resolved to their symbols but do not include source and line number information
// take for example sqldk.dll!SpinlockBase::Sleep+0x2d0
// in these cases, we may want to 're-resolve' them to a symbol using DIA so that later
// we can embed source / line number information if that is available now (this is important for some
// Microsoft internal cases where customers send us stacks resolved with public PDBs but internally we
// have private PDBs so we want to now leverage the extra information provided in the private PDBs.)
var matchAlreadySymbolized = rgxAlreadySymbolizedFrame.Match(currentFrame);
if (matchAlreadySymbolized.Success && _diautils.ContainsKey(matchAlreadySymbolized.Groups["module"].Value)) {
var myDIAsession = _diautils[matchAlreadySymbolized.Groups["module"].Value]._IDiaSession;
myDIAsession.findChildrenEx(myDIAsession.globalScope, SymTagEnum.SymTagNull, matchAlreadySymbolized.Groups["symbolizedfunc"].Value, 0, out IDiaEnumSymbols matchedSyms);
if (matchedSyms.count > 0) {
for (uint tmpOrdinal = 0; tmpOrdinal < matchedSyms.count; tmpOrdinal++) {
IDiaSymbol tmpSym = matchedSyms.Item(tmpOrdinal);
var rva = tmpSym.relativeVirtualAddress;
string offsetString = matchAlreadySymbolized.Groups["offset"].Value;
int numberBase = offsetString.ToUpperInvariant().StartsWith("0X", StringComparison.CurrentCulture) ? 16 : 10;
uint offset = Convert.ToUInt32(offsetString, numberBase);
rva += offset;
myDIAsession.findLinesByRVA(rva, 0, out IDiaEnumLineNumbers enumLineNums);
string tmpsourceInfo = DiaUtil.GetSourceInfo(enumLineNums,
_diautils[matchAlreadySymbolized.Groups["module"].Value].HasSourceInfo);
if (tmpOrdinal > 0) {
finalCallstack.Append(" OR ");
}
finalCallstack.AppendFormat(CultureInfo.CurrentCulture, "{0}!{1}{2}\t{3}", matchAlreadySymbolized.Groups["module"].Value,
matchAlreadySymbolized.Groups["symbolizedfunc"].Value, includeOffsets ? "+" + offsetString : string.Empty, tmpsourceInfo);
Marshal.ReleaseComObject(tmpSym);
}
Marshal.ReleaseComObject(matchedSyms);
}
else {
// in the rare case that the symbol does not exist, return frame as-is
finalCallstack.Append(currentFrame);
}
finalCallstack.AppendLine();
continue;
}
}
var match = rgxModuleName.Match(currentFrame);
if (match.Success) {
var matchedModuleName = match.Groups["module"].Value;
if (_diautils.ContainsKey(matchedModuleName)) {
string processedFrame = ProcessFrameModuleOffset(_diautils, matchedModuleName, match.Groups["offset"].Value, includeSourceInfo, includeOffsets, showInlineFrames);
if (!string.IsNullOrEmpty(processedFrame)) {
// typically this is because we could not find the offset in any known function range
finalCallstack.AppendLine(processedFrame);
}
else {
finalCallstack.AppendLine(currentFrame);
}
}
else {
finalCallstack.AppendLine(currentFrame.Trim());
}
}
else {
finalCallstack.AppendLine(currentFrame.Trim());
}
}
return finalCallstack.ToString();
}
/// This function will check if we have a module corresponding to the load address. Only used for pure virtual address format frames.
private bool TryObtainModuleOffset(ulong virtAddress, out string moduleName, out uint offset) {
var matchedModule = from mod in LoadedModules
where (mod.BaseAddress <= virtAddress && virtAddress <= mod.EndAddress)
select mod;
// we must have exactly one match (else either there's no matching module or we've got flawed load address data
if (matchedModule.Count() != 1) {
moduleName = null;
offset = 0;
return false;
}
moduleName = matchedModule.First().ModuleName;
// compute the offset / RVA now
offset = (uint)(virtAddress - matchedModule.First().BaseAddress);
return true;
}
/// This is the most important function in this whole utility! It uses DIA to lookup the symbol based on RVA offset
/// It also looks up line number information if available and then formats all of this information for returning to caller
private string ProcessFrameModuleOffset(Dictionary<string, DiaUtil> _diautils, string moduleName, string offset, bool includeSourceInfo, bool includeOffset, bool showInlineFrames) {
bool useUndecorateLogic = false;
// the offsets in the XE output are in hex, so we convert to base-10 accordingly
var rva = Convert.ToUInt32(offset, 16);
var symKey = moduleName + rva.ToString(CultureInfo.CurrentCulture);
string result = null;
this.rwLockCachedSymbols.EnterReadLock();
if (this.cachedSymbols.ContainsKey(symKey)) {
result = this.cachedSymbols[symKey];
}
this.rwLockCachedSymbols.ExitReadLock();
if (!string.IsNullOrEmpty(result)) {
// value was in cache
return result;
}
// process the function name (symbol); initially we look for 'block' symbols, which have a parent function; typically this is seen in kernelbase.dll
// (not very important for XE callstacks but important if you have an assert or non-yielding stack in SQLDUMPnnnn.txt files...)
_diautils[moduleName]._IDiaSession.findSymbolByRVAEx(rva, SymTagEnum.SymTagBlock, out IDiaSymbol mysym, out int displacement);
if (mysym != null) {
uint blockAddress = mysym.addressOffset;
// if we did find a block symbol then we look for its parent till we find either a function or public symbol
// an addition check is on the name of the symbol being non-null and non-empty
while (!(mysym.symTag == (uint)SymTagEnum.SymTagFunction || mysym.symTag == (uint)Dia.SymTagEnum.SymTagPublicSymbol) && string.IsNullOrEmpty(mysym.name)) {
mysym = mysym.lexicalParent;
}
// Calculate offset into the function by assuming that the final lexical parent we found in the loop above
// is the actual start of the function. Then the difference between (the original block start function start + displacement)
// and final lexical parent's start addresses is the final "displacement" / offset to be displayed
displacement = (int)(blockAddress - mysym.addressOffset + displacement);
}
else {
// we did not find a block symbol, so let's see if we get a Function symbol itself
// generally this is going to return mysym as null for most users (because public PDBs do not tag the functions as Function
// they instead are tagged as PublicSymbol)
_diautils[moduleName]._IDiaSession.findSymbolByRVAEx(rva, SymTagEnum.SymTagFunction, out mysym, out displacement);
if (mysym == null) {
useUndecorateLogic = true;
// based on previous remarks, look for public symbol near the offset / RVA
_diautils[moduleName]._IDiaSession.findSymbolByRVAEx(rva, SymTagEnum.SymTagPublicSymbol, out mysym, out displacement);
}
}
if (mysym == null) {
// if all attempts to locate a matching symbol have failed, return null
return null;
}
// try to find if we have source and line number info and include it based on the param
string sourceInfo = string.Empty;
var pdbHasSourceInfo = _diautils[moduleName].HasSourceInfo;
if (includeSourceInfo) {
_diautils[moduleName]._IDiaSession.findLinesByRVA(rva, 0, out IDiaEnumLineNumbers enumLineNums);
sourceInfo = DiaUtil.GetSourceInfo(enumLineNums, pdbHasSourceInfo);
}
var symbolizedFrame = DiaUtil.GetSymbolizedFrame(moduleName, mysym, useUndecorateLogic, includeOffset, displacement);
// Process inline functions, but only if private PDBs are in use
string inlineFrameAndSourceInfo = string.Empty;
if (showInlineFrames && pdbHasSourceInfo) {
inlineFrameAndSourceInfo = DiaUtil.ProcessInlineFrames(moduleName, useUndecorateLogic, includeOffset, includeSourceInfo, rva, mysym, pdbHasSourceInfo);
}
result = (inlineFrameAndSourceInfo + symbolizedFrame + "\t" + sourceInfo).Trim();
// make sure we cleanup COM allocations for the resolved sym
Marshal.FinalReleaseComObject(mysym);
this.rwLockCachedSymbols.EnterWriteLock();
if (!this.cachedSymbols.ContainsKey(symKey)) {
this.cachedSymbols.Add(symKey, result);
}
this.rwLockCachedSymbols.ExitWriteLock();
return result;
}
/// This helper function parses the output of the sys.dm_os_loaded_modules query and constructs an internal map of each modules start and end virtual address
public bool ProcessBaseAddresses(string baseAddressesString) {
bool retVal = true;
if (string.IsNullOrEmpty(baseAddressesString)) {
// changed this to return true because this is not a true error condition
return true;
}
LoadedModules.Clear();
var rgxmoduleaddress = new Regex(@"^\s*(?<filepath>.+)(\t+| +)(?<baseaddress>(0x)*[0-9a-fA-F`]+)\s*$", RegexOptions.Multiline);
var mcmodules = rgxmoduleaddress.Matches(baseAddressesString);
if (mcmodules.Count == 0) {
// it is likely that we have malformed input, cannot ignore this so return false.
return false;
}
try {
foreach (Match matchedmoduleinfo in mcmodules) {
LoadedModules.Add(new ModuleInfo() {
ModuleName = Path.GetFileNameWithoutExtension(matchedmoduleinfo.Groups["filepath"].Value),
BaseAddress = Convert.ToUInt64(matchedmoduleinfo.Groups["baseaddress"].Value.Replace("`", string.Empty), 16),
EndAddress = ulong.MaxValue // stub this with an 'infinite' end address; only the highest loaded module will end up with this value finally
});
}
} catch (FormatException) {
// typically errors with non-numeric info passed to Convert.ToUInt64
retVal = false;
} catch (ArgumentException) {
// typically these are malformed paths passed to Path.GetFileNameWithoutExtension
retVal = false;
}
// sort them by base address
LoadedModules = (from mod in LoadedModules orderby mod.BaseAddress select mod).ToList();
// loop through the list, computing their end address
for (int moduleIndex = 1; moduleIndex < LoadedModules.Count; moduleIndex++) {
// the previous modules end address will be current module's end address - 1 byte
LoadedModules[moduleIndex - 1].EndAddress = LoadedModules[moduleIndex].BaseAddress - 1;
}
return retVal;
}
/// <summary>
/// This is what the caller will invoke to resolve symbols
/// </summary>
/// <param name="inputCallstackText">the input call stack text or XML</param>
/// <param name="symPath">PDB search paths; separated by semi-colons. The first path containing a 'matching' PDB will be used.</param>
/// <param name="searchPDBsRecursively">search for PDBs recursively in each path specified</param>
/// <param name="dllPaths">DLL search paths. this is optional unless the call stack has frames of the form dll!OrdinalNNN+offset</param>
/// <param name="searchDLLRecursively">Search for DLLs recursively in each path specified. The first path containing a 'matching' DLL will be used.</param>
/// <param name="framesOnSingleLine">Mostly set this to false except when frames are on the same line and separated by spaces.</param>
/// <param name="includeSourceInfo">This is used to control whether source information is included (in the case that private PDBs are available)</param>
/// <param name="relookupSource">Boolean used to control if we attempt to relookup source information</param>
/// <param name="includeOffsets">Whether to output func offsets or not as part of output</param>
/// <param name="showInlineFrames">Boolean, whether to resolve and show inline frames in the output</param>
/// <param name="cachePDB">Boolean, whether to cache PDBs locally</param>
/// <param name="outputFilePath">File path, used if output is directly written to a file</param>
/// <returns></returns>
public string ResolveCallstacks(string inputCallstackText, string symPath, bool searchPDBsRecursively, List<string> dllPaths,
bool searchDLLRecursively, bool framesOnSingleLine, bool includeSourceInfo, bool relookupSource, bool includeOffsets,
bool showInlineFrames, bool cachePDB, string outputFilePath) {
// check if the user has provided a list of modules, each with comma-separated which can be structured fairly flexibly as long as they contain the following pieces of info
// per row, in different fields: PDB file name (including .pdb extension), OR module file name (.dll or .exe extension); a GUID representing the matching PDB GUID
// the very last field in the row should be an integer specifying the PDB "age" field. in such cases, the below function will return a non-zero list of Symbol objects which internally contain these parsed values for PDB name, GUID and age
var syms = ModuleInfoHelper.ParseModuleInfo(inputCallstackText);
if (syms.Count > 0) {
// if the user has provided such a list of module info, proceed to actually use dbghelp.dll / symsrv.dll to download thos PDBs and get local paths for them
var paths = SymSrvHelpers.GetFolderPathsForPDBs(this, symPath, syms.Values.ToList());
// we then "inject" those local PDB paths as higher priority than any possible user provided paths
symPath = string.Join(";", paths) + ";" + symPath;
}
this.cancelRequested = false;
this.cachedSymbols.Clear();
// delete and recreate the cached PDB folder
var symCacheFolder = Path.Combine(Path.GetTempPath(), "SymCache");
if (Directory.Exists(symCacheFolder)) {
new DirectoryInfo(symCacheFolder).GetFiles("*", SearchOption.AllDirectories).ToList().ForEach(file => file.Delete());
}
else {
Directory.CreateDirectory(symCacheFolder);
}
var finalCallstack = new StringBuilder();
var xmldoc = new XmlDocument() { XmlResolver = null };
bool isXMLdoc = false;
// we evaluate if the input is XML containing multiple stacks
try {
this.PercentComplete = 0;
this.StatusMessage = "Inspecting input to determine processing plan...";
using (var sreader = new StringReader(inputCallstackText)) {
using (var reader = XmlReader.Create(sreader, new XmlReaderSettings() { XmlResolver = null })) {
xmldoc.Load(reader);
}
}
isXMLdoc = true;
} catch (XmlException) {
// do nothing because this is not a XML doc
}
var listOfCallStacks = new List<StackWithCount>();
if (!isXMLdoc) {
this.StatusMessage = "Input being treated as a single callstack...";
listOfCallStacks.Add(new StackWithCount() {
Callstack = inputCallstackText,
Count = 1
});
}
else {
this.StatusMessage = "Input is well formed XML, proceeding...";
// since the input was XML containing multiple stacks, construct the list of stacks to process
int stacknum = 0;
var allstacknodes = xmldoc.SelectNodes("/HistogramTarget/Slot");
// handle the case wherein we are dealing with a ring buffer output with individual events and not a histogram
if (0 == allstacknodes.Count) {
allstacknodes = xmldoc.SelectNodes("//event[count(./action[@name = 'callstack']) > 0]");
if (allstacknodes.Count > 0) {
this.StatusMessage = "Preprocessing XEvent events...";
// process individual callstacks
foreach (XmlNode currstack in allstacknodes) {
if (this.cancelRequested) {
return "Operation cancelled.";
}
var callstackTextNode = currstack.SelectSingleNode("./action[@name = 'callstack'][1]/value[1]");
var callstackText = callstackTextNode.InnerText;
// proceed to extract the surrounding XML markup
callstackTextNode.ParentNode.RemoveChild(callstackTextNode);
var eventXMLMarkup = currstack.OuterXml.Replace("\r", string.Empty).Replace("\n", string.Empty);
var candidatestack = string.Format(CultureInfo.CurrentCulture, "Event details: {0}:{2}{2}{1}", eventXMLMarkup, callstackText, Environment.NewLine);
listOfCallStacks.Add(new StackWithCount() {Callstack = candidatestack, Count = 1});
stacknum++;
this.PercentComplete = (int)((double)stacknum / allstacknodes.Count * 100.0);
}
}
else {
this.StatusMessage = "XML input was detected but it does not appear to be a known schema. Cannot proceed, sorry!";
}
}
else {
this.StatusMessage = "Preprocessing XEvent histogram slots...";
// process histograms
foreach (XmlNode currstack in allstacknodes) {
if (this.cancelRequested) {
return "Operation cancelled.";
}
var slotcount = int.Parse(currstack.Attributes["count"].Value, CultureInfo.CurrentCulture);
var candidatestack = string.Format(CultureInfo.CurrentCulture, "Slot_{0}\t[count:{1}]:{3}{3}{2}", stacknum, slotcount, currstack.SelectSingleNode("./value[1]").InnerText, Environment.NewLine);
listOfCallStacks.Add(new StackWithCount() {Callstack = candidatestack, Count = slotcount});
stacknum++;
this.PercentComplete = (int)((double)stacknum / allstacknodes.Count * 100.0);
}
}
}
this.StatusMessage = "Resolving callstacks to symbols...";
this.globalCounter = 0;
// (re-)initialize the DLL Ordinal Map
this.dllMapHelper.Initialize();
// Create a pool of threads to process in parallel
int numThreads = Math.Min(listOfCallStacks.Count, Environment.ProcessorCount);
List<Thread> threads = new List<Thread>();
for (int threadOrdinal = 0; threadOrdinal < numThreads; threadOrdinal++) {
var tmpThread = new Thread(ProcessCallStack);
threads.Add(tmpThread);
tmpThread.Start(new ThreadParams() {dllPaths = dllPaths, framesOnSingleLine = framesOnSingleLine, includeOffsets = includeOffsets,includeSourceInfo = includeSourceInfo,
showInlineFrames = showInlineFrames, listOfCallStacks = listOfCallStacks, numThreads = numThreads, relookupSource = relookupSource,
searchDLLRecursively = searchDLLRecursively, searchPDBsRecursively = searchPDBsRecursively, symPath = symPath, threadOrdinal = threadOrdinal, cachePDB = cachePDB});
}
foreach (var tmpThread in threads) {
tmpThread.Join();
}
if (this.cancelRequested) {
return "Operation cancelled.";
}
this.StatusMessage = "Done with symbol resolution, finalizing output...";
this.globalCounter = 0;
// populate the output
if (!string.IsNullOrEmpty(outputFilePath)) {
this.StatusMessage = $@"Writing output to file {outputFilePath}";
using (var outStream = new StreamWriter(outputFilePath, false)) {
foreach (var currstack in listOfCallStacks) {
if (this.cancelRequested) {
return "Operation cancelled.";
}
if (!string.IsNullOrEmpty(currstack.Resolvedstack)) {
outStream.WriteLine(currstack.Resolvedstack);
}
else {
if (!string.IsNullOrEmpty(currstack.Callstack.Trim())) {
outStream.WriteLine("WARNING: No output to show. This may indicate an internal error!");
break;
}
}
this.globalCounter++;
this.PercentComplete = (int)((double)this.globalCounter / listOfCallStacks.Count * 100.0);
}
}
}
else {
this.StatusMessage = "Consolidating output for screen display...";
foreach (var currstack in listOfCallStacks) {
if (this.cancelRequested) {
return "Operation cancelled.";
}
if (!string.IsNullOrEmpty(currstack.Resolvedstack)) {
finalCallstack.AppendLine(currstack.Resolvedstack);
}
else {
if (!string.IsNullOrEmpty(currstack.Callstack)) {
finalCallstack = new StringBuilder("WARNING: No output to show. This may indicate an internal error!");
break;
}
}
this.globalCounter++;
this.PercentComplete = (int)((double)this.globalCounter / listOfCallStacks.Count * 100.0);
}
}
// Unfortunately the below is necessary to ensure that the handles to the cached PDB files opened by DIA
// and later deleted at the next invocation of this function, are released deterministically
// This is despite we correctly releasing those interface pointers using Marshal.FinalReleaseComObject
// Thankfully we only need to resort to this if the caller wants to cache PDBs in the temp folder
if (cachePDB) {
GC.Collect();
GC.WaitForPendingFinalizers();
}
this.StatusMessage = "Finished!";
if (string.IsNullOrEmpty(outputFilePath)) {
return finalCallstack.ToString();
}
else {
return $@"Output has been saved to {outputFilePath}";
}
}
/// Function executed by worker threads to process callstacks. Threads work on portions of the listOfCallStacks based on their thread ordinal.
private void ProcessCallStack(Object obj) {
SafeNativeMethods.EstablishActivationContext();
var tp = (ThreadParams)obj;
Dictionary<string, DiaUtil> _diautils = new Dictionary<string, DiaUtil>();
for (int tmpStackIndex = 0; tmpStackIndex < tp.listOfCallStacks.Count; tmpStackIndex++) {
if (this.cancelRequested) {
break;
}
if (tmpStackIndex % tp.numThreads != tp.threadOrdinal) {
continue;
}
var currstack = tp.listOfCallStacks[tmpStackIndex];
// split the callstack into lines, and for each line try to resolve
string ordinalresolvedstack;
ordinalresolvedstack = this.dllMapHelper.LoadDllsIfApplicable(currstack.Callstack, tp.searchDLLRecursively, tp.dllPaths);
// sometimes we see call stacks which are arranged horizontally (this typically is seen when copy-pasting directly
// from the SSMS XEvent window (copying the callstack field without opening it in its own viewer)
// in that case, space is a valid delimiter, and we need to support that as an option
var delims = tp.framesOnSingleLine ? new char[3] { ' ', '\t', '\n' } : new char[1] { '\n' };
var callStackLines = ordinalresolvedstack.Replace('\r', ' ').Split(delims, StringSplitOptions.RemoveEmptyEntries);
// process any frames which are purely virtual address (in such cases, the caller should have specified base addresses)
callStackLines = PreProcessVAs(callStackLines);
// locate the PDBs and populate their DIA session helper classes
if (DiaUtil.LocateandLoadPDBs(_diautils, tp.symPath, tp.searchPDBsRecursively, Preprocessors.EnumModuleNames(callStackLines), tp.cachePDB)) {
// resolve symbols by using DIA
currstack.Resolvedstack = ResolveSymbols(_diautils, callStackLines, tp.includeSourceInfo, tp.relookupSource, tp.includeOffsets, tp.showInlineFrames);
}
else {
currstack.Resolvedstack = string.Empty;
break;
}
var localCounter = Interlocked.Increment(ref this.globalCounter);
this.PercentComplete = (int)((double)localCounter / tp.listOfCallStacks.Count * 100.0);
}
// cleanup any older COM objects
if (_diautils != null) {
foreach (var diautil in _diautils.Values) {
diautil.Dispose();
}
}
SafeNativeMethods.DestroyActivationContext();
}
/// This method generates a PowerShell script to automate download of matched PDBs from the public symbol server.
public static List<Symbol> GetSymbolDetailsForBinaries(List<string> dllPaths, bool recurse) {
if (dllPaths == null || dllPaths.Count == 0) {
return new List<Symbol>();
}
var symbolsFound = new List<Symbol>();
var moduleNames = new string[] { "ntdll", "kernel32", "kernelbase", "ntoskrnl", "sqldk", "sqlmin", "sqllang", "sqltses", "sqlaccess", "qds", "hkruntime", "hkengine", "hkcompile", "sqlos", "sqlservr" };
foreach (var currentModule in moduleNames) {
string finalFilePath = null;
foreach (var currPath in dllPaths) {
if (!Directory.Exists(currPath)) {
continue;
}
var foundFiles = from f in Directory.EnumerateFiles(currPath, currentModule + ".*", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
where f.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) || f.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)
select f;
if (foundFiles.Any()) {
finalFilePath = foundFiles.First();
break;
}
}
if (!string.IsNullOrEmpty(finalFilePath)) {
using (var dllFileStream = new FileStream(finalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
using (var dllImage = new PEImage(dllFileStream, false)) {
var internalPDBName = dllImage.DefaultPdb.Path;
var pdbGuid = dllImage.DefaultPdb.Guid;
var pdbAge = dllImage.DefaultPdb.Revision;
var usablePDBName = Path.GetFileNameWithoutExtension(internalPDBName);
var newSymbol = new Symbol() {PDBName = usablePDBName, InternalPDBName = internalPDBName,
DownloadURL = string.Format(CultureInfo.CurrentCulture, @"https://msdl.microsoft.com/download/symbols/{0}.pdb/{1}/{0}.pdb",
usablePDBName, pdbGuid.ToString("N", CultureInfo.CurrentCulture) + pdbAge.ToString(CultureInfo.CurrentCulture)), FileVersion = dllImage.GetFileVersionInfo().FileVersion};
newSymbol.DownloadVerified = Symbol.IsURLValid(new Uri(newSymbol.DownloadURL));
symbolsFound.Add(newSymbol);
}
}
}
}
return symbolsFound;
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
rwLockCachedSymbols.Dispose();
}
disposedValue = true;
}
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

10
Engine/StackWithCount.cs Normal file
Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
/// helper class for cases where we have XML output
class StackWithCount {
internal string Callstack;
internal string Resolvedstack;
internal int Count;
}
}

69
Engine/SymSrvHelpers.cs Normal file
Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
public static class SymSrvHelpers {
static int processId = Process.GetCurrentProcess().Id;
/// Wrapper around the symsrv.dll functionality to initialize the symbol load handler for this process.
private static bool InitSymSrv(string symPath) {
return SafeNativeMethods.SymInitialize((IntPtr)processId, symPath, false);
}
/// Un-initialize the symbol load handler for this process.
private static bool CleanupSymSrv() {
return SafeNativeMethods.SymCleanup((IntPtr)processId);
}
/// Private method to locate the local path for a matching PDB. Implicitly handles symbol download if needed.
private static string GetLocalSymbolFolderForModule(string pdbFilename, string pdbGuid, int pdbAge) {
const int MAX_PATH = 4096;
StringBuilder outPath = new StringBuilder(MAX_PATH);
var guid = Guid.Parse(pdbGuid);
int rawsize = Marshal.SizeOf(guid);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.StructureToPtr(guid, buffer, false);
bool success = SafeNativeMethods.SymFindFileInPath((IntPtr)processId, null, pdbFilename, buffer, pdbAge, 0, 8, outPath, IntPtr.Zero, IntPtr.Zero);
if (!success) {
return String.Empty;
}
return outPath.ToString();
}
/// Public method to return local PDB file paths for specified symbols.
public static List<string> GetFolderPathsForPDBs(StackResolver parent, string symPath, List<Symbol> syms) {
var retval = new List<string>();
Contract.Requires(null != syms);
Contract.Requires(null != parent);
if (!InitSymSrv(symPath)) {
return retval;
}
int progress = 0;
foreach (var sym in syms) {
parent.StatusMessage = string.Format(CultureInfo.CurrentCulture, $"Finding local PDB path for {sym.PDBName}");
var path = GetLocalSymbolFolderForModule(sym.PDBName, sym.PDBGuid, sym.PDBAge);
if (!string.IsNullOrEmpty(path)) {
retval.Add(Path.GetDirectoryName(path));
parent.StatusMessage = string.Format(CultureInfo.CurrentCulture, $"Successfully found local PDB at {path}");
}
else {
parent.StatusMessage = string.Format(CultureInfo.CurrentCulture, $"Could not find local PDB for {sym.PDBName}");
}
progress++;
parent.PercentComplete = (int)((double)progress / syms.Count * 100.0);
}
CleanupSymSrv();
return retval;
}
}
}

39
Engine/Symbol.cs Normal file
Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Newtonsoft.Json;
using System;
using System.Net;
public class Symbol {
public string PDBName;
[JsonIgnore]
public string InternalPDBName;
[JsonIgnore]
public string PDBGuid;
[JsonIgnore]
public int PDBAge;
public string DownloadURL;
public bool DownloadVerified;
public string FileVersion;
public static bool IsURLValid(Uri url) {
try {
var request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "HEAD";
var response = request.GetResponse() as HttpWebResponse;
response.Close();
} catch (WebException) {
return false;
}
return true;
}
}
}

21
Engine/ThreadParams.cs Normal file
Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System.Collections.Generic;
internal class ThreadParams {
internal int threadOrdinal;
internal List<StackWithCount> listOfCallStacks;
internal string symPath;
internal bool searchPDBsRecursively;
internal List<string> dllPaths;
internal bool searchDLLRecursively;
internal bool framesOnSingleLine;
internal bool includeSourceInfo;
internal bool showInlineFrames;
internal bool relookupSource;
internal bool includeOffsets;
internal int numThreads;
internal bool cachePDB;
}
}

119
Engine/XELHelper.cs Normal file
Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using Microsoft.SqlServer.XEvent.XELite;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
internal class XELHelper {
/// Read a XEL file, consume all callstacks, optionally bucketize them, and in all cases, return the information as equivalent XML
internal static Tuple<int, string> ExtractFromXEL(StackResolver parent, string[] xelFiles, bool bucketize) {
Contract.Requires(xelFiles != null);
parent.cancelRequested = false;
var callstackSlots = new Dictionary<string, long>();
var callstackRaw = new Dictionary<string, string>();
var xmlEquivalent = new StringBuilder();
// the below feels quite hacky. Unfortunately till such time that we have strong typing in XELite I believe this is unavoidable
var relevantKeyNames = new string[] { "callstack", "call_stack", "stack_frames" };
foreach (var xelFileName in xelFiles) {
if (File.Exists(xelFileName)) {
parent.StatusMessage = $@"Reading {xelFileName}...";
var xeStream = new XEFileEventStreamer(xelFileName);
xeStream.ReadEventStream(
() => {
return Task.CompletedTask;
},
evt => {
var allStacks = (from actTmp in evt.Actions
where relevantKeyNames.Contains(actTmp.Key.ToLower(CultureInfo.CurrentCulture))
select actTmp.Value as string)
.Union(
from fldTmp in evt.Fields
where relevantKeyNames.Contains(fldTmp.Key.ToLower(CultureInfo.CurrentCulture))
select fldTmp.Value as string);
foreach (var callStackString in allStacks) {
if (string.IsNullOrEmpty(callStackString)) {
continue;
}
if (bucketize) {
lock (callstackSlots) {
if (!callstackSlots.ContainsKey(callStackString)) {
callstackSlots.Add(callStackString, 1);
}
else {
callstackSlots[callStackString]++;
}
}
}
else {
var evtId = string.Format(CultureInfo.CurrentCulture, "File: {0}, Timestamp: {1}, UUID: {2}:", xelFileName, evt.Timestamp.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffff", CultureInfo.CurrentCulture), evt.UUID);
lock (callstackRaw) {
if (!callstackRaw.ContainsKey(evtId)) {
callstackRaw.Add(evtId, callStackString);
}
else {
callstackRaw[evtId] += $"{Environment.NewLine}{callStackString}";
}
}
}
}
return Task.CompletedTask;
},
CancellationToken.None).Wait();
}
}
parent.StatusMessage = "Finished reading file(s), finalizing output...";
int finalEventCount;
if (bucketize) {
xmlEquivalent.AppendLine("<HistogramTarget>");
parent.globalCounter = 0;
foreach (var item in callstackSlots.OrderByDescending(key => key.Value)) {
xmlEquivalent.AppendFormat(CultureInfo.CurrentCulture,
"<Slot count=\"{0}\"><value>{1}</value></Slot>",
item.Value,
item.Key);
xmlEquivalent.AppendLine();
parent.globalCounter++;
parent.PercentComplete = (int)((double)parent.globalCounter / callstackSlots.Count * 100.0);
}
xmlEquivalent.AppendLine("</HistogramTarget>");
finalEventCount = callstackSlots.Count;
}
else {
xmlEquivalent.AppendLine("<Events>");
parent.globalCounter = 0;
var hasOverflow = false;
foreach (var item in callstackRaw.OrderBy(key => key.Key)) {
if (xmlEquivalent.Length < int.MaxValue * 0.90) {
xmlEquivalent.AppendFormat(CultureInfo.CurrentCulture, "<event key=\"{0}\"><action name='callstack'><value>{1}</value></action></event>", item.Key, item.Value);
xmlEquivalent.AppendLine();
}
else {
hasOverflow = true;
}
parent.globalCounter++;
parent.PercentComplete = (int)((double)parent.globalCounter / callstackRaw.Count * 100.0);
}
if (hasOverflow) xmlEquivalent.AppendLine("<!-- WARNING: output was truncated due to size limits -->");
xmlEquivalent.AppendLine("</Events>");
finalEventCount = callstackRaw.Count;
}
parent.StatusMessage = $@"Finished processing {xelFiles.Length} XEL files";
return new Tuple<int, string>(finalEventCount, xmlEquivalent.ToString());
}
}
}

4
Engine/app.config Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!--
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
--><configuration><runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0"/></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/></dependentAssembly><dependentAssembly><assemblyIdentity name="Microsoft.Diagnostics.NETCore.Client" publicKeyToken="31bf3856ad364e35" culture="neutral"/><bindingRedirect oldVersion="0.0.0.0-0.2.2.37102" newVersion="0.2.2.37102"/></dependentAssembly></assemblyBinding></runtime><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup></configuration>

13
Engine/app.manifest Normal file
Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
-->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name = "SQLCallStackResolver.Engine" version="2.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" processorArchitecture="amd64" name="msdia140.dll" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>

Просмотреть файл

@ -0,0 +1,16 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License - see LICENSE file in this repo.
param ($binaryDnldURL)
$localpath = ".\BinaryDependencies.zip"
if (-not (test-path $localpath)) {
try {
Invoke-WebRequest -UseBasicParsing -uri $binaryDnldURL -OutFile $localpath -ErrorAction Ignore
Expand-Archive -Path $localpath -DestinationPath "."
} catch {}
}
if (-not (test-path (".\DIA\msdia140.dll"))) {
Write-Warning "You must manually obtain msdia140.dll, msdia140.dll.manifest and associated necessary Visual C++ runtime dependency DLLs (msvcp140.dll, vcruntime140.dll and vcruntime140_1.dll are redistributable components of Visual Studio 2019 subject to terms as published [here](https://docs.microsoft.com/en-us/visualstudio/releases/2019/redistribution). Windows Debugging Tools DLLs (dbghelp.dll and symsrv.dll) as per the terms published at https://docs.microsoft.com/en-us/legal/windows-sdk/redist#debugging-tools-for-windows."
}

1
Engine/packages.config Normal file
Просмотреть файл

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><packages><package id="Microsoft.Diagnostics.NETCore.Client" version="0.2.137102" targetFramework="net472" /><package id="Microsoft.Diagnostics.Runtime" version="2.0.161401" targetFramework="net472" /><package id="Microsoft.SqlServer.XEvent.XELite" version="2019.11.20.2" targetFramework="net472" /><package id="System.Buffers" version="4.5.1" targetFramework="net472" /><package id="System.Collections.Immutable" version="1.7.1" targetFramework="net472" /><package id="System.Memory" version="4.5.4" targetFramework="net472" /><package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" /><package id="System.Reflection.Metadata" version="1.8.1" targetFramework="net472" /><package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" /></packages>

4
GUI/App.config Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!--
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
--><configuration><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /></startup><appSettings><add key="PDBDownloadFolder" value="c:\temp" /><add key="SQLBuildInfoUpdateURLs" value="https://raw.githubusercontent.com/microsoft/SQLCallStackResolver/main/lastupdated.txt;https://raw.githubusercontent.com/arvindshmicrosoft/SQLCallStackResolver/main/lastupdated.txt" /><add key="SQLBuildInfoURLs" value="https://raw.githubusercontent.com/microsoft/SQLCallStackResolver/main/sqlbuildinfo.json;https://raw.githubusercontent.com/arvindshmicrosoft/SQLCallStackResolver/main/sqlbuildinfo.json" /></appSettings><runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="Microsoft.Diagnostics.NETCore.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-0.2.2.37102" newVersion="0.2.2.37102" /></dependentAssembly></assemblyBinding></runtime></configuration>

597
GUI/MainForm.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,597 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
partial class MainForm {
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing) {
if (disposing) {
_resolver.Dispose();
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.genericOpenFileDlg = new System.Windows.Forms.OpenFileDialog();
this.splitContainer2 = new System.Windows.Forms.SplitContainer();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.callStackInput = new System.Windows.Forms.TextBox();
this.finalOutput = new System.Windows.Forms.TextBox();
this.groupBox4 = new System.Windows.Forms.GroupBox();
this.GetPDBDnldScript = new System.Windows.Forms.Button();
this.DLLrecurse = new System.Windows.Forms.CheckBox();
this.BinaryPathPicker = new System.Windows.Forms.Button();
this.binaryPaths = new System.Windows.Forms.TextBox();
this.label2 = new System.Windows.Forms.Label();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.BucketizeXEL = new System.Windows.Forms.CheckBox();
this.LoadXELButton = new System.Windows.Forms.Button();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.cachePDB = new System.Windows.Forms.CheckBox();
this.selectSQLPDB = new System.Windows.Forms.Button();
this.PDBPathPicker = new System.Windows.Forms.Button();
this.pdbRecurse = new System.Windows.Forms.CheckBox();
this.pdbPaths = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.outputFilePathPicker = new System.Windows.Forms.Button();
this.outputFilePath = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
this.FramesOnSingleLine = new System.Windows.Forms.CheckBox();
this.IncludeLineNumbers = new System.Windows.Forms.CheckBox();
this.EnterBaseAddresses = new System.Windows.Forms.Button();
this.includeOffsets = new System.Windows.Forms.CheckBox();
this.RelookupSource = new System.Windows.Forms.CheckBox();
this.ResolveCallStackButton = new System.Windows.Forms.Button();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.progressBar = new System.Windows.Forms.ToolStripProgressBar();
this.cancelButton = new System.Windows.Forms.ToolStripDropDownButton();
this.formToolTip = new System.Windows.Forms.ToolTip(this.components);
this.genericSaveFileDlg = new System.Windows.Forms.SaveFileDialog();
this.showInlineFrames = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
this.splitContainer2.Panel1.SuspendLayout();
this.splitContainer2.Panel2.SuspendLayout();
this.splitContainer2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
this.groupBox4.SuspendLayout();
this.groupBox3.SuspendLayout();
this.groupBox2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
//
// splitContainer2
//
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
this.splitContainer2.Location = new System.Drawing.Point(0, 0);
this.splitContainer2.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.splitContainer2.Name = "splitContainer2";
this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer2.Panel1
//
this.splitContainer2.Panel1.Controls.Add(this.splitContainer1);
//
// splitContainer2.Panel2
//
this.splitContainer2.Panel2.Controls.Add(this.groupBox4);
this.splitContainer2.Panel2.Controls.Add(this.groupBox3);
this.splitContainer2.Panel2.Controls.Add(this.groupBox2);
this.splitContainer2.Panel2.Controls.Add(this.groupBox1);
this.splitContainer2.Panel2.Controls.Add(this.ResolveCallStackButton);
this.splitContainer2.Size = new System.Drawing.Size(1305, 828);
this.splitContainer2.SplitterDistance = 510;
this.splitContainer2.TabIndex = 30;
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
this.splitContainer1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.splitContainer1.Name = "splitContainer1";
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.callStackInput);
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.finalOutput);
this.splitContainer1.Size = new System.Drawing.Size(1305, 510);
this.splitContainer1.SplitterDistance = 431;
this.splitContainer1.TabIndex = 30;
//
// callStackInput
//
this.callStackInput.AllowDrop = true;
this.callStackInput.Dock = System.Windows.Forms.DockStyle.Fill;
this.callStackInput.Location = new System.Drawing.Point(0, 0);
this.callStackInput.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.callStackInput.MaxLength = 999999999;
this.callStackInput.Multiline = true;
this.callStackInput.Name = "callStackInput";
this.callStackInput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.callStackInput.Size = new System.Drawing.Size(431, 510);
this.callStackInput.TabIndex = 8;
this.callStackInput.Text = resources.GetString("callStackInput.Text");
this.callStackInput.WordWrap = false;
this.callStackInput.DragDrop += new System.Windows.Forms.DragEventHandler(this.CallStackInput_DragDrop);
this.callStackInput.DragEnter += new System.Windows.Forms.DragEventHandler(this.CallStackInput_DragOver);
//
// finalOutput
//
this.finalOutput.Dock = System.Windows.Forms.DockStyle.Fill;
this.finalOutput.Location = new System.Drawing.Point(0, 0);
this.finalOutput.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.finalOutput.MaxLength = 999999999;
this.finalOutput.Multiline = true;
this.finalOutput.Name = "finalOutput";
this.finalOutput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.finalOutput.Size = new System.Drawing.Size(870, 510);
this.finalOutput.TabIndex = 8;
this.finalOutput.WordWrap = false;
//
// groupBox4
//
this.groupBox4.Controls.Add(this.GetPDBDnldScript);
this.groupBox4.Controls.Add(this.DLLrecurse);
this.groupBox4.Controls.Add(this.BinaryPathPicker);
this.groupBox4.Controls.Add(this.binaryPaths);
this.groupBox4.Controls.Add(this.label2);
this.groupBox4.Location = new System.Drawing.Point(15, 292);
this.groupBox4.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.groupBox4.Name = "groupBox4";
this.groupBox4.Padding = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.groupBox4.Size = new System.Drawing.Size(1276, 53);
this.groupBox4.TabIndex = 33;
this.groupBox4.TabStop = false;
this.groupBox4.Text = "SPECIAL CASES";
//
// GetPDBDnldScript
//
this.GetPDBDnldScript.Location = new System.Drawing.Point(947, 16);
this.GetPDBDnldScript.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.GetPDBDnldScript.Name = "GetPDBDnldScript";
this.GetPDBDnldScript.Size = new System.Drawing.Size(323, 30);
this.GetPDBDnldScript.TabIndex = 13;
this.GetPDBDnldScript.Text = "Generate PDB download script";
this.GetPDBDnldScript.UseVisualStyleBackColor = true;
this.GetPDBDnldScript.Click += new System.EventHandler(this.GetPDBDnldScript_Click);
//
// DLLrecurse
//
this.DLLrecurse.AutoSize = true;
this.DLLrecurse.Location = new System.Drawing.Point(673, 20);
this.DLLrecurse.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.DLLrecurse.Name = "DLLrecurse";
this.DLLrecurse.Size = new System.Drawing.Size(264, 21);
this.DLLrecurse.TabIndex = 10;
this.DLLrecurse.Text = "Search for DLLs and EXE recursively";
this.DLLrecurse.UseVisualStyleBackColor = true;
//
// BinaryPathPicker
//
this.BinaryPathPicker.Location = new System.Drawing.Point(635, 18);
this.BinaryPathPicker.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.BinaryPathPicker.Name = "BinaryPathPicker";
this.BinaryPathPicker.Size = new System.Drawing.Size(33, 23);
this.BinaryPathPicker.TabIndex = 20;
this.BinaryPathPicker.Text = "...";
this.BinaryPathPicker.UseVisualStyleBackColor = true;
this.BinaryPathPicker.Click += new System.EventHandler(this.BinaryPathPicker_Click);
//
// binaryPaths
//
this.binaryPaths.Location = new System.Drawing.Point(261, 18);
this.binaryPaths.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.binaryPaths.Name = "binaryPaths";
this.binaryPaths.Size = new System.Drawing.Size(367, 22);
this.binaryPaths.TabIndex = 9;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(7, 21);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(252, 17);
this.label2.TabIndex = 8;
this.label2.Text = "Specify Path(s) to SQL Server binaries";
this.formToolTip.SetToolTip(this.label2, "Only need to do this if you are dealing with incomplete stacks collected by -T365" +
"6 OR if you need to get PowerShell commands to download PDBs for a specific buil" +
"d of SQL");
//
// groupBox3
//
this.groupBox3.Controls.Add(this.BucketizeXEL);
this.groupBox3.Controls.Add(this.LoadXELButton);
this.groupBox3.Location = new System.Drawing.Point(15, 91);
this.groupBox3.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox3.Size = new System.Drawing.Size(1276, 64);
this.groupBox3.TabIndex = 32;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "STEP 1: Either directly paste raw callstack(s) in textbox above, or import XEL fi" +
"le(s)";
//
// BucketizeXEL
//
this.BucketizeXEL.AutoSize = true;
this.BucketizeXEL.Checked = true;
this.BucketizeXEL.CheckState = System.Windows.Forms.CheckState.Checked;
this.BucketizeXEL.Location = new System.Drawing.Point(332, 28);
this.BucketizeXEL.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.BucketizeXEL.Name = "BucketizeXEL";
this.BucketizeXEL.Size = new System.Drawing.Size(676, 21);
this.BucketizeXEL.TabIndex = 22;
this.BucketizeXEL.Text = "Aggregate similar callstacks from XEL (generally leave checked unless you need in" +
"dividual event data)";
this.BucketizeXEL.UseVisualStyleBackColor = true;
//
// LoadXELButton
//
this.LoadXELButton.Location = new System.Drawing.Point(11, 18);
this.LoadXELButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.LoadXELButton.Name = "LoadXELButton";
this.LoadXELButton.Size = new System.Drawing.Size(304, 37);
this.LoadXELButton.TabIndex = 15;
this.LoadXELButton.Text = "Select XEL files and import callstacks";
this.LoadXELButton.UseVisualStyleBackColor = true;
this.LoadXELButton.Click += new System.EventHandler(this.LoadXELButton_Click);
//
// groupBox2
//
this.groupBox2.Controls.Add(this.cachePDB);
this.groupBox2.Controls.Add(this.selectSQLPDB);
this.groupBox2.Controls.Add(this.PDBPathPicker);
this.groupBox2.Controls.Add(this.pdbRecurse);
this.groupBox2.Controls.Add(this.pdbPaths);
this.groupBox2.Controls.Add(this.label1);
this.groupBox2.Location = new System.Drawing.Point(15, 160);
this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox2.Size = new System.Drawing.Size(1276, 65);
this.groupBox2.TabIndex = 31;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "STEP 2: Either use preset symbol downloads or set custom PDB search paths";
//
// cachePDB
//
this.cachePDB.AutoSize = true;
this.cachePDB.Location = new System.Drawing.Point(1076, 27);
this.cachePDB.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.cachePDB.Name = "cachePDB";
this.cachePDB.Size = new System.Drawing.Size(109, 21);
this.cachePDB.TabIndex = 33;
this.cachePDB.Text = "Cache PDBs";
this.formToolTip.SetToolTip(this.cachePDB, "This option will copy PDBs from the paths specified to the %TEMP%\\SymCache folder" +
". It is highly recommended to use this if you have a UNC path specified.");
this.cachePDB.UseVisualStyleBackColor = true;
//
// selectSQLPDB
//
this.selectSQLPDB.Location = new System.Drawing.Point(9, 23);
this.selectSQLPDB.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.selectSQLPDB.Name = "selectSQLPDB";
this.selectSQLPDB.Size = new System.Drawing.Size(377, 31);
this.selectSQLPDB.TabIndex = 32;
this.selectSQLPDB.Text = "Use public PDBs for a known SQL Server build";
this.selectSQLPDB.UseVisualStyleBackColor = true;
this.selectSQLPDB.Click += new System.EventHandler(this.SelectSQLPDB_Click);
//
// PDBPathPicker
//
this.PDBPathPicker.Location = new System.Drawing.Point(825, 27);
this.PDBPathPicker.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.PDBPathPicker.Name = "PDBPathPicker";
this.PDBPathPicker.Size = new System.Drawing.Size(33, 23);
this.PDBPathPicker.TabIndex = 29;
this.PDBPathPicker.Text = "...";
this.PDBPathPicker.UseVisualStyleBackColor = true;
this.PDBPathPicker.Click += new System.EventHandler(this.PDBPathPicker_Click);
//
// pdbRecurse
//
this.pdbRecurse.AutoSize = true;
this.pdbRecurse.Checked = true;
this.pdbRecurse.CheckState = System.Windows.Forms.CheckState.Checked;
this.pdbRecurse.Location = new System.Drawing.Point(864, 27);
this.pdbRecurse.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.pdbRecurse.Name = "pdbRecurse";
this.pdbRecurse.Size = new System.Drawing.Size(207, 21);
this.pdbRecurse.TabIndex = 28;
this.pdbRecurse.Text = "Search for PDBs recursively";
this.pdbRecurse.UseVisualStyleBackColor = true;
//
// pdbPaths
//
this.pdbPaths.Location = new System.Drawing.Point(507, 26);
this.pdbPaths.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.pdbPaths.Name = "pdbPaths";
this.pdbPaths.Size = new System.Drawing.Size(312, 22);
this.pdbPaths.TabIndex = 27;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(392, 30);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(109, 17);
this.label1.TabIndex = 26;
this.label1.Text = "Path(s) to PDBs";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.showInlineFrames);
this.groupBox1.Controls.Add(this.outputFilePathPicker);
this.groupBox1.Controls.Add(this.outputFilePath);
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.FramesOnSingleLine);
this.groupBox1.Controls.Add(this.IncludeLineNumbers);
this.groupBox1.Controls.Add(this.EnterBaseAddresses);
this.groupBox1.Controls.Add(this.includeOffsets);
this.groupBox1.Controls.Add(this.RelookupSource);
this.groupBox1.Location = new System.Drawing.Point(15, 2);
this.groupBox1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.groupBox1.Size = new System.Drawing.Size(1276, 84);
this.groupBox1.TabIndex = 30;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "STEP 0: Input and output options";
//
// outputFilePathPicker
//
this.outputFilePathPicker.Location = new System.Drawing.Point(1224, 22);
this.outputFilePathPicker.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.outputFilePathPicker.Name = "outputFilePathPicker";
this.outputFilePathPicker.Size = new System.Drawing.Size(33, 23);
this.outputFilePathPicker.TabIndex = 32;
this.outputFilePathPicker.Text = "...";
this.outputFilePathPicker.UseVisualStyleBackColor = true;
this.outputFilePathPicker.Click += new System.EventHandler(this.outputFilePathPicker_Click);
//
// outputFilePath
//
this.outputFilePath.Location = new System.Drawing.Point(833, 21);
this.outputFilePath.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.outputFilePath.Name = "outputFilePath";
this.outputFilePath.Size = new System.Drawing.Size(384, 22);
this.outputFilePath.TabIndex = 31;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(616, 25);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(209, 17);
this.label3.TabIndex = 30;
this.label3.Text = "OUTPUT: Redirect output to file";
//
// FramesOnSingleLine
//
this.FramesOnSingleLine.AutoSize = true;
this.FramesOnSingleLine.Location = new System.Drawing.Point(9, 21);
this.FramesOnSingleLine.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.FramesOnSingleLine.Name = "FramesOnSingleLine";
this.FramesOnSingleLine.Size = new System.Drawing.Size(301, 21);
this.FramesOnSingleLine.TabIndex = 23;
this.FramesOnSingleLine.Text = "INPUT: Callstack frames are in a single line";
this.formToolTip.SetToolTip(this.FramesOnSingleLine, "Required if copy-pasting XE callstack from SSMS");
this.FramesOnSingleLine.UseVisualStyleBackColor = true;
//
// IncludeLineNumbers
//
this.IncludeLineNumbers.AutoSize = true;
this.IncludeLineNumbers.Checked = true;
this.IncludeLineNumbers.CheckState = System.Windows.Forms.CheckState.Checked;
this.IncludeLineNumbers.Location = new System.Drawing.Point(806, 50);
this.IncludeLineNumbers.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.IncludeLineNumbers.Name = "IncludeLineNumbers";
this.IncludeLineNumbers.Size = new System.Drawing.Size(270, 21);
this.IncludeLineNumbers.TabIndex = 16;
this.IncludeLineNumbers.Text = "OUTPUT: Source lines (private PDBs)";
this.IncludeLineNumbers.UseVisualStyleBackColor = true;
//
// EnterBaseAddresses
//
this.EnterBaseAddresses.Location = new System.Drawing.Point(411, 21);
this.EnterBaseAddresses.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.EnterBaseAddresses.Name = "EnterBaseAddresses";
this.EnterBaseAddresses.Size = new System.Drawing.Size(193, 47);
this.EnterBaseAddresses.TabIndex = 3;
this.EnterBaseAddresses.Text = "INPUT: Specify base addresses for modules";
this.formToolTip.SetToolTip(this.EnterBaseAddresses, "Required for working with XEL files and hex address-only callstacks");
this.EnterBaseAddresses.UseVisualStyleBackColor = true;
this.EnterBaseAddresses.Click += new System.EventHandler(this.EnterBaseAddresses_Click);
//
// includeOffsets
//
this.includeOffsets.AutoSize = true;
this.includeOffsets.Location = new System.Drawing.Point(616, 50);
this.includeOffsets.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.includeOffsets.Name = "includeOffsets";
this.includeOffsets.Size = new System.Drawing.Size(173, 21);
this.includeOffsets.TabIndex = 17;
this.includeOffsets.Text = "OUTPUT: Func offsets";
this.includeOffsets.UseVisualStyleBackColor = true;
//
// RelookupSource
//
this.RelookupSource.AutoSize = true;
this.RelookupSource.Location = new System.Drawing.Point(9, 47);
this.RelookupSource.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.RelookupSource.Name = "RelookupSource";
this.RelookupSource.Size = new System.Drawing.Size(398, 21);
this.RelookupSource.TabIndex = 16;
this.RelookupSource.Text = "INPUT: Re-lookup source (rare case, needs private PDBs)";
this.RelookupSource.UseVisualStyleBackColor = true;
//
// ResolveCallStackButton
//
this.ResolveCallStackButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.ResolveCallStackButton.Location = new System.Drawing.Point(15, 230);
this.ResolveCallStackButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.ResolveCallStackButton.Name = "ResolveCallStackButton";
this.ResolveCallStackButton.Size = new System.Drawing.Size(1276, 55);
this.ResolveCallStackButton.TabIndex = 29;
this.ResolveCallStackButton.Text = "STEP 3: Resolve callstacks!";
this.ResolveCallStackButton.UseVisualStyleBackColor = true;
this.ResolveCallStackButton.Click += new System.EventHandler(this.ResolveCallstacks_Click);
//
// statusStrip1
//
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.statusLabel,
this.progressBar,
this.cancelButton});
this.statusStrip1.Location = new System.Drawing.Point(0, 798);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 19, 0);
this.statusStrip1.Size = new System.Drawing.Size(1305, 30);
this.statusStrip1.TabIndex = 31;
this.statusStrip1.Text = "statusStrip1";
//
// statusLabel
//
this.statusLabel.AutoSize = false;
this.statusLabel.Name = "statusLabel";
this.statusLabel.Size = new System.Drawing.Size(700, 24);
this.statusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// progressBar
//
this.progressBar.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
this.progressBar.AutoSize = false;
this.progressBar.Name = "progressBar";
this.progressBar.Size = new System.Drawing.Size(100, 22);
//
// cancelButton
//
this.cancelButton.AutoSize = false;
this.cancelButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.cancelButton.Enabled = false;
this.cancelButton.Image = ((System.Drawing.Image)(resources.GetObject("cancelButton.Image")));
this.cancelButton.ImageTransparentColor = System.Drawing.Color.Magenta;
this.cancelButton.Name = "cancelButton";
this.cancelButton.ShowDropDownArrow = false;
this.cancelButton.Size = new System.Drawing.Size(100, 28);
this.cancelButton.Text = "Cancel";
this.cancelButton.TextImageRelation = System.Windows.Forms.TextImageRelation.TextBeforeImage;
this.cancelButton.Visible = false;
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
//
// showInlineFrames
//
this.showInlineFrames.AutoSize = true;
this.showInlineFrames.Checked = true;
this.showInlineFrames.CheckState = System.Windows.Forms.CheckState.Checked;
this.showInlineFrames.Location = new System.Drawing.Point(1093, 50);
this.showInlineFrames.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.showInlineFrames.Name = "showInlineFrames";
this.showInlineFrames.Size = new System.Drawing.Size(176, 21);
this.showInlineFrames.TabIndex = 33;
this.showInlineFrames.Text = "OUTPUT: Inline frames";
this.showInlineFrames.UseVisualStyleBackColor = true;
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.ClientSize = new System.Drawing.Size(1305, 828);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.splitContainer2);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "SQLCallstackResolver (http://aka.ms/sqlstack)";
this.Load += new System.EventHandler(this.MainForm_Load);
this.splitContainer2.Panel1.ResumeLayout(false);
this.splitContainer2.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
this.splitContainer2.ResumeLayout(false);
this.splitContainer1.Panel1.ResumeLayout(false);
this.splitContainer1.Panel1.PerformLayout();
this.splitContainer1.Panel2.ResumeLayout(false);
this.splitContainer1.Panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.groupBox4.ResumeLayout(false);
this.groupBox4.PerformLayout();
this.groupBox3.ResumeLayout(false);
this.groupBox3.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.OpenFileDialog genericOpenFileDlg;
private System.Windows.Forms.SplitContainer splitContainer2;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.TextBox callStackInput;
private System.Windows.Forms.TextBox finalOutput;
private System.Windows.Forms.CheckBox DLLrecurse;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox binaryPaths;
private System.Windows.Forms.Button BinaryPathPicker;
private System.Windows.Forms.Button GetPDBDnldScript;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.CheckBox FramesOnSingleLine;
private System.Windows.Forms.CheckBox BucketizeXEL;
private System.Windows.Forms.Button LoadXELButton;
private System.Windows.Forms.Button EnterBaseAddresses;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.Button selectSQLPDB;
private System.Windows.Forms.Button PDBPathPicker;
private System.Windows.Forms.CheckBox pdbRecurse;
private System.Windows.Forms.TextBox pdbPaths;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.CheckBox IncludeLineNumbers;
private System.Windows.Forms.CheckBox includeOffsets;
private System.Windows.Forms.CheckBox RelookupSource;
private System.Windows.Forms.Button ResolveCallStackButton;
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel statusLabel;
private System.Windows.Forms.CheckBox cachePDB;
private System.Windows.Forms.ToolTip formToolTip;
private System.Windows.Forms.GroupBox groupBox4;
private System.Windows.Forms.Button outputFilePathPicker;
private System.Windows.Forms.TextBox outputFilePath;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.SaveFileDialog genericSaveFileDlg;
private System.Windows.Forms.ToolStripProgressBar progressBar;
private System.Windows.Forms.ToolStripDropDownButton cancelButton;
private System.Windows.Forms.CheckBox showInlineFrames;
}
}

435
GUI/MainForm.cs Normal file
Просмотреть файл

@ -0,0 +1,435 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using System.IO;
using System.Net;
using System.Globalization;
using System.Configuration;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Threading;
public partial class MainForm : Form {
public MainForm() {
InitializeComponent();
}
private StackResolver _resolver = new StackResolver();
private Task<string> backgroundTask;
private CancellationTokenSource backgroundCTS { get; set; }
private CancellationToken backgroundCT;
private string _baseAddressesString = null;
internal static string SqlBuildInfoFileName = @"sqlbuildinfo.json";
internal static string LastUpdatedTimestampFileName = @"lastupdated.txt";
internal static string LastUpdatedTimestampFormat = "yyyy-MM-dd HH:mm";
internal static string LastUpdatedTimestampCulture = "en-US";
private void ResolveCallstacks_Click(object sender, EventArgs e) {
List<string> dllPaths = null;
if (!string.IsNullOrEmpty(binaryPaths.Text)) {
dllPaths = binaryPaths.Text.Split(';').ToList();
}
var res = this._resolver.ProcessBaseAddresses(this._baseAddressesString);
if (!res) {
MessageBox.Show(
this,
"Cannot interpret the module base address information. Make sure you just have the output of the following query (no column headers, no other columns) copied from SSMS using the Grid Results\r\n\r\nselect name, base_address from sys.dm_os_loaded_modules where name not like '%.rll'",
"Unable to load base address information",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
int numLinesOfInput = callStackInput.Text.Length - callStackInput.Text.Replace(Environment.NewLine, string.Empty).Length;
if (1 == numLinesOfInput && !FramesOnSingleLine.Checked) {
if (DialogResult.Yes == MessageBox.Show(this,
"Maybe this is intentional, but your input seems to have all the frames on a single line, but the 'Callstack frames are in single line' checkbox is unchecked. " +
"This may cause problems resolving symbols. Would you like to enable this?",
"Enable the 'frames on single line' option?",
MessageBoxButtons.YesNo)) {
FramesOnSingleLine.Checked = true;
FramesOnSingleLine.Refresh();
this.Refresh();
Application.DoEvents();
}
}
if (numLinesOfInput > 1 && FramesOnSingleLine.Checked) {
if (DialogResult.Yes == MessageBox.Show(this,
"Your input seems to have multiple lines, but the 'Callstack frames are in single line' checkbox is checked. " +
"This may cause problems resolving symbols. Would you like to uncheck this setting?",
"Disable the 'frames on single line' option?",
MessageBoxButtons.YesNo)) {
FramesOnSingleLine.Checked = false;
FramesOnSingleLine.Refresh();
this.Refresh();
Application.DoEvents();
}
}
if (!pdbPaths.Text.Contains(@"\\") && cachePDB.Checked) {
if (DialogResult.Yes == MessageBox.Show(this,
"Cache PDBs is only recommended when getting symbols from UNC paths. " +
"Would you like to disable this?",
"Disable symbol file cache?",
MessageBoxButtons.YesNo)) {
cachePDB.Checked = false;
cachePDB.Refresh();
this.Refresh();
Application.DoEvents();
}
}
if (pdbPaths.Text.Contains(@"\\") && !cachePDB.Checked) {
if (DialogResult.Yes == MessageBox.Show(this,
"When getting symbols from UNC paths, SQLCallStackResolver can temporary cache a copy when resolving symbols. " +
"This may speed things up especially when the UNC path is over a WAN and when you have a number of callstacks to resolve. " +
"Would you like to enable this?",
"Enable symbol file cache?",
MessageBoxButtons.YesNo)) {
cachePDB.Checked = true;
cachePDB.Refresh();
this.Refresh();
Application.DoEvents();
}
}
this.backgroundTask = Task.Run(() => {
return this._resolver.ResolveCallstacks(callStackInput.Text,
pdbPaths.Text,
pdbRecurse.Checked,
dllPaths,
DLLrecurse.Checked,
FramesOnSingleLine.Checked,
IncludeLineNumbers.Checked,
RelookupSource.Checked,
includeOffsets.Checked,
showInlineFrames.Checked,
cachePDB.Checked,
outputFilePath.Text
);
});
this.MonitorBackgroundTask(backgroundTask);
finalOutput.Text = backgroundTask.Result;
}
private void DisableCancelButton() {
this.cancelButton.Enabled = false;
this.cancelButton.Visible = false;
}
private void EnableCancelButton() {
this.cancelButton.Enabled = true;
this.cancelButton.Visible = true;
}
private void EnterBaseAddresses_Click(object sender, EventArgs e) {
using (var baseAddressForm = new MultilineInput(this._baseAddressesString, true)) {
baseAddressForm.StartPosition = FormStartPosition.CenterParent;
DialogResult res = baseAddressForm.ShowDialog(this);
if (res == DialogResult.OK) {
this._baseAddressesString = baseAddressForm.baseaddressesstring;
}
else {
return;
}
}
}
private void CallStackInput_KeyDown(object sender, KeyEventArgs e) {
if (e.Control && e.KeyCode == Keys.A) {
callStackInput.SelectAll();
}
}
private void FinalOutput_KeyDown(object sender, KeyEventArgs e) {
if (e.Control && e.KeyCode == Keys.A) {
finalOutput.SelectAll();
}
}
private void GetPDBDnldScript_Click(object sender, EventArgs e) {
this.ShowStatus("Getting PDB download script... please wait. This may take a while!");
var symDetails = StackResolver.GetSymbolDetailsForBinaries(binaryPaths.Text.Split(';').ToList(),
DLLrecurse.Checked);
if (0 == symDetails.Count) {
return;
}
var fakeBuild = new SQLBuildInfo() {
SymbolDetails = symDetails
};
var downloadCmds = SQLBuildInfo.GetDownloadScriptPowerShell(fakeBuild, false);
this.ShowStatus(string.Empty);
using (var outputCmds = new MultilineInput(downloadCmds.ToString(CultureInfo.CurrentCulture), false)) {
outputCmds.StartPosition = FormStartPosition.CenterParent;
outputCmds.ShowDialog(this);
}
}
private void ShowStatus(string txt) {
this.statusLabel.Text = txt;
Application.DoEvents();
}
private void LoadXELButton_Click(object sender, EventArgs e) {
genericOpenFileDlg.Multiselect = true;
genericOpenFileDlg.CheckPathExists = true;
genericOpenFileDlg.CheckFileExists = true;
genericOpenFileDlg.FileName = String.Empty;
genericOpenFileDlg.Filter = "XEL files (*.xel)|*.xel|All files (*.*)|*.*";
genericOpenFileDlg.Title = "Select XEL file";
var res = genericOpenFileDlg.ShowDialog(this);
if (res != DialogResult.Cancel) {
this.ShowStatus("Loading from XEL files; please wait. This may take a while!");
this.backgroundTask = Task.Run(() => {
return this._resolver.ExtractFromXEL(genericOpenFileDlg.FileNames, BucketizeXEL.Checked).Item2;
});
this.MonitorBackgroundTask(backgroundTask);
callStackInput.Text = backgroundTask.Result;
this.ShowStatus("Finished importing callstacks from XEL file(s)!");
}
}
private void MonitorBackgroundTask(Task backgroundTask) {
using (backgroundCTS = new CancellationTokenSource()) {
backgroundCT = backgroundCTS.Token;
this.EnableCancelButton();
while (!backgroundTask.Wait(30)) {
if (backgroundCT.IsCancellationRequested) {
this._resolver.CancelRunningTasks();
}
this.ShowStatus(_resolver.StatusMessage);
this.progressBar.Value = _resolver.PercentComplete;
this.statusStrip1.Refresh();
Application.DoEvents();
}
// refresh it one last time to ensure that the last status message is displayed
this.ShowStatus(_resolver.StatusMessage);
this.progressBar.Value = _resolver.PercentComplete;
this.statusStrip1.Refresh();
Application.DoEvents();
this.DisableCancelButton();
}
}
private void CallStackInput_DragDrop(object sender, DragEventArgs e) {
if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true) {
e.Effect = DragDropEffects.All;
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Length != 0) {
var allFilesContent = new StringBuilder();
// sample the first file selected and if it is XEL assume all the files are XEL
// if there is any other format in between, it will be rejected by the ExtractFromXEL code
if (Path.GetExtension(files[0]).ToLower(CultureInfo.CurrentCulture) == ".xel") {
this.ShowStatus("XEL file was dragged; please wait while we extract events from the file");
allFilesContent.AppendLine(this._resolver.ExtractFromXEL(files, BucketizeXEL.Checked).Item2);
this.ShowStatus(string.Empty);
}
else {
// handle the files as text input
foreach (var currFile in files) {
allFilesContent.AppendLine(File.ReadAllText(currFile));
}
}
callStackInput.Text = allFilesContent.ToString();
}
}
}
private void CallStackInput_DragOver(object sender, DragEventArgs e) {
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}
private void PDBPathPicker_Click(object sender, EventArgs e) {
genericOpenFileDlg.Multiselect = false;
genericOpenFileDlg.CheckPathExists = false;
genericOpenFileDlg.CheckFileExists = false;
genericOpenFileDlg.FileName = "select folder only";
genericOpenFileDlg.Filter = "All files (*.*)|*.*";
genericOpenFileDlg.Title = "Select FOLDER path to your PDBs";
var res = genericOpenFileDlg.ShowDialog(this);
if (res != DialogResult.Cancel) {
pdbPaths.AppendText((pdbPaths.TextLength == 0 ? string.Empty : ";") + Path.GetDirectoryName(genericOpenFileDlg.FileName));
}
}
private void BinaryPathPicker_Click(object sender, EventArgs e) {
genericOpenFileDlg.Multiselect = false;
genericOpenFileDlg.CheckPathExists = false;
genericOpenFileDlg.CheckFileExists = false;
genericOpenFileDlg.FileName = "select folder only";
genericOpenFileDlg.Filter = "All files (*.*)|*.*";
genericOpenFileDlg.Title = "Select FOLDER path to the SQL binaries";
var res = genericOpenFileDlg.ShowDialog(this);
if (res != DialogResult.Cancel) {
binaryPaths.AppendText((binaryPaths.TextLength == 0 ? string.Empty : ";") + Path.GetDirectoryName(genericOpenFileDlg.FileName));
}
}
private void SelectSQLPDB_Click(object sender, EventArgs e) {
using (var sqlbuildsForm = new SQLBuildsForm {
pathToPDBs = ConfigurationManager.AppSettings["PDBDownloadFolder"]
}) {
sqlbuildsForm.StartPosition = FormStartPosition.CenterParent;
DialogResult res = sqlbuildsForm.ShowDialog(this);
this.pdbPaths.AppendText((pdbPaths.TextLength == 0 ? string.Empty : ";") + sqlbuildsForm.lastDownloadedSymFolder);
}
}
private void MainForm_Load(object sender, EventArgs e) {
DateTime lastUpdDateTimeServer = DateTime.MinValue;
DateTime lastUpdDateTimeLocal = DateTime.MinValue;
var sqlBuildInfoUpdateURLs = ConfigurationManager.AppSettings["SQLBuildInfoUpdateURLs"].Split(';');
var sqlBuildInfoURLs = ConfigurationManager.AppSettings["SQLBuildInfoURLs"].Split(';');
// get the timestamp of the first valid file within SQLBuildInfoURLs
foreach (var url in sqlBuildInfoUpdateURLs) {
HttpWebResponse httpResp = null;
var httpReq = (HttpWebRequest)WebRequest.Create(new Uri(url));
try {
httpResp = (HttpWebResponse)httpReq.GetResponse();
} catch (WebException) {
}
if (httpResp != null) {
if (httpResp.StatusCode == HttpStatusCode.OK) {
// get content of lastupdated.txt from upstream (GitHub) repo
using (var strm = new StreamReader(httpResp.GetResponseStream())) {
string lastUpd = strm.ReadToEnd().Trim();
lastUpdDateTimeServer = DateTime.ParseExact(lastUpd,
LastUpdatedTimestampFormat,
new CultureInfo(LastUpdatedTimestampCulture)
);
}
// get content of local lastupdated.txt (if it exists)
if (File.Exists(LastUpdatedTimestampFileName)) {
using (var strm = new StreamReader(LastUpdatedTimestampFileName)) {
string lastUpd = strm.ReadToEnd().Trim();
lastUpdDateTimeLocal = DateTime.ParseExact(lastUpd,
LastUpdatedTimestampFormat,
new CultureInfo(LastUpdatedTimestampCulture)
);
}
} else {
lastUpdDateTimeLocal = DateTime.MinValue;
}
if (lastUpdDateTimeServer > lastUpdDateTimeLocal) {
// if the server timestamp > local timestamp, prompt to download
var res = MessageBox.Show(this,
"The SQLBuildInfo.json file was updated recently on GitHub. Do you wish to update your copy with the newer version?",
"SQL Build info updated",
MessageBoxButtons.YesNo);
if (DialogResult.Yes == res) {
foreach (var jsonURL in sqlBuildInfoURLs) {
httpResp = null;
httpReq = (HttpWebRequest)WebRequest.Create(new Uri(jsonURL));
try {
httpResp = (HttpWebResponse)httpReq.GetResponse();
} catch (WebException) {
}
if (httpResp != null) {
if (httpResp.StatusCode == HttpStatusCode.OK) {
try {
using (var strm = new StreamReader(httpResp.GetResponseStream())) {
var jsonContent = strm.ReadToEnd();
// update local copy of build info file
using (var writer = new StreamWriter(SqlBuildInfoFileName)) {
writer.Write(jsonContent);
writer.Flush();
writer.Close();
}
// update local last updated timestamp
using (var wr = new StreamWriter(LastUpdatedTimestampFileName, false)) {
wr.Write(lastUpdDateTimeServer.ToString(
LastUpdatedTimestampFormat,
new CultureInfo(LastUpdatedTimestampCulture)
));
}
}
} catch (WebException) {
MessageBox.Show(this,
"Could not download SQL Build Info file due to HTTP errors.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
}
}
}
break;
}
}
}
}
private void outputFilePathPicker_Click(object sender, EventArgs e) {
genericSaveFileDlg.FileName = "resolvedstacks.txt";
genericSaveFileDlg.Filter = "Text files (*.txt)|*.txt";
genericSaveFileDlg.Title = "Save output as";
var res = genericSaveFileDlg.ShowDialog(this);
if (res != DialogResult.Cancel) {
outputFilePath.Text = genericSaveFileDlg.FileName;
}
}
private void cancelButton_Click(object sender, EventArgs e) {
this.backgroundCTS.Cancel();
}
}
}

40
GUI/MainForm.resx Normal file
Просмотреть файл

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?><root><xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xsd:import namespace="http://www.w3.org/XML/1998/namespace" /><xsd:element name="root" msdata:IsDataSet="true"><xsd:complexType><xsd:choice maxOccurs="unbounded"><xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element><xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /><xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /><xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /><xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element></xsd:choice></xsd:complexType></xsd:element></xsd:schema><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><metadata name="genericOpenFileDlg.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>135, 17</value></metadata><data name="callStackInput.Text" xml:space="preserve"><value>**** First see STEP 0 below ****
**** STEP 1: replace SAMPLE callstack with actual callstack(s) ****
**** OR import callstacks from XEL file(s) ****
sqldk.dll+0x0000000000047645
sqldk.dll+0x0000000000001960
sqldk.dll+0x00000000000012DF
sqlmin.dll+0x000000000000187C
sqlmin.dll+0x00000000000361FE
sqlmin.dll+0x00000000000083BE
sqlmin.dll+0x000000000000861C
sqlmin.dll+0x000000000000A3D8
sqlmin.dll+0x0000000000009E96
sqlmin.dll+0x000000000000C314
sqlmin.dll+0x000000000000934D
sqlmin.dll+0x00000000000149D0
sqlmin.dll+0x00000000000DCAE2
sqlmin.dll+0x00000000000DC84B
sqlmin.dll+0x000000000018C41E
sqlmin.dll+0x000000000018B388
sqlmin.dll+0x000000000018BF6B
sqlmin.dll+0x000000000018B1A1
sqllang.dll+0x00000000002C1FFE
sqllang.dll+0x00000000002C1F5A
sqllang.dll+0x0000000000243917
sqllang.dll+0x00000000002436B7
sqllang.dll+0x00000000002C1CD2
sqllang.dll+0x00000000002C1A80
</value></data><metadata name="formToolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>17, 17</value></metadata><metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>294, 17</value></metadata><assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /><data name="cancelButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"><value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
</value></data><metadata name="genericSaveFileDlg.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>410, 17</value></metadata></root>

112
GUI/MultilineInput.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
partial class MultilineInput {
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MultilineInput));
this.OkButton = new System.Windows.Forms.Button();
this.FormCancelButton = new System.Windows.Forms.Button();
this.InputAddresses = new System.Windows.Forms.TextBox();
this.loadFromFile = new System.Windows.Forms.Button();
this.fileDlg = new System.Windows.Forms.OpenFileDialog();
this.SuspendLayout();
//
// OkButton
//
this.OkButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.OkButton.Location = new System.Drawing.Point(180, 320);
this.OkButton.Margin = new System.Windows.Forms.Padding(2);
this.OkButton.Name = "OkButton";
this.OkButton.Size = new System.Drawing.Size(47, 23);
this.OkButton.TabIndex = 0;
this.OkButton.Text = "OK";
this.OkButton.UseVisualStyleBackColor = true;
//
// FormCancelButton
//
this.FormCancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.FormCancelButton.Location = new System.Drawing.Point(239, 320);
this.FormCancelButton.Margin = new System.Windows.Forms.Padding(2);
this.FormCancelButton.Name = "FormCancelButton";
this.FormCancelButton.Size = new System.Drawing.Size(56, 23);
this.FormCancelButton.TabIndex = 1;
this.FormCancelButton.Text = "Cancel";
this.FormCancelButton.UseVisualStyleBackColor = true;
//
// InputAddresses
//
this.InputAddresses.AllowDrop = true;
this.InputAddresses.Location = new System.Drawing.Point(0, 0);
this.InputAddresses.Margin = new System.Windows.Forms.Padding(2);
this.InputAddresses.MaxLength = 999999999;
this.InputAddresses.Multiline = true;
this.InputAddresses.Name = "InputAddresses";
this.InputAddresses.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.InputAddresses.Size = new System.Drawing.Size(468, 313);
this.InputAddresses.TabIndex = 2;
this.InputAddresses.Text = resources.GetString("InputAddresses.Text");
this.InputAddresses.WordWrap = false;
this.InputAddresses.DragDrop += new System.Windows.Forms.DragEventHandler(this.InputAddresses_DragDrop);
this.InputAddresses.DragOver += new System.Windows.Forms.DragEventHandler(this.InputAddresses_DragOver);
this.InputAddresses.KeyDown += new System.Windows.Forms.KeyEventHandler(this.InputAddresses_KeyDown);
//
// loadFromFile
//
this.loadFromFile.Location = new System.Drawing.Point(12, 320);
this.loadFromFile.Name = "loadFromFile";
this.loadFromFile.Size = new System.Drawing.Size(93, 23);
this.loadFromFile.TabIndex = 3;
this.loadFromFile.Text = "Load from file";
this.loadFromFile.UseVisualStyleBackColor = true;
this.loadFromFile.Click += new System.EventHandler(this.loadFromFile_Click);
//
// fileDlg
//
this.fileDlg.Filter = "Text files|*.txt";
//
// MultilineInput
//
this.AcceptButton = this.OkButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(467, 349);
this.Controls.Add(this.loadFromFile);
this.Controls.Add(this.InputAddresses);
this.Controls.Add(this.FormCancelButton);
this.Controls.Add(this.OkButton);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Margin = new System.Windows.Forms.Padding(2);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "MultilineInput";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "MultilineInput";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button OkButton;
private System.Windows.Forms.Button FormCancelButton;
private System.Windows.Forms.TextBox InputAddresses;
private System.Windows.Forms.Button loadFromFile;
private System.Windows.Forms.OpenFileDialog fileDlg;
}
}

73
GUI/MultilineInput.cs Normal file
Просмотреть файл

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System.IO;
using System.Text;
using System.Windows.Forms;
public partial class MultilineInput : Form {
public MultilineInput(string initialtext, bool showFilepicker) {
InitializeComponent();
if (!string.IsNullOrEmpty(initialtext)) {
this.InputAddresses.Text = initialtext;
}
if (showFilepicker) {
loadFromFile.Visible = true;
}
else {
loadFromFile.Visible = false;
}
}
public string baseaddressesstring {
get {
return this.InputAddresses.Text;
}
}
private void InputAddresses_KeyDown(object sender, KeyEventArgs e) {
if (e.Control && e.KeyCode == Keys.A) {
InputAddresses.SelectAll();
}
}
private void InputAddresses_DragDrop(object sender, DragEventArgs e) {
if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true) {
e.Effect = DragDropEffects.All;
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Length != 0) {
var allFilesContent = new StringBuilder();
foreach (var currFile in files) {
allFilesContent.AppendLine(File.ReadAllText(currFile));
}
InputAddresses.Text = allFilesContent.ToString();
}
}
}
private void InputAddresses_DragOver(object sender, DragEventArgs e) {
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}
private void loadFromFile_Click(object sender, System.EventArgs e) {
fileDlg.Multiselect = false;
fileDlg.CheckPathExists = true;
fileDlg.CheckFileExists = true;
fileDlg.FileName = string.Empty;
fileDlg.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
fileDlg.Title = "Select file";
var res = fileDlg.ShowDialog(this);
if (res != DialogResult.Cancel) {
InputAddresses.Text = File.ReadAllText(fileDlg.FileName);
}
}
}
}

16
GUI/MultilineInput.resx Normal file
Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><root><xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xsd:import namespace="http://www.w3.org/XML/1998/namespace" /><xsd:element name="root" msdata:IsDataSet="true"><xsd:complexType><xsd:choice maxOccurs="unbounded"><xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element><xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /><xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /><xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /><xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element></xsd:choice></xsd:complexType></xsd:element></xsd:schema><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><data name="InputAddresses.Text" xml:space="preserve"><value>**** If your stacks are hexadecimal addresses only, then please replace entire contents with the output of the following query without column headers and no other columns copied from SSMS using the Grid Results
select name, base_address from sys.dm_os_loaded_modules where name not like '%.rll'
SAMPLE values shown below - again, replace ALL of the contents shown in this box with the results of the above query!!
C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqlservr.exe 0x00007FF6191C0000
C:\windows\SYSTEM32\ntdll.dll 0x00007FFB0C1C0000
C:\windows\System32\KERNEL32.DLL 0x00007FFB0B4B0000
C:\windows\System32\KERNELBASE.dll 0x00007FFB092D0000
...
C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqlmin.dll 0x00007FFAE0CB0000
C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\SQLOS.dll 0x00007FFAE8F70000
C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn\sqllang.dll 0x00007FFADE700000
...
C:\windows\System32\MSASN1.dll 0x00007FFB08670000</value></data><metadata name="fileDlg.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>17, 17</value></metadata></root>

18
GUI/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var mainForm = new MainForm()) {
Application.Run(mainForm);
}
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SQLCallStackResolver")]
[assembly: AssemblyDescription("Utility which helps resolve SQL Server XE callstacks to their readable form")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("SQLCallStackResolver")]
[assembly: AssemblyCopyright("Copyright (c) Microsoft")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f65803b0-f5b2-4fa4-99b2-c91600e77e26")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.1.0.0")]
[assembly: AssemblyFileVersion("2.1.0.0")]
[assembly: NeutralResourcesLanguage("en")]

63
GUI/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

Просмотреть файл

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

26
GUI/Properties/Settings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

214
GUI/SQLBuildsForm.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,214 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
partial class SQLBuildsForm {
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.downloadStatus = new System.Windows.Forms.ToolStripStatusLabel();
this.downloadProgress = new System.Windows.Forms.ToolStripProgressBar();
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.checkPDBAvail = new System.Windows.Forms.Button();
this.dnldButton = new System.Windows.Forms.Button();
this.splitContainer2 = new System.Windows.Forms.SplitContainer();
this.treeviewSyms = new System.Windows.Forms.TreeView();
this.searchLabel = new System.Windows.Forms.Label();
this.searchText = new System.Windows.Forms.TextBox();
this.findNext = new System.Windows.Forms.Button();
this.statusStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
this.splitContainer2.Panel1.SuspendLayout();
this.splitContainer2.Panel2.SuspendLayout();
this.splitContainer2.SuspendLayout();
this.SuspendLayout();
//
// statusStrip1
//
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.downloadStatus,
this.downloadProgress});
this.statusStrip1.Location = new System.Drawing.Point(0, 645);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 13, 0);
this.statusStrip1.Size = new System.Drawing.Size(548, 25);
this.statusStrip1.TabIndex = 2;
this.statusStrip1.Text = "statusStrip1";
//
// downloadStatus
//
this.downloadStatus.Name = "downloadStatus";
this.downloadStatus.Size = new System.Drawing.Size(0, 19);
//
// downloadProgress
//
this.downloadProgress.Name = "downloadProgress";
this.downloadProgress.Size = new System.Drawing.Size(100, 17);
//
// splitContainer1
//
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;
this.splitContainer1.IsSplitterFixed = true;
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
this.splitContainer1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.splitContainer1.Name = "splitContainer1";
this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer1.Panel1
//
this.splitContainer1.Panel1.Controls.Add(this.splitContainer2);
//
// splitContainer1.Panel2
//
this.splitContainer1.Panel2.Controls.Add(this.checkPDBAvail);
this.splitContainer1.Panel2.Controls.Add(this.dnldButton);
this.splitContainer1.Size = new System.Drawing.Size(548, 670);
this.splitContainer1.SplitterDistance = 600;
this.splitContainer1.TabIndex = 3;
//
// checkPDBAvail
//
this.checkPDBAvail.Anchor = System.Windows.Forms.AnchorStyles.None;
this.checkPDBAvail.Location = new System.Drawing.Point(88, 7);
this.checkPDBAvail.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.checkPDBAvail.Name = "checkPDBAvail";
this.checkPDBAvail.Size = new System.Drawing.Size(171, 32);
this.checkPDBAvail.TabIndex = 3;
this.checkPDBAvail.Text = "Check PDB availability";
this.checkPDBAvail.UseVisualStyleBackColor = true;
this.checkPDBAvail.Click += new System.EventHandler(this.CheckPDBAvail_Click);
//
// dnldButton
//
this.dnldButton.Anchor = System.Windows.Forms.AnchorStyles.None;
this.dnldButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.dnldButton.Location = new System.Drawing.Point(265, 7);
this.dnldButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.dnldButton.Name = "dnldButton";
this.dnldButton.Size = new System.Drawing.Size(189, 32);
this.dnldButton.TabIndex = 2;
this.dnldButton.Text = "Download PDBs";
this.dnldButton.UseVisualStyleBackColor = true;
this.dnldButton.Click += new System.EventHandler(this.DownloadPDBs);
//
// splitContainer2
//
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
this.splitContainer2.IsSplitterFixed = true;
this.splitContainer2.Location = new System.Drawing.Point(0, 0);
this.splitContainer2.Name = "splitContainer2";
this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// splitContainer2.Panel1
//
this.splitContainer2.Panel1.Controls.Add(this.findNext);
this.splitContainer2.Panel1.Controls.Add(this.searchText);
this.splitContainer2.Panel1.Controls.Add(this.searchLabel);
//
// splitContainer2.Panel2
//
this.splitContainer2.Panel2.Controls.Add(this.treeviewSyms);
this.splitContainer2.Size = new System.Drawing.Size(548, 600);
this.splitContainer2.SplitterDistance = 35;
this.splitContainer2.TabIndex = 0;
//
// treeviewSyms
//
this.treeviewSyms.Dock = System.Windows.Forms.DockStyle.Fill;
this.treeviewSyms.Location = new System.Drawing.Point(0, 0);
this.treeviewSyms.Margin = new System.Windows.Forms.Padding(4);
this.treeviewSyms.Name = "treeviewSyms";
this.treeviewSyms.Size = new System.Drawing.Size(548, 561);
this.treeviewSyms.TabIndex = 1;
//
// searchLabel
//
this.searchLabel.AutoSize = true;
this.searchLabel.Location = new System.Drawing.Point(10, 12);
this.searchLabel.Name = "searchLabel";
this.searchLabel.Size = new System.Drawing.Size(150, 17);
this.searchLabel.TabIndex = 0;
this.searchLabel.Text = "SQL version / keyword";
//
// searchText
//
this.searchText.Location = new System.Drawing.Point(166, 9);
this.searchText.Name = "searchText";
this.searchText.Size = new System.Drawing.Size(296, 22);
this.searchText.TabIndex = 1;
//
// findNext
//
this.findNext.Location = new System.Drawing.Point(468, 9);
this.findNext.Name = "findNext";
this.findNext.Size = new System.Drawing.Size(75, 23);
this.findNext.TabIndex = 2;
this.findNext.Text = "Find";
this.findNext.UseVisualStyleBackColor = true;
this.findNext.Click += new System.EventHandler(this.findNext_Click);
//
// SQLBuildsForm
//
this.AcceptButton = this.dnldButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(548, 670);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.splitContainer1);
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "SQLBuildsForm";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "Select SQL build";
this.Load += new System.EventHandler(this.Treeview_Load);
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.splitContainer1.Panel1.ResumeLayout(false);
this.splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false);
this.splitContainer2.Panel1.ResumeLayout(false);
this.splitContainer2.Panel1.PerformLayout();
this.splitContainer2.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
this.splitContainer2.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel downloadStatus;
private System.Windows.Forms.ToolStripProgressBar downloadProgress;
private System.Windows.Forms.SplitContainer splitContainer1;
private System.Windows.Forms.Button dnldButton;
private System.Windows.Forms.Button checkPDBAvail;
private System.Windows.Forms.SplitContainer splitContainer2;
private System.Windows.Forms.TreeView treeviewSyms;
private System.Windows.Forms.TextBox searchText;
private System.Windows.Forms.Label searchLabel;
private System.Windows.Forms.Button findNext;
}
}

149
GUI/SQLBuildsForm.cs Normal file
Просмотреть файл

@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows.Forms;
public partial class SQLBuildsForm : Form {
public string pathToPDBs = string.Empty;
public string lastDownloadedSymFolder = string.Empty;
private bool activeDownload = false;
public SQLBuildsForm() {
InitializeComponent();
}
private void Treeview_Load(object sender, EventArgs e) {
var allBuilds = SQLBuildInfo.GetSqlBuildInfo(MainForm.SqlBuildInfoFileName);
var sqlMajorVersions = allBuilds.Values.Select(b => b.ProductMajorVersion).OrderByDescending(b => b).Distinct();
treeviewSyms.Nodes.AddRange(sqlMajorVersions.Select(b => new TreeNode(b) { Name = b }).ToArray());
foreach (var ver in sqlMajorVersions) {
var prodLevels = allBuilds.Values.Where(b => b.ProductMajorVersion == ver).Select(b => b.ProductLevel).OrderByDescending(b => b).Distinct();
treeviewSyms.Nodes[ver].Nodes.AddRange(prodLevels.Select(pl => new TreeNode(pl) { Name = pl }).ToArray());
// finally within each product level get the individual builds
foreach (var pl in prodLevels) {
var blds = allBuilds.Values.Where(b => b.ProductMajorVersion == ver && b.ProductLevel == pl && b.SymbolDetails.Count > 0).Distinct().OrderByDescending(b => b.BuildNumber);
treeviewSyms.Nodes[ver].Nodes[pl].Nodes.AddRange(blds.Select(bld => new TreeNode(bld.ToString()) { Name = bld.ToString(), Tag = bld }).ToArray());
}
}
}
private void DownloadPDBs(object sender, EventArgs e) {
if (treeviewSyms.SelectedNode is null) {
return;
}
if (treeviewSyms.SelectedNode.Tag is SQLBuildInfo bld) {
if (bld.SymbolDetails.Count > 0) {
dnldButton.Enabled = false;
lastDownloadedSymFolder = $@"{pathToPDBs}\{bld.BuildNumber}";
Directory.CreateDirectory(lastDownloadedSymFolder);
using (var client = new WebClient()) {
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(Client_DownloadFileCompleted);
foreach (var sym in bld.SymbolDetails) {
var url = sym.DownloadURL;
if (!string.IsNullOrEmpty(url)) {
var uri = new Uri(url);
string filename = Path.GetFileName(uri.LocalPath);
if (File.Exists($@"{lastDownloadedSymFolder}\{filename}")) {
continue;
}
downloadStatus.Text = filename;
activeDownload = true;
client.DownloadFileAsync(new Uri(url), $@"{lastDownloadedSymFolder}\{filename}");
while (activeDownload) {
Thread.Sleep(300);
Application.DoEvents();
}
}
}
downloadStatus.Text = "Done.";
dnldButton.Enabled = true;
}
}
}
}
void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {
double bytesIn = e.BytesReceived;
double totalBytes = e.TotalBytesToReceive;
double percentage = bytesIn / totalBytes * 100;
downloadProgress.ProgressBar.Value = (int)percentage;
statusStrip1.Refresh();
}
void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) {
downloadProgress.ProgressBar.Value = 0;
statusStrip1.Refresh();
activeDownload = false;
}
private void CheckPDBAvail_Click(object sender, EventArgs e) {
if (treeviewSyms.SelectedNode is null) {
return;
}
if (treeviewSyms.SelectedNode.Tag is SQLBuildInfo bld) {
if (bld.SymbolDetails.Count > 0) {
List<string> failedUrls = new List<string>();
foreach (var sym in bld.SymbolDetails) {
var url = sym.DownloadURL;
downloadStatus.Text = url;
if (!Symbol.IsURLValid(new Uri(url))) {
failedUrls.Add(url);
}
}
if (failedUrls.Count > 0) {
MessageBox.Show(string.Join(",", failedUrls));
}
else {
downloadStatus.Text = "All PDBs for this build are available!";
}
}
}
}
private void findNext_Click(object sender, EventArgs e) {
foreach (TreeNode node in treeviewSyms.Nodes) {
if (CheckIfAnyNodesMatch(node)) {
return;
}
}
downloadStatus.Text = "No matches found.";
treeviewSyms.SelectedNode = null;
treeviewSyms.Refresh();
Application.DoEvents();
}
private bool CheckIfAnyNodesMatch(TreeNode node) {
if (node.Tag is SQLBuildInfo bld) {
if (bld.ToString().ToLower(CultureInfo.CurrentCulture).Contains(searchText.Text.ToLower(CultureInfo.CurrentCulture))) {
treeviewSyms.SelectedNode = node;
treeviewSyms.Select();
treeviewSyms.Refresh();
Application.DoEvents();
return true;
}
}
foreach (TreeNode child in node.Nodes) {
if (CheckIfAnyNodesMatch(child)) {
return true;
}
}
return false;
}
}
}

1
GUI/SQLBuildsForm.resx Normal file
Просмотреть файл

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><root><xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xsd:import namespace="http://www.w3.org/XML/1998/namespace" /><xsd:element name="root" msdata:IsDataSet="true"><xsd:complexType><xsd:choice maxOccurs="unbounded"><xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element><xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /><xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /><xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /><xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element></xsd:choice></xsd:complexType></xsd:element></xsd:schema><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"><value>17, 17</value></metadata></root>

Просмотреть файл

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F65803B0-F5B2-4FA4-99B2-C91600E77E26}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver</RootNamespace>
<AssemblyName>SQLCallStackResolver</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\Target\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>..\Target\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System">
<HintPath>..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Configuration" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="MultilineInput.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MultilineInput.Designer.cs">
<DependentUpon>MultilineInput.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SQLBuildsForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="SQLBuildsForm.Designer.cs">
<DependentUpon>SQLBuildsForm.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="MultilineInput.resx">
<DependentUpon>MultilineInput.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<EmbeddedResource Include="SQLBuildsForm.resx">
<DependentUpon>SQLBuildsForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Engine\SQLCallstackResolver.Engine.csproj">
<Project>{782bbd60-ee45-43cd-8a4f-0505efe4ff1a}</Project>
<Name>SQLCallstackResolver.Engine</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -1,24 +1,65 @@
# Project
[![](https://github.com/microsoft/SQLCallStackResolver/workflows/Build%20SQLCallStackResolver/badge.svg)](https://github.com/microsoft/SQLCallStackResolver/actions)
# Installation
Please refer to the Releases section for a ready-to-run set of binaries. Release 2.0 is self-contained, XCOPY / UnZip and run - no external installation required other than .NET Framework 4.7.2 or above. We now include the [msdia140.dll](https://blogs.msdn.microsoft.com/calvin_hsia/2016/07/30/whats-in-a-pdb-file-use-the-debug-interface-access-sdk/) and [XELite](https://www.nuget.org/packages/Microsoft.SqlServer.XEvent.XELite/) as part of the ZIP file with the rest of the binaries. Note that SQLCallStackResolver 2.0 is released as purely for 64-bit Intel/AMD family (x64) Windows OS. Release 2.0 also uses registration-free COM activation of msdia140.dll, which is included in the ZIP. There is no longer a need to explicitly register this DLL.
> This repo has been populated by an initial template to help get you started. Please
> make sure to update the content to build a great experience for community-building.
Note: DIA, and associated necessary Visual C++ runtime dependency DLLs (msvcp140.dll, vcruntime140.dll and vcruntime140_1.dll are redistributable components of Visual Studio 2019 subject to terms as published [here](https://docs.microsoft.com/en-us/visualstudio/releases/2019/redistribution). Windows Debugging Tools DLLs (dbghelp.dll and symsrv.dll) are used as per the terms published [here](https://docs.microsoft.com/en-us/legal/windows-sdk/redist#debugging-tools-for-windows).
As the maintainer of this project, please make a few updates:
# Usage
The tool comes with a pre-populated example in the textboxes, just follow that example with your real-world stack(s). The textbox on the left accepts the following types of input:
- Improving this README.MD file to provide a great experience
- Updating SUPPORT.MD with content about this project's support experience
- Understanding the security reporting process in SECURITY.MD
- Remove this section from the README
* Individual callstack extracted from XML, with DLL + offset notation
* Multiple callstacks in Histogram XML markup (multiple-instance case of above)
* Older format with just virtual addresses [[1]](#footnote1)
* dll!Ordinal### format [[2]](#footnote2)
* Output from SQLDumper (SQLDumpNNNN.TXT file) - at least the sections which have stack frames [[5]](#footnote5)
In all cases you must provided a symbol search path [[3]](#footnote3),[[4]](#footnote4). If a symbol server is used in this path, there must be corresponding information in the input callstack to help identify which PDBs to get from the symbol server. See [[6]](#footnote6).
## Usage example #1
This is a trivial case, where the user enters the callstack in DLL + offset notation, and selects one of the pre-populated list of SQL builds to download symbols. The user then clicks the Resolve Callstacks button, and obtains the symbolized output in the right-hand side textbox.
![](images/1_ModOffset_Text.gif)
## Usage example #2
This is a typical use case, where the user imports events from a XEL file. Because the XEL file does not have the module base addresses, the user first inputs those. They then select one of the pre-populated list of SQL builds to download symbols and finally click the Resolve Callstacks button to obtain the symbolized output in the right-hand side textbox.
![](images/2_XEL_Address.gif)
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
## Building
* You will need Visual Studio 2019+ installed to build this solution.
* Once you load the SLN, you might need to fix the reference to msdia140typelib_clr0200.dll (found under `<<VisualStudioFolder>>\Team Tools\Performance Tools\Plugins\` or `<<VisualStudioFolder>>\Common7\IDE\Extensions\TestPlatform)`. You can also get this file by installing [Performance Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#performance-tools-for-visual-studio-2019)
* Access to NuGet is needed to fetch and restore package dependencies.
* You will also need to manually obtain the current versions of the following files:
* symsrv.dll and dbghelp.dll (from the x64 / AMD64 Windows Debugger package, part of Windows SDK and many other tools)
* msdia140.dll, msdia140.dll.manifest and associated necessary Visual C++ runtime dependency DLLs (msvcp140.dll, vcruntime140.dll and vcruntime140_1.dll are redistributable components of Visual Studio 2019 subject to terms as published [here](https://docs.microsoft.com/en-us/visualstudio/releases/2019/redistribution). Windows Debugging Tools DLLs (dbghelp.dll and symsrv.dll) as per the terms published [here](https://docs.microsoft.com/en-us/legal/windows-sdk/redist#debugging-tools-for-windows).
* Tests are implemented using [xUnit](https://xunit.net/docs/getting-started/netfx/visual-studio#run-tests-visualstudio). Please try to ensure that tests are passing before submitting a PR. In order to run tests, you will need to run the Tests\TestCases\downloadsyms.ps1 file to gather pre-requisites. Watch for warnings from the script - there are typically 3 files you will need to gather manually (instructions provided in the PowerShell script).
* When a PR is submitted, there is a GitHub Actions workflow which will build the project and run tests. PRs cannot merge till the workflow succeeds.
# Notes
1. <a name="footnote1"></a>In this case you need to populate the Base Addresses with the output of the following query from the actual SQL instance when the XE was captured:
``` sql
select name, base_address from sys.dm_os_loaded_modules where name not like '%.rll'
```
2. <a name="footnote2"></a>In this case, you need to press the Module Paths button where you will be prompted to enter the path to a folder containing the images of the DLLs involved. For example you can point to C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Binn for SQL 2016 RTM CU1
3. <a name="footnote3"></a>This has to be path to a folder or a set of such paths (can be UNC as well) each separated with a semicolon (;). Use the checkbox to specify if sub-folders need to be checked in each case. If multiple paths might contain matching PDBs, the first path from the left which contained the PDB wins. There is no means to know if the PDB is matched with the build that your are using - you need to ensure that the folder path(s) are correct!
4. <a name="footnote4"></a>To obtain public PDBs for major SQL releases, PowerShell scripts are available in the SQLCallStackResolver [Wiki](https://github.com/arvindshmicrosoft/SQLCallStackResolver/wiki/Obtaining-symbol-files-(.PDB)-for-SQL-Server-Releases)
5. <a name="footnote5"></a>This is partial support at the moment - subsequently I will add a 'cleansing' option where it will strip out just the 'Short Stack Dump' sections and resolve the frames therein.
6. <a name="footnote6"></a>It is possible to use a symbol server in the symbol search path. For example, the symbol search path can be specified as `srv*c:\syms*https://msdl.microsoft.com/download/symbols`. In such a case, the corresponding callstack input should have a minimal set of details included in separate lines, each containing comma-separated values. Each of these lines should include the following minimum set of information:
* DLL file name, for example `ntdll.dll`
* PDB file name, for example `ntdll.pdb`
* PDB GUID, for example `1EB9FACB-04C7-3C5D-EA71-60764CD333D0`
* The very last comma-separated field will be taken as the PDB Age.
All other fields in the line are ignored. Here is an example of a complete line with the minimum information necessary:
`ntdll.dll,ntdll.pdb,1EB9FACB-04C7-3C5D-EA71-60764CD333D0,1`
When such information is included (usually after the main callstack input), it is parsed and used to retrieve PDBs from the symbol server specified. If matching PDBs are not found locally for whatever reason (wrong paths, wrong metadata, failed download etc.), the symbol resolution step will just return back the original raw input.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
@ -26,8 +67,4 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

Просмотреть файл

@ -1,23 +1,9 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.5 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
**Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
@ -26,16 +12,11 @@ Please include the requested information listed below (as much as you can provid
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

50
SQLCallStackResolver.sln Normal file
Просмотреть файл

@ -0,0 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30104.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLCallstackResolver.Engine", "Engine\SQLCallstackResolver.Engine.csproj", "{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLCallstackResolver.GUI", "GUI\SQLCallstackResolver.GUI.csproj", "{F65803B0-F5B2-4FA4-99B2-C91600E77E26}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{551EED46-2D05-4EDC-84E8-8FBCCA7619C2}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
.github\workflows\build.yml = .github\workflows\build.yml
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLCalLStackResolver.Tests", "Tests\SQLCalLStackResolver.Tests.csproj", "{34EDE1D2-801A-407F-9F09-FF9572DB8771}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B89FBCCD-DEB5-47C4-BF0F-5448A17B82A4}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}.Debug|x64.ActiveCfg = Debug|x64
{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}.Debug|x64.Build.0 = Debug|x64
{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}.Release|x64.ActiveCfg = Release|x64
{782BBD60-EE45-43CD-8A4F-0505EFE4FF1A}.Release|x64.Build.0 = Release|x64
{F65803B0-F5B2-4FA4-99B2-C91600E77E26}.Debug|x64.ActiveCfg = Debug|x64
{F65803B0-F5B2-4FA4-99B2-C91600E77E26}.Debug|x64.Build.0 = Debug|x64
{F65803B0-F5B2-4FA4-99B2-C91600E77E26}.Release|x64.ActiveCfg = Release|x64
{F65803B0-F5B2-4FA4-99B2-C91600E77E26}.Release|x64.Build.0 = Release|x64
{34EDE1D2-801A-407F-9F09-FF9572DB8771}.Debug|x64.ActiveCfg = Debug|x64
{34EDE1D2-801A-407F-9F09-FF9572DB8771}.Debug|x64.Build.0 = Debug|x64
{34EDE1D2-801A-407F-9F09-FF9572DB8771}.Release|x64.ActiveCfg = Release|x64
{34EDE1D2-801A-407F-9F09-FF9572DB8771}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {58EA4908-ACF8-446A-BDF1-F90967D7DEC5}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -1,25 +1,7 @@
# TODO: The maintainer of this repo has not yet edited this file
**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
- **No CSS support:** Fill out this template with information about how to file issues and get help.
- **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
- **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
*Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
# Support
## How to file issues and get help
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
issues before filing new issues to avoid duplicates. For new issues, file your bug or
feature request as a new Issue.
For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. For help and questions about using this project, please again use the GitHub Issues for this project to ask questions.
## Microsoft Support Policy
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
This sample code is not supported under any Microsoft standard support program or service. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts or sample code, be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SQLCalLStackResolver xUnit Tests")]
[assembly: AssemblyDescription("Unit tests for SQLCallStackResolver")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("SQLCalLStackResolver")]
[assembly: AssemblyCopyright("Copyright (c) Microsoft. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("34ede1d2-801a-407f-9f09-ff9572db8771")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
<Import Project="..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" />
<Import Project="..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props" Condition="Exists('..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props')" />
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{34EDE1D2-801A-407F-9F09-FF9572DB8771}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver</RootNamespace>
<AssemblyName>SQLCalLStackResolver.xUnit.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\Target\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>..\Target\Debug\SQLCalLStackResolver.xUnit.Tests.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\Target\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Tests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<Content Include="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Engine\SQLCallstackResolver.Engine.csproj">
<Project>{782bbd60-ee45-43cd-8a4f-0505efe4ff1a}</Project>
<Name>SQLCallstackResolver.Engine</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.console.2.4.1\build\xunit.runner.console.props'))" />
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props'))" />
</Target>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
</Project>

Просмотреть файл

@ -0,0 +1,31 @@
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\sqlservr.exe 0x00007FF6E0900000
C:\Windows\SYSTEM32\ntdll.dll 0x00007FFEC3980000
C:\Windows\System32\KERNEL32.DLL 0x00007FFEC2C30000
C:\Windows\System32\KERNELBASE.dll 0x00007FFEC0700000
C:\Windows\System32\ADVAPI32.dll 0x00007FFEC2530000
C:\Windows\System32\msvcrt.dll 0x00007FFEC0F10000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\SQLOS.dll 0x00007FFEB62E0000
C:\Windows\SYSTEM32\pdh.dll 0x00007FFEBCAC0000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\sqlmin.dll 0x00007FFEAC0E0000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\sqlTsEs.dll 0x00007FFEA8DD0000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\sqldk.dll 0x00007FFEABC10000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\sqllang.dll 0x00007FFEA9660000
C:\Windows\System32\OLEAUT32.dll 0x00007FFEC25E0000
C:\Windows\System32\msvcp_win.dll 0x00007FFEBFE80000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\opends60.dll 0x00007FFEB2270000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\qds.dll 0x00007FFEA2A80000
C:\Windows\SYSTEM32\MSVCP120.dll 0x00007FFEB66E0000
C:\Windows\System32\psapi.dll 0x00007FFEC24C0000
C:\Windows\System32\kernel.appcore.dll 0x00007FFEBFE10000
C:\Program Files\Microsoft SQL Server\130\Shared\instapi130.dll 0x00007FFEA1B10000
C:\Windows\SYSTEM32\CRYPTSP.dll 0x00007FFEBF860000
C:\Windows\system32\rsaenh.dll 0x00007FFEBF2C0000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\Resources\1033\sqlevn70.rll 0x000002B8A7AE0000
C:\Windows\system32\RESUTILS.DLL 0x00007FFEB6670000
C:\Windows\SYSTEM32\VERSION.dll 0x00007FFEB8D40000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\hkruntime.dll 0x00007FFE9B380000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\hkcompile.dll 0x00007FFE9B230000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\hkengine.dll 0x00007FFE9ACC0000
C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\Binn\dbghelp.dll 0x0000000063E90000
C:\Windows\System32\SHLWAPI.dll 0x00007FFEC3910000
C:\Windows\system32\msv1_0.DLL 0x00007FFEBF650000

Просмотреть файл

@ -0,0 +1,27 @@
name base_address
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\sqlservr.exe 0x00007FF72FF00000
C:\WINDOWS\SYSTEM32\ntdll.dll 0x00007FFB63130000
C:\WINDOWS\System32\KERNEL32.DLL 0x00007FFB62890000
C:\WINDOWS\System32\KERNELBASE.dll 0x00007FFB60A60000
C:\WINDOWS\SYSTEM32\apphelp.dll 0x00007FFB5DA30000
C:\WINDOWS\System32\ADVAPI32.dll 0x00007FFB61470000
C:\WINDOWS\SYSTEM32\NETAPI32.dll 0x00007FFB55150000
C:\WINDOWS\SYSTEM32\pdh.dll 0x00007FFB3B890000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\SQLOS.dll 0x00007FFB49DE0000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\sqlTsEs.dll 0x00007FFAF5710000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\sqldk.dll 0x00007FFAF2AD0000
C:\WINDOWS\System32\OLEAUT32.dll 0x00007FFB61310000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\opends60.dll 0x00007FFB49DA0000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\svl.dll 0x00007FFB45BC0000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\qds.dll 0x00007FFB124B0000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\sqlmin.dll 0x00007FFAD08A0000
C:\WINDOWS\SYSTEM32\MSVCP120.dll 0x00007FFB0FD70000
C:\WINDOWS\SYSTEM32\MSVCR120.dll 0x00007FFB0FE20000
C:\WINDOWS\System32\CRYPT32.dll 0x00007FFB60EA0000
C:\WINDOWS\System32\WS2_32.dll 0x00007FFB62B70000
C:\WINDOWS\SYSTEM32\WINMM.dll 0x00007FFB4BB70000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\sqllang.dll 0x00007FFACB6F0000
C:\WINDOWS\SYSTEM32\Secur32.dll 0x00007FFB5F720000
C:\WINDOWS\SYSTEM32\WINHTTP.dll 0x00007FFB5F900000
C:\WINDOWS\SYSTEM32\ODBC32.dll 0x00007FFB35220000
C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn\secforwarder.dll 0x00007FFB49D00000

Просмотреть файл

@ -0,0 +1,100 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License - see LICENSE file in this repo.
param ($sqldkDownloadURL, $spinsXelZipDownloadURL, $waitCompletedXelDownloadUrl)
$msdlurl = "https://msdl.microsoft.com/download/symbols/"
### TestBlockResolution
mkdir -Force ".\TestBlockResolution" -ErrorAction Ignore
$localpath = ".\TestBlockResolution\kernelbase.pdb"
if (-not (test-path $localpath)) {
Invoke-WebRequest -UseBasicParsing -uri ($msdlurl + "kernelbase.pdb/E26F9607943644BB8CDE6C806006A3F01/kernelbase.pdb") -OutFile $localpath
dir $localpath
}
### SourceInformation
mkdir -Force ".\SourceInformation" -ErrorAction Ignore
$localpath = ".\SourceInformation\Wdf01000.pdb"
if (-not (test-path $localpath)) {
Invoke-WebRequest -UseBasicParsing -uri ($msdlurl + "Wdf01000.pdb/C9EC293769A815AB22B5909C290FB9631/Wdf01000.pdb") -OutFile $localpath
dir $localpath
}
### TestOrdinal
mkdir -Force ".\TestOrdinal" -ErrorAction Ignore
$localpath = ".\TestOrdinal\sqldk.pdb"
if (-not (test-path $localpath)) {
Invoke-WebRequest -UseBasicParsing -uri ($msdlurl + "sqldk.pdb/6a1934433512464b8b8ed905ad930ee62/sqldk.pdb") -OutFile $localpath
dir $localpath
}
$localpath = ".\TestOrdinal\sqldk.zip"
if (-not (test-path $localpath)) {
try {
Invoke-WebRequest -UseBasicParsing -uri $sqldkDownloadURL -OutFile $localpath -ErrorAction Ignore
Expand-Archive -Path $localpath -DestinationPath ".\TestOrdinal"
dir ".\TestOrdinal"
} catch {}
}
$localpath = ".\TestOrdinal\sqldk.dll"
if (-not (test-path $localpath)) {
Write-Warning "You must manually download CU14 for SQL 2016 SP1 (KB 4488535) and extract the sqldk.dll from that install to TestOrdinal\sqldk.dll"
}
### ImportXEL
mkdir -Force ".\ImportXEL" -ErrorAction Ignore
$localpath = ".\ImportXEL\XESpins_0_131627061603030000.xel"
if (-not (test-path $localpath)) {
try {
Invoke-WebRequest -UseBasicParsing -uri $spinsXelZipDownloadURL -OutFile ($localpath + ".zip") -ErrorAction Ignore
Expand-Archive -Path ($localpath + ".zip") -DestinationPath ".\ImportXEL"
}
catch{ }
if (-not (test-path $localpath)){
Write-Warning "You must manually download and extract XESpins_0_131627061603030000.xel. You can do that from https://github.com/arvindshmicrosoft/SQLCallStackResolver/raw/main/docs/SQLSat696/Demos/LOCK_HASH/XESpins_0_131627061603030000.zip"
}
}
### ImportIndividualXELEvents, SymbolFileCaching
$localpath = ".\ImportXEL\xe_wait_completed_0_132353446563350000.xel"
if (-not (test-path $localpath)){
try{
Invoke-WebRequest -UseBasicParsing -uri $waitCompletedXelDownloadUrl -OutFile $localpath -ErrorAction Ignore
}
catch {}
if (-not (test-path $localpath)){
Write-Warning "You must manually download and extract xe_wait_completed_0_132353446563350000.xel. You can do that from https://github.com/arvindshmicrosoft/SQLCallStackResolver/raw/main/docs/SQLSat696/Demos/xe_wait_completed_0_132353446563350000.xel"
}
}
# SQL Server 2016 SP1 SP1 - 13.0.4001.0 - x64 (KB3182545)
$outputFolder = '.\sqlsyms\13.0.4001.0\x64'
mkdir -f $outputFolder
if (-not (Test-Path "$outputFolder\sqldk.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqldk.pdb/1d3fa75eb35540e287b2e012d69785df2/sqldk.pdb' -OutFile "$outputFolder\sqldk.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\sqlmin.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlmin.pdb/d38058f49e7c4d62970677e4315f1f1c2/sqlmin.pdb' -OutFile "$outputFolder\sqlmin.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\sqllang.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqllang.pdb/cb9e5b8e0483423cb122da4ad87534d52/sqllang.pdb' -OutFile "$outputFolder\sqllang.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\sqltses.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqltses.pdb/13cb00e6ed4d46789fadceb55abddfe92/sqltses.pdb' -OutFile "$outputFolder\sqltses.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\sqlaccess.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlaccess.pdb/c6d08b108b154f8b8431f090dbaab1c92/sqlaccess.pdb' -OutFile "$outputFolder\sqlaccess.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\qds.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/qds.pdb/c1220065fb9e4e61919175ac9792a2bc2/qds.pdb' -OutFile "$outputFolder\qds.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\hkruntime.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkruntime.pdb/f0fd3061c4be4486b3308828ea99276e1/hkruntime.pdb' -OutFile "$outputFolder\hkruntime.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\hkengine.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkengine.pdb/f53682311a4e427ba43cc7908850cf9d1/hkengine.pdb' -OutFile "$outputFolder\hkengine.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\hkcompile.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkcompile.pdb/f42433ff7c4b4c52b53875da10d4684e1/hkcompile.pdb' -OutFile "$outputFolder\hkcompile.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\SQLOS.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/SQLOS.pdb/961e76a609b04a7c935cd8ad827f23381/SQLOS.pdb' -OutFile "$outputFolder\SQLOS.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
if (-not (Test-Path "$outputFolder\sqlservr.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlservr.pdb/e6e24f9a081b42e3b9e22e1f6414b9b22/sqlservr.pdb' -OutFile "$outputFolder\sqlservr.pdb" } # File version 2015.0130.4001.00 ((SQL16_PCU_Main).161028-1734)
# SQL Server 2017 RTM CU15+GDR - 14.0.3192.2 - x64 (KB4505225)
$outputFolder = '.\sqlsyms\14.0.3192.2\x64'
mkdir -f $outputFolder
if (-not (Test-Path "$outputFolder\SqlDK.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/SqlDK.pdb/122fc135abf24465ba9e6be0a6274eb32/SqlDK.pdb' -OutFile "$outputFolder\SqlDK.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\sqlmin.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlmin.pdb/207221dfd01a4ecda2e45a5be4afa8342/sqlmin.pdb' -OutFile "$outputFolder\sqlmin.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\sqllang.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqllang.pdb/7f9c184f5b2944cc8c1bdba07670f8412/sqllang.pdb' -OutFile "$outputFolder\sqllang.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\SqlTsEs.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/SqlTsEs.pdb/9559bc0f3b5e4cef8a84ce08601fe3df2/SqlTsEs.pdb' -OutFile "$outputFolder\SqlTsEs.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\sqlaccess.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlaccess.pdb/24b12b3f43be4a27a4139c39849ef5e33/sqlaccess.pdb' -OutFile "$outputFolder\sqlaccess.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\qds.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/qds.pdb/efd30261c88042a2a49e19b21f90ef002/qds.pdb' -OutFile "$outputFolder\qds.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\hkruntime.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkruntime.pdb/cb6a1cd50b654d0a8474f4ed74255e6c1/hkruntime.pdb' -OutFile "$outputFolder\hkruntime.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\hkengine.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkengine.pdb/139c16954bd04111a21c3d8e834e5ea41/hkengine.pdb' -OutFile "$outputFolder\hkengine.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\hkcompile.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/hkcompile.pdb/e0243d6070c94969a2aabfc1c32fb0a61/hkcompile.pdb' -OutFile "$outputFolder\hkcompile.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\SQLOS.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/SQLOS.pdb/439bff18122840658f0e9240ffda29301/SQLOS.pdb' -OutFile "$outputFolder\SQLOS.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)
if (-not (Test-Path "$outputFolder\sqlservr.pdb")) { Invoke-WebRequest -uri 'https://msdl.microsoft.com/download/symbols/sqlservr.pdb/107813d2dfe94332aec5cba570dfa4082/sqlservr.pdb' -OutFile "$outputFolder\sqlservr.pdb" } # File version 2017.0140.3192.02 ((SQLServer2017-CU14).190615-0703)

483
Tests/Tests.cs Normal file
Просмотреть файл

@ -0,0 +1,483 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
namespace Microsoft.SqlServer.Utils.Misc.SQLCallStackResolver {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using Xunit;
/// Class implementing xUnit tests.
public class Tests {
/// Validate that "block symbols" in a PDB are resolved correctly.
[Fact]
public void BlockResolution() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\TestBlockResolution";
var ret = csr.ResolveCallstacks("Return Addr: 00007FF830D4CDA4 Module(KERNELBASE+000000000009CDA4)", pdbPath, false, null, false, false, false, false, true, false, false, null);
Assert.Equal("KERNELBASE!SignalObjectAndWait+147716", ret.Trim());
}
}
/// Test the resolution of OrdinalNNN symbols to their actual names.
[Fact]
public void OrdinalBasedSymbol() {
using (var csr = new StackResolver()) {
var dllPaths = new List<string>{@"..\..\Tests\TestCases\TestOrdinal"};
var ret = csr.ResolveCallstacks("sqldk!Ordinal298+00000000000004A5", @"..\..\Tests\TestCases\TestOrdinal", false, dllPaths, false, false, false, false, true, false, false, null);
Assert.Equal("sqldk!SOS_Scheduler::SwitchContext+941", ret.Trim());
}
}
/// Test the resolution of a "regular" symbol with input specifying a hex offset into module.
[Fact]
public void RegularSymbolHexOffset() {
using (var csr = new StackResolver()) {
var ret = csr.ResolveCallstacks("sqldk+0x40609\r\nsqldk+40609", @"..\..\Tests\TestCases\TestOrdinal", false, null, false, false, false, false, true, false, false, null);
var expectedSymbol = "sqldk!MemoryClerkInternal::AllocatePagesWithFailureMode+644";
Assert.Equal(expectedSymbol + Environment.NewLine + expectedSymbol, ret.Trim());
}
}
/// Test the resolution of a "regular" symbol with virtual address as input.
[Fact]
public void RegularSymbolVirtualAddress() {
using (var csr = new StackResolver()) {
var moduleAddressesGood = @"c:\mssql\binn\sqldk.dll 00000001`00400000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGood));
var ret = csr.ResolveCallstacks("0x000000010042249f", @"..\..\Tests\TestCases\TestOrdinal", false, null, false, false, false, false, true, false, false, null);
var expectedSymbol = "sqldk!Spinlock<244,2,1>::SpinToAcquireWithExponentialBackoff+349";
Assert.Equal(expectedSymbol, ret.Trim());
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputEmptyString() {
using (var csr = new StackResolver()) {
Assert.True(csr.ProcessBaseAddresses(string.Empty));
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputJunkString() {
using (var csr = new StackResolver()) {
var moduleAddressesBad = @"hello wor1213ld";
Assert.False(csr.ProcessBaseAddresses(moduleAddressesBad));
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputColHeaders() {
using (var csr = new StackResolver()) {
var moduleAddressesColHeader = File.ReadAllText(@"..\..\Tests\TestCases\ImportXEL\xe_wait_base_addresses.txt");
var loadstatus = csr.ProcessBaseAddresses(moduleAddressesColHeader);
Assert.True(loadstatus);
Assert.Equal(26, csr.LoadedModules.Count);
var sqllang = csr.LoadedModules.Where(m => m.ModuleName == "sqllang").First();
Assert.NotNull(sqllang);
Assert.Equal(0x00007FFACB6F0000UL, sqllang.BaseAddress);
Assert.Equal(0x00007FFAD089FFFFUL, sqllang.EndAddress);
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputFullPathSingleLine() {
using (var csr = new StackResolver()) {
var moduleAddressesGood = @"c:\mssql\binn\sqldk.dll 0000000100400000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGood));
Assert.Single(csr.LoadedModules);
Assert.Equal("sqldk", csr.LoadedModules[0].ModuleName);
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputSingleLineBacktick() {
using (var csr = new StackResolver()) {
var moduleAddressesGoodBacktick = @"c:\mssql\binn\sqldk.dll 00000001`00400000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGoodBacktick));
Assert.Single(csr.LoadedModules);
Assert.Equal("sqldk", csr.LoadedModules[0].ModuleName);
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputModuleNameOnlySingleLine() {
using (var csr = new StackResolver()) {
var moduleAddressesGoodModuleNameOnly0x = @"sqldk.dll 0000000100400000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGoodModuleNameOnly0x));
Assert.Single(csr.LoadedModules);
Assert.Equal("sqldk", csr.LoadedModules[0].ModuleName);
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputModuleNameOnlySingleLine0x() {
using (var csr = new StackResolver()) {
var moduleAddressesGoodModuleNameOnly0x = @"sqldk.dll 0x0000000100400000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGoodModuleNameOnly0x));
Assert.Single(csr.LoadedModules);
Assert.Equal("sqldk", csr.LoadedModules[0].ModuleName);
}
}
/// Check the processing of module base address information.
[Fact]
public void ModuleLoadAddressInputFullPathsTwoModules() {
using (var csr = new StackResolver()) {
var moduleAddressesGood = @"c:\mssql\binn\sqldk.dll 0000000100400000
c:\mssql\binn\sqllang.dll 0000000105600000";
Assert.True(csr.ProcessBaseAddresses(moduleAddressesGood));
Assert.Equal(2, csr.LoadedModules.Count);
Assert.Equal("sqldk", csr.LoadedModules[0].ModuleName);
Assert.Equal("sqllang", csr.LoadedModules[1].ModuleName);
}
}
/// Test the resolution of a "regular" symbol with input specifying a hex offset into module
/// but do not include the resolved symbol's offset in final output.
[Fact]
public void RegularSymbolHexOffsetNoOutputOffset() {
using (var csr = new StackResolver()) {
var dllPaths = new List<string>{@"..\..\Tests\TestCases\TestOrdinal"};
var ret = csr.ResolveCallstacks("sqldk+0x40609", @"..\..\Tests\TestCases\TestOrdinal", false, null, false, false, false, false, false, false, false, null);
var expectedSymbol = "sqldk!MemoryClerkInternal::AllocatePagesWithFailureMode";
Assert.Equal(expectedSymbol, ret.Trim());
}
}
/// Check whether symbol details for a given binary are correct.
[Fact]
public void TestGetSymDetails() {
var dllPaths = new List<string>{@"..\..\Tests\TestCases\TestOrdinal"};
var ret = StackResolver.GetSymbolDetailsForBinaries(dllPaths, true);
Assert.Single(ret);
Assert.Equal("https://msdl.microsoft.com/download/symbols/sqldk.pdb/6a1934433512464b8b8ed905ad930ee62/sqldk.pdb", ret[0].DownloadURL);
Assert.True(ret[0].DownloadVerified);
Assert.Equal("2015.0130.4560.00 ((SQL16_SP1_QFE-CU).190312-0204)", ret[0].FileVersion);
}
/// Make sure that caching PDB files is working. To do this we must use XEL input to trigger multiple worker threads.
[Fact]
public void SymbolFileCaching() {
using (var csr = new StackResolver()) {
var ret = csr.ExtractFromXEL(new[] { @"..\..\Tests\TestCases\ImportXEL\xe_wait_completed_0_132353446563350000.xel" }, false);
Assert.Equal(550, ret.Item1);
var status = csr.ProcessBaseAddresses(File.ReadAllText(@"..\..\Tests\TestCases\ImportXEL\xe_wait_base_addresses.txt"));
Assert.True(status);
Assert.Equal(26, csr.LoadedModules.Count);
var pdbPath = @"..\..\Tests\TestCases\sqlsyms\14.0.3192.2\x64";
var symres = csr.ResolveCallstacks(ret.Item2, pdbPath, false, null, false, false, false, false, false, false, true, null);
Assert.Contains(@"sqldk!XeSosPkg::wait_completed::Publish
sqldk!SOS_Scheduler::UpdateWaitTimeStats
sqldk!SOS_Task::PostWait
sqllang!SOS_Task::Sleep
sqllang!YieldAndCheckForAbort
sqllang!OptimizerUtil::YieldAndCheckForMemoryAndAbort
sqllang!OptTypeVRSetArray::IFindSet
sqllang!CConstraintProp::FEquivalent
sqllang!CJoinEdge::FConstrainsColumnSolvably
sqllang!CStCollOuterJoin::CardForColumns
sqllang!CStCollGroupBy::CStCollGroupBy
sqllang!CCardFrameworkSQL12::CardDistinct
sqllang!CCostUtils::CalcLoopJoinCachedInfo
sqllang!CCostUtils::PcctxLoopJoinHelper
sqllang!COpArg::PcctxCalculateNormalizeCtx
sqllang!CTask_OptInputs::Perform
sqllang!CMemo::ExecuteTasks
sqllang!CMemo::PerformOptimizationStage
sqllang!CMemo::OptimizeQuery
sqllang!COptContext::PexprSearchPlan
sqllang!COptContext::PcxteOptimizeQuery
sqllang!COptContext::PqteOptimizeWrapper
sqllang!PqoBuild
sqllang!CStmtQuery::InitQuery", symres.Trim(), StringComparison.CurrentCulture);
}
}
/// Validate that source information is retrieved correctly. This test uses symbols for a Windows Driver Kit module, Wdf01000.sys,
/// because private PDBs for that module are legitimately available on the Microsoft public symbols servers. https://github.com/microsoft/Windows-Driver-Frameworks/releases if interested.
[Fact]
public void SourceInformation() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\SourceInformation";
var ret = csr.ResolveCallstacks("Wdf01000+17f27", pdbPath, false, null, false, false, true, false, true, false, false, null);
Assert.Equal("Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143\t(minkernel\\wdf\\framework\\shared\\inc\\private\\common\\FxPkgPnp.hpp:4127)", ret.Trim());
}
}
/// Validate that source information is retrieved correctly.This test uses symbols for a Windows Driver Kit module, Wdf01000.sys,
/// because private PDBs for that module are legitimately available on the Microsoft public symbols servers. https://github.com/microsoft/Windows-Driver-Frameworks/releases if interested.
[Fact]
public void SourceInformationLineInfoOff() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\SourceInformation";
var ret = csr.ResolveCallstacks("Wdf01000+17f27", pdbPath, false, null, false, false, false, false, true, false, false, null);
Assert.Equal("Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143", ret.Trim());
}
}
/// Validate that source information is retrieved correctly when "re-looking up" a symbol based on input which was already symbolized (but missing source info).
/// This test uses symbols for a Windows Driver Kit module, Wdf01000.sys, because private PDBs for that module are legitimately available on the Microsoft public symbols servers.https://github.com/microsoft/Windows-Driver-Frameworks/releases if interested.
[Fact]
public void RelookupSourceInformation() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\SourceInformation";
var ret = csr.ResolveCallstacks("Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143", pdbPath, false, null, false, false, true, true, true, false, false, null);
Assert.Equal("Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143\t(minkernel\\wdf\\framework\\shared\\inc\\private\\common\\FxPkgPnp.hpp:4127)", ret.Trim());
}
}
/// Validate importing callstack events from XEL files into histogram buckets.
[Fact]
public void ImportBinResolveXELEvents() {
using (var csr = new StackResolver()) {
var ret = csr.ExtractFromXEL(new[] { @"..\..\Tests\TestCases\ImportXEL\XESpins_0_131627061603030000.xel" }, true);
Assert.Equal(4, ret.Item1);
var xmldoc = new XmlDocument() { XmlResolver = null };
bool isXMLdoc = false;
try {
using (var sreader = new StringReader(ret.Item2)) {
using (var reader = XmlReader.Create(sreader, new XmlReaderSettings() { XmlResolver = null })) {
xmldoc.Load(reader);
}
}
isXMLdoc = true;
} catch (XmlException) {
// do nothing because this is not a XML doc
}
Assert.True(isXMLdoc);
var slotNodes = xmldoc.SelectNodes("/HistogramTarget/Slot");
Assert.Equal(4, slotNodes.Count);
int eventCountFromXML = 0;
foreach (XmlNode slot in slotNodes) {
eventCountFromXML += int.Parse(slot.Attributes["count"].Value, CultureInfo.CurrentCulture);
}
Assert.Equal(3051540, eventCountFromXML);
csr.ProcessBaseAddresses(File.ReadAllText(@"..\..\Tests\TestCases\ImportXEL\base_addresses.txt"));
Assert.Equal(31, csr.LoadedModules.Count);
var pdbPath = @"..\..\Tests\TestCases\sqlsyms\13.0.4001.0\x64";
var symres = csr.ResolveCallstacks(ret.Item2, pdbPath, false, null, false, false, true, false, true, false, false, null);
Assert.Contains(@"sqldk!XeSosPkg::spinlock_backoff::Publish+425
sqldk!SpinlockBase::Sleep+182
sqlmin!Spinlock<143,7,1>::SpinToAcquireWithExponentialBackoff+363
sqlmin!lck_lockInternal+2042
sqlmin!MDL::LockGenericLocal+382
sqlmin!MDL::LockGenericIdsLocal+101
sqlmin!CMEDCacheEntryFactory::GetProxiedCacheEntryById+263
sqlmin!CMEDProxyDatabase::GetOwnerByOwnerId+122
sqllang!CSECAccessAuditBase::SetSecurable+427
sqllang!CSECManager::_AccessCheck+151
sqllang!CSECManager::AccessCheck+2346
sqllang!FHasEntityPermissionsWithAuditState+1505
sqllang!FHasEntityPermissions+165
sqllang!CSQLObject::FPostCacheLookup+2562
sqllang!CSQLSource::Transform+2194
sqllang!CSQLSource::Execute+944
sqllang!CStmtExecProc::XretLocalExec+622
sqllang!CStmtExecProc::XretExecExecute+1153
sqllang!CXStmtExecProc::XretExecute+56
sqllang!CMsqlExecContext::ExecuteStmts<1,1>+1037
sqllang!CMsqlExecContext::FExecute+2718
sqllang!CSQLSource::Execute+2435
sqllang!process_request+3681
sqllang!process_commands_internal+735", symres, StringComparison.CurrentCulture);
}
}
/// Validate importing individual callstack events from XEL files.
[Fact]
public void ImportIndividualXELEvents() {
using (var csr = new StackResolver()) {
var ret = csr.ExtractFromXEL(new[] { @"..\..\Tests\TestCases\ImportXEL\xe_wait_completed_0_132353446563350000.xel" }, false);
Assert.Equal(550, ret.Item1);
}
}
/// Validate importing "single-line" callstack (such as when the input is copy-pasted from SSMS).
[Fact]
public void SingleLineCallStack() {
using (var csr = new StackResolver()) {
csr.ProcessBaseAddresses(File.ReadAllText(@"..\..\Tests\TestCases\ImportXEL\base_addresses.txt"));
Assert.Equal(31, csr.LoadedModules.Count);
var pdbPath = @"..\..\Tests\TestCases\sqlsyms\13.0.4001.0\x64";
var callStack = @"callstack 0x00007FFEABD0D919 0x00007FFEABC4D45D 0x00007FFEAC0F7EE0 0x00007FFEAC0F80CF 0x00007FFEAC1EE447 0x00007FFEAC1EE6F5 0x00007FFEAC1D48B0 0x00007FFEAC71475A 0x00007FFEA9A708F1 0x00007FFEA9991FB9 0x00007FFEA9993D21 0x00007FFEA99B59F1 0x00007FFEA99B5055 0x00007FFEA99B2B8F 0x00007FFEA9675AD1 0x00007FFEA9671EFB 0x00007FFEAA37D83D 0x00007FFEAA37D241 0x00007FFEAA379F98 0x00007FFEA96719CA 0x00007FFEA9672933 0x00007FFEA9672041 0x00007FFEA967A82B 0x00007FFEA9681542 ";
var symres = csr.ResolveCallstacks(callStack, pdbPath, false, null, false, true, true, false, true, false, false, null);
Assert.Equal(@"callstack
sqldk!XeSosPkg::spinlock_backoff::Publish+425
sqldk!SpinlockBase::Sleep+182
sqlmin!Spinlock<143,7,1>::SpinToAcquireWithExponentialBackoff+363
sqlmin!lck_lockInternal+2042
sqlmin!MDL::LockGenericLocal+382
sqlmin!MDL::LockGenericIdsLocal+101
sqlmin!CMEDCacheEntryFactory::GetProxiedCacheEntryById+263
sqlmin!CMEDProxyDatabase::GetOwnerByOwnerId+122
sqllang!CSECAccessAuditBase::SetSecurable+427
sqllang!CSECManager::_AccessCheck+151
sqllang!CSECManager::AccessCheck+2346
sqllang!FHasEntityPermissionsWithAuditState+1505
sqllang!FHasEntityPermissions+165
sqllang!CSQLObject::FPostCacheLookup+2562
sqllang!CSQLSource::Transform+2194
sqllang!CSQLSource::Execute+944
sqllang!CStmtExecProc::XretLocalExec+622
sqllang!CStmtExecProc::XretExecExecute+1153
sqllang!CXStmtExecProc::XretExecute+56
sqllang!CMsqlExecContext::ExecuteStmts<1,1>+1037
sqllang!CMsqlExecContext::FExecute+2718
sqllang!CSQLSource::Execute+2435
sqllang!process_request+3681
sqllang!process_commands_internal+735", symres.Trim());
}
}
/// Test for inline frame resolution. This test uses symbols for a Windows Driver Kit module, Wdf01000.sys, because private PDBs for that module are legitimately available on the
/// Microsoft public symbols servers. https://github.com/microsoft/Windows-Driver-Frameworks/releases if interested.
[Fact]
public void InlineFrameResolution() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\SourceInformation";
var ret = csr.ResolveCallstacks("Wdf01000+17f27", pdbPath, false, null, false, false, true, false, true, true, false, null);
Assert.Equal(
@"(Inline Function) Wdf01000!Mx::MxLeaveCriticalRegion+12 (minkernel\wdf\framework\shared\inc\primitives\km\MxGeneralKm.h:198)
(Inline Function) Wdf01000!FxWaitLockInternal::ReleaseLock+62 (minkernel\wdf\framework\shared\inc\private\common\FxWaitLock.hpp:305)
(Inline Function) Wdf01000!FxEnumerationInfo::ReleaseParentPowerStateLock+62 (minkernel\wdf\framework\shared\inc\private\common\FxPkgPnp.hpp:510)
Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143 (minkernel\wdf\framework\shared\inc\private\common\FxPkgPnp.hpp:4127)",
ret.Trim());
}
}
/// Test for inline frame resolution without source lines included This test uses symbols for a Windows Driver Kit module, Wdf01000.sys,
/// because private PDBs for that module are legitimately available on the Microsoft public symbols servers. https://github.com/microsoft/Windows-Driver-Frameworks/releases if interested.
[Fact]
public void InlineFrameResolutionNoSourceInfo() {
using (var csr = new StackResolver()) {
var pdbPath = @"..\..\Tests\TestCases\SourceInformation";
var ret = csr.ResolveCallstacks("Wdf01000+17f27", pdbPath, false, null, false, false, false, false, true, true, false, null);
Assert.Equal(@"(Inline Function) Wdf01000!Mx::MxLeaveCriticalRegion+12
(Inline Function) Wdf01000!FxWaitLockInternal::ReleaseLock+62
(Inline Function) Wdf01000!FxEnumerationInfo::ReleaseParentPowerStateLock+62
Wdf01000!FxPkgPnp::PowerPolicyCanChildPowerUp+143", ret.Trim());
}
}
/// Tests the parsing and extraction of PDB details from a set of rows each with commma-separated fields. This sample mixes up \r\n and \n line-endings.
[Fact]
public void ExtractModuleInfo() {
var ret = ModuleInfoHelper.ParseModuleInfo("\"ntdll.dll\",\"10.0.19041.662\",2056192,666871280,2084960,\"ntdll.pdb\",\"{1EB9FACB-04C7-3C5D-EA71-60764CD333D0}\",0,1\r\n" +
"\"VCRUNTIME140.dll\",\"14.16.27033.0\",86016,1563486943,105788,\"vcruntime140.amd64.pdb\",\"{AF138C3F-2933-4097-8883-C1071B13375E}\",0,1\r\n" +
"\r\n" +
"sqlservr.exe,7ef4ea08-777a-43b7-8bce-4da6f0fa43c7,2\r\n" +
"\"KERNELBASE.dll\",\"10.0.19041.662\",2920448,3965251605,2936791,\"kernelbase.pdb\",\"{1FBE0B2B-89D1-37F0-1510-431FFFBA123E}\",0,1\n" +
"\"kernel32.dll\",\"10.0.19041.662\",774144,1262097423,770204,\"kernel32.pdb\",\"{54448D8E-EFC5-AB3C-7193-D2C7A6DF9008}\",0,1\r\n");
Assert.Equal(5, ret.Count);
Assert.Equal("ntdll.pdb", ret["ntdll"].PDBName);
Assert.Equal("1EB9FACB04C73C5DEA7160764CD333D0", ret["ntdll"].PDBGuid, ignoreCase: true);
Assert.Equal(1, ret["ntdll"].PDBAge);
Assert.Equal("vcruntime140.amd64.pdb", ret["VCRUNTIME140"].PDBName);
Assert.Equal("AF138C3F293340978883C1071B13375E", ret["VCRUNTIME140"].PDBGuid, ignoreCase: true);
Assert.Equal(1, ret["VCRUNTIME140"].PDBAge);
Assert.Equal("sqlservr.pdb", ret["sqlservr"].PDBName);
Assert.Equal("7ef4ea08777a43b78bce4da6f0fa43c7", ret["sqlservr"].PDBGuid, ignoreCase: true);
Assert.Equal(2, ret["sqlservr"].PDBAge);
}
/// Tests the parsing and extraction of PDB details from a set of rows each with commma-separated fields.
[Fact]
public void ExtractModuleInfoEmptyString() {
var ret = ModuleInfoHelper.ParseModuleInfo(string.Empty);
Assert.Empty(ret);
}
/// Test obtaining a local path for symbols downloaded from a symbol server.
[Fact]
public void SymSrvLocalPaths() {
var ret = ModuleInfoHelper.ParseModuleInfo(
"\"ntdll.dll\",\"10.0.19041.662\",2056192,666871280,2084960,\"ntdll.pdb\",\"{1EB9FACB-04C7-3C5D-EA71-60764CD333D0}\",0,1\r\n" +
"\"VCRUNTIME140.dll\",\"14.16.27033.0\",86016,1563486943,105788,\"vcruntime140.amd64.pdb\",\"{AF138C3F-2933-4097-8883-C1071B13375E}\",0,1\r\n" +
"\r\n" +
"sqlservr.exe,7ef4ea08-777a-43b7-8bce-4da6f0fa43c7,2\r\n" +
"\"KERNELBASE.dll\",\"10.0.19041.662\",2920448,3965251605,2936791,\"kernelbase.pdb\",\"{1FBE0B2B-89D1-37F0-1510-431FFFBA123E}\",0,1\n" +
"\"kernel32.dll\",\"10.0.19041.662\",774144,1262097423,770204,\"kernel32.pdb\",\"{54448D8E-EFC5-AB3C-7193-D2C7A6DF9008}\",0,1\r\n");
using (var csr = new StackResolver()) {
var paths = SymSrvHelpers.GetFolderPathsForPDBs(csr, "srv*https://msdl.microsoft.com/download/symbols", ret.Values.ToList());
Assert.Equal(5, paths.Count);
}
}
/// End-to-end test with stacks being resolved based on symbols from symsrv.
[Fact]
public void E2ESymSrv() {
using (var csr = new StackResolver()) {
var pdbPath = @"srv*https://msdl.microsoft.com/download/symbols";
var input = @"ntdll+0x9F7E4
KERNELBASE+0x38973
VCRUNTIME140+0xB8F0
ntdll+0xA479F
ntdll+0x4BEF
ntdll+0x89E6
KERNELBASE+0x396C9
" +
"\"ntdll.dll\",\"10.0.17763.1490\",2019328,462107166,2009368,\"ntdll.pdb\",\"{C374E059-5793-9B92-6525-386A66A2D3F5}\",0,1\r\n" +
"\"KERNELBASE.dll\",\"10.0.17763.1518\",2707456,4281343292,2763414,\"kernelbase.pdb\",\"{E77E26E7-D1C4-72BB-2C05-DD17624A9E58}\",0,1\r\n" +
"\"VCRUNTIME140.dll\",\"14.16.27033.0\",86016,1563486943,105788,\"vcruntime140.amd64.pdb\",\"{AF138C3F-2933-4097-8883-C1071B13375E}\",0,1\r\n";
var ret = csr.ResolveCallstacks(input, pdbPath, false, null, false, false, true, false, true, false, false, null);
var expected = @"ntdll!NtWaitForSingleObject+20
KERNELBASE!WaitForSingleObjectEx+147
VCRUNTIME140!__C_specific_handler+160 (d:\agent\_work\2\s\src\vctools\crt\vcruntime\src\eh\riscchandler.cpp:290)
ntdll!RtlpExecuteHandlerForException+15
ntdll!RtlDispatchException+1039
ntdll!RtlRaiseException+790
KERNELBASE!RaiseException+105
" +
"\"ntdll.dll\",\"10.0.17763.1490\",2019328,462107166,2009368,\"ntdll.pdb\",\"{C374E059-5793-9B92-6525-386A66A2D3F5}\",0,1\r\n" +
"\"KERNELBASE.dll\",\"10.0.17763.1518\",2707456,4281343292,2763414,\"kernelbase.pdb\",\"{E77E26E7-D1C4-72BB-2C05-DD17624A9E58}\",0,1\r\n" +
"\"VCRUNTIME140.dll\",\"14.16.27033.0\",86016,1563486943,105788,\"vcruntime140.amd64.pdb\",\"{AF138C3F-2933-4097-8883-C1071B13375E}\",0,1";
Assert.Equal(expected.Trim(), ret.Trim());
}
}
/// End-to-end test with stacks being resolved based on symbols from symsrv.
[Fact]
public void E2ESymSrvNoSympath() {
using (var csr = new StackResolver()) {
var pdbPath = string.Empty;
var input = @"ntdll+0x9F7E4
KERNELBASE+0x38973
VCRUNTIME140+0xB8F0
ntdll+0xA479F
ntdll+0x4BEF
ntdll+0x89E6
KERNELBASE+0x396C9
" +
"\"ntdll.dll\",\"10.0.17763.1490\",2019328,462107166,2009368,\"ntdll.pdb\",\"{C374E059-5793-9B92-6525-386A66A2D3F5}\",0,1\r\n" +
"\"KERNELBASE.dll\",\"10.0.17763.1518\",2707456,4281343292,2763414,\"kernelbase.pdb\",\"{E77E26E7-D1C4-72BB-2C05-DD17624A9E58}\",0,1\r\n" +
"\"VCRUNTIME140.dll\",\"14.16.27033.0\",86016,1563486943,105788,\"vcruntime140.amd64.pdb\",\"{AF138C3F-2933-4097-8883-C1071B13375E}\",0,1\r\n";
var ret = csr.ResolveCallstacks(input, pdbPath, false, null, false, false, true, false, true, false, false, null);
Assert.Equal(input.Trim(), ret.Trim());
}
}
}
}

4
Tests/app.config Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!--
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License - see LICENSE file in this repo.
--><configuration><runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="Microsoft.Diagnostics.NETCore.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-0.2.2.37102" newVersion="0.2.2.37102" /></dependentAssembly></assemblyBinding></runtime><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /></startup></configuration>

1
Tests/packages.config Normal file
Просмотреть файл

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><packages><package id="xunit" version="2.4.1" targetFramework="net472" /><package id="xunit.abstractions" version="2.0.3" targetFramework="net472" /><package id="xunit.analyzers" version="0.10.0" targetFramework="net472" /><package id="xunit.assert" version="2.4.1" targetFramework="net472" /><package id="xunit.core" version="2.4.1" targetFramework="net472" /><package id="xunit.extensibility.core" version="2.4.1" targetFramework="net472" /><package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net472" /><package id="xunit.runner.console" version="2.4.1" targetFramework="net472" developmentDependency="true" /><package id="xunit.runner.visualstudio" version="2.4.1" targetFramework="net472" developmentDependency="true" /></packages>

4
Tests/xunit.runner.json Normal file
Просмотреть файл

@ -0,0 +1,4 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"shadowCopy": false
}

Двоичные данные
images/1_ModOffset_Text.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 820 KiB

Двоичные данные
images/2_XEL_Address.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.6 MiB