Published
Friday, August 15, 2008 8:54 AM
by
martin
Recently in our app we were getting a System.IO.FileNotFoundException. The trouble was, the exception detail didn't tell us what particular file was missing. Our .NET app has a dependency on a COM DLL, which in turn has good, old-fashioned, dynamic-linkage dependencies on other DLLs.
Initially, I tried Process Monitor from sysinternals to log all filesystem access, but no joy there. This clearly wasn't normal File I/O, it was a module load that was failing. By the way, you can run those sysinternals utilities directly from http://live.sysinternals.com now, which is handy.
There might be a simple way to resolve this problem, but I couldn't think of it, so I resorted to a fairly obscure method, involving WinDbg. When I was at Microsoft we used to joke that Microsoft really has only one tool, and WinDbg is it. You can do everything in there. It's not easy to get started with it, but I do recommend persevering. It's best if you work near someone that already knows it, and get them to show you.
I guessed that the failure was occurring in a call to LoadLibrary or LoadLibraryEx, so I wanted to put a breakpoint on those functions and look at the string that was passed in - the name of the module to load. Here are the commands I entered into WinDbg...
bp kernel32!LoadLibraryW "r $t0 = ebp+8; r $t1 = @@c++( *((long *)@$t0 )); dc $t1 L64; g"
bp kernel32!LoadLibraryExW "r $t0 = ebp+8; r $t1 = @@c++( *((long *)@$t0 )); dc $t1 L64; g"
Each command sets a breakpoint in the relevant function, and defines a command to run when the debugger hits that breakpoint. In each case I find a pointer to the first parameter (which is at ebp+8), then I dereference this pointer and dump out 64 characters found at that address. The final "g" is to make the debugger automatically continue from the breakpoint.
What did this achieve? Well, running the app in the debugger dumped out all the names of the modules that were loaded by these two functions. When the FileNotFoundException was thrown, I just had to look at the most recent filename dumped out and that was the module that failed to load.
Almost done, but not quite. The name I just found is of the module that couldn't be loaded, but it could be found alright. Let's call it "banana.dll". It couldn't be loaded because it had a dependency on another DLL that couldn't be found. To find out what that might be, I did a "dumpbin /imports banana.dll". That told me what DLLs are imported by banana.dll. I just had to look through that list to find the one that was missing from my filesystem.
This is a case of "blogging for posterity" in case I ever need to do this again. Hopefully others can benefit from it too, though :-)
Finally, I have to question the wisdom of using FileNotFoundException to represent module load failures. No wonder the exception detail couldn't tell me which file was missing. Perhaps there should be a separate exception class for this?
Update: there is a simpler way :-)
A colleague just told me that Depends.exe can solve this problem. Open your .exe with Depends.exe and use the Profile menu. It hooks into the LoadLibrary (and other) calls and dumps out any failures. That would have saved me some time...