VS2005

How Windows CRT Checks For Managed Module

While debugging an issue I came across an interesting piece of code in Windows CRT source. So far I have been using the code below to determine if an executable is managed or native. Basically I am just checking for the presence of IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR Data Directory entry in PE headers.

 
int CheckCLRHeader(PBYTE pbFile)
{
    PIMAGE_DOS_HEADER pDOSHeader;
    PIMAGE_NT_HEADERS pNTHeader;
    PIMAGE_OPTIONAL_HEADER32 pNTHeader32;
    PIMAGE_OPTIONAL_HEADER64 pNTHeader64;
    PIMAGE_DATA_DIRECTORY pDataDirectory;
 
	if(NULL == pbFile)
	{
		cout << "Invalid file" << endl;
		return 0;
	}
 
	pDOSHeader = reinterpret_cast
< PIMAGE_DOS_HEADER >(pbFile);
	if(IMAGE_DOS_SIGNATURE != pDOSHeader->e_magic)
	{
		cout << "Cannot find DOS header. Not an executable file" << endl;
		return 0;
	}
 
	pNTHeader = ImageNtHeader(pbFile);
	if(NULL == pNTHeader)
	{
		cout << "Cannot find PE header. Invalid or corrupt file" << endl;
		return 0;
	}
 
	if(IMAGE_NT_SIGNATURE != pNTHeader->Signature)
	{
		cout << "Cannot fine PE signature. Invalid or corrupt file" << endl;
		return 0;
	}
 
	switch(pNTHeader->OptionalHeader.Magic)
	{
		case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
			pNTHeader32 = reinterpret_cast
< PIMAGE_OPTIONAL_HEADER32 >(&pNTHeader->OptionalHeader);
			if(pNTHeader32->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
			{
				cout << "No CLR Data Dictionary. Not a Managed Assembly" << endl;
				return 0;
			}
			pDataDirectory = pNTHeader32->DataDirectory;
			break;
 
		case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
			pNTHeader64 = reinterpret_cast
< PIMAGE_OPTIONAL_HEADER64 >(&pNTHeader->OptionalHeader);
			if(pNTHeader64->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
			{
				cout << "No CLR Data Dictionary. Not a Managed Assembly" << endl;
				return 0;
			}
			pDataDirectory = pNTHeader64->DataDirectory;
			break;
 
		default:
			cout << "Invalid NT header. Invalid or corrupt file" << endl;
			return 0;
	}
 
	if(NULL == pDataDirectory)
	{
		cout << "Cannot find data directories. Invalid or corrupt file" << endl;
		return 0;
	}
 
	if(0 == pDataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress ||
		0 == pDataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size)
	{
		cout << "COM Data Directory not found. Not a Managed Assembly" << endl;
		return 0;
	}
 
	cout << "Managed Assembly" << endl;
 
	return 1;
}
 

The code for check_managed_app function in crtexe.c is similar but slightly different in how it checks for the same IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR but only checks for presence of VirtualAddress.

 
/***
*check_managed_app() - Check for a managed executable
*
*Purpose:
*       Determine if the EXE the startup code is linked into is a managed app
*       by looking for the COM Runtime Descriptor in the Image Data Directory
*       of the PE or PE+ header.
*
*Entry:
*       None
*
*Exit:
*       1 if managed app, 0 if not.
*
*Exceptions:
*
*******************************************************************************/
 
static int __cdecl check_managed_app (
        void
        )
{
        PIMAGE_DOS_HEADER pDOSHeader;
        PIMAGE_NT_HEADERS pPEHeader;
        PIMAGE_OPTIONAL_HEADER32 pNTHeader32;
        PIMAGE_OPTIONAL_HEADER64 pNTHeader64;
 
        pDOSHeader = (PIMAGE_DOS_HEADER)&amp;__ImageBase;
        if ( pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE )
            return 0;
 
        pPEHeader = (PIMAGE_NT_HEADERS)((char *)pDOSHeader +
                                        pDOSHeader->e_lfanew);
        if ( pPEHeader->Signature != IMAGE_NT_SIGNATURE )
            return 0;
 
        pNTHeader32 = (PIMAGE_OPTIONAL_HEADER32)&pPEHeader->OptionalHeader;
        switch ( pNTHeader32->Magic ) {
        case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
            /* PE header */
            /* prefast assumes we are overflowing __ImageBase */
#pragma warning(push)
#pragma warning(disable:26000)
            if ( pNTHeader32->NumberOfRvaAndSizes <=
                    IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
                return 0;
#pragma warning(pop)
            return !! pNTHeader32 ->
                      DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
                      VirtualAddress;
        case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
            /* PE+ header */
            pNTHeader64 = (PIMAGE_OPTIONAL_HEADER64)pNTHeader32;
            if ( pNTHeader64->NumberOfRvaAndSizes <=
                    IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )
                return 0;
            return !! pNTHeader64 ->
                      DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] .
                      VirtualAddress;
        }
 
        /* Not PE or PE+, so not managed */
        return 0;
}
 

I came across this code while debugging past the main() function. I thought this might be a new piece of code in CRT source that comes with VS2008 only to be proven wrong when searching through the CRT source that comes with all versions of Visual Studio starting from VS2002. Another interesting discovery (at least for me) is that similar code is in various CRT source files. In VS2008 CRT source the files crt0dat.c, crt0.c and crtexe.c contain check_managed_app function in Win32 CRT and crt0dat.c in WinCE CRT. In VS2005 the same function is in crt0.c, crt0dat.c and crtexe.c. In VS2003 and VS2002 it is in crt0.c and crtexe.c. There are other gems of code in CRT source waiting to be discovered!

.NET Framework
VS2005
VS2008

Comments Off

Permalink

How to determine event subscribers

While debugging a reasonably complex .NET application, many times you may come across a situation where you want to know which different methods have subscribed to your event. If you are using VS2005 or VS2008 then DebuggerTypeProxyAttribute, a relatively unknown feature of Visual Studio, comes to your rescue. For a long time Visual Studio has allowed developers to customize the information about the variable that is displayed in the IDE during debugging. Before VS2005 the customization was done using mcee_cs.dat (C#), mcee_mc.dat (MC++), and autoexp.dat (C++) files. Starting with VS2005, this customization is taken to next level. VS2005 introduces two new attributes, DebuggerDisplayAttribute and DebuggerTypeProxyAttribute, that allows you to change the information displayed in the Visual Studio debugger. Typically one would apply these attributes to the class in the code which the VS debugger will use to display information. However, there is a trick in which you can use these attributes outside of the original code. This means that you can change the information displayed about ANY class, even the classes that you did not write yourself. VS2005 and above use AutoExp.dll which provides display information about various types to the debugger. The code for AutoExp.dll is provided in AutoExp.cs file with the Visual Studio installation in Common7\Packages\Debugger\Visualizers\Original\ folder. You can add your own code to this file, compile the AutoExp.dll, and place it in either the Common7\Packages\Debugger\Visualizers folder or in the My Documents\Visual Studio 2005 (or Visual Studio 2008)\Visualizers folder. I learned about this trick a while back from John Robbins' excellent book Debugging .NET 2.0 Applications. It turns out that Visual Studio loads all the valid assemblies from the Visualizers folder, and if the assembly contains Visualizer or any debugger related objects then it will use it during the debugging session. Now back to the topic of this post; to easily display which methods have subscribed to an event I wrote a class called MulticastDelegateDebuggerProxy for MulticastDelegate class. Since, all the events are delegates that are derived from MulticastDelegate class, my MulticastDelegateDebuggerProxy class will be used for all the events. The DebuggerTypeProxyAttribute is used at the assembly level to tell Visual Studio debugger that MulticastDelegateDebuggerProxy class is the proxy for MulticastDelegate class. The code for MulticastDelegateDebuggerProxy is listed below.

 
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
 
[assembly: DebuggerTypeProxy(typeof(MulticastDelegateDebuggerProxy),
		Target = typeof(MulticastDelegate))]
 
public class MulticastDelegateDebuggerProxy
{
	private string[] m_methods;
 
	public MulticastDelegateDebuggerProxy(System.MulticastDelegate del)
	{
		m_methods = GetMethodList(del);
	}
 
	[DebuggerDisplay(@"Subscribers:\{{Methods.Length}}")]
	public string[] Methods
	{
		get { return m_methods; }
	}
	private string[] GetMethodList(System.MulticastDelegate myEvent)
	{
		string retType = myEvent.Method.ReturnType.Name;
 
		Delegate[] delegates = myEvent.GetInvocationList();
		string[] methods = new string[delegates.Length];
 
		for (int i = 0; i &lt; delegates.Length; ++i)
		{
			Delegate del = delegates[i];
			MethodInfo m = del.Method;
			string accessModifier =
				m.IsPrivate ? "private" : (m.IsPublic ? "public" : (m.IsFamily ? "protected" : ""));
			StringBuilder sb = new StringBuilder();
			foreach (ParameterInfo param in m.GetParameters())
			{
				sb.Append(param.ParameterType.FullName).Append(" ").Append(param.Name).Append(",");
			}
			if (sb.Length &gt; 0)
			{
				sb.Remove(sb.Length - 1, 1);
			}
			string isStatic = m.IsStatic ? "static" : string.Empty;
			string isVirtual = m.IsVirtual ?
				(((m.Attributes &amp; MethodAttributes.NewSlot) == MethodAttributes.ReuseSlot)
				? "override" : "virtual") : string.Empty;
			methods[i] = string.Format("{0} {1} {2} {3} {4}.{5}({6})",
				accessModifier, isStatic, isVirtual, retType, m.ReflectedType.FullName, m.Name, sb.ToString());
		}
 
		return methods;
	}
}

The information displayed in VS2005 by default is shown below.
Default Event information in VS2005

The information displayed in VS2005 with MulticastDelegateDebuggerProxy is shown below. The MulticastDelegateDebuggerProxy displays fully qualified method name and the parameter names used in the method.

Event information in VS2005 with MulticastDelegateDebuggerProxy
In VS2008, the default information displayed is slightly better than VS2005 as it shows _invocationList by default which can be drilled down to the actual methods. However, the default display can get ambiguous as shown in the screenshot below. The MulticastDelegateDebuggerProxy does a much better job at displaying the same information.

Default event information in VS2008

.NET Framework
Debugging
VS2005
VS2008

Comments Off

Permalink