CLR 4 Does Not Use LoadLibrary to Load Assemblies
You may be asking yourself: …who cares? Well, first of all it’s good to know. I haven’t noticed a public service announcement to the above. It is an implementation detail, however—CLR assemblies are not even guaranteed to be implemented using files, not to mention DLL files in a specific format that are loaded using the LoadLibrary Win32 API.
However, there are several tools and scenarios which have come to rely on the fact the CLR loads assemblies using LoadLibrary. For example, up to CLR 4, if you wanted to know which .NET assemblies were loaded in your process, a fairly reliable heuristic would be to fire up Sysinternals Process Explorer and look at the DLLs view of a given process. This doesn’t work for CLR 4, as you can see here:
Note that although the .NET Assemblies tab shows clearly an assembly called “TheClassLibrary”, it’s not present in the DLLs list.
I wouldn’t be so concerned with not seeing a DLL in the modules list if there weren’t additional utilities relying on an assembly being “loaded” in the Windows loader sense. One of them has to do with debugging symbols.
SOS and similar tools need debugging symbols for managed code to display source file and line level information. For example, if you use the !CLRStack command to display the managed stack of a certain thread, you expect source-and-line information but it requires PDB files with debugging information for the binary whose methods appear on the stack.
What’s the problem? The problem is that if the .NET assemblies (DLLs) are not actually loaded into the process, the debugger does not load symbols for them. Without symbols, debugger extensions like SOS or SOSEX can’t use the debugging information, so here’s the kind of stack you would get:
0:000> !mk
ESP RetAddr
00:U 0043ef1c 75d473ea KERNEL32!ReadConsoleInternal+0x15
01:U 0043ef24 75d47041 KERNEL32!ReadConsoleA+0x40
02:U 0043efac 75ccf489 KERNEL32!ReadFileImplementation+0x75
03:U 0043eff4 0fa8cc7c MSVCR100D!_read_nolock+0x62c [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 230]
04:U 0043f088 0fa8c5c9 MSVCR100D!_read+0x219 [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 92]
05:U 0043f0d8 0f9e1093 MSVCR100D!_filbuf+0x113 [f:\dd\vctools\crt_bld\self_x86\crt\src\_filbuf.c @ 136]
06:U 0043f100 0f9df5ab MSVCR100D!getc+0x20b [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetc.c @ 75]
07:U 0043f15c 0f9df660 MSVCR100D!_fgetchar+0x10 [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 37]
08:U 0043f168 0f9df67a MSVCR100D!getchar+0xa [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 47]
09:U 0043f170 0f8f1396 TheUnmanagedDll!fnTheUnmanagedDll+0x26 [c:\temp\theunmanageddll.cpp @ 12]
…snipped…
0b:M 0043f2a8 002900c7 TheClassLibrary.TheClass.TheMethod()(+0x1 IL)(+0x6 Native)
0c:M 0043f2b0 0029008c AssemblyLoadingChange.Program.Main(System.String[])(+0x1 IL)(+0x7 Native) [c:\temp\Program.cs @ 12]
0d:U 0043f2bc 62ea21db clr!CallDescrWorker+0x33
…snipped for brevity…
Note the bold lines—there are three frames here: frame 09 is an unmanaged frame for which we have symbols, so we get line-level information. Frame 0c is a managed frame that lies in an .exe file, which is always loaded so there are symbols loaded for it as well. But frame 0b is a managed frame that lies in a managed assembly (DLL)—it’s not loaded, so there are no symbols and no line-level information available.
I’m sure these aren’t the only two examples where you would want .NET assemblies to be loaded into the process in the Windows loader sense. What are yours?