Skip to content

Commit ae0de2b

Browse files
author
Christophe Nasarre
committed
Use ClrMDExports (from Kevin Gosse) to allow lldb/WinDBG extension
1 parent f48122e commit ae0de2b

18 files changed

+209
-156
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The few "debugging extensions" that have been created at Criteo to help post-mor
44
- as a [stand alone tool](./Documentation/ClrMDStudio.md) to load a .NET application memory dump and start automatic thread, thread pool, tasks and timer analysis.
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.
7-
[zip](./binaries/gsose-1.5.1_x64.zip)
7+
[zip](./binaries/gsose-1.5.2_x64.zip)
88

99
More analyzers and commands will be added as needed.
1010

binaries/gsose-1.5.1_x64.zip

-353 KB
Binary file not shown.

binaries/gsose-1.5.2_x64.zip

482 KB
Binary file not shown.

src/gsose/ConcurrentDictionary.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Runtime.InteropServices;
4+
using ClrMDExports;
45
using ClrMDStudio;
56
using Microsoft.Diagnostics.Runtime;
67
using RGiesecke.DllExport;
@@ -12,21 +13,17 @@ public partial class DebuggerExtensions
1213
[DllExport("dcd")]
1314
public static void dcd(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1415
{
15-
OnDumpConcurrentDictionary(client, args);
16+
DebuggingContext.Execute(client, args, OnDumpConcurrentDictionary);
1617
}
1718

1819
[DllExport("DumpConcurrentDictionary")]
1920
public static void DumpConcurrentDictionary(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
2021
{
21-
OnDumpConcurrentDictionary(client, args);
22+
DebuggingContext.Execute(client, args, OnDumpConcurrentDictionary);
2223
}
2324

24-
public static void OnDumpConcurrentDictionary(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
25+
public static void OnDumpConcurrentDictionary(ClrRuntime runtime, string args)
2526
{
26-
// Must be the first thing in our extension.
27-
if (!InitApi(client))
28-
return;
29-
3027
// parse the command argument
3128
if (args.StartsWith("0x"))
3229
{
@@ -44,12 +41,12 @@ public static void OnDumpConcurrentDictionary(IntPtr client, [MarshalAs(Unmanage
4441
return;
4542
}
4643

47-
ShowConcurrentDictionary(address);
44+
ShowConcurrentDictionary(runtime, address);
4845
}
4946

50-
private static void ShowConcurrentDictionary(ulong address)
47+
private static void ShowConcurrentDictionary(ClrRuntime runtime, ulong address)
5148
{
52-
var heap = Runtime.Heap;
49+
var heap = runtime.Heap;
5350
ClrType t = heap.GetObjectType(address);
5451
if (t == null)
5552
{
@@ -60,7 +57,7 @@ private static void ShowConcurrentDictionary(ulong address)
6057
try
6158
{
6259
// different implementations between .NET Core and .NET Framework
63-
var helper = new ClrMDHelper(Runtime);
60+
var helper = new ClrMDHelper(runtime);
6461

6562
var cd = heap.GetProxy(address);
6663
Console.WriteLine($"{cd.GetClrType().Name}");

src/gsose/ConcurrentQueue.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Runtime.InteropServices;
4+
using ClrMDExports;
45
using ClrMDStudio;
56
using Microsoft.Diagnostics.Runtime;
67
using RGiesecke.DllExport;
@@ -12,21 +13,17 @@ public partial class DebuggerExtensions
1213
[DllExport("dcq")]
1314
public static void dcq(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1415
{
15-
OnDumpConcurrentQueue(client, args);
16+
DebuggingContext.Execute(client, args, OnDumpConcurrentQueue);
1617
}
1718

1819
[DllExport("DumpConcurrentQueue")]
1920
public static void DumpConcurrentQueue(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
2021
{
21-
OnDumpConcurrentQueue(client, args);
22+
DebuggingContext.Execute(client, args, OnDumpConcurrentQueue);
2223
}
2324

24-
public static void OnDumpConcurrentQueue(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
25+
public static void OnDumpConcurrentQueue(ClrRuntime runtime, string args)
2526
{
26-
// Must be the first thing in our extension.
27-
if (!InitApi(client))
28-
return;
29-
3027
// parse the command argument
3128
if (string.IsNullOrEmpty(args))
3229
{
@@ -60,12 +57,12 @@ public static void OnDumpConcurrentQueue(IntPtr client, [MarshalAs(UnmanagedType
6057
{
6158
showItemType = arguments.Any(arg => arg == "-t");
6259
}
63-
ShowConcurrentQueue(reference, showItemType);
60+
ShowConcurrentQueue(runtime, reference, showItemType);
6461
}
6562

66-
private static void ShowConcurrentQueue(ulong address, bool showItemType)
63+
private static void ShowConcurrentQueue(ClrRuntime runtime, ulong address, bool showItemType)
6764
{
68-
var heap = Runtime.Heap;
65+
var heap = runtime.Heap;
6966
ClrType t = heap.GetObjectType(address);
7067
if (t == null)
7168
{
@@ -76,7 +73,7 @@ private static void ShowConcurrentQueue(ulong address, bool showItemType)
7673
try
7774
{
7875
// different implementations between .NET Core and .NET Framework
79-
var helper = new ClrMDHelper(Runtime);
76+
var helper = new ClrMDHelper(runtime);
8077
var cq = heap.GetProxy(address);
8178
int count = 0;
8279
foreach (var item in ClrMDHelper.EnumerateConcurrentQueue(cq, helper.IsNetCore()))

src/gsose/DumpHeap.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Linq;
55
using System.Runtime.InteropServices;
6+
using ClrMDExports;
67

78
namespace gsose
89
{
@@ -11,27 +12,27 @@ public partial class DebuggerExtensions
1112
[DllExport("heapstat")]
1213
public static void HeapStat(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1314
{
14-
// Must be the first thing in our extension.
15-
if (!InitApi(client))
16-
return;
17-
15+
DebuggingContext.Execute(client, args, OnHeapStat);
16+
}
1817

18+
public static void OnHeapStat(ClrRuntime runtime, string args)
19+
{
1920
// Use ClrMD as normal, but ONLY cache the copy of ClrRuntime (this.Runtime). All other
2021
// types you get out of ClrMD (such as ClrHeap, ClrTypes, etc) should be discarded and
2122
// reobtained every run.
22-
ClrHeap heap = Runtime.Heap;
23+
ClrHeap heap = runtime.Heap;
2324

2425
var stats = from obj in heap.EnumerateObjectAddresses()
25-
let t = heap.GetObjectType(obj)
26-
group obj by t into g
27-
let size = g.Sum(p => (uint)g.Key.GetSize(p))
28-
orderby size
29-
select new
30-
{
31-
Size = size,
32-
Count = g.Count(),
33-
Name = g.Key.Name
34-
};
26+
let t = heap.GetObjectType(obj)
27+
group obj by t into g
28+
let size = g.Sum(p => (uint)g.Key.GetSize(p))
29+
orderby size
30+
select new
31+
{
32+
Size = size,
33+
Count = g.Count(),
34+
Name = g.Key.Name
35+
};
3536

3637
// Console.WriteLine now writes to the debugger.
3738
foreach (var entry in stats)

src/gsose/GarbageCollector.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Linq;
66
using System.Runtime.InteropServices;
77
using System.Text;
8+
using ClrMDExports;
9+
using Microsoft.Diagnostics.Runtime;
810

911
namespace gsose
1012
{
@@ -13,26 +15,22 @@ public partial class DebuggerExtensions
1315
[DllExport("gci")]
1416
public static void gci(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1517
{
16-
OnGCInfo(client, args);
18+
DebuggingContext.Execute(client, args, OnGCInfo);
1719
}
1820
[DllExport("gcinfo")]
1921
public static void gcinfo(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
2022
{
21-
OnGCInfo(client, args);
23+
DebuggingContext.Execute(client, args, OnGCInfo);
2224
}
2325
[DllExport("GCInfo")]
2426
public static void GCInfo(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
2527
{
26-
OnGCInfo(client, args);
28+
DebuggingContext.Execute(client, args, OnGCInfo);
2729
}
2830

29-
private static void OnGCInfo(IntPtr client, string args)
31+
private static void OnGCInfo(ClrRuntime runtime, string args)
3032
{
31-
// Must be the first thing in our extension.
32-
if (!InitApi(client))
33-
return;
34-
35-
var helper = new ClrMDHelper(Runtime);
33+
var helper = new ClrMDHelper(runtime);
3634
var segments = helper.ComputeGCSegments();
3735

3836
ListGenerations(segments);

src/gsose/Help.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Runtime.InteropServices;
3+
using ClrMDExports;
4+
using Microsoft.Diagnostics.Runtime;
35
using RGiesecke.DllExport;
46

57
namespace gsose
@@ -9,12 +11,12 @@ public partial class DebuggerExtensions
911
[DllExport("Help")]
1012
public static void Help(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1113
{
12-
OnHelp(client, args);
14+
DebuggingContext.Execute(client, args, OnHelp);
1315
}
1416
[DllExport("help")]
1517
public static void help(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1618
{
17-
OnHelp(client, args);
19+
DebuggingContext.Execute(client, args, OnHelp);
1820
}
1921

2022
const string _help =
@@ -251,12 +253,8 @@ public static void help(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string a
251253
" 5 - 0x000001D10DF5B480 | NetCoreConsoleApp.InstanceInConcurrentDataStructures\r\n" +
252254
"\r\n";
253255
//
254-
private static void OnHelp(IntPtr client, string args)
256+
private static void OnHelp(ClrRuntime runtime, string args)
255257
{
256-
// Must be the first thing in our extension.
257-
if (!InitApi(client))
258-
return;
259-
260258
string command = args;
261259
if (args != null)
262260
command = args.ToLower();

src/gsose/Init.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Runtime.CompilerServices;
5+
6+
namespace ClrMDExports
7+
{
8+
internal class Init
9+
{
10+
[RGiesecke.DllExport.DllExport("DebugExtensionInitialize")]
11+
public static int DebugExtensionInitialize(ref uint version, ref uint flags)
12+
{
13+
var extensionFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
14+
15+
try
16+
{
17+
var file = Path.Combine(extensionFolder, "ClrMDExports.dll");
18+
19+
var assemblyName = AssemblyName.GetAssemblyName(file);
20+
21+
ForceAssemblyLoad(assemblyName);
22+
}
23+
catch (Exception ex)
24+
{
25+
Console.WriteLine("Could not load ClrMDExports: " + ex);
26+
}
27+
28+
InitializeDebuggingContext();
29+
30+
// Set the extension version to 1, which expects exports with this signature:
31+
// void _stdcall function(IDebugClient *client, const char *args)
32+
version = DEBUG_EXTENSION_VERSION(1, 0);
33+
flags = 0;
34+
return 0;
35+
}
36+
37+
[MethodImpl(MethodImplOptions.NoInlining)]
38+
private static void InitializeDebuggingContext()
39+
{
40+
Private.Initialization.IsWinDbg = true;
41+
}
42+
43+
private static void ForceAssemblyLoad(AssemblyName assemblyName)
44+
{
45+
var codeBase = assemblyName.CodeBase;
46+
47+
if (codeBase.StartsWith("file://"))
48+
{
49+
codeBase = codeBase.Substring(8).Replace('/', '\\');
50+
}
51+
52+
ResolveEventHandler assemblyResolve = (sender, args) => args.Name == assemblyName.FullName ? Assembly.LoadFrom(codeBase) : null;
53+
54+
AppDomain.CurrentDomain.AssemblyResolve += assemblyResolve;
55+
56+
Assembly.Load(assemblyName.FullName);
57+
58+
AppDomain.CurrentDomain.AssemblyResolve -= assemblyResolve;
59+
}
60+
61+
static uint DEBUG_EXTENSION_VERSION(uint major, uint minor)
62+
{
63+
return ((((major) & 0xffff) << 16) | ((minor) & 0xffff));
64+
}
65+
}
66+
}

src/gsose/PinnedObjects.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Linq;
66
using System.Runtime.InteropServices;
7+
using ClrMDExports;
78

89
namespace gsose
910
{
@@ -12,37 +13,33 @@ public partial class DebuggerExtensions
1213
[DllExport("po")]
1314
public static void po(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1415
{
15-
OnPinnedObjects(client, args);
16+
DebuggingContext.Execute(client, args, OnPinnedObjects);
1617
}
1718
[DllExport("pinnedobjects")]
1819
public static void pinnedobjects(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
1920
{
20-
OnPinnedObjects(client, args);
21+
DebuggingContext.Execute(client, args, OnPinnedObjects);
2122
}
2223
[DllExport("PinnedObjects")]
2324
public static void PinnedObjects(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string args)
2425
{
25-
OnPinnedObjects(client, args);
26+
DebuggingContext.Execute(client, args, OnPinnedObjects);
2627
}
2728

28-
private static void OnPinnedObjects(IntPtr client, string args)
29+
private static void OnPinnedObjects(ClrRuntime runtime, string args)
2930
{
30-
// Must be the first thing in our extension.
31-
if (!InitApi(client))
32-
return;
33-
3431
int minInstanceCount;
3532
if (!int.TryParse(args, out minInstanceCount))
3633
minInstanceCount = 1;
3734

3835
// Use ClrMD as normal, but ONLY cache the copy of ClrRuntime (this.Runtime). All other
3936
// types you get out of ClrMD (such as ClrHeap, ClrTypes, etc) should be discarded and
4037
// reobtained every run.
41-
ClrHeap heap = Runtime.Heap;
38+
ClrHeap heap = runtime.Heap;
4239

4340
// Console.WriteLine now writes to the debugger.
4441

45-
ClrMDHelper helper = new ClrMDHelper(Runtime);
42+
ClrMDHelper helper = new ClrMDHelper(runtime);
4643

4744
try
4845
{

0 commit comments

Comments
 (0)