DCSIMG
CLR 4 Does Not Use LoadLibrary to Load Assemblies - All Your Base Are Belong To Us

All Your Base Are Belong To Us

Mostly .NET internals and other kinds of gory details

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:

image 
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?

Comments

Alois Kraus said:

I did read the breaking changes for .NET 4 time ago and was shocked that LoadLibrary was no longer used to load assemblies. This will break my tracing configuration tool which enumeraters all modules from all processes and does display onle the processes which have my tracing library loaded. This makes it much easier to select the right process.

I know that during the development of Visual Studio 2010 memory consumption was a big problem because it constantly ran out of memory. Only then Microsoft did discover that every .NET assembly was loaded twice (once as memory mapped file and once via LoadLibrary) which meant that every assembly consumed twice as much virtual address space as needed. Now they have turned it off but I cannot say that I am happy with this decision. At least not until I can enumerate with a .NET helper class all memory mapped file objects and retrieve their name ranging from Windows XP up to Windows 7.

Yours,

 Alois Kraus

# January 17, 2011 10:34 PM

James L said:

Wow, that's a bit of a problem.  We were thinking of moving to .Net 4 soon, but we sometimes need to perform crash & hang dump analysis.  There must be a way to load modules in manually?  Does the new SOS not handle this it all?

# January 18, 2011 4:52 PM

Sasha Goldshtein said:

@Alois: You can (fairly easily) enumerate all handles in the system or those pertaining to a specific process. There is an excellent walkthrough here: forum.sysinternals.com/howto-enumerate-handles_topic18892.html

@James: It's not such a deal-breaker, the only thing missing is the mapping back to the source. You could always open the dump in Visual Studio itself to see the source mapping, and do the advanced analysis in a WinDbg instance.

# March 21, 2011 6:32 AM

Dan said:

Hi Sasha,

Given the above, do you have any thoughts on how to efficiently retrieve the file version for a 4.0 assembly in a dump file? It has me stumped at the moment.

lmvm doesn't work anymore. The information isn't included in !dumpassembly or !dumpmodule output.

The only way I've come up with so far is to use !dumpdomain, find the assembly in the list, use !savemodule on the module address, and then look at the properties on the resulting file, but that is less than ideal.

# August 1, 2012 12:34 AM

Sasha Goldshtein said:

@Dan: for GAC assemblies you'd have the version information in the directory path (which !dumpdomain outputs), but I'm afraid that for non-GAC assemblies I don't know a better way off the top of my head. Granted, the version information is there in memory (file header etc.), so it should be fairly easy to retrieve with some plugin work, but nothing out of the box AFAIK.

# August 11, 2012 12:53 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: