During my journeys through CorDebug I’ve often needed to access Windows operating system image files that I didn’t have on my local machine. This is actually a pretty normal situation to be in when debugging crash dumps. More often that not the crash dump you’ll be analyzing would have originated from a machine other than your own, like one of your servers. That server would most likely have been running operating system files with a version different to that running on your local machine. Your local machine might not even have the files at all. Naturally that makes analyzing the crash dump a challenge as all of the data contained in it references the files on the machine it was created on, not the machine it’s being analyzed on. This is especially true of “mscordacwks.dll”, which is vital when it comes to analyzing crash dumps of .Net processes (at least when using WinDBG and SOS).
I knew that it was possible to download symbol files from the Microsoft symbol server. That lead me to wonder whether it was possible to download operating system files as well. I asked in the MSDN forums and my suspicions were confirmed: you can indeed download image files from the symbol server. Finding out how to download them wasn’t so easy though.
I couldn’t find any mention of an API call that could be used to download image files, so I eventually just went through the Symbol Handling API (scroll down to the “Symbol Handler” heading) method by method until I found SymFindFileInPath. This looked pretty promising, since the parameters matched the information I had on hand. I tried it and it worked, one image file downloaded! What follows is a description of the C# code involved to get this working.
DbgHelp and the Symbol Handling API
First things first: the functions I’ll be using are all contained in the DbgHelp.dll library. This library contains various debugging functions, mostly importantly: symbol handling functions and minidump handling functions. For those interested you can read my write up on the minidump functions here. The library is included as part of the operating system, however that version is almost never going to be the most recent version, so you should always get the latest from the Debugging Tools For Windows page. See here for more details. In addition, for the Symbol handling functions you will also need “symsrv.dll”, which does not seem to ship with operating system, but is included with the Debugging Tools. Make sure you include both of these files in the same folder as the one your .exe is in.
For the basic functionality I’ll be using 3 functions:
To enable logging (which I highly recommend when you first start) I’ll use:
If you haven’t called native methods from C# in the past then I recommend you quickly read my previous post which will take you through the basics. Now lets move on to writing the C# versions of the functions that will be used.
SymInitialize initializes the symbol handler for a process. It’s important to note the “for a process” part. This call should only be invoked once during the lifetime of your process, unless you have called SymCleanup, in which case it can be called again.
It’s native declaration is:
BOOL WINAPI SymInitialize( _In_ HANDLE hProcess, _In_opt_ PCTSTR UserSearchPath, _In_ BOOL fInvadeProcess );
Converting that to C# we get:
[DllImport("dbghelp.dll", CharSet = CharSet.Unicode)] public static extern bool SymInitialize( IntPtr hProcess, [MarshalAs(UnmanagedType.LPWStr)] string UserSearchPath, bool fInvadeProcess);
- hProcess. This is a handle to your process. You can use System.Diagnostics.Process.GetCurrentProcess() to get access to it.
- UserSearchPath. This parameter tells the symbol handler where to look for symbols. It is a delimited list of folders or symbol servers that should be searched when attempting to find a requested symbol. If you pass null then either the current directory or environment variables will be used. I have the _NT_SYMBOL_PATH environment variable set to symsrv*symsrv.dll*c:\symbols*http://msdl.microsoft.com/download/symbols, so I pass null to make sure the environment variable is used.
- fInvadeProcess. I’ll be setting this to false as I’m not interesting in looking up the symbols for my current process (I want the symbols of the crash dump I’m looking at instead).
Call this function once you are done using the symbol handler in order for it’s resources to be cleaned up.
BOOL WINAPI SymCleanup( _In_ HANDLE hProcess );
[DllImport("dbghelp.dll")] public static extern bool SymCleanup(IntPtr hProcess);
- hProcess. This is the same process Id you passed in to SymInitialize.
Now that we can initialize and cleanup the symbol handler it’s time to put it to use.
As I mentioned I’m going to be using SymFindFileInPath to download Windows image files. This function can actually be used in one of two ways: to either download pdb files or image files. The values you pass in to the parameters will depend on which kind of files you intend retrieving. I’ll naturally be focusing on the values needed for image files.
BOOL WINAPI SymFindFileInPath( _In_ HANDLE hProcess, _In_opt_ PCTSTR SearchPath, _In_ PCTSTR FileName, _In_opt_ PVOID id, _In_ DWORD two, _In_ DWORD three, _In_ DWORD flags, _Out_ PTSTR FilePath, _In_opt_ PFINDFILEINPATHCALLBACK callback, _In_opt_ PVOID context );
[DllImport("dbghelp.dll", CharSet=CharSet.Unicode)] public static extern bool SymFindFileInPath( IntPtr hProcess, [MarshalAs(UnmanagedType.LPWStr)] string SearchPath, [MarshalAs(UnmanagedType.LPWStr)] string FileName, IntPtr id, Int32 two, Int32 three, Int32 flags, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder filePath, IntPtr callback, IntPtr context);
- hProcess. This is the same process Id you passed in to SymInitialize.
- SearchPath. I initialized the search path when I called SymInitialize, so I’ll be passing null here, indicating that the current search path should be used.
- FileName. This is the name of the file you are looking for e.g. mscorwks.dll
- id. For image files this is the TimeDateStamp of the original image as found in its PE header e.g. 0x4D8C16AC
- two. For image files this is the SizeOfImage field, also extracted from the PE header.
- three. For image files this is 0 (unused).
- flags. This parameter indicates the type of data in the id parameter. I’ll be passing SSRVOPT_DWORD as the id parameter contains a DWORD.
- filePath. This is the path of the returned file. A note on marshalling this parameter: I’ve flag the function with “CharSet=CharSet.Unicode”, this allows me to use the Unicode version of it, and thus I marshal the strings as wide strings using UnmanagedType.LPWStr.
- callback. This is a user defined function that you can use to determine whether searching should stop or continue when a file is located. I pass null for this parameter as I have no need to use it.
- context. You can use this parameter to pass custom data to the callback function. I haven’t used the callback function, so I pass null.
Here’s an example of what a call would look like:
const int MAX_PATH = 260; int processId = System.Diagnostics.Process.GetCurrentProcess().Id; StringBuilder outPath = new StringBuilder(MAX_PATH); success = SymFindFileInPath((IntPtr)processId, null, "mscorwks.dll", (IntPtr) 1301026476, // 0x4D8C16AC 5943296, // 0x005AB000 0, 2, //SSRVOPT_DWORD outPath, IntPtr.Zero, IntPtr.Zero);
That’s all you need to be able to download image files from a symbol server. It took a bit of work to figure out and get it working but at the end it’s pretty simple.
One challenge getting this working is figuring out what’s gone wrong when it doesn’t work! I’ve lost quite a bit of time during my testing trying to figure out why it was returning false with a GetLastError() of ERROR_FILE_NOT_FOUND. In that case it was because I didn’t have symsrv.dll in the same folder as my test application.