Merge pull request #523 from AArnott/dumpasync
Swap out C++ !dumpasync extension for C# version
This commit is contained in:
Коммит
5788b1aba3
|
@ -55,14 +55,14 @@ steps:
|
|||
msbuildArgs: /t:build /m /bl:"$(Build.ArtifactStagingDirectory)/build_logs/msbuild_x86.binlog"
|
||||
platform: x86
|
||||
configuration: $(BuildConfiguration)
|
||||
displayName: Build AsyncDebugTools x86
|
||||
displayName: Build SosThreadingTools x86
|
||||
|
||||
- task: VSBuild@1
|
||||
inputs:
|
||||
msbuildArgs: /t:build /m /bl:"$(Build.ArtifactStagingDirectory)/build_logs/msbuild_x64.binlog"
|
||||
platform: x64
|
||||
configuration: $(BuildConfiguration)
|
||||
displayName: Build AsyncDebugTools x64
|
||||
displayName: Build SosThreadingTools x64
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run tests
|
||||
|
@ -131,8 +131,8 @@ steps:
|
|||
md $(Build.ArtifactStagingDirectory)/deployables\x86
|
||||
md $(Build.ArtifactStagingDirectory)/deployables\x64
|
||||
Get-ChildItem bin\Packages\$(BuildConfiguration)\NuGet\*.nupkg -rec |% { copy $_ $(Build.ArtifactStagingDirectory)\deployables }
|
||||
copy bin\AsyncDebugTools/x86/$(BuildConfiguration)/AsyncDebugTools.dll $(Build.ArtifactStagingDirectory)\deployables\x86
|
||||
copy bin\AsyncDebugTools/x64/$(BuildConfiguration)/AsyncDebugTools.dll $(Build.ArtifactStagingDirectory)\deployables\x64
|
||||
copy bin\SosThreadingTools/x86/$(BuildConfiguration)/net472/SosThreadingTools.dll $(Build.ArtifactStagingDirectory)\deployables\x86
|
||||
copy bin\SosThreadingTools/x64/$(BuildConfiguration)/net472/SosThreadingTools.dll $(Build.ArtifactStagingDirectory)\deployables\x64
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
|
|
|
@ -8,15 +8,15 @@ for an app hang that is waiting for async methods to complete.
|
|||
|
||||
Using this tool requires that you download and install [WinDbg][WinDbg], which can attach to a running process or DMP file. The `!dumpasync` extension is only available for WinDbg.
|
||||
|
||||
The `!dumpasync` extension itself is exported from the `AsyncDebugTools.dll` library, which you can acquire from [our releases page](https://github.com/Microsoft/vs-threading/releases).
|
||||
The `!dumpasync` extension itself is exported from the `SosThreadingTools.dll` library, which you can acquire from [our releases page](https://github.com/Microsoft/vs-threading/releases).
|
||||
|
||||
## Usage
|
||||
|
||||
Use WinDbg to open your DMP file or attach to your process, then execute the following commands.
|
||||
Be sure to use either the x86 or x64 version of the `AsyncDebugTools.dll` library, consistent with the version of WinDbg you are running.
|
||||
Be sure to use either the x86 or x64 version of the `SosThreadingTools.dll` library, consistent with the version of WinDbg you are running.
|
||||
|
||||
```windbg
|
||||
.load path\to\AsyncDebugTools.dll
|
||||
.load path\to\SosThreadingTools.dll
|
||||
!dumpasync
|
||||
```
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
EXPORTS
|
||||
dumpasync
|
||||
DebugExtensionNotify
|
||||
DebugExtensionInitialize
|
||||
DebugExtensionUninitialize
|
|
@ -1,225 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.props" Condition="Exists('..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.props')" />
|
||||
<Import Project="..\..\packages\MicroBuild.2.0.54\build\MicroBuild.props" Condition="Exists('..\..\packages\MicroBuild.2.0.54\build\MicroBuild.props')" />
|
||||
<Import Project="..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.props" Condition="Exists('..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>AsyncDebugTools</RootNamespace>
|
||||
<MicroBuild_DoNotStrongNameSign>true</MicroBuild_DoNotStrongNameSign>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>..\..\bin\$(MSBuildProjectName)\x86\$(Configuration)\</OutDir>
|
||||
<IntDir>..\..\obj\$(MSBuildProjectName)\x86\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>..\..\bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>..\..\obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>..\..\bin\$(MSBuildProjectName)\x86\$(Configuration)\</OutDir>
|
||||
<IntDir>..\..\obj\$(MSBuildProjectName)\x86\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>..\..\bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>..\..\obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<ModuleDefinitionFile>AsyncDebugTools.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<ModuleDefinitionFile>AsyncDebugTools.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<ModuleDefinitionFile>AsyncDebugTools.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<ModuleDefinitionFile>AsyncDebugTools.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="ReadMe.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DbgExts.h" />
|
||||
<ClInclude Include="DML.h" />
|
||||
<ClInclude Include="Helpers.h" />
|
||||
<ClInclude Include="outputcallbacks.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="ThreadPoolExhausted.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dumpasync.cpp" />
|
||||
<ClCompile Include="DbgExts.cpp" />
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
</PrecompiledHeader>
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DML.cpp" />
|
||||
<ClCompile Include="Helpers.cpp" />
|
||||
<ClCompile Include="outputcallbacks.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThreadPoolExhausted.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsyncDebugTools.def" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets')" />
|
||||
<Import Project="..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.targets" Condition="Exists('..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.targets')" />
|
||||
<Import Project="..\..\packages\MicroBuild.2.0.54\build\MicroBuild.targets" Condition="Exists('..\..\packages\MicroBuild.2.0.54\build\MicroBuild.targets')" />
|
||||
<Import Project="..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.targets" Condition="Exists('..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.targets')" />
|
||||
</ImportGroup>
|
||||
<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\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.Core.0.3.0\build\MicroBuild.Core.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.2.0.54\build\MicroBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.2.0.54\build\MicroBuild.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.2.0.54\build\MicroBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.2.0.54\build\MicroBuild.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\MicroBuild.VisualStudio.2.0.54\build\MicroBuild.VisualStudio.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,75 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="ReadMe.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DbgExts.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Helpers.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="outputcallbacks.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThreadPoolExhausted.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DML.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DbgExts.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="outputcallbacks.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThreadPoolExhausted.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dumpasync.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DML.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="AsyncDebugTools.def">
|
||||
<Filter>Source Files</Filter>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,37 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
BOOL PreferDML(PDEBUG_CLIENT pDebugClient)
|
||||
{
|
||||
BOOL bPreferDML = FALSE;
|
||||
IDebugControl* pDebugControl;
|
||||
if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl),
|
||||
(void **)& pDebugControl)))
|
||||
{
|
||||
ULONG ulOptions = 0;
|
||||
if (SUCCEEDED(pDebugControl->GetEngineOptions(&ulOptions)))
|
||||
{
|
||||
bPreferDML = (ulOptions & DEBUG_ENGOPT_PREFER_DML);
|
||||
}
|
||||
pDebugControl->Release();
|
||||
}
|
||||
return bPreferDML;
|
||||
}
|
||||
|
||||
BOOL AbilityDML(PDEBUG_CLIENT pDebugClient)
|
||||
{
|
||||
BOOL bAbilityDML = FALSE;
|
||||
IDebugAdvanced2* pDebugAdvanced2;
|
||||
if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugAdvanced2),
|
||||
(void **)& pDebugAdvanced2)))
|
||||
{
|
||||
HRESULT hr = 0;
|
||||
if (SUCCEEDED(hr = pDebugAdvanced2->Request(
|
||||
DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE,
|
||||
NULL, 0, NULL, 0, NULL)))
|
||||
{
|
||||
if (hr == S_OK) bAbilityDML = TRUE;
|
||||
}
|
||||
pDebugAdvanced2->Release();
|
||||
}
|
||||
return bAbilityDML;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
#include "stdafx.h"
|
||||
|
||||
BOOL PreferDML(PDEBUG_CLIENT pDebugClient);
|
||||
BOOL AbilityDML(PDEBUG_CLIENT pDebugClient);
|
|
@ -1,40 +0,0 @@
|
|||
// dbgexts.cpp
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "dbgexts.h"
|
||||
|
||||
extern "C" HRESULT CALLBACK
|
||||
DebugExtensionInitialize(PULONG Version, PULONG Flags)
|
||||
{
|
||||
*Version = DEBUG_EXTENSION_VERSION(EXT_MAJOR_VER, EXT_MINOR_VER);
|
||||
*Flags = 0; // Reserved for future use.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
extern "C" void CALLBACK
|
||||
DebugExtensionNotify(ULONG Notify, ULONG64 Argument)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Argument);
|
||||
switch (Notify)
|
||||
{
|
||||
// A debugging session is active. The session may not necessarily be suspended.
|
||||
case DEBUG_NOTIFY_SESSION_ACTIVE:
|
||||
break;
|
||||
// No debugging session is active.
|
||||
case DEBUG_NOTIFY_SESSION_INACTIVE:
|
||||
break;
|
||||
// The debugging session has suspended and is now accessible.
|
||||
case DEBUG_NOTIFY_SESSION_ACCESSIBLE:
|
||||
break;
|
||||
// The debugging session has started running and is now inaccessible.
|
||||
case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
extern "C" void CALLBACK
|
||||
DebugExtensionUninitialize(void)
|
||||
{
|
||||
return;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// dbgexts.h
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbgeng.h>
|
||||
|
||||
#define EXT_MAJOR_VER 1
|
||||
#define EXT_MINOR_VER 0
|
|
@ -1,764 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "Helpers.h"
|
||||
#include "outputcallbacks.h"
|
||||
|
||||
bool TryFindSOS(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_Out_ std::string &strSOS)
|
||||
{
|
||||
strSOS.clear();
|
||||
|
||||
std::string strOutput;
|
||||
if (SUCCEEDED(Execute(pDebugClient, ".extmatch /e *\\sos*.dll Threads", strOutput))
|
||||
&& !strOutput.empty())
|
||||
{
|
||||
size_t pos = strOutput.find_first_of("\r\n");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
strOutput.erase(pos);
|
||||
}
|
||||
|
||||
size_t dot = strOutput.rfind('.');
|
||||
if (dot != std::string::npos)
|
||||
{
|
||||
strSOS = strOutput.substr(1, dot - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return !strSOS.empty();
|
||||
}
|
||||
|
||||
bool EnsureLoadSOS(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_Out_ std::string &strSOS,
|
||||
_Out_ std::string &strOutput)
|
||||
{
|
||||
strSOS.clear();
|
||||
strOutput.clear();
|
||||
|
||||
if (TryFindSOS(pDebugClient, strSOS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(Execute(pDebugClient, ".loadby sos.dll clr", strOutput)))
|
||||
{
|
||||
return TryFindSOS(pDebugClient, strSOS);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string GetFullCommand(
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const std::string &strCommand)
|
||||
{
|
||||
return "!" + strSOS + "." + strCommand;
|
||||
}
|
||||
|
||||
HRESULT Execute(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_In_ const std::string &strCommand,
|
||||
_Out_ std::string &strOutput)
|
||||
{
|
||||
HRESULT hr = E_FAIL;
|
||||
strOutput.clear();
|
||||
|
||||
CComPtr<IDebugClient> srpNewClient;
|
||||
if (SUCCEEDED(pDebugClient->CreateClient(&srpNewClient)))
|
||||
{
|
||||
COutputCallbacks *pOutputCallbacks = new COutputCallbacks();
|
||||
if (SUCCEEDED(srpNewClient->SetOutputMask(pOutputCallbacks->SupportedMask()))
|
||||
&& SUCCEEDED(srpNewClient->SetOutputCallbacks(pOutputCallbacks)))
|
||||
{
|
||||
CComQIPtr<IDebugControl> srpNewControl(srpNewClient);
|
||||
if (srpNewControl)
|
||||
{
|
||||
hr = srpNewControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED, strCommand.c_str(), DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);
|
||||
if (pOutputCallbacks->BufferError())
|
||||
{
|
||||
strOutput = pOutputCallbacks->BufferError();
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = E_FAIL; // Ensure returning failure when there is error message.
|
||||
}
|
||||
}
|
||||
else if (pOutputCallbacks->BufferNormal())
|
||||
{
|
||||
strOutput = pOutputCallbacks->BufferNormal();
|
||||
}
|
||||
|
||||
// Print the command outputs if enable verbose mode.
|
||||
{
|
||||
CComQIPtr<IDebugControl> srpControl(pDebugClient);
|
||||
srpControl->Output(DEBUG_OUTPUT_VERBOSE, "Execute command: %s\n%s\n", strCommand.c_str(), strOutput.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
srpNewClient->SetOutputCallbacks(nullptr);
|
||||
}
|
||||
|
||||
pOutputCallbacks->Release();
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void SplitString(
|
||||
_In_ const std::string &strText,
|
||||
_In_ const std::string &strDelim,
|
||||
_Out_ std::vector<std::string> &tokens)
|
||||
{
|
||||
tokens.clear();
|
||||
std::string strCopyText(strText);
|
||||
char *pszContext = nullptr;
|
||||
char *pszToken = strtok_s(const_cast<char *>(strCopyText.c_str()), strDelim.c_str(), &pszContext);
|
||||
while (pszToken != nullptr)
|
||||
{
|
||||
tokens.push_back(pszToken);
|
||||
pszToken = strtok_s(nullptr, strDelim.c_str(), &pszContext);
|
||||
}
|
||||
}
|
||||
|
||||
static std::regex g_addressPattern("[0-9a-fA-F]{8}");
|
||||
|
||||
void ExtractExceptionInfosFromThreadsOutput(
|
||||
_In_ const std::string &strOutput,
|
||||
_Out_ std::vector<ExceptionInfo> &exceptionInfos)
|
||||
{
|
||||
static std::string strDelim = " ()";
|
||||
|
||||
exceptionInfos.clear();
|
||||
|
||||
std::istringstream iss(strOutput);
|
||||
std::string strLine;
|
||||
std::vector<std::string> tokens;
|
||||
while (std::getline(iss, strLine))
|
||||
{
|
||||
SplitString(strLine, strDelim, tokens);
|
||||
for (auto i = tokens.size() - 1; i >= 10; --i)
|
||||
{
|
||||
if (std::regex_match(tokens[i], g_addressPattern))
|
||||
{
|
||||
exceptionInfos.push_back(ExceptionInfo{tokens[0], tokens[i]});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TryParsePrintExceptionOutput(
|
||||
_In_ const std::string &strOutput,
|
||||
_Out_ ExceptionDetail &detail)
|
||||
{
|
||||
detail = ExceptionDetail{};
|
||||
|
||||
std::istringstream iss(strOutput);
|
||||
std::string strLine;
|
||||
|
||||
// Exception object
|
||||
{
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
if (!std::regex_search(strLine, match, g_addressPattern))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail.m_strExceptionObject = match.str();
|
||||
}
|
||||
|
||||
// Exception type
|
||||
{
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = strLine.find_last_of(" :");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail.m_strExceptionType = strLine.substr(pos + 1);
|
||||
}
|
||||
|
||||
// Skip Message
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// InnerException [optional]
|
||||
{
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
if (std::regex_search(strLine, match, g_addressPattern))
|
||||
{
|
||||
detail.m_strInnerExceptionObject = match.str();
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace (generated)
|
||||
{
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SP IP Function
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = strLine.rfind("Function");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (std::getline(iss, strLine) && !strLine.empty())
|
||||
{
|
||||
detail.m_stackTraces.push_back(strLine.substr(pos));
|
||||
}
|
||||
|
||||
if (detail.m_stackTraces.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip StackTraceString
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// HResult
|
||||
{
|
||||
if (!std::getline(iss, strLine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = strLine.find_last_of(" :");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail.m_strHResult = strLine.substr(pos + 1);
|
||||
}
|
||||
|
||||
detail.m_strRawText = strOutput;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollectExceptionDetails(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const std::string &strExceptionObject,
|
||||
_Out_ std::vector<ExceptionDetail> &exceptionDetailsIncludingInnerExceptions)
|
||||
{
|
||||
std::string strPrintExceptionCommand = GetFullCommand(strSOS, "PrintException");
|
||||
std::string strException = strExceptionObject;
|
||||
while (!strException.empty())
|
||||
{
|
||||
std::string strCommand = strPrintExceptionCommand + " " + strException;
|
||||
strException.clear();
|
||||
|
||||
std::string strOutput;
|
||||
if (SUCCEEDED(Execute(pDebugClient, strCommand, strOutput)))
|
||||
{
|
||||
ExceptionDetail detail;
|
||||
if (TryParsePrintExceptionOutput(strOutput, detail))
|
||||
{
|
||||
exceptionDetailsIncludingInnerExceptions.push_back(detail);
|
||||
strException = detail.m_strInnerExceptionObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> g_immunizedSymbolsStartWithIgnoreCase =
|
||||
{
|
||||
"mscorlib",
|
||||
"System",
|
||||
"Microsoft_VisualStudio_Validation",
|
||||
"Microsoft_VisualStudio_Threading",
|
||||
"Microsoft_Collections_Immutable",
|
||||
|
||||
"Microsoft.VisualStudio.Validation",
|
||||
"Microsoft.VisualStudio.Threading",
|
||||
"Microsoft.Collections.Immutable",
|
||||
"Microsoft.VisualStudio.Shell.VsTaskLibraryHelper",
|
||||
"Microsoft.VisualStudio.Services.VsTask",
|
||||
"Microsoft.VisualStudio.ProjectSystem.ThreadHandlingMultithreaded",
|
||||
"Microsoft.VisualStudio.ProjectSystem.VS.HResult",
|
||||
"Microsoft.VisualStudio.Project.VisualC.VCProjectEngine.ApartmentMarshaler.Invoke",
|
||||
};
|
||||
|
||||
static std::vector<std::string> g_immunizedSymbolsContain =
|
||||
{
|
||||
"ErrorUtilities.",
|
||||
"ErrorHandler.",
|
||||
"ThrowOnFailure",
|
||||
"ThrowIfNotOnUIThread",
|
||||
"HrInvoke",
|
||||
};
|
||||
|
||||
bool IsImmunized(_In_ const std::string &strFrame)
|
||||
{
|
||||
for (const std::string &strImmunizedSymbolStartWithIgnoreCase : g_immunizedSymbolsStartWithIgnoreCase)
|
||||
{
|
||||
if (strFrame.length() > strImmunizedSymbolStartWithIgnoreCase.length()
|
||||
&& 0 == _strnicmp(strFrame.c_str(), strImmunizedSymbolStartWithIgnoreCase.c_str(), strImmunizedSymbolStartWithIgnoreCase.length()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string &strImmunizedSymbolsContain : g_immunizedSymbolsContain)
|
||||
{
|
||||
if (strFrame.find(strImmunizedSymbolsContain) != std::string::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string GetSymbolFromFrame(_In_ const std::string &strFrame)
|
||||
{
|
||||
size_t pos = strFrame.find('(');
|
||||
std::string strSymbol = strFrame.substr(0, pos);
|
||||
|
||||
// Normalize NGENed image name.
|
||||
pos = strSymbol.find("_ni!");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
strSymbol.erase(pos, 3);
|
||||
}
|
||||
|
||||
return strSymbol;
|
||||
}
|
||||
|
||||
std::string GuessBlamedSymbol(
|
||||
_In_ const ExceptionDetail &detail)
|
||||
{
|
||||
for (const std::string &strFrame : detail.m_stackTraces)
|
||||
{
|
||||
if (!IsImmunized(strFrame))
|
||||
{
|
||||
return GetSymbolFromFrame(strFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fallback to the last frame.
|
||||
return GetSymbolFromFrame(detail.m_stackTraces.back());
|
||||
}
|
||||
|
||||
bool IsDueToHangDetected(
|
||||
_In_ const ExceptionDetail &detail)
|
||||
{
|
||||
for (const std::string &stack : detail.m_stackTraces)
|
||||
{
|
||||
if (stack.find("OnHangDetected") != std::string::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::regex g_framePattern("^[0-9a-f]{8} [0-9a-f]{8} ([a-z].+)", std::regex_constants::icase);
|
||||
|
||||
bool ParseClrStackFrame(
|
||||
_In_ const std::string &strFrame,
|
||||
_Out_ std::string &strFunc)
|
||||
{
|
||||
std::smatch matches;
|
||||
if (std::regex_match(strFrame, matches, g_framePattern))
|
||||
{
|
||||
strFunc = matches[1].str();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseKStackFrame(
|
||||
_In_ const std::string &strFrame,
|
||||
_Out_ std::string &strFunc)
|
||||
{
|
||||
static std::regex s_funcPattern("[0-9a-fA-F-]{8} [0-9a-fA-F-]{8} (.+!.+\\+0x[0-9a-fA-F]+)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(strFrame, match, s_funcPattern))
|
||||
{
|
||||
strFunc = match[1].str();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* !threadpool
|
||||
CPU utilization: 3%
|
||||
Worker Thread: Total: 129 Running: 110 Idle: 0 MaxLimit: 2047 MinLimit: 12
|
||||
Work Request in Queue: 0
|
||||
--------------------------------------
|
||||
Number of Timers: 1
|
||||
--------------------------------------
|
||||
Completion Port Thread:Total: 4 Free: 4 MaxFree: 24 CurrentLimit: 4 MaxLimit: 1000 MinLimit: 12
|
||||
*/
|
||||
bool GetThreadPoolStatus(
|
||||
_In_ const std::string &strThreadPoolOutput,
|
||||
_Out_ ThreadPoolStatus &status)
|
||||
{
|
||||
static std::regex s_workerThreadPattern("^Worker Thread: Total: (\\d+) Running: (\\d+)", std::regex::icase);
|
||||
|
||||
std::stringstream ss(strThreadPoolOutput);
|
||||
std::string strLine;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_search(strLine, match, s_workerThreadPattern))
|
||||
{
|
||||
status.m_iRunningWorkers = std::atoi(match[2].str().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int GetNumberOfThreads(
|
||||
_In_ PDEBUG_CLIENT pDebugClient)
|
||||
{
|
||||
static std::regex s_threadIdPattern("^\\s*(\\d+)\\s*Id:", std::regex::icase);
|
||||
|
||||
std::string strOutput;
|
||||
if (SUCCEEDED(Execute(pDebugClient, "~*", strOutput)))
|
||||
{
|
||||
std::stringstream ss(strOutput);
|
||||
std::string strLine;
|
||||
int threadId = -1;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
std::smatch match;
|
||||
if (std::regex_search(strLine, match, s_threadIdPattern))
|
||||
{
|
||||
threadId = std::atoi(match[1].str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return threadId + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct FieldHeaderInfo
|
||||
{
|
||||
std::string m_strName;
|
||||
size_t m_iStart;
|
||||
size_t m_iLength;
|
||||
};
|
||||
|
||||
bool ParseFieldHeaders(
|
||||
_In_ const std::string &strHeaders,
|
||||
_Out_ std::vector<FieldHeaderInfo> &headers)
|
||||
{
|
||||
static std::regex s_fieldHeaderPattern("^\\s+(\\S+)");
|
||||
|
||||
headers.clear();
|
||||
std::smatch match;
|
||||
std::string::const_iterator begin = strHeaders.begin();
|
||||
while (std::regex_search(begin, strHeaders.end(), match, s_fieldHeaderPattern))
|
||||
{
|
||||
FieldHeaderInfo header{};
|
||||
header.m_strName.assign(match[1].first, match[1].second);
|
||||
header.m_iStart = begin - strHeaders.begin();
|
||||
header.m_iLength = match.length();
|
||||
headers.push_back(std::move(header));
|
||||
begin += match.length();
|
||||
}
|
||||
|
||||
if (!headers.empty())
|
||||
{
|
||||
headers.back().m_iLength = std::string::npos;
|
||||
}
|
||||
|
||||
return headers.size() == 8;
|
||||
}
|
||||
|
||||
void Trim(_Inout_ std::string &strText)
|
||||
{
|
||||
size_t start = strText.find_first_not_of(' ');
|
||||
if (start == std::string::npos)
|
||||
{
|
||||
strText.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t end = strText.find_last_not_of(' ');
|
||||
if (end - start + 1 < strText.length())
|
||||
{
|
||||
strText.erase(end + 1);
|
||||
strText.erase(0, start);
|
||||
}
|
||||
}
|
||||
|
||||
void SetFieldValue(
|
||||
_Inout_ FieldInfo &field,
|
||||
_In_ const std::string &strName,
|
||||
_Inout_ std::string &&strValue)
|
||||
{
|
||||
Trim(strValue);
|
||||
|
||||
if (strName == "MT")
|
||||
{
|
||||
field.m_strMethodTable = strValue;
|
||||
}
|
||||
else if (strName == "Offset")
|
||||
{
|
||||
field.m_iOffset = atoi(strValue.c_str());
|
||||
}
|
||||
else if (strName == "VT")
|
||||
{
|
||||
field.m_fIsValueType = atoi(strValue.c_str()) != 0;
|
||||
}
|
||||
else if (strName == "Value")
|
||||
{
|
||||
field.m_strValue = strValue;
|
||||
}
|
||||
else if (strName == "Name")
|
||||
{
|
||||
field.m_strName = strValue;
|
||||
}
|
||||
}
|
||||
|
||||
/* !do 032f2398
|
||||
Name: Microsoft.VisualStudio.Services.Settings.SettingsPackage+<ShellInitPlusFiveSeconds>d__1
|
||||
MethodTable: 6f6a03fc
|
||||
EEClass: 6f58da24
|
||||
Size: 32(0x20) bytes
|
||||
File: C:\windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.VisualStudio.Shell.UI.Internal\v4.0_14.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.Shell.UI.Internal.dll
|
||||
Fields:
|
||||
MT Field Offset Type VT Attr Value Name
|
||||
6cc19158 4001249 8 System.Int32 1 instance 1 <>1__state
|
||||
6cc223fc 400124a c ...TaskMethodBuilder 1 instance 032f23a4 <>t__builder
|
||||
6cbe4fd0 400124b 18 ...olean, mscorlib]] 1 instance 032f23b0 <>u__1
|
||||
6cc17670 400124c 4 System.Object 0 instance 0a4862f4 <>u__2
|
||||
*/
|
||||
bool ParseDumpObjectOutput(
|
||||
_In_ const std::string &strDumpObject,
|
||||
_Out_ ObjectInfo &objectInfo)
|
||||
{
|
||||
objectInfo.m_strType.clear();
|
||||
objectInfo.m_fields.clear();
|
||||
|
||||
std::stringstream ss(strDumpObject);
|
||||
std::string strLine;
|
||||
bool fFoundFields = false;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
size_t sep = strLine.find(':');
|
||||
if (sep != std::string::npos)
|
||||
{
|
||||
if (strncmp(strLine.c_str(), "Name", sep) == 0)
|
||||
{
|
||||
size_t start = strLine.find_first_not_of(' ', sep + 1);
|
||||
if (start != std::string::npos)
|
||||
{
|
||||
objectInfo.m_strType.assign(strLine, start);
|
||||
}
|
||||
}
|
||||
if (strncmp(strLine.c_str(), "String", sep) == 0)
|
||||
{
|
||||
size_t start = strLine.find_first_not_of(' ', sep + 1);
|
||||
if (start != std::string::npos)
|
||||
{
|
||||
objectInfo.m_string.assign(strLine, start);
|
||||
}
|
||||
}
|
||||
else if (strncmp(strLine.c_str(), "Fields", sep) == 0)
|
||||
{
|
||||
fFoundFields = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fFoundFields && !objectInfo.m_strType.empty())
|
||||
{
|
||||
if (std::getline(ss, strLine))
|
||||
{
|
||||
std::vector<FieldHeaderInfo> headers;
|
||||
if (ParseFieldHeaders(strLine, headers))
|
||||
{
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
if (strLine.empty() || isspace(strLine[0]) || (int)strLine.length() <= headers.back().m_iStart )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
int fieldIndex = 0;
|
||||
FieldInfo fieldInfo{};
|
||||
for (const FieldHeaderInfo &headerInfo : headers)
|
||||
{
|
||||
// SOS doesn't align all fields with head correctly, causing the code reads wrong values and names.
|
||||
if (fieldIndex < 6)
|
||||
{
|
||||
std::string strValue(strLine, headerInfo.m_iStart, headerInfo.m_iLength);
|
||||
SetFieldValue(fieldInfo, headerInfo.m_strName, std::move(strValue));
|
||||
start = headerInfo.m_iStart + headerInfo.m_iLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (start < (int)strLine.length() && isspace(strLine[start]))
|
||||
{
|
||||
start++;
|
||||
}
|
||||
|
||||
end = start;
|
||||
while (end < (int)strLine.length() && !isspace(strLine[end]))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
|
||||
std::string strValue(strLine, start, end - start);
|
||||
SetFieldValue(fieldInfo, headerInfo.m_strName, std::move(strValue));
|
||||
start = end;
|
||||
}
|
||||
|
||||
fieldIndex++;
|
||||
}
|
||||
|
||||
objectInfo.m_fields.push_back(std::move(fieldInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !objectInfo.m_strType.empty() && !objectInfo.m_fields.empty();
|
||||
}
|
||||
|
||||
/* !name2ee mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner
|
||||
Module: 6c7f1000
|
||||
Assembly: mscorlib.dll
|
||||
Token: 020008be
|
||||
MethodTable: 6cbe4dbc
|
||||
EEClass: 6c855780
|
||||
Name: System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner
|
||||
*/
|
||||
bool ParseName2EEOutput(
|
||||
_In_ const std::string &strName2EE,
|
||||
_Out_ std::string &strMethodTable)
|
||||
{
|
||||
std::stringstream ss(strName2EE);
|
||||
std::string strLine;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
size_t sep = strLine.find(':');
|
||||
if (sep != std::string::npos)
|
||||
{
|
||||
size_t start = strLine.find_first_not_of(' ', sep + 1);
|
||||
if (start != std::string::npos)
|
||||
{
|
||||
if (strncmp(strLine.c_str(), "MethodTable", sep) == 0)
|
||||
{
|
||||
strMethodTable.assign(strLine, start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetFieldInfo(
|
||||
_In_ const ObjectInfo &objectInfo,
|
||||
_In_ const std::string &strFieldName,
|
||||
_Out_ FieldInfo &fieldInfo)
|
||||
{
|
||||
for (const FieldInfo &fi: objectInfo.m_fields)
|
||||
{
|
||||
if (fi.m_strName == strFieldName)
|
||||
{
|
||||
fieldInfo = fi;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindFieldAndGetObjectInfo(
|
||||
_In_ PDEBUG_CLIENT pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const ObjectInfo &objectInfo,
|
||||
_In_ const std::string &strFieldName,
|
||||
_Out_ ObjectInfo &fieldObjectInfo)
|
||||
{
|
||||
FieldInfo fieldInfo{};
|
||||
if (!GetFieldInfo(objectInfo, strFieldName, fieldInfo) || fieldInfo.m_strValue == "00000000")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string strCommand;
|
||||
if (fieldInfo.m_fIsValueType)
|
||||
{
|
||||
if (fieldInfo.m_strMethodTable == "00000000")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
strCommand.append("dumpvc ")
|
||||
.append(fieldInfo.m_strMethodTable)
|
||||
.append(" ")
|
||||
.append(fieldInfo.m_strValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
strCommand.append("do ")
|
||||
.append(fieldInfo.m_strValue);
|
||||
}
|
||||
|
||||
std::string strOutput;
|
||||
HRESULT hr = Execute(pDebugClient, GetFullCommand(strSOS, strCommand), strOutput);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
CComQIPtr<IDebugControl> srpControl(pDebugClient);
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to run !%s: %s\n", strCommand.c_str(), strOutput.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseDumpObjectOutput(strOutput, fieldObjectInfo))
|
||||
{
|
||||
CComQIPtr<IDebugControl> srpControl(pDebugClient);
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to parse the output of !%s: %s\n", strCommand.c_str(), strOutput.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fieldObjectInfo.m_strAddress = fieldInfo.m_strValue;
|
||||
return true;
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
struct ExceptionInfo
|
||||
{
|
||||
std::string m_strThreadId;
|
||||
std::string m_strExceptionObject;
|
||||
};
|
||||
|
||||
struct ExceptionDetail
|
||||
{
|
||||
std::string m_strExceptionObject;
|
||||
std::string m_strExceptionType;
|
||||
std::string m_strInnerExceptionObject;
|
||||
std::vector<std::string> m_stackTraces;
|
||||
std::string m_strHResult;
|
||||
std::string m_strRawText;
|
||||
};
|
||||
|
||||
struct ThreadPoolStatus
|
||||
{
|
||||
int m_iRunningWorkers = 0;
|
||||
};
|
||||
|
||||
struct FieldInfo
|
||||
{
|
||||
std::string m_strMethodTable;
|
||||
int m_iOffset;
|
||||
bool m_fIsValueType;
|
||||
std::string m_strValue;
|
||||
std::string m_strName;
|
||||
};
|
||||
|
||||
struct ObjectInfo
|
||||
{
|
||||
std::string m_strAddress;
|
||||
std::string m_strType;
|
||||
std::string m_string;
|
||||
std::vector<FieldInfo> m_fields;
|
||||
};
|
||||
|
||||
bool EnsureLoadSOS(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_Out_ std::string &strSOS,
|
||||
_Out_ std::string &strOutput);
|
||||
|
||||
std::string GetFullCommand(
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const std::string &strCommand);
|
||||
|
||||
HRESULT Execute(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_In_ const std::string &strCommand,
|
||||
_Out_ std::string &strOutput);
|
||||
|
||||
void ExtractExceptionInfosFromThreadsOutput(
|
||||
_In_ const std::string &strOutput,
|
||||
_Out_ std::vector<ExceptionInfo> &exceptionInfos);
|
||||
|
||||
void CollectExceptionDetails(
|
||||
_In_ IDebugClient* pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const std::string &strExceptionObject,
|
||||
_Out_ std::vector<ExceptionDetail> &exceptionDetailsIncludingInnerExceptions);
|
||||
|
||||
std::string GuessBlamedSymbol(
|
||||
_In_ const ExceptionDetail &detail);
|
||||
|
||||
bool IsDueToHangDetected(
|
||||
_In_ const ExceptionDetail &detail);
|
||||
|
||||
bool ParseClrStackFrame(
|
||||
_In_ const std::string &strFrame,
|
||||
_Out_ std::string &strFunc);
|
||||
|
||||
bool ParseKStackFrame(
|
||||
_In_ const std::string &strFrame,
|
||||
_Out_ std::string &strFunc);
|
||||
|
||||
bool IsImmunized(
|
||||
_In_ const std::string &strFrame);
|
||||
|
||||
std::string GetSymbolFromFrame(
|
||||
_In_ const std::string &strFrame);
|
||||
|
||||
bool GetThreadPoolStatus(
|
||||
_In_ const std::string &strThreadPoolOutput,
|
||||
_Out_ ThreadPoolStatus &status);
|
||||
|
||||
int GetNumberOfThreads(
|
||||
_In_ PDEBUG_CLIENT pDebugClient);
|
||||
|
||||
bool ParseDumpObjectOutput(
|
||||
_In_ const std::string &strDumpObject,
|
||||
_Out_ ObjectInfo &objectInfo);
|
||||
|
||||
bool ParseName2EEOutput(
|
||||
_In_ const std::string &strName2EE,
|
||||
_Out_ std::string &strMethodTable);
|
||||
|
||||
bool GetFieldInfo(
|
||||
_In_ const ObjectInfo &objectInfo,
|
||||
_In_ const std::string &strFieldName,
|
||||
_Out_ FieldInfo &fieldInfo);
|
||||
|
||||
bool FindFieldAndGetObjectInfo(
|
||||
_In_ PDEBUG_CLIENT pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const ObjectInfo &objectInfo,
|
||||
_In_ const std::string &strFieldName,
|
||||
_Out_ ObjectInfo &fieldObjectInfo);
|
|
@ -1,143 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "dbgexts.h"
|
||||
#include "Helpers.h"
|
||||
|
||||
static int s_iThresholdToDetectThreadPoolExhausted = 20;
|
||||
static int s_iThresholdToBlameCallStack = 10;
|
||||
|
||||
struct Thread
|
||||
{
|
||||
int m_id;
|
||||
std::vector<std::string> m_frames;
|
||||
};
|
||||
|
||||
struct LessThanFrames
|
||||
{
|
||||
bool operator() (const std::vector<std::string> *pLeft, const std::vector<std::string> *pRight) const
|
||||
{
|
||||
if (pLeft->size() != pRight->size())
|
||||
{
|
||||
return pLeft->size() < pRight->size();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pLeft->size(); ++i)
|
||||
{
|
||||
int result = pLeft->at(i).compare(pRight->at(i));
|
||||
if (result != 0)
|
||||
{
|
||||
return result < 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool IsThreadPoolExhausted(
|
||||
PDEBUG_CLIENT pDebugClient,
|
||||
const std::string &strSOS)
|
||||
{
|
||||
// !ThreadPool
|
||||
std::string strOutput;
|
||||
ThreadPoolStatus threadPoolStatus;
|
||||
if (SUCCEEDED(Execute(pDebugClient, GetFullCommand(strSOS, "ThreadPool"), strOutput))
|
||||
&& GetThreadPoolStatus(strOutput, threadPoolStatus)
|
||||
&& threadPoolStatus.m_iRunningWorkers >= s_iThresholdToDetectThreadPoolExhausted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsClrThreadPoolWorkingThread(const std::string &strOutput)
|
||||
{
|
||||
std::stringstream ss(strOutput);
|
||||
std::string strLine;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
if (strLine.find("clr!ThreadpoolMgr::ExecuteWorkRequest") != std::string::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT OnThreadPoolExhausted(
|
||||
PDEBUG_CLIENT pDebugClient,
|
||||
const std::string &strSOS)
|
||||
{
|
||||
CComQIPtr<IDebugControl> srpControl(pDebugClient);
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
int iThreads = GetNumberOfThreads(pDebugClient);
|
||||
std::string strOutput;
|
||||
std::vector<Thread> threads;
|
||||
threads.reserve(iThreads);
|
||||
for (int i = 1; i < iThreads; ++i) // Skip the main thread
|
||||
{
|
||||
char szCommand[0x20] = { 0 };
|
||||
if (sprintf_s(szCommand, "~%dk", i) > 0
|
||||
&& SUCCEEDED(Execute(pDebugClient, szCommand, strOutput))
|
||||
&& IsClrThreadPoolWorkingThread(strOutput))
|
||||
{
|
||||
std::vector<std::string> frames;
|
||||
std::stringstream ss(strOutput);
|
||||
std::string strLine;
|
||||
std::string strFunc;
|
||||
while (std::getline(ss, strLine))
|
||||
{
|
||||
if (ParseKStackFrame(strLine, strFunc))
|
||||
{
|
||||
frames.push_back(strFunc);
|
||||
}
|
||||
}
|
||||
|
||||
if (!frames.empty())
|
||||
{
|
||||
threads.push_back({ i, std::move(frames) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Thread &thread : threads)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_VERBOSE, "%d\n", thread.m_id);
|
||||
for (const std::string &strFrame : thread.m_frames)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_VERBOSE, "\t%s\n", strFrame.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Group the threads by the frames
|
||||
std::map<const std::vector<std::string> *, int, LessThanFrames> groups;
|
||||
for (const Thread &thread : threads)
|
||||
{
|
||||
const std::vector<std::string> *pKey = &thread.m_frames;
|
||||
++groups[pKey];
|
||||
}
|
||||
|
||||
// Sort by count of the threads
|
||||
std::vector<std::pair<const std::vector<std::string> *, int>> items(groups.size());
|
||||
std::copy(groups.cbegin(), groups.cend(), items.begin());
|
||||
std::sort(items.begin(), items.end(), [](const auto &x, const auto &y) { return x.second > y.second; });
|
||||
|
||||
for (const auto &item : items)
|
||||
{
|
||||
if (item.second >= s_iThresholdToBlameCallStack)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, "Detected thread pool exhausted due to this callstack: (%d threads)\n", item.second);
|
||||
for (const std::string &strFrame : *item.first)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, "\t%s\n", strFrame.c_str());
|
||||
}
|
||||
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
bool IsThreadPoolExhausted(
|
||||
PDEBUG_CLIENT pDebugClient,
|
||||
const std::string &strSOS);
|
||||
|
||||
HRESULT OnThreadPoolExhausted(
|
||||
PDEBUG_CLIENT pDebugClient,
|
||||
const std::string &strSOS);
|
|
@ -1,18 +0,0 @@
|
|||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "stdafx.h"
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved
|
||||
)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "dbgexts.h"
|
||||
#include "Helpers.h"
|
||||
#include "ThreadPoolExhausted.h"
|
||||
#include "DML.h"
|
||||
#include <memory>
|
||||
|
||||
struct StateMachineNode
|
||||
{
|
||||
ObjectInfo m_objectInfo;
|
||||
StateMachineNode *m_pNext;
|
||||
StateMachineNode *m_pPrevious;
|
||||
int m_state;
|
||||
int m_iDepth;
|
||||
std::string m_task;
|
||||
};
|
||||
|
||||
bool GetContinuation(
|
||||
_In_ PDEBUG_CLIENT pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const ObjectInfo &stateMachine,
|
||||
_Out_ std::string &strContinuation);
|
||||
|
||||
HRESULT CALLBACK
|
||||
dumpasync(PDEBUG_CLIENT pDebugClient, PCSTR args)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(args);
|
||||
|
||||
CComQIPtr<IDebugControl> srpControl(pDebugClient);
|
||||
|
||||
std::string strSOS;
|
||||
std::string strOutput;
|
||||
if (!EnsureLoadSOS(pDebugClient, strSOS, strOutput))
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to load SOS.dll: %s\n", strOutput.c_str());
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
if (IsThreadPoolExhausted(pDebugClient, strSOS))
|
||||
{
|
||||
return OnThreadPoolExhausted(pDebugClient, strSOS);
|
||||
}
|
||||
*/
|
||||
|
||||
std::string strAsyncMethodBuilderModules[] = {
|
||||
"mscorlib.dll", // .NET Framework
|
||||
"System.Private.CoreLib.dll", // CoreCLR
|
||||
};
|
||||
|
||||
HRESULT hr;
|
||||
std::string strMethodTable;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hr = Execute(pDebugClient, GetFullCommand(strSOS, "name2ee " + strAsyncMethodBuilderModules[i] + "!System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner"), strOutput);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to run !name2ee: %s\n", strOutput.c_str());
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (ParseName2EEOutput(strOutput, strMethodTable))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strMethodTable.empty())
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to parse the output of !name2ee: %s\n", strOutput.c_str());
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
hr = Execute(pDebugClient, GetFullCommand(strSOS, "dumpheap -short -mt " + strMethodTable), strOutput);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to run !dumpheap: %s\n", strOutput.c_str());
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Extract the inner m_stateMachine.
|
||||
std::stringstream ss(strOutput);
|
||||
std::string strObject;
|
||||
ObjectInfo objectInfo{};
|
||||
FieldInfo fieldInfo{};
|
||||
std::vector<std::unique_ptr<StateMachineNode>> stateMachineNodes;
|
||||
while (std::getline(ss, strObject))
|
||||
{
|
||||
// TODO: avoid calling !do 00000000 which will always error out.
|
||||
hr = Execute(pDebugClient, GetFullCommand(strSOS, "do " + strObject), strOutput);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to run !do: %s\n", strOutput.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ParseDumpObjectOutput(strOutput, objectInfo))
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_ERROR, "Failed to parse the output of !do: %s\n", strOutput.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, objectInfo, "m_stateMachine", objectInfo))
|
||||
{
|
||||
if (GetFieldInfo(objectInfo, "<>1__state", fieldInfo)
|
||||
&& atoi(fieldInfo.m_strValue.c_str()) >= -1)
|
||||
{
|
||||
auto found = std::find_if(stateMachineNodes.begin(), stateMachineNodes.end(), [&](const std::unique_ptr<StateMachineNode> &node)
|
||||
{
|
||||
return node->m_objectInfo.m_strAddress == objectInfo.m_strAddress;
|
||||
});
|
||||
|
||||
// state machine can be reported more than once...
|
||||
if (found != stateMachineNodes.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stateMachineNodes.push_back(std::make_unique<StateMachineNode>());
|
||||
stateMachineNodes.back()->m_objectInfo = std::move(objectInfo);
|
||||
stateMachineNodes.back()->m_state = atoi(fieldInfo.m_strValue.c_str());
|
||||
|
||||
ObjectInfo asyncBuilder{};
|
||||
FieldInfo taskFieldInfo{};
|
||||
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, stateMachineNodes.back()->m_objectInfo, "<>t__builder", asyncBuilder))
|
||||
{
|
||||
ObjectInfo taskObject{};
|
||||
while (!GetFieldInfo(asyncBuilder, "m_task", taskFieldInfo)
|
||||
&& FindFieldAndGetObjectInfo(pDebugClient, strSOS, asyncBuilder, "m_builder", asyncBuilder));
|
||||
|
||||
if (!taskFieldInfo.m_strValue.empty())
|
||||
{
|
||||
stateMachineNodes.back()->m_task = taskFieldInfo.m_strValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link the state machines via the continuation.
|
||||
std::string strContinuation;
|
||||
for (size_t i = 0; i < stateMachineNodes.size(); ++i)
|
||||
{
|
||||
if (GetContinuation(pDebugClient, strSOS, stateMachineNodes[i]->m_objectInfo, strContinuation))
|
||||
{
|
||||
auto found = std::find_if(stateMachineNodes.begin(), stateMachineNodes.end(), [&](const std::unique_ptr<StateMachineNode> &node)
|
||||
{
|
||||
return node->m_objectInfo.m_strAddress == strContinuation;
|
||||
});
|
||||
|
||||
if (found != stateMachineNodes.end())
|
||||
{
|
||||
StateMachineNode *pContinuation = (*found).get();
|
||||
stateMachineNodes[i]->m_pNext = pContinuation;
|
||||
pContinuation->m_pPrevious = (stateMachineNodes[i]).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix Joinable Task chains
|
||||
for (std::unique_ptr<StateMachineNode> &node : stateMachineNodes)
|
||||
{
|
||||
if (node->m_pPrevious == nullptr)
|
||||
{
|
||||
ObjectInfo joinableTask{};
|
||||
FieldInfo wrappedTask{};
|
||||
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, node->m_objectInfo, "<>4__this", joinableTask)
|
||||
&& GetFieldInfo(joinableTask, "wrappedTask", wrappedTask))
|
||||
{
|
||||
std::string task = wrappedTask.m_strValue;
|
||||
auto found = std::find_if(stateMachineNodes.begin(), stateMachineNodes.end(), [&](const std::unique_ptr<StateMachineNode> &node)
|
||||
{
|
||||
return node->m_task == task;
|
||||
});
|
||||
|
||||
if (found != stateMachineNodes.end())
|
||||
{
|
||||
StateMachineNode *pPrevious = (*found).get();
|
||||
node->m_pPrevious = pPrevious;
|
||||
pPrevious->m_pNext = node.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the depth
|
||||
for (std::unique_ptr<StateMachineNode> &node : stateMachineNodes)
|
||||
{
|
||||
node->m_iDepth = 0;
|
||||
if (node->m_pPrevious == nullptr)
|
||||
{
|
||||
for (const StateMachineNode *p = node.get(); p != nullptr; p = p->m_pNext)
|
||||
{
|
||||
node->m_iDepth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by depth DESC
|
||||
std::sort(stateMachineNodes.begin(), stateMachineNodes.end(), [](const std::unique_ptr<StateMachineNode> &x, const std::unique_ptr<StateMachineNode> &y)
|
||||
{
|
||||
return x->m_iDepth > y->m_iDepth;
|
||||
});
|
||||
|
||||
bool fOutputDML = PreferDML(pDebugClient) && AbilityDML(pDebugClient);
|
||||
|
||||
// Dump the state machines
|
||||
for (const std::unique_ptr<StateMachineNode> &node : stateMachineNodes)
|
||||
{
|
||||
if (node->m_iDepth > 0)
|
||||
{
|
||||
std::string strIndent;
|
||||
for (const StateMachineNode *p = node.get(); p != nullptr; p = p->m_pNext)
|
||||
{
|
||||
if (fOutputDML)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, strIndent.c_str());
|
||||
srpControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, "<link cmd=\"!do %s\">%s</link>",
|
||||
p->m_objectInfo.m_strAddress.c_str(),
|
||||
p->m_objectInfo.m_strAddress.c_str());
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, " <%d> %s\n", p->m_state, p->m_objectInfo.m_strType.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, "%s%s <%d> %s\n", strIndent.c_str(), p->m_objectInfo.m_strAddress.c_str(), p->m_state, p->m_objectInfo.m_strType.c_str());
|
||||
}
|
||||
|
||||
strIndent.append(".");
|
||||
}
|
||||
|
||||
if (node->m_iDepth > 1)
|
||||
{
|
||||
srpControl->Output(DEBUG_OUTPUT_NORMAL, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool GetContinuationFromTarget(
|
||||
_In_ PDEBUG_CLIENT pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const ObjectInfo &targetObject,
|
||||
_Out_ std::string &strContinuation)
|
||||
{
|
||||
FieldInfo stateMachineField{};
|
||||
if (GetFieldInfo(targetObject, "m_stateMachine", stateMachineField))
|
||||
{
|
||||
strContinuation = stateMachineField.m_strValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectInfo nextContinuationObject{};
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, targetObject, "m_continuation", nextContinuationObject))
|
||||
{
|
||||
ObjectInfo targetObject{};
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, nextContinuationObject, "_target", targetObject)
|
||||
&& GetContinuationFromTarget(pDebugClient, strSOS, targetObject, strContinuation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetContinuation(
|
||||
_In_ PDEBUG_CLIENT pDebugClient,
|
||||
_In_ const std::string &strSOS,
|
||||
_In_ const ObjectInfo &stateMachine,
|
||||
_Out_ std::string &strContinuation)
|
||||
{
|
||||
ObjectInfo builderObject{};
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, stateMachine, "<>t__builder", builderObject))
|
||||
{
|
||||
ObjectInfo taskObject{};
|
||||
while (!FindFieldAndGetObjectInfo(pDebugClient, strSOS, builderObject, "m_task", taskObject)
|
||||
&& FindFieldAndGetObjectInfo(pDebugClient, strSOS, builderObject, "m_builder", builderObject));
|
||||
|
||||
if (!taskObject.m_strType.empty())
|
||||
{
|
||||
ObjectInfo continuationObject{};
|
||||
ObjectInfo actionObject{};
|
||||
ObjectInfo targetObject{};
|
||||
while (FindFieldAndGetObjectInfo(pDebugClient, strSOS, taskObject, "m_continuationObject", continuationObject))
|
||||
{
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, continuationObject, "m_action", actionObject)
|
||||
&& FindFieldAndGetObjectInfo(pDebugClient, strSOS, actionObject, "_target", targetObject))
|
||||
{
|
||||
if (GetContinuationFromTarget(pDebugClient, strSOS, targetObject, strContinuation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, continuationObject, "_target", targetObject))
|
||||
{
|
||||
if (GetContinuationFromTarget(pDebugClient, strSOS, targetObject, strContinuation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectInfo continuationTaskObject{};
|
||||
ObjectInfo stateObject{};
|
||||
if (FindFieldAndGetObjectInfo(pDebugClient, strSOS, continuationObject, "m_task", continuationTaskObject)
|
||||
&& FindFieldAndGetObjectInfo(pDebugClient, strSOS, continuationTaskObject, "m_stateObject", stateObject)
|
||||
&& FindFieldAndGetObjectInfo(pDebugClient, strSOS, stateObject, "_target", targetObject))
|
||||
{
|
||||
if (GetContinuationFromTarget(pDebugClient, strSOS, targetObject, strContinuation))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
taskObject = continuationObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "dbgexts.h"
|
||||
#include "outputcallbacks.h"
|
||||
|
||||
#define MAX_OUTPUTCALLBACKS_BUFFER 0x1000000 // 1Mb
|
||||
#define MAX_OUTPUTCALLBACKS_LENGTH 0x0FFFFFF // 1Mb - 1
|
||||
|
||||
STDMETHODIMP COutputCallbacks::QueryInterface(__in REFIID InterfaceId, __out PVOID* Interface)
|
||||
{
|
||||
*Interface = NULL;
|
||||
if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) || IsEqualIID(InterfaceId, __uuidof(IDebugOutputCallbacks)))
|
||||
{
|
||||
*Interface = (IDebugOutputCallbacks *)this;
|
||||
InterlockedIncrement(&m_ref);
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) COutputCallbacks::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_ref);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) COutputCallbacks::Release()
|
||||
{
|
||||
if (InterlockedDecrement(&m_ref) == 0)
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return m_ref;
|
||||
}
|
||||
|
||||
STDMETHODIMP COutputCallbacks::Output(__in ULONG Mask, __in PCSTR Text)
|
||||
{
|
||||
if ((Mask & DEBUG_OUTPUT_NORMAL) == DEBUG_OUTPUT_NORMAL)
|
||||
{
|
||||
if (m_pBufferNormal == NULL)
|
||||
{
|
||||
m_nBufferNormal = 0;
|
||||
m_pBufferNormal = (PCHAR)malloc(sizeof(CHAR)*(MAX_OUTPUTCALLBACKS_BUFFER));
|
||||
if (m_pBufferNormal == NULL) return E_OUTOFMEMORY;
|
||||
m_pBufferNormal[0] = '\0';
|
||||
m_pBufferNormal[MAX_OUTPUTCALLBACKS_LENGTH] = '\0';
|
||||
}
|
||||
size_t len = strlen(Text);
|
||||
if (len > (MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferNormal))
|
||||
{
|
||||
len = MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferNormal;
|
||||
}
|
||||
if (len > 0)
|
||||
{
|
||||
memcpy(&m_pBufferNormal[m_nBufferNormal], Text, len);
|
||||
m_nBufferNormal += len;
|
||||
m_pBufferNormal[m_nBufferNormal] = '\0';
|
||||
}
|
||||
}
|
||||
if ((Mask & DEBUG_OUTPUT_ERROR) == DEBUG_OUTPUT_ERROR)
|
||||
{
|
||||
if (m_pBufferError == NULL)
|
||||
{
|
||||
m_nBufferError = 0;
|
||||
m_pBufferError = (PCHAR)malloc(sizeof(CHAR)*(MAX_OUTPUTCALLBACKS_BUFFER));
|
||||
if (m_pBufferError == NULL) return E_OUTOFMEMORY;
|
||||
m_pBufferError[0] = '\0';
|
||||
m_pBufferError[MAX_OUTPUTCALLBACKS_LENGTH] = '\0';
|
||||
}
|
||||
size_t len = strlen(Text);
|
||||
if (len >= (MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferError))
|
||||
{
|
||||
len = MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferError;
|
||||
}
|
||||
if (len > 0)
|
||||
{
|
||||
memcpy(&m_pBufferError[m_nBufferError], Text, len);
|
||||
m_nBufferError += len;
|
||||
m_pBufferError[m_nBufferError] = '\0';
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void COutputCallbacks::Clear()
|
||||
{
|
||||
if (m_pBufferNormal)
|
||||
{
|
||||
free(m_pBufferNormal);
|
||||
m_pBufferNormal = NULL;
|
||||
m_nBufferNormal = 0;
|
||||
}
|
||||
if (m_pBufferError)
|
||||
{
|
||||
free(m_pBufferError);
|
||||
m_pBufferError = NULL;
|
||||
m_nBufferError = 0;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
#ifndef __OUTPUTCALLBACKS_H__
|
||||
#define __OUTPUTCALLBACKS_H__
|
||||
|
||||
#include "dbgexts.h"
|
||||
|
||||
class COutputCallbacks : public IDebugOutputCallbacks
|
||||
{
|
||||
private:
|
||||
long m_ref;
|
||||
PCHAR m_pBufferNormal;
|
||||
size_t m_nBufferNormal;
|
||||
PCHAR m_pBufferError;
|
||||
size_t m_nBufferError;
|
||||
|
||||
public:
|
||||
COutputCallbacks()
|
||||
{
|
||||
m_ref = 1;
|
||||
m_pBufferNormal = NULL;
|
||||
m_nBufferNormal = 0;
|
||||
m_pBufferError = NULL;
|
||||
m_nBufferError = 0;
|
||||
}
|
||||
|
||||
~COutputCallbacks()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
STDMETHOD(QueryInterface)(__in REFIID InterfaceId, __out PVOID* Interface);
|
||||
STDMETHOD_(ULONG, AddRef)();
|
||||
STDMETHOD_(ULONG, Release)();
|
||||
|
||||
// IDebugOutputCallbacks
|
||||
STDMETHOD(Output)(__in ULONG Mask, __in PCSTR Text);
|
||||
|
||||
// Helpers
|
||||
ULONG SupportedMask() { return DEBUG_OUTPUT_NORMAL | DEBUG_OUTPUT_ERROR; }
|
||||
PCHAR BufferNormal() { return m_pBufferNormal; }
|
||||
PCHAR BufferError() { return m_pBufferError; }
|
||||
void Clear();
|
||||
};
|
||||
|
||||
#endif // __OUTPUTCALLBACKS_H__
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MicroBuild" version="2.0.54" targetFramework="native" developmentDependency="true" />
|
||||
<package id="MicroBuild.Core" version="0.3.0" targetFramework="native" developmentDependency="true" />
|
||||
<package id="MicroBuild.VisualStudio" version="2.0.54" targetFramework="native" developmentDependency="true" />
|
||||
<package id="Nerdbank.GitVersioning" version="2.1.23" targetFramework="native" developmentDependency="true" />
|
||||
</packages>
|
|
@ -1,8 +0,0 @@
|
|||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// WinDbgExt1.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// TODO: reference any additional headers you need in STDAFX.H
|
||||
// and not in this file
|
|
@ -1,26 +0,0 @@
|
|||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "targetver.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
// Windows Header Files:
|
||||
#include <windows.h>
|
||||
|
||||
// TODO: reference additional headers your program requires here
|
||||
#include <DbgEng.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <atlstr.h>
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Including SDKDDKVer.h defines the highest available Windows platform.
|
||||
|
||||
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
|
||||
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
|
||||
|
||||
#include <SDKDDKVer.h>
|
|
@ -27,10 +27,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Thre
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsolatedTestHost", "IsolatedTestHost\IsolatedTestHost.csproj", "{BA4643D8-E6B2-4DED-882F-4827F3AB6AB0}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AsyncDebugTools", "AsyncDebugTools\AsyncDebugTools.vcxproj", "{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Threading.Analyzers.CodeFixes", "Microsoft.VisualStudio.Threading.Analyzers.CodeFixes\Microsoft.VisualStudio.Threading.Analyzers.CodeFixes.csproj", "{3BDB8F46-A39C-422B-8B0E-89E98B83073F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SosThreadingTools", "SosThreadingTools\SosThreadingTools.csproj", "{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -89,16 +89,6 @@ Global
|
|||
{BA4643D8-E6B2-4DED-882F-4827F3AB6AB0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA4643D8-E6B2-4DED-882F-4827F3AB6AB0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BA4643D8-E6B2-4DED-882F-4827F3AB6AB0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Debug|x64.Build.0 = Debug|x64
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Debug|x86.Build.0 = Debug|Win32
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Release|x64.ActiveCfg = Release|x64
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Release|x64.Build.0 = Release|x64
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Release|x86.ActiveCfg = Release|Win32
|
||||
{CF348EC1-1E7D-47DE-8431-8C61D5CAF924}.Release|x86.Build.0 = Release|Win32
|
||||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
@ -107,6 +97,16 @@ Global
|
|||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3BDB8F46-A39C-422B-8B0E-89E98B83073F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Debug|x64.Build.0 = Debug|x64
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Debug|x86.Build.0 = Debug|x86
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Release|x64.ActiveCfg = Release|x64
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Release|x64.Build.0 = Release|x64
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Release|x86.ActiveCfg = Release|x86
|
||||
{7177DEEE-D14D-4A4A-BF6E-8B0CDC26B624}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static class Commands
|
||||
{
|
||||
private const string DumpAsyncCommand = "dumpasync";
|
||||
|
||||
private static Dictionary<string, ICommandHandler> commandHandlers;
|
||||
|
||||
static Commands()
|
||||
{
|
||||
commandHandlers = new Dictionary<string, ICommandHandler>(StringComparer.OrdinalIgnoreCase);
|
||||
commandHandlers.Add("dumpasync", new DumpAsyncCommand());
|
||||
}
|
||||
|
||||
[DllExport(DumpAsyncCommand)]
|
||||
internal static void DumpAsync(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
|
||||
{
|
||||
ExecuteCommand(client, DumpAsyncCommand, args);
|
||||
}
|
||||
|
||||
private static void ExecuteCommand(IntPtr client, string command, [MarshalAs(UnmanagedType.LPStr)] string args)
|
||||
{
|
||||
ICommandHandler handler;
|
||||
if (!commandHandlers.TryGetValue(command, out handler))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebuggerContext context = DebuggerContext.GetDebuggerContext(client);
|
||||
if (context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handler.Execute(context, args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Diagnostics.Runtime;
|
||||
using Microsoft.Diagnostics.Runtime.Interop;
|
||||
|
||||
internal class DebuggerContext
|
||||
{
|
||||
private const string ClrMD = "Microsoft.Diagnostics.Runtime";
|
||||
|
||||
/// <summary>
|
||||
/// The singleton instance used in a debug session.
|
||||
/// </summary>
|
||||
private static DebuggerContext instance;
|
||||
|
||||
static DebuggerContext()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||
}
|
||||
|
||||
private DebuggerContext(IDebugClient debugClient, DataTarget dataTarget, ClrRuntime runtime, DebuggerOutput output)
|
||||
{
|
||||
this.DebugClient = debugClient;
|
||||
this.DataTarget = dataTarget;
|
||||
this.Runtime = runtime;
|
||||
this.Output = output;
|
||||
}
|
||||
|
||||
internal ClrRuntime Runtime { get; }
|
||||
|
||||
internal DebuggerOutput Output { get; }
|
||||
|
||||
internal IDebugClient DebugClient { get; }
|
||||
|
||||
internal IDebugControl DebugControl => (IDebugControl)this.DebugClient;
|
||||
|
||||
private DataTarget DataTarget { get; }
|
||||
|
||||
internal static DebuggerContext GetDebuggerContext(IntPtr ptrClient)
|
||||
{
|
||||
// On our first call to the API:
|
||||
// 1. Store a copy of IDebugClient in DebugClient.
|
||||
// 2. Replace Console's output stream to be the debugger window.
|
||||
// 3. Create an instance of DataTarget using the IDebugClient.
|
||||
if (instance == null)
|
||||
{
|
||||
object client = Marshal.GetUniqueObjectForIUnknown(ptrClient);
|
||||
var debugClient = (IDebugClient)client;
|
||||
|
||||
var output = new DebuggerOutput(debugClient);
|
||||
|
||||
var dataTarget = DataTarget.CreateFromDebuggerInterface(debugClient);
|
||||
|
||||
ClrRuntime runtime = null;
|
||||
|
||||
// If our ClrRuntime instance is null, it means that this is our first call, or
|
||||
// that the dac wasn't loaded on any previous call. Find the dac loaded in the
|
||||
// process (the user must use .cordll), then construct our runtime from it.
|
||||
|
||||
// Just find a module named mscordacwks and assume it's the one the user
|
||||
// loaded into windbg.
|
||||
Process p = Process.GetCurrentProcess();
|
||||
foreach (ProcessModule module in p.Modules)
|
||||
{
|
||||
if (module.FileName.ToLower().Contains("mscordacwks"))
|
||||
{
|
||||
// TODO: This does not support side-by-side CLRs.
|
||||
runtime = dataTarget.ClrVersions.Single().CreateRuntime(module.FileName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the user didn't run .cordll.
|
||||
if (runtime == null)
|
||||
{
|
||||
output.WriteLine("Mscordacwks.dll not loaded into the debugger.");
|
||||
output.WriteLine("Run .cordll to load the dac before running this command.");
|
||||
}
|
||||
|
||||
if (runtime != null)
|
||||
{
|
||||
instance = new DebuggerContext(debugClient, dataTarget, runtime, output);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we already had a runtime, flush it for this use. This is ONLY required
|
||||
// for a live process or iDNA trace. If you use the IDebug* apis to detect
|
||||
// that we are debugging a crash dump you may skip this call for better perf.
|
||||
// instance.Runtime.Flush();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
|
||||
{
|
||||
if (args.Name.Contains(ClrMD))
|
||||
{
|
||||
string codebase = Assembly.GetExecutingAssembly().CodeBase;
|
||||
|
||||
if (codebase.StartsWith("file://"))
|
||||
{
|
||||
codebase = codebase.Substring(8).Replace('/', '\\');
|
||||
}
|
||||
|
||||
string directory = Path.GetDirectoryName(codebase);
|
||||
string path = Path.Combine(directory, ClrMD) + ".dll";
|
||||
return Assembly.LoadFile(path);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using Microsoft.Diagnostics.Runtime.Interop;
|
||||
|
||||
internal class DebuggerOutput
|
||||
{
|
||||
private IDebugClient client;
|
||||
private IDebugControl control;
|
||||
|
||||
internal DebuggerOutput(IDebugClient client)
|
||||
{
|
||||
this.client = client;
|
||||
this.control = (IDebugControl)client;
|
||||
}
|
||||
|
||||
internal void WriteString(string message)
|
||||
{
|
||||
this.control.ControlledOutput(DEBUG_OUTCTL.ALL_CLIENTS, DEBUG_OUTPUT.NORMAL, message);
|
||||
}
|
||||
|
||||
internal void WriteLine(string message)
|
||||
{
|
||||
this.WriteString(message + "\n");
|
||||
}
|
||||
|
||||
internal void WriteObjectAddress(ulong address)
|
||||
{
|
||||
this.WriteDml($"<link cmd=\"!do {address:x}\">{address:x8}</link>");
|
||||
}
|
||||
|
||||
internal void WriteThreadLink(uint threadId)
|
||||
{
|
||||
this.WriteDml($"<link cmd=\"~~[{threadId:x}]kp\">Thread TID:[{threadId:x}]</link>");
|
||||
}
|
||||
|
||||
internal void WriteMethodInfo(string name, ulong address)
|
||||
{
|
||||
this.WriteDml($"<link cmd=\".open -a 0x{address:x}\" alt=\"Try to open source, you might need click more than once to get it work.\">{name}</link>");
|
||||
}
|
||||
|
||||
internal void WriteStringWithLink(string message, string linkCommand)
|
||||
{
|
||||
this.WriteDml($"<link cmd=\"{linkCommand}\">{message}</link>");
|
||||
}
|
||||
|
||||
private void WriteDml(string dml)
|
||||
{
|
||||
this.control.ControlledOutput(DEBUG_OUTCTL.AMBIENT_DML, DEBUG_OUTPUT.NORMAL, dml);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,495 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Diagnostics.Runtime;
|
||||
using Microsoft.Diagnostics.Runtime.Interop;
|
||||
|
||||
internal class DumpAsyncCommand : ICommandHandler
|
||||
{
|
||||
public void Execute(DebuggerContext context, string args)
|
||||
{
|
||||
ClrHeap heap = context.Runtime.Heap;
|
||||
|
||||
var allStateMachines = new List<AsyncStateMachine>();
|
||||
var knownStateMachines = new Dictionary<ulong, AsyncStateMachine>();
|
||||
|
||||
GetAllStateMachines(context, heap, allStateMachines, knownStateMachines);
|
||||
|
||||
ChainStateMachinesBasedOnTaskContinuations(context, knownStateMachines);
|
||||
ChainStateMachinesBasedOnJointableTasks(context, allStateMachines);
|
||||
MarkThreadingBlockTasks(context, allStateMachines);
|
||||
MarkUIThreadDependingTasks(context, allStateMachines);
|
||||
FixBrokenDependencies(allStateMachines);
|
||||
|
||||
PrintOutStateMachines(allStateMachines, context.Output);
|
||||
this.LoadCodePages(context, allStateMachines);
|
||||
}
|
||||
|
||||
private static void GetAllStateMachines(DebuggerContext context, ClrHeap heap, List<AsyncStateMachine> allStateMachines, Dictionary<ulong, AsyncStateMachine> knownStateMachines)
|
||||
{
|
||||
foreach (var obj in heap.GetObjectsOfType("System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var stateMachine = obj.GetObjectField("m_stateMachine");
|
||||
if (!knownStateMachines.ContainsKey(stateMachine.Address))
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = stateMachine.GetField<int>("<>1__state");
|
||||
if (state >= -1)
|
||||
{
|
||||
ClrObject taskField = default(ClrObject);
|
||||
var asyncBuilder = stateMachine.TryGetValueClassField("<>t__builder");
|
||||
if (asyncBuilder.HasValue)
|
||||
{
|
||||
while (asyncBuilder.HasValue)
|
||||
{
|
||||
taskField = asyncBuilder.TryGetObjectField("m_task");
|
||||
if (!taskField.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
asyncBuilder = asyncBuilder.TryGetValueClassField("m_builder");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// CLR debugger may not be able to access t__builder, when NGEN assemblies are being used, and the type of the field could be lost.
|
||||
// Our workaround is to pick up the first Task object referenced by the state machine, which seems to be correct.
|
||||
// That function works with the raw data structure (like how GC scans the object, so it doesn't depend on symbols.
|
||||
foreach (ClrObject referencedObject in stateMachine.EnumerateObjectReferences(true))
|
||||
{
|
||||
if (!referencedObject.IsNull && referencedObject.Type != null)
|
||||
{
|
||||
if (string.Equals(referencedObject.Type.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal) || string.Equals(referencedObject.Type.BaseType?.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal))
|
||||
{
|
||||
taskField = referencedObject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var asyncState = new AsyncStateMachine(state, stateMachine, taskField);
|
||||
allStateMachines.Add(asyncState);
|
||||
knownStateMachines.Add(stateMachine.Address, asyncState);
|
||||
|
||||
if (stateMachine.Type != null)
|
||||
{
|
||||
foreach (var method in stateMachine.Type.Methods)
|
||||
{
|
||||
if (method.Name == "MoveNext" && method.NativeCode != ulong.MaxValue)
|
||||
{
|
||||
asyncState.CodeAddress = method.NativeCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Output.WriteLine($"Fail to process state machine {stateMachine.Address:x} Type:'{stateMachine.Type.Name}' Module:'{stateMachine.Type.Module.Name}' Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Output.WriteLine($"Fail to process AsyncStateMachine Runner {obj.Address:x} Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChainStateMachinesBasedOnTaskContinuations(DebuggerContext context, Dictionary<ulong, AsyncStateMachine> knownStateMachines)
|
||||
{
|
||||
foreach (var stateMachine in knownStateMachines.Values)
|
||||
{
|
||||
var taskObject = stateMachine.Task;
|
||||
try
|
||||
{
|
||||
while (!taskObject.IsNull)
|
||||
{
|
||||
// 3 cases in order to get the _target:
|
||||
// 1. m_continuationObject.m_action._target
|
||||
// 2. m_continuationObject._target
|
||||
// 3. m_continuationObject.m_task.m_stateObject._target
|
||||
var continuationObject = taskObject.TryGetObjectField("m_continuationObject");
|
||||
if (continuationObject.IsNull)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ChainStateMachineBasedOnTaskContinuations(knownStateMachines, stateMachine, continuationObject);
|
||||
|
||||
taskObject = continuationObject;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Output.WriteLine($"Fail to fix continuation of state {stateMachine.StateMachine.Address:x} Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChainStateMachineBasedOnTaskContinuations(Dictionary<ulong, AsyncStateMachine> knownStateMachines, AsyncStateMachine stateMachine, ClrObject continuationObject)
|
||||
{
|
||||
var continuationAction = continuationObject.TryGetObjectField("m_action");
|
||||
|
||||
// case 1
|
||||
var continuationTarget = continuationAction.TryGetObjectField("_target");
|
||||
if (continuationTarget.IsNull)
|
||||
{
|
||||
// case 2
|
||||
continuationTarget = continuationObject.TryGetObjectField("_target");
|
||||
if (continuationTarget.IsNull)
|
||||
{
|
||||
// case 3
|
||||
continuationTarget = continuationObject.TryGetObjectField("m_task").TryGetObjectField("m_stateObject").TryGetObjectField("_target");
|
||||
}
|
||||
}
|
||||
|
||||
while (!continuationTarget.IsNull)
|
||||
{
|
||||
// now get the continuation from the target
|
||||
var continuationTargetStateMachine = continuationTarget.TryGetObjectField("m_stateMachine");
|
||||
if (!continuationTargetStateMachine.IsNull)
|
||||
{
|
||||
AsyncStateMachine targetAsyncState;
|
||||
if (knownStateMachines.TryGetValue(continuationTargetStateMachine.Address, out targetAsyncState) && targetAsyncState != stateMachine)
|
||||
{
|
||||
stateMachine.Next = targetAsyncState;
|
||||
stateMachine.DependentCount++;
|
||||
targetAsyncState.Previous = stateMachine;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextContinuation = continuationTarget.TryGetObjectField("m_continuation");
|
||||
continuationTarget = nextContinuation.TryGetObjectField("_target");
|
||||
}
|
||||
}
|
||||
|
||||
var items = continuationObject.TryGetObjectField("_items");
|
||||
if (!items.IsNull && items.IsArray && items.ContainsPointers)
|
||||
{
|
||||
foreach (var promise in items.EnumerateObjectReferences(true))
|
||||
{
|
||||
if (!promise.IsNull)
|
||||
{
|
||||
var innerContinuationObject = promise.TryGetObjectField("m_continuationObject");
|
||||
if (!innerContinuationObject.IsNull)
|
||||
{
|
||||
ChainStateMachineBasedOnTaskContinuations(knownStateMachines, stateMachine, innerContinuationObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChainStateMachineBasedOnTaskContinuations(knownStateMachines, stateMachine, promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChainStateMachinesBasedOnJointableTasks(DebuggerContext context, List<AsyncStateMachine> allStateMachines)
|
||||
{
|
||||
foreach (var stateMachine in allStateMachines)
|
||||
{
|
||||
if (stateMachine.Previous == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var joinableTask = stateMachine.StateMachine.TryGetObjectField("<>4__this");
|
||||
var wrappedTask = joinableTask.TryGetObjectField("wrappedTask");
|
||||
if (!wrappedTask.IsNull)
|
||||
{
|
||||
var previousStateMachine = allStateMachines
|
||||
.FirstOrDefault(s => s.Task.Address == wrappedTask.Address);
|
||||
if (previousStateMachine != null && stateMachine != previousStateMachine)
|
||||
{
|
||||
stateMachine.Previous = previousStateMachine;
|
||||
previousStateMachine.Next = stateMachine;
|
||||
previousStateMachine.DependentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Output.WriteLine($"Fail to fix continuation of state {stateMachine.StateMachine.Address:x} Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkThreadingBlockTasks(DebuggerContext context, List<AsyncStateMachine> allStateMachines)
|
||||
{
|
||||
foreach (var thread in context.Runtime.Threads)
|
||||
{
|
||||
var stackFrame = thread.EnumerateStackTrace().Take(50).FirstOrDefault(
|
||||
f => string.Equals(f.Method?.Name, "CompleteOnCurrentThread", StringComparison.Ordinal) &&
|
||||
string.Equals(f.Method.Type?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal));
|
||||
|
||||
if (stackFrame != null)
|
||||
{
|
||||
var visitedObjects = new HashSet<ulong>();
|
||||
foreach (var stackObject in thread.EnumerateStackObjects())
|
||||
{
|
||||
if (string.Equals(stackObject.Type?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal) ||
|
||||
string.Equals(stackObject.Type?.BaseType?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal))
|
||||
{
|
||||
if (visitedObjects.Add(stackObject.Object))
|
||||
{
|
||||
var joinableTaskObject = new ClrObject(stackObject.Object, stackObject.Type);
|
||||
int state = joinableTaskObject.GetField<int>("state");
|
||||
if ((state & 0x10) == 0x10)
|
||||
{
|
||||
// This flag indicates the JTF is blocking the thread
|
||||
var wrappedTask = joinableTaskObject.TryGetObjectField("wrappedTask");
|
||||
if (!wrappedTask.IsNull)
|
||||
{
|
||||
var blockingStateMachine = allStateMachines
|
||||
.FirstOrDefault(s => s.Task.Address == wrappedTask.Address);
|
||||
if (blockingStateMachine != null)
|
||||
{
|
||||
blockingStateMachine.BlockedThread = thread.OSThreadId;
|
||||
blockingStateMachine.BlockedJoinableTask = joinableTaskObject;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkUIThreadDependingTasks(DebuggerContext context, List<AsyncStateMachine> allStateMachines)
|
||||
{
|
||||
foreach (var stateMachine in allStateMachines)
|
||||
{
|
||||
if (stateMachine.Previous == null && stateMachine.State >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var awaitField = stateMachine.StateMachine.Type.GetFieldByName($"<>u__{stateMachine.State + 1}");
|
||||
if (awaitField != null && awaitField.IsValueClass && string.Equals(awaitField.Type?.Name, "Microsoft.VisualStudio.Threading.JoinableTaskFactory+MainThreadAwaiter"))
|
||||
{
|
||||
var awaitObject = stateMachine.StateMachine.TryGetValueClassField($"<>u__{stateMachine.State + 1}");
|
||||
if (awaitObject != null)
|
||||
{
|
||||
stateMachine.SwitchToMainThreadTask = awaitObject.TryGetObjectField("job");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void FixBrokenDependencies(List<AsyncStateMachine> allStateMachines)
|
||||
{
|
||||
foreach (var stateMachine in allStateMachines)
|
||||
{
|
||||
if (stateMachine.Previous != null && stateMachine.Previous.Next != stateMachine)
|
||||
{
|
||||
// If the previous task actually has two continuations, we end up in a one way dependencies chain, we need fix it in the future.
|
||||
stateMachine.AlterPrevious = stateMachine.Previous;
|
||||
stateMachine.Previous = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintOutStateMachines(List<AsyncStateMachine> allStateMachines, DebuggerOutput output)
|
||||
{
|
||||
int loopMark = -1;
|
||||
foreach (var stateMachine in allStateMachines)
|
||||
{
|
||||
int depth = 0;
|
||||
if (stateMachine.Previous == null)
|
||||
{
|
||||
var p = stateMachine;
|
||||
while (p != null)
|
||||
{
|
||||
depth++;
|
||||
if (p.Depth == loopMark)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
p.Depth = loopMark;
|
||||
p = p.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateMachine.AlterPrevious != null)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
|
||||
stateMachine.Depth = depth;
|
||||
loopMark--;
|
||||
}
|
||||
|
||||
var printedMachines = new HashSet<AsyncStateMachine>();
|
||||
|
||||
foreach (var node in allStateMachines
|
||||
.Where(m => m.Depth > 0)
|
||||
.OrderByDescending(m => m.Depth)
|
||||
.ThenByDescending(m => m.SwitchToMainThreadTask.Address))
|
||||
{
|
||||
bool multipleLineBlock = PrintAsyncStateMachineChain(output, node, printedMachines);
|
||||
|
||||
if (multipleLineBlock)
|
||||
{
|
||||
output.WriteLine(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
// Print nodes which we didn't print because of loops.
|
||||
if (allStateMachines.Count > printedMachines.Count)
|
||||
{
|
||||
output.WriteLine("States form dependencies loop -- could be an error caused by the analysis tool");
|
||||
foreach (var node in allStateMachines)
|
||||
{
|
||||
if (!printedMachines.Contains(node))
|
||||
{
|
||||
PrintAsyncStateMachineChain(output, node, printedMachines);
|
||||
output.WriteLine(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PrintAsyncStateMachineChain(DebuggerOutput output, AsyncStateMachine node, HashSet<AsyncStateMachine> printedMachines)
|
||||
{
|
||||
int nLevel = 0;
|
||||
bool multipleLineBlock = false;
|
||||
|
||||
var loopDetection = new HashSet<AsyncStateMachine>();
|
||||
for (var p = node; p != null; p = p.Next)
|
||||
{
|
||||
printedMachines.Add(p);
|
||||
|
||||
if (nLevel > 0)
|
||||
{
|
||||
output.WriteString("..");
|
||||
multipleLineBlock = true;
|
||||
}
|
||||
else if (p.AlterPrevious != null)
|
||||
{
|
||||
output.WriteObjectAddress(p.AlterPrevious.StateMachine.Address);
|
||||
output.WriteString($" <{p.AlterPrevious.State}> * {p.AlterPrevious.StateMachine.Type.Name} @ ");
|
||||
output.WriteMethodInfo($"{p.AlterPrevious.CodeAddress:x}", p.AlterPrevious.CodeAddress);
|
||||
output.WriteLine(string.Empty);
|
||||
output.WriteString("..");
|
||||
multipleLineBlock = true;
|
||||
}
|
||||
else if (!p.SwitchToMainThreadTask.IsNull)
|
||||
{
|
||||
output.WriteObjectAddress(p.SwitchToMainThreadTask.Address);
|
||||
output.WriteLine(".SwitchToMainThreadAsync");
|
||||
output.WriteString("..");
|
||||
multipleLineBlock = true;
|
||||
}
|
||||
|
||||
output.WriteObjectAddress(p.StateMachine.Address);
|
||||
string doubleDependentTaskMark = p.DependentCount > 1 ? " * " : " ";
|
||||
output.WriteString($" <{p.State}>{doubleDependentTaskMark}{p.StateMachine.Type.Name} @ ");
|
||||
output.WriteMethodInfo($"{p.CodeAddress:x}", p.CodeAddress);
|
||||
output.WriteLine(string.Empty);
|
||||
|
||||
if (!loopDetection.Add(p))
|
||||
{
|
||||
output.WriteLine("!!Loop task dependencies");
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.Next == null && p.BlockedThread.HasValue)
|
||||
{
|
||||
output.WriteString("-- ");
|
||||
output.WriteThreadLink(p.BlockedThread.Value);
|
||||
output.WriteString(" - JoinableTask: ");
|
||||
output.WriteObjectAddress(p.BlockedJoinableTask.Address);
|
||||
|
||||
int state = p.BlockedJoinableTask.GetField<int>("state");
|
||||
if ((state & 0x20) == 0x20)
|
||||
{
|
||||
output.WriteLine(" SynchronouslyBlockingMainThread");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
multipleLineBlock = true;
|
||||
}
|
||||
|
||||
nLevel++;
|
||||
}
|
||||
|
||||
return multipleLineBlock;
|
||||
}
|
||||
|
||||
private void LoadCodePages(DebuggerContext context, List<AsyncStateMachine> allStateMachines)
|
||||
{
|
||||
var loadedAddresses = new HashSet<ulong>();
|
||||
foreach (var stateMachine in allStateMachines)
|
||||
{
|
||||
ulong codeAddress = stateMachine.CodeAddress;
|
||||
if (loadedAddresses.Add(codeAddress))
|
||||
{
|
||||
context.DebugControl.Execute(DEBUG_OUTCTL.IGNORE, $"u {codeAddress} {codeAddress}", DEBUG_EXECUTE.NOT_LOGGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncStateMachine
|
||||
{
|
||||
public AsyncStateMachine(int state, ClrObject stateMachine, ClrObject task)
|
||||
{
|
||||
this.State = state;
|
||||
this.StateMachine = stateMachine;
|
||||
this.Task = task;
|
||||
}
|
||||
|
||||
public int State { get; } // -1 == currently running, 0 = still waiting on first await, 2= before the 3rd await
|
||||
|
||||
public ClrObject StateMachine { get; }
|
||||
|
||||
public ClrObject Task { get; }
|
||||
|
||||
public AsyncStateMachine Previous { get; set; }
|
||||
|
||||
public AsyncStateMachine Next { get; set; }
|
||||
|
||||
public int DependentCount { get; set; }
|
||||
|
||||
public int Depth { get; set; }
|
||||
|
||||
public uint? BlockedThread { get; set; }
|
||||
|
||||
public ClrObject BlockedJoinableTask { get; set; }
|
||||
|
||||
public ClrObject SwitchToMainThreadTask { get; set; }
|
||||
|
||||
public AsyncStateMachine AlterPrevious { get; set; }
|
||||
|
||||
public ulong CodeAddress { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"state = {this.State} Depth {this.Depth} StateMachine = {this.StateMachine} Task = {this.Task}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static class ExtensionContext
|
||||
{
|
||||
[DllExport(nameof(DebugExtensionInitialize))]
|
||||
internal static int DebugExtensionInitialize(ref uint version, ref uint flags)
|
||||
{
|
||||
// Set the extension version to 1, which expects exports with this signature:
|
||||
// void _stdcall function(IDebugClient *client, const char *args)
|
||||
version = DEBUG_EXTENSION_VERSION(1, 0);
|
||||
flags = 0;
|
||||
|
||||
AppDomain currentDomain = AppDomain.CurrentDomain;
|
||||
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static uint DEBUG_EXTENSION_VERSION(uint major, uint minor)
|
||||
{
|
||||
return ((major & 0xffff) << 16) | (minor & 0xffff);
|
||||
}
|
||||
|
||||
private static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
|
||||
{
|
||||
string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Assembly assembly = Assembly.LoadFrom(assemblyPath);
|
||||
return assembly;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
internal interface ICommandHandler
|
||||
{
|
||||
void Execute(DebuggerContext context, string args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<CodeAnalysisRuleSet>SosThreadingTools.ruleset</CodeAnalysisRuleSet>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DllExportOurILAsm>false</DllExportOurILAsm>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="1.0.2" />
|
||||
<PackageReference Include="DllExport" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(NuGetPackageRoot)\dllexport\1.5.2\tools\net.r_eg.DllExport.targets" Condition="Exists('$(NuGetPackageRoot)\dllexport\1.5.2\tools\net.r_eg.DllExport.targets')" />
|
||||
</Project>
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Microsoft Managed Recommended Rules" Description="These rules focus on the most critical problems in your code, including potential security holes, application crashes, and other important logic and design errors. It is recommended to include this rule set in any custom rule set you create for your projects." ToolsVersion="10.0">
|
||||
<Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
|
||||
<Name Resource="MinimumRecommendedRules_Name" />
|
||||
<Description Resource="MinimumRecommendedRules_Description" />
|
||||
</Localization>
|
||||
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
|
||||
<Rule Id="CA1001" Action="Warning" />
|
||||
<Rule Id="CA1009" Action="Warning" />
|
||||
<Rule Id="CA1016" Action="Warning" />
|
||||
<Rule Id="CA1033" Action="Warning" />
|
||||
<Rule Id="CA1049" Action="Warning" />
|
||||
<Rule Id="CA1060" Action="Warning" />
|
||||
<Rule Id="CA1061" Action="Warning" />
|
||||
<Rule Id="CA1063" Action="Warning" />
|
||||
<Rule Id="CA1065" Action="Warning" />
|
||||
<Rule Id="CA1301" Action="Warning" />
|
||||
<Rule Id="CA1400" Action="Warning" />
|
||||
<Rule Id="CA1401" Action="Warning" />
|
||||
<Rule Id="CA1403" Action="Warning" />
|
||||
<Rule Id="CA1404" Action="Warning" />
|
||||
<Rule Id="CA1405" Action="Warning" />
|
||||
<Rule Id="CA1410" Action="Warning" />
|
||||
<Rule Id="CA1415" Action="Warning" />
|
||||
<Rule Id="CA1821" Action="Warning" />
|
||||
<Rule Id="CA1900" Action="Warning" />
|
||||
<Rule Id="CA1901" Action="Warning" />
|
||||
<Rule Id="CA2002" Action="Warning" />
|
||||
<Rule Id="CA2100" Action="Warning" />
|
||||
<Rule Id="CA2101" Action="Warning" />
|
||||
<Rule Id="CA2108" Action="Warning" />
|
||||
<Rule Id="CA2111" Action="Warning" />
|
||||
<Rule Id="CA2112" Action="Warning" />
|
||||
<Rule Id="CA2114" Action="Warning" />
|
||||
<Rule Id="CA2116" Action="Warning" />
|
||||
<Rule Id="CA2117" Action="Warning" />
|
||||
<Rule Id="CA2122" Action="Warning" />
|
||||
<Rule Id="CA2123" Action="Warning" />
|
||||
<Rule Id="CA2124" Action="Warning" />
|
||||
<Rule Id="CA2126" Action="Warning" />
|
||||
<Rule Id="CA2131" Action="Warning" />
|
||||
<Rule Id="CA2132" Action="Warning" />
|
||||
<Rule Id="CA2133" Action="Warning" />
|
||||
<Rule Id="CA2134" Action="Warning" />
|
||||
<Rule Id="CA2137" Action="Warning" />
|
||||
<Rule Id="CA2138" Action="Warning" />
|
||||
<Rule Id="CA2140" Action="Warning" />
|
||||
<Rule Id="CA2141" Action="Warning" />
|
||||
<Rule Id="CA2146" Action="Warning" />
|
||||
<Rule Id="CA2147" Action="Warning" />
|
||||
<Rule Id="CA2149" Action="Warning" />
|
||||
<Rule Id="CA2200" Action="Warning" />
|
||||
<Rule Id="CA2202" Action="Warning" />
|
||||
<Rule Id="CA2207" Action="Warning" />
|
||||
<Rule Id="CA2212" Action="Warning" />
|
||||
<Rule Id="CA2213" Action="Warning" />
|
||||
<Rule Id="CA2214" Action="Warning" />
|
||||
<Rule Id="CA2216" Action="Warning" />
|
||||
<Rule Id="CA2220" Action="Warning" />
|
||||
<Rule Id="CA2229" Action="Warning" />
|
||||
<Rule Id="CA2231" Action="Warning" />
|
||||
<Rule Id="CA2232" Action="Warning" />
|
||||
<Rule Id="CA2235" Action="Warning" />
|
||||
<Rule Id="CA2236" Action="Warning" />
|
||||
<Rule Id="CA2237" Action="Warning" />
|
||||
<Rule Id="CA2238" Action="Warning" />
|
||||
<Rule Id="CA2240" Action="Warning" />
|
||||
<Rule Id="CA2241" Action="Warning" />
|
||||
<Rule Id="CA2242" Action="Warning" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<Rule Id="SA1600" Action="Hidden" />
|
||||
<Rule Id="SA1601" Action="Hidden" />
|
||||
<Rule Id="SA1602" Action="Hidden" />
|
||||
</Rules>
|
||||
</RuleSet>
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace CpsDbg
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Diagnostics.Runtime;
|
||||
|
||||
internal static class Utilities
|
||||
{
|
||||
internal static ClrObject TryGetObjectField(this ClrObject clrObject, string fieldName)
|
||||
{
|
||||
if (!clrObject.IsNull)
|
||||
{
|
||||
var field = clrObject.Type.GetFieldByName(fieldName);
|
||||
if (field != null && field.IsObjectReference)
|
||||
{
|
||||
ulong address = field.GetAddress(clrObject.Address);
|
||||
ulong reference;
|
||||
if (clrObject.Type.Heap.ReadPointer(address, out reference) && reference != 0)
|
||||
{
|
||||
return new ClrObject(reference, clrObject.Type.Heap.GetObjectType(reference));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default(ClrObject);
|
||||
}
|
||||
|
||||
internal static ClrValueClass? TryGetValueClassField(this ClrObject clrObject, string fieldName)
|
||||
{
|
||||
if (!clrObject.IsNull)
|
||||
{
|
||||
var field = clrObject.Type.GetFieldByName(fieldName);
|
||||
if (field != null && field.Type.IsValueClass)
|
||||
{
|
||||
// System.Console.WriteLine("{0} {1:x} Field {2} {3} {4} {5}", clrObject.Type.Name, clrObject.Address, fieldName, field.Type.Name, field.Type.IsValueClass, field.Type.IsRuntimeType);
|
||||
return clrObject.GetValueClassField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static ClrObject TryGetObjectField(this ClrValueClass? clrObject, string fieldName)
|
||||
{
|
||||
if (clrObject != null)
|
||||
{
|
||||
var field = clrObject.Value.Type.GetFieldByName(fieldName);
|
||||
if (field != null && field.IsObjectReference)
|
||||
{
|
||||
return clrObject.Value.GetObjectField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return default(ClrObject);
|
||||
}
|
||||
|
||||
internal static ClrValueClass? TryGetValueClassField(this ClrValueClass? clrObject, string fieldName)
|
||||
{
|
||||
if (clrObject.HasValue)
|
||||
{
|
||||
var field = clrObject.Value.Type.GetFieldByName(fieldName);
|
||||
if (field != null && field.IsValueClass)
|
||||
{
|
||||
return clrObject.Value.GetValueClassField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static IEnumerable<ClrObject> GetObjectsOfType(this ClrHeap heap, string typeName)
|
||||
{
|
||||
return heap.EnumerateObjects().Where(obj => string.Equals(obj.Type?.Name, typeName, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
|
||||
"settings": {
|
||||
"documentationRules": {
|
||||
"companyName": "Microsoft Corporation",
|
||||
"xmlHeader": false,
|
||||
"fileNamingConvention": "metadata"
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче