Tuesday, October 20, 2009

Dependency Checker

This is a simple light weight utility to find which all dll/exe/ocx in a folder uses a particular dll.
Source File(DependsChecker_Src.zip)

My development environment usually includes large number of binaries. And I was working in some core library module which is used by many other dlls. So whenever I change something in the header file of this code library, I have to rebuild all the dependent libraries. Rebuilding the entire environment was not practical all times. So I had to create such an utility so that I can find out who all uses this code library and can rebuild those modules only.

How to Use

  1. In the “Dll Name” edit box, enter the name of the dll for which we have to search for.
  2. In the “Path” edit box, enter the folder in which we have to search for dlls/exe/ocx that uses dll entered in the “Dll Name” edit box.
  3. Click “Start” button.

Upon clicking the Start button, the list control starts’ populating the Dll, OCX and EXE’s that uses the specified dll. Please note that this application will not list dynamic dependencies and so we cannot enter ocx or COM dll as “Dll Name”.

How it works

When the start button is clicked, the application loop through each Dll, OCX and EXE’s in the specified folder. It opens each binary, parse the PE (Portable Executable) file format.
For example consider that user32.dll is specified in the "Dll name" edit box and the path is "c:\windows\system32". When user clicks on the “start” button, the application took a loop to find EXEs, dll's and OCXs in the specified folder. Intitally it enumarets all the EXEs. Suppose we got "calc.exe" as file name. The next thing to do is to map the binary file to memory. This is performed with the help of CreateFileMapping() and MapViewOfFile() function.

CFile fl;
if( !fl.Open( csExeName, CFile::modeReadCFile::shareDenyNone, 0 ))
CString csMsg;
csMsg.Format( _T(" Failed to open file %s"), csExeName );
AfxMessageBox( csMsg );
HANDLE hSM = CreateFileMapping( (HANDLE)fl.m_hFile, 0,PAGE_READONLY,0,0, _T("some_sM"));
LPVOID pBinary = MapViewOfFile( hSM,FILE_MAP_READ,0,0, fl.GetLength());
if( CheckForDependecy( csDllName, pBinary ))
m_List.InsertItem( 0, fl.GetFileName() );
UnmapViewOfFile( pBinary );
CloseHandle( hSM );

As you can see in the above code, after the binary is loaded to the memory, the memory address is passed to the CheckForDependecy() function. The function cast the memory address to a IMAGE_DOS_HEADER pointer.
After this, it will try to get the IMAGE_NT_HEADERS from pDOSHeader. Usually the 16 bit binaries will not be having this header. So we will ignore such binaries at this point.

// Get the PE header.
PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);
if( IsBadReadPtr( pNTHeader, sizeof( IMAGE_NT_HEADERS)))
return false;

May be because of the property of data in the file, it may occur that the pNTHeader points to a valid memory. So we will add one more checking to confirm that we are dealing with PE file (The famous DRWATSON.EXE fails at this checking!!!ya it is not a PE file).

char* pSig = (char*)&pNTHeader->Signature;
if( pSig[0] != 'P' pSig[1] != 'E')
return false;

The next task is to find the IMAGE_IMPORT_DESCRIPTOR. The IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. VirtualAddress holds the relative address of IMAGE_IMPORT_DESCRIPTOR from the starting of the memory. We can use the ImageRvaToVa() API to convert the relative adress to virtual address. There's one IMAGE_IMPORT_DESCRIPTOR for each imported executable. So we will loop through each item and check whether it is for importing the specified dll (“user32.dll” in our case )

PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageRvaToVa( pNTHeader, lpStartAddress, pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
VirtualAddress, 0 );
while( pImportDesc && pImportDesc->Name )
// pImportDesc->Name holds the name of the import library
PVOID pName = ImageRvaToVa( pNTHeader, lpStartAddress, pImportDesc->Name , 0 );
PSTR szCurrMod = (PSTR)pName;
if( IsBadReadPtr( szCurrMod, 1 ))
CString cs = szCurrMod;
if( 0 == cs.CollateNoCase( csDllName ))
// everything pass. We have found one dependency
return true;
// Move to the next import library

And for the records, let me say that, there are 1111 files in my system32 folder that has dependency with the user32.dll( Vista SP1 ).