Skip to content

Commit 4061a2c

Browse files
author
Christophe Nasarre
committed
Add pstacks in gsose and as a .NET Core console application to show merged threads call stacks a la Visual Studio "Parallel Stacks"
1 parent 3d73018 commit 4061a2c

19 files changed

+857
-38
lines changed

Documentation/gsose.md

+42-8
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ gsose is a debugger extension DLL designed to dig into CLR data structures.
99
Functions are listed by category and shortcut names are listed in parenthesis.
1010
Type "!help " for detailed info on that function.
1111
12-
Thread Pool Timers
12+
Thread Pool Threads
1313
----------------------------- -----------------------------
14-
TpQueue(tpq) TimerInfo (ti)
14+
TpQueue(tpq) ParallelStacks (pstacks)
1515
TpRunning(tpr)
1616
17-
Tasks Strings
17+
Tasks Timers
1818
----------------------------- -----------------------------
19-
TkState (tks) StringDuplicates (sd)
19+
TkState (tks) TimerInfo (ti)
2020
GetMethodName (gmn)
2121
22-
Data structures
23-
-----------------------------
24-
DumpConcurrentDictionary (dcd)
22+
Data structures Strings
23+
----------------------------- -----------------------------
24+
DumpConcurrentDictionary (dcd) StringDuplicates (sd)
2525
DumpConcurrentQueue (dcq)
2626
2727
Garbage Collector
@@ -32,6 +32,40 @@ PinnedObjects (po)
3232

3333

3434

35+
## ParallelStacks (pstacks)
36+
```
37+
!pstacks merges parallel stacks
38+
0:000> !pstacks
39+
________________________________________________
40+
~~~~
41+
1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
42+
...
43+
1 System.Console.ReadLine()
44+
1 NetCoreConsoleApp.Program.Main(String[])
45+
46+
________________________________________________
47+
~~~~
48+
1 System.Threading.Monitor.Wait(Object, Int32, Boolean)
49+
...
50+
1 System.Threading.Tasks.Task.Wait()
51+
1 NetCoreConsoleApp.Program+c.b__1_4(Object)
52+
~~~~
53+
2 System.Threading.Monitor.Wait(Object, Int32, Boolean)
54+
...
55+
2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()
56+
3 System.Threading.Tasks.Task.InnerInvoke()
57+
4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)
58+
...
59+
4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()
60+
4 System.Threading.Tasks.Task.ExecuteWorkItem()
61+
7 System.Threading.ThreadPoolWorkQueue.Dispatch()
62+
7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
63+
64+
==> 8 threads with 2 roots
65+
```
66+
67+
68+
3569
## TpQueue (tpq)
3670
```
3771
!TpQueue lists the enqueued workitems in the Clr Thread Pool followed by a summary of the different tasks/work items.
@@ -211,7 +245,7 @@ System.Collections.Concurrent.ConcurrentDictionary<System.Int32,NetCoreConsoleAp
211245
!GCInfo
212246
213247
!GCInfo lists generations per segments. Show pinned objects with -pinned and object instances count/size with -stat (by default)
214-
0:000> !gci -pinned
248+
0:000> !gci [-stat] [-pinned]
215249
13 - 7 generations
216250
LOH | 9F06001000 - 9F0CDDB8C8 ( 115,189,960)
217251

Documentation/pstacks.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# pstacks
2+
3+
This .NET Core tool allows you to load a .NET application memory dump file and displays the merged threads call stacks à la Visual Studio "Parallel Stacks".
4+
![pstacks_result](./pstacks.png)
5+
6+
If you need to specify the path to the mscordac file, pass it as the second parameter (just after the dump file name)
7+
8+
## Platforms
9+
As a .NET Core console application, it runs on Windows and Linux.
10+
Note that it is not possible to attach to a live process on Linux (but could be adapted on Windows is needed)

Documentation/pstacks.png

97.8 KB
Loading

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2016-2018 Nasarre Christophe
3+
Copyright (c) 2016-2019 Nasarre Christophe
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The few "debugging extensions" that have been created at Criteo to help post-mor
55
[zip](./binaries/ClrMDStudio-1.5.1_x64.zip)
66
- as a [WinDBG extension](./Documentation/gsose.md) to get the same level of details plus more commands such as getting a method signature based on its address.
77
[zip](./binaries/gsose-1.5.3_x64.zip)
8+
- as a [.NET Core console tool](./Documentation/pstacks.md) to load a .NET application memory dump and show merged threads call stack à la Visual Studio "parallel stacks" (works also on Linux).
9+
[zip](./binaries/pstacks-1.0.1.zip)
810

911
More analyzers and commands will be added as needed.
1012

@@ -38,15 +40,19 @@ More commands will be added as needed.
3840

3941

4042
## Source Code
41-
The `DebuggingExtensions` Visual Studio 2017 solution contains two projects:
43+
The `DebuggingExtensions` Visual Studio 2017 solution contains three projects:
4244

4345
1. `ClrMDStudio`: WPF application that loads a dump file on which commands to be executed
4446

4547
2. `gsose`: "***G**rand **S**on **O**f **S**trike **E**xtension*" for WinDBG that exposes the same commands (and more)
4648

49+
3. `pstacks`: .NET Core console application that loads a dump file and shows merged parallel stacks
50+
51+
4752

4853
These projects depends on Nuget packages:
4954

5055
- [ClrMD](https://github.com/Microsoft/clrmd): C# library to explore dump files.
5156
- [DynaMD](https://github.com/kevingosse/DynaMD): C# `dynamic`-based helpers on top of ClrMD.
57+
- [ClrMDExports](https://github.com/kevingosse/ClrMDExports): Helper to write WinDBG/LLDB extensionss on top of ClrMD.
5258

Binary file not shown.

binaries/pstacks-1.0.1.zip

281 KB
Binary file not shown.

src/DebuggingExtensions.sln

+24
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,54 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClrMDStudio", "ClrMDStudio\
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gsose", "gsose\gsose.csproj", "{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}"
99
EndProject
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParallelStacks", "ParallelStacks\ParallelStacks.csproj", "{D17509EA-830E-4A29-A959-16D2D1E7C7E3}"
11+
EndProject
1012
Global
1113
GlobalSection(SolutionConfigurationPlatforms) = preSolution
14+
Debug|Any CPU = Debug|Any CPU
1215
Debug|x64 = Debug|x64
1316
Debug|x86 = Debug|x86
17+
Release|Any CPU = Release|Any CPU
1418
Release|x64 = Release|x64
1519
Release|x86 = Release|x86
1620
EndGlobalSection
1721
GlobalSection(ProjectConfigurationPlatforms) = postSolution
22+
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
1824
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|x64.ActiveCfg = Debug|x64
1925
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|x64.Build.0 = Debug|x64
2026
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|x86.ActiveCfg = Debug|x86
2127
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Debug|x86.Build.0 = Debug|x86
28+
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|Any CPU.Build.0 = Release|Any CPU
2230
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|x64.ActiveCfg = Release|x64
2331
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|x64.Build.0 = Release|x64
2432
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|x86.ActiveCfg = Release|x86
2533
{A03DF28C-DBEE-454E-A31C-0F38AD23BFAD}.Release|x86.Build.0 = Release|x86
34+
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|Any CPU.Build.0 = Debug|Any CPU
2636
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|x64.ActiveCfg = Debug|x64
2737
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|x64.Build.0 = Debug|x64
2838
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|x86.ActiveCfg = Debug|x86
2939
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Debug|x86.Build.0 = Debug|x86
40+
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|Any CPU.Build.0 = Release|Any CPU
3042
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|x64.ActiveCfg = Release|x64
3143
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|x64.Build.0 = Release|x64
3244
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|x86.ActiveCfg = Release|x86
3345
{3FCA8012-3BAD-41E4-91F4-534AA9A44F96}.Release|x86.Build.0 = Release|x86
46+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
48+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|x64.ActiveCfg = Debug|x64
49+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|x64.Build.0 = Debug|x64
50+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|x86.ActiveCfg = Debug|Any CPU
51+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Debug|x86.Build.0 = Debug|Any CPU
52+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
53+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|Any CPU.Build.0 = Release|Any CPU
54+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|x64.ActiveCfg = Release|x64
55+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|x64.Build.0 = Release|x64
56+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|x86.ActiveCfg = Release|Any CPU
57+
{D17509EA-830E-4A29-A959-16D2D1E7C7E3}.Release|x86.Build.0 = Release|Any CPU
3458
EndGlobalSection
3559
GlobalSection(SolutionProperties) = preSolution
3660
HideSolutionNode = FALSE

src/ParallelStacks/ParallelStack.cs

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Microsoft.Diagnostics.Runtime;
5+
6+
namespace ParallelStacks
7+
{
8+
public class ParallelStack
9+
{
10+
public static ParallelStack Build(ClrRuntime runtime)
11+
{
12+
var ps = new ParallelStack();
13+
var stackFrames = new List<ClrStackFrame>(64);
14+
foreach (var thread in runtime.Threads)
15+
{
16+
stackFrames.Clear();
17+
18+
foreach (var stackFrame in thread.StackTrace.Reverse())
19+
{
20+
if (stackFrame.Kind != ClrStackFrameType.ManagedMethod)
21+
continue;
22+
23+
stackFrames.Add(stackFrame);
24+
}
25+
26+
if (stackFrames.Count == 0)
27+
continue;
28+
29+
ps.AddStack(thread.ManagedThreadId, stackFrames.ToArray());
30+
}
31+
32+
return ps;
33+
}
34+
35+
private bool _useDml;
36+
37+
private ParallelStack(ClrStackFrame frame = null)
38+
{
39+
Stacks = new List<ParallelStack>();
40+
ThreadIds = new List<int>();
41+
Frame = (frame == null) ? null : new StackFrame(frame);
42+
}
43+
44+
public List<ParallelStack> Stacks { get; }
45+
46+
public StackFrame Frame { get; }
47+
48+
public List<int> ThreadIds { get; set; }
49+
50+
public void WriteToConsole(bool useDml = false)
51+
{
52+
_useDml = useDml;
53+
WriteStack(this);
54+
}
55+
56+
private const int padding = 5;
57+
private void WriteStack(ParallelStack stack, int increment = 0)
58+
{
59+
var alignment = new string(' ', padding * increment);
60+
if (stack.Stacks.Count == 0)
61+
{
62+
var lastFrame = stack.Frame;
63+
Console.Write($"{Environment.NewLine}{alignment}");
64+
WriteFrameSeparator(" ~~~~");
65+
//Console.Write($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,padding} ");
66+
WriteCount($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,padding} ");
67+
WriteFrame(lastFrame);
68+
return;
69+
}
70+
71+
foreach (var nextStackFrame in stack.Stacks.OrderBy(s => s.ThreadIds.Count))
72+
{
73+
WriteStack(nextStackFrame,
74+
(nextStackFrame.ThreadIds.Count == stack.ThreadIds.Count) ? increment : increment + 1);
75+
}
76+
77+
var currentFrame = stack.Frame;
78+
//Console.Write($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,padding} ");
79+
WriteCount($"{Environment.NewLine}{alignment}{stack.ThreadIds.Count,padding} ");
80+
WriteFrame(currentFrame);
81+
}
82+
83+
private void WriteFrame(StackFrame frame)
84+
{
85+
if (!string.IsNullOrEmpty(frame.TypeName))
86+
{
87+
var namespaces = frame.TypeName.Split('.');
88+
for (int i = 0; i < namespaces.Length - 1; i++)
89+
{
90+
WriteNamespace(namespaces[i]);
91+
WriteSeparator(".");
92+
}
93+
WriteMethodType(namespaces[namespaces.Length - 1]);
94+
WriteSeparator(".");
95+
}
96+
97+
WriteMethod(frame.MethodName);
98+
WriteSeparator("(");
99+
100+
var parameters = frame.Signature;
101+
for (int current = 0; current < parameters.Count; current++)
102+
{
103+
var parameter = parameters[current];
104+
// handle byref case
105+
var pos = parameter.LastIndexOf(" ByRef");
106+
if (pos != -1)
107+
{
108+
WriteType(parameter.Substring(0, pos));
109+
WriteDark(" ByRef");
110+
}
111+
else
112+
{
113+
WriteType(parameter);
114+
}
115+
if (current < parameters.Count - 1) WriteSeparator(", ");
116+
}
117+
WriteSeparator(")");
118+
}
119+
120+
private void WriteCount(string count)
121+
{
122+
if (_useDml)
123+
{
124+
Console.Write($"<col fg=\"srcpair\" bg=\"wbg\">{count}</col>");
125+
return;
126+
}
127+
128+
WriteWithColor(count, ConsoleColor.Cyan);
129+
}
130+
private void WriteNamespace(string ns)
131+
{
132+
WriteWithColor(ns, ConsoleColor.DarkCyan);
133+
}
134+
private void WriteType(string type)
135+
{
136+
WriteWithColor(type, ConsoleColor.Gray);
137+
}
138+
private void WriteSeparator(string separator)
139+
{
140+
WriteWithColor(separator, ConsoleColor.White);
141+
}
142+
private void WriteDark(string separator)
143+
{
144+
WriteWithColor(separator, ConsoleColor.DarkGray);
145+
}
146+
private void WriteMethod(string method)
147+
{
148+
if (_useDml)
149+
{
150+
Console.Write($"<col fg=\"srckw\" bg=\"wbg\">{method}</col>");
151+
return;
152+
}
153+
154+
WriteWithColor(method, ConsoleColor.Cyan);
155+
}
156+
private void WriteMethodType(string type)
157+
{
158+
if (_useDml)
159+
{
160+
Console.Write($"<b><col fg=\"srckw\" bg=\"wbg\">{type}</col></b>");
161+
return;
162+
}
163+
164+
WriteWithColor(type, ConsoleColor.DarkCyan);
165+
}
166+
private void WriteFrameSeparator(string text)
167+
{
168+
if (_useDml)
169+
{
170+
Console.Write($"<b><col fg=\"srcpair\" bg=\"wbg\">{text}</col></b>");
171+
return;
172+
}
173+
174+
WriteWithColor(text, ConsoleColor.Yellow);
175+
}
176+
177+
private void WriteWithColor(string text, ConsoleColor color)
178+
{
179+
var current = Console.ForegroundColor;
180+
Console.ForegroundColor = color;
181+
Console.Write(text);
182+
Console.ForegroundColor = current;
183+
}
184+
185+
private void AddStack(int threadId, ClrStackFrame[] frames, int index = 0)
186+
{
187+
ThreadIds.Add(threadId);
188+
var firstFrame = frames[index].DisplayString;
189+
var callstack = Stacks.FirstOrDefault(s => s.Frame.Text == firstFrame);
190+
if (callstack == null)
191+
{
192+
callstack = new ParallelStack(frames[index]);
193+
Stacks.Add(callstack);
194+
}
195+
196+
if (index == frames.Length - 1)
197+
{
198+
callstack.ThreadIds.Add(threadId);
199+
return;
200+
}
201+
202+
callstack.AddStack(threadId, frames, index + 1);
203+
}
204+
}
205+
}

0 commit comments

Comments
 (0)