CLR 4 Does Not Use LoadLibrary to Load Assemblies

January 16, 2011

8 comments

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?

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

8 comments

  1. Alois KrausJanuary 17, 2011 ב 10:34 PM

    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

    Reply
  2. James LJanuary 18, 2011 ב 4:52 PM

    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?

    Reply
  3. Sasha GoldshteinMarch 21, 2011 ב 6:32 AM

    @Alois: You can (fairly easily) enumerate all handles in the system or those pertaining to a specific process. There is an excellent walkthrough here: http://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.

    Reply
  4. DanAugust 1, 2012 ב 12:34 AM

    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.

    Reply
  5. Sasha GoldshteinAugust 11, 2012 ב 12:53 PM

    @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.

    Reply
  6. EvgenyApril 25, 2015 ב 11:47 AM

    It seems that LoadLibrary is being called in newer .NET versions, do you know when was it changed?

    Reply
    1. Sasha Goldshtein
      Sasha GoldshteinJune 5, 2015 ב 12:21 PM

      Nope, I don’t. But it’s certainly a welcome change.

      Reply
  7. EvgenyAugust 16, 2016 ב 2:09 AM

    I was actually wrong in my previous reply, they only seem to be calling LoadLibrary in CoreCLR.

    The managed DLLs however, supposed to appear in Process Explorer as it displays all memory mapped files (no idea why it’s missing in your screenshot).

    A workaround for the debugger issue is to execute:
    .reload /f image_name=image_base_address
    to give the image’s location.

    Reply