Skip to content

Commit 7a1b2f2

Browse files
committed
Add emdump tool and restructure sources
1 parent 5c0f8f8 commit 7a1b2f2

File tree

7 files changed

+349
-0
lines changed

7 files changed

+349
-0
lines changed

emdump/Dumper.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.IO;
4+
using System.Diagnostics;
5+
6+
namespace eMDump
7+
{
8+
public class Dumper
9+
{
10+
internal enum MINIDUMP_TYPE
11+
{
12+
MiniDumpNormal = 0x00000000,
13+
MiniDumpWithDataSegs = 0x00000001,
14+
MiniDumpWithFullMemory = 0x00000002,
15+
MiniDumpWithHandleData = 0x00000004,
16+
MiniDumpFilterMemory = 0x00000008,
17+
MiniDumpScanMemory = 0x00000010,
18+
MiniDumpWithUnloadedModules = 0x00000020,
19+
MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
20+
MiniDumpFilterModulePaths = 0x00000080,
21+
MiniDumpWithProcessThreadData = 0x00000100,
22+
MiniDumpWithPrivateReadWriteMemory = 0x00000200,
23+
MiniDumpWithoutOptionalData = 0x00000400,
24+
MiniDumpWithFullMemoryInfo = 0x00000800,
25+
MiniDumpWithThreadInfo = 0x00001000,
26+
MiniDumpWithCodeSegs = 0x00002000,
27+
MiniDumpWithoutAuxiliaryState = 0x4000,
28+
MiniDumpWithFullAuxiliaryState = 0x8000,
29+
MiniDumpWithPrivateWriteCopyMemory = 0x10000,
30+
MiniDumpIgnoreInaccessibleMemory = 0x20000,
31+
MiniDumpWithTokenInformation = 0x40000
32+
}
33+
34+
[DllImport("dbghelp.dll")]
35+
static extern bool MiniDumpWriteDump(
36+
IntPtr hProcess,
37+
Int32 ProcessId,
38+
IntPtr hFile,
39+
MINIDUMP_TYPE DumpType,
40+
IntPtr ExceptionParam,
41+
IntPtr UserStreamParam,
42+
IntPtr CallackParam);
43+
44+
public static bool MiniDumpToFile(Process process, string filename)
45+
{
46+
using (FileStream fsToDump = new FileStream(filename, FileMode.Create))
47+
{
48+
MiniDumpWriteDump(process.Handle, process.Id,
49+
fsToDump.SafeFileHandle.DangerousGetHandle(),
50+
MINIDUMP_TYPE.MiniDumpWithPrivateReadWriteMemory |
51+
MINIDUMP_TYPE.MiniDumpWithDataSegs |
52+
MINIDUMP_TYPE.MiniDumpWithHandleData |
53+
MINIDUMP_TYPE.MiniDumpWithUnloadedModules |
54+
MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo |
55+
MINIDUMP_TYPE.MiniDumpWithThreadInfo |
56+
MINIDUMP_TYPE.MiniDumpWithTokenInformation,
57+
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
58+
fsToDump.Close();
59+
}
60+
61+
return true;
62+
}
63+
}
64+
}

emdump/Program.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.IO.Compression;
5+
using System.Windows.Forms;
6+
7+
namespace eMDump
8+
{
9+
class Program
10+
{
11+
static void Main(string[] args)
12+
{
13+
try
14+
{
15+
Process[] processes;
16+
17+
if (args.Length > 0)
18+
{
19+
int processId;
20+
Process process;
21+
22+
if (int.TryParse(args[0], out processId) &&
23+
(process = Process.GetProcessById(processId)) != null)
24+
{
25+
processes = new[] { process };
26+
}
27+
else
28+
{
29+
MessageBox.Show(
30+
"Memory dump not performed, because the process was not found. Ensure the application is running");
31+
return;
32+
}
33+
}
34+
else
35+
{
36+
processes = Process.GetProcessesByName("MailClient");
37+
if (processes.Length == 0)
38+
processes = Process.GetProcessesByName("eM Client");
39+
if (processes.Length == 0)
40+
{
41+
MessageBox.Show(
42+
"Memory dump not performed, because the process was not found. Ensure the application is running");
43+
return;
44+
}
45+
}
46+
47+
string fileName = Path.Combine(
48+
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
49+
"eM Client Dump " + DateTime.Now.ToString("MM-dd-yyyy HH-mm-ss") + ".zip");
50+
using (var outputZipFile = new FileStream(fileName, FileMode.CreateNew))
51+
using (var zipArchive = new ZipArchive(outputZipFile, ZipArchiveMode.Create))
52+
{
53+
foreach (var process in processes)
54+
{
55+
string tempFileName = Path.GetTempFileName();
56+
if (Dumper.MiniDumpToFile(process, tempFileName))
57+
zipArchive.CreateEntryFromFile(tempFileName, process.Id + ".dmp");
58+
File.Delete(tempFileName);
59+
}
60+
}
61+
62+
MessageBox.Show(String.Format("Memory dump complete. File location: {0}", fileName));
63+
}
64+
catch (Exception e)
65+
{
66+
MessageBox.Show("Memory dump failed because of the following error:\n" + e.Message);
67+
}
68+
}
69+
}
70+
}

emdump/emdump.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>WinExe</OutputType>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<PublishTrimmed>true</PublishTrimmed>
6+
<PublishSingleFile>true</PublishSingleFile>
7+
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<Compile Include="../shared/MessageBox.cs" Link="MessageBox.cs" />
11+
</ItemGroup>
12+
</Project>

emstackdump/Program.cs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using Microsoft.Diagnostics.NETCore.Client;
2+
using Microsoft.Diagnostics.Symbols;
3+
using Microsoft.Diagnostics.Tracing;
4+
using Microsoft.Diagnostics.Tracing.Etlx;
5+
using Microsoft.Diagnostics.Tracing.Stacks;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Diagnostics.Tracing;
10+
using System.IO;
11+
using System.Linq;
12+
using System.Threading.Tasks;
13+
using System.Windows.Forms;
14+
15+
namespace emstackdump
16+
{
17+
class Program
18+
{
19+
static void Main(string[] args)
20+
{
21+
IList<string> tempNetTraceFilenames = Array.Empty<string>();
22+
IList<string> tempEtlxFilenames = Array.Empty<string>();
23+
24+
try
25+
{
26+
var publishedProcessesPids = DiagnosticsClient.GetPublishedProcesses().ToHashSet();
27+
var processIds = Process.GetProcessesByName("MailClient").Select(process => process.Id).Where(id => publishedProcessesPids.Contains(id)).ToList();
28+
29+
if (processIds.Count == 0)
30+
{
31+
processIds = Process.GetProcessesByName("eM Client").Select(process => process.Id).Where(id => publishedProcessesPids.Contains(id)).ToList();
32+
}
33+
34+
if (processIds.Count == 0)
35+
{
36+
MessageBox.Show("Memory dump not performed, because the process was not found. Ensure the application is running");
37+
return;
38+
}
39+
40+
var tempPath = Path.Combine(Path.GetTempPath(), "em" + Guid.NewGuid().ToString());
41+
tempNetTraceFilenames = processIds.Select(id => tempPath + "." + id.ToString() + ".etlx").ToList();
42+
var collectionTasks = new List<Task>();
43+
for (int i = 0; i < processIds.Count; i++)
44+
{
45+
collectionTasks.Add(CollectTrace(processIds[i], tempNetTraceFilenames[i]));
46+
}
47+
Task.WaitAll(collectionTasks.ToArray());
48+
49+
string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "eM Client Stack Dump " + DateTime.Now.ToString("MM-dd-yyyy HH-mm-ss") + ".txt");
50+
using var outputFile = new StreamWriter(fileName, false);
51+
52+
// using the generated trace file, symbolocate and compute stacks.
53+
using var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath };
54+
tempEtlxFilenames = new List<string>();
55+
for (int i = 0; i < processIds.Count; i++)
56+
{
57+
var tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilenames[i]);
58+
tempEtlxFilenames.Add(tempEtlxFilename);
59+
60+
outputFile.WriteLine("-- Process {0} --", processIds[i]);
61+
62+
using (var eventLog = new TraceLog(tempEtlxFilename))
63+
{
64+
var stackSource = new MutableTraceEventStackSource(eventLog) { OnlyManagedCodeStacks = true };
65+
var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader);
66+
computer.GenerateThreadTimeStacks(stackSource);
67+
68+
var samplesForThread = new Dictionary<int, List<StackSourceSample>>();
69+
70+
stackSource.ForEach((sample) =>
71+
{
72+
var stackIndex = sample.StackIndex;
73+
while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread ("))
74+
stackIndex = stackSource.GetCallerIndex(stackIndex);
75+
76+
// long form for: int.Parse(threadFrame["Thread (".Length..^1)])
77+
// Thread id is in the frame name as "Thread (<ID>)"
78+
string template = "Thread (";
79+
string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false);
80+
int threadId = int.Parse(threadFrame.Substring(template.Length, threadFrame.Length - (template.Length + 1)));
81+
82+
if (samplesForThread.TryGetValue(threadId, out var samples))
83+
{
84+
samples.Add(sample);
85+
}
86+
else
87+
{
88+
samplesForThread[threadId] = new List<StackSourceSample>() { sample };
89+
}
90+
});
91+
92+
// For every thread recorded in our trace, print the first stack
93+
foreach (var (threadId, samples) in samplesForThread)
94+
{
95+
outputFile.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}");
96+
PrintStack(outputFile, threadId, samples[0], stackSource);
97+
}
98+
}
99+
}
100+
101+
MessageBox.Show(String.Format("Memory dump complete. File location: {0}", fileName));
102+
}
103+
catch (Exception e)
104+
{
105+
MessageBox.Show("Memory dump failed because of the following error:\n" + e.Message);
106+
}
107+
finally
108+
{
109+
foreach (var fileName in tempNetTraceFilenames)
110+
File.Delete(fileName);
111+
foreach (var fileName in tempEtlxFilenames)
112+
File.Delete(fileName);
113+
}
114+
}
115+
116+
static List<EventPipeProvider> providers = new List<EventPipeProvider>()
117+
{
118+
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
119+
};
120+
121+
private static async Task CollectTrace(int processId, string tempNetTraceFilename)
122+
{
123+
var client = new DiagnosticsClient(processId);
124+
using var session = client.StartEventPipeSession(providers);
125+
// collect a *short* trace with stack samples
126+
// the hidden '--duration' flag can increase the time of this trace in case 10ms
127+
// is too short in a given environment, e.g., resource constrained systems
128+
// N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect
129+
// the symbol data in rundown.
130+
using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
131+
{
132+
Task copyTask = session.EventStream.CopyToAsync(fs);
133+
await Task.Delay(TimeSpan.FromSeconds(5));
134+
session.Stop();
135+
await copyTask;
136+
}
137+
}
138+
139+
private static void PrintStack(TextWriter output, int threadId, StackSourceSample stackSourceSample, StackSource stackSource)
140+
{
141+
output.WriteLine($"Thread (0x{threadId:X}):");
142+
var stackIndex = stackSourceSample.StackIndex;
143+
while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false).StartsWith("Thread ("))
144+
{
145+
output.WriteLine($" {stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), verboseName: false)}"
146+
.Replace("UNMANAGED_CODE_TIME", "[Native Frames]"));
147+
stackIndex = stackSource.GetCallerIndex(stackIndex);
148+
}
149+
output.WriteLine();
150+
}
151+
}
152+
}

emstackdump/emstackdump.csproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<PublishTrimmed>true</PublishTrimmed>
7+
<PublishSingleFile>true</PublishSingleFile>
8+
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.621003" />
13+
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.22" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<Compile Include="../shared/MessageBox.cs" Link="MessageBox.cs" />
18+
</ItemGroup>
19+
20+
</Project>

nuget.config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
5+
<clear />
6+
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
7+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
8+
</packageSources>
9+
</configuration>

shared/MessageBox.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace System.Windows.Forms
4+
{
5+
class MessageBox
6+
{
7+
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
8+
static extern int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType);
9+
10+
public static void Show(string text)
11+
{
12+
if (OperatingSystem.IsWindows())
13+
{
14+
MessageBoxW(IntPtr.Zero, text, null, 0);
15+
}
16+
else
17+
{
18+
Console.WriteLine(text);
19+
}
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)