使用CLRMD时通过Symbol Server找Dac的位置来初始化ClrRuntime
在我昨天的博客使用CLRMD编写一个自己的C#调试器介绍了一下如果使用CLRMD的API,其中第二个例子是打开一个CLR的dump,来遍历所有线程,把call stack打印出来,类似在每个线程上运行!clrstack
命令。
昨天的那个小例子有一个潜在的问题,就是如果dump是别的机器生成的,自己机器上没有装匹配的clr时,函数TryGetDacLocation
会返回null
。这样就不能正确的初始化ClrRuntime
了。
ClrMD: Fetching DAC Libraries From Symbol Servers介绍了如果通过使用dbghelp.dll
(在安装windbg或者debugdia时会装在你的电脑上)来从symbol路径里面找Dac。我参考这篇文章写了一个读取dump,打印异常的小程序。
先看输出吧,和在windbg里面用sos扩展敲!pe
的效果很类似,不过自动把inner exception也打印出来了。
1 | ThreadID: 11EC |
加载dump的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30private static void LoadDump(string file)
{
using (DataTarget dataTarget = DataTarget.LoadCrashDump(file))
{
// use microsoft public symbol server to find the DAC
string symcache = @"D:\NotBackedUp\SymCache";
DacLocator dacloc = DacLocator.FromPublicSymbolServer(symcache);
string dacLocation = dacloc.FindDac(dataTarget.ClrVersions[0]);
ClrRuntime runtime = dataTarget.CreateRuntime(dacLocation);
foreach (ClrThread thread in runtime.Threads)
{
if (thread.CurrentException != null)
{
Console.WriteLine("ThreadID: {0:X}", thread.OSThreadId);
Console.WriteLine("Managed ThreadID: {0:X}", thread.ManagedThreadId);
Console.WriteLine("Exception Address: {0:X}", thread.CurrentException.Address);
ClrException ex = thread.CurrentException;
PrintException(ex);
if (ex.Inner !=null)
{
Console.WriteLine("Inner Exception:");
PrintException(ex.Inner);
}
}
}
}
}
打印exception的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26private static void PrintException(ClrException ex)
{
Console.WriteLine("Exception Type: {0}", ex.Type.Name);
try
{
Console.WriteLine("Message: {0}", ex.Message);
}
catch
{
Console.WriteLine("Message: No Message due to mini dump!");
}
Console.WriteLine("Call stack:");
Console.WriteLine(" {0,12:X} {1,12:X} {2}", "SP", "IP", "Function");
foreach (var frame in ex.StackTrace)
{
Console.WriteLine(" {0,12:X} {1,12:X} {2}", frame.StackPointer, frame.InstructionPointer, frame.DisplayString);
}
try
{
Console.WriteLine("HResult: {0:X}", ex.HResult);
}
catch
{
}
}
注意这里有两个try,是因为我发现如果打开的mini dump,在拿Messages和HResult时会出exception。
通过symbol路径寻找Dac的代码如下:(主要是上面提到的那篇文章的代码)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107public class DacLocator : IDisposable
{
const int sfImage = 0;
[ ]
static extern bool SymInitialize(IntPtr hProcess, String symPath, bool fInvadeProcess);
[ ]
static extern bool SymCleanup(IntPtr hProcess);
[ ]
static extern bool SymFindFileInPath(IntPtr hProcess, String searchPath, String filename, uint id, uint two, uint three, uint flags, StringBuilder filePath, IntPtr callback, IntPtr context);
[ ]
static extern LibrarySafeHandle LoadLibrary(String name);
[ ]
static extern bool FreeLibrary(IntPtr hModule);
private String searchPath;
private LibrarySafeHandle dbghelpModule;
private Process ourProcess;
private DacLocator(String searchPath)
{
this.searchPath = searchPath;
ourProcess = Process.GetCurrentProcess();
dbghelpModule = LoadLibrary("dbghelp.dll");
if (dbghelpModule.IsInvalid)
{
throw new Win32Exception(String.Format("Could not load dbghelp.dll: {0}", Marshal.GetLastWin32Error()));
}
if (!SymInitialize(ourProcess.Handle, searchPath, false))
{
throw new Win32Exception(String.Format("SymInitialize() failed: {0}", Marshal.GetLastWin32Error()));
}
}
public static DacLocator FromPublicSymbolServer(String localCache)
{
return new DacLocator(String.Format("SRV*{0}*http://msdl.microsoft.com/download/symbols", localCache));
}
public static DacLocator FromEnvironment()
{
String ntSymbolPath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH");
return new DacLocator(ntSymbolPath);
}
public static DacLocator FromSearchPath(String searchPath)
{
return new DacLocator(searchPath);
}
public String FindDac(ClrInfo clrInfo)
{
String dac = clrInfo.TryGetDacLocation();
if (String.IsNullOrEmpty(dac))
{
dac = FindDac(clrInfo.DacInfo.FileName, clrInfo.DacInfo.TimeStamp, clrInfo.DacInfo.FileSize);
}
return dac;
}
public String FindDac(String dacname, uint timestamp, uint fileSize)
{
// attempt using the symbol server
StringBuilder symbolFile = new StringBuilder(2048);
if (SymFindFileInPath(ourProcess.Handle, searchPath, dacname,
timestamp, fileSize, 0, 0x02, symbolFile, IntPtr.Zero, IntPtr.Zero))
{
return symbolFile.ToString();
}
else
{
throw new Win32Exception(String.Format("SymFindFileInPath() failed: {0}", Marshal.GetLastWin32Error()));
}
}
public void Dispose()
{
if (ourProcess != null)
{
SymCleanup(ourProcess.Handle);
ourProcess = null;
}
if (dbghelpModule != null && !dbghelpModule.IsClosed)
{
dbghelpModule.Dispose();
dbghelpModule = null;
}
}
class LibrarySafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public LibrarySafeHandle()
: base(true)
{
}
public LibrarySafeHandle(IntPtr handle)
: base(true)
{
this.SetHandle(handle);
}
protected override bool ReleaseHandle()
{
return FreeLibrary(this.handle);
}
}
}