/*++ Copyright (c) 2000 Microsoft Corporation Module Name: dc.cpp Abstract: Command line utility for dumping importing information from DLL's and executables. Command Line Options: /v Verbose mode /s:Func Only display functions matching search string "Func" /o:File Send output to File /f Sort by function, not by module. History: 05/10/2000 t-michkr Created --*/ #include #include #include #include #include #include // For some reason, this is needed to compile in // the sdktools tree. #define strdup _strdup #define stricmp _stricmp #define strnicmp _strnicmp // Structure containing all info relevent to an imported function. struct SFunction { // The name this function is imported by. char* m_szName; // Ordinal number, Win3.1 compatibility. int m_iOrdinal; // Lookup index into a DLL's export table // for quick patching. int m_iHint; // Starting address of the function. DWORD m_dwAddress; // Whether or not it is a delayed import. bool m_fDelayedImport; // Forwarded function name char* m_szForward; // Link to next function SFunction* m_pNext; SFunction() { m_szName = m_szForward = 0; m_iOrdinal = m_iHint = -1; m_dwAddress = static_cast(-1); m_fDelayedImport = false; m_pNext = 0; } }; // A module used by the executable (ie, DLL's) struct SModule { // The name of this module char* m_szName; // All functions imported from this module SFunction* m_pFunctions; // Link to next module SModule* m_pNext; SModule() { m_szName = 0; m_pFunctions = 0; m_pNext = 0; } }; // All modules imported by the executable. SModule* g_pModules = 0; void InsertFunctionSorted(SModule* pMod, SFunction* pFunc) { // Special case, insert at front if(pMod->m_pFunctions == 0 || stricmp(pMod->m_pFunctions->m_szName, pFunc->m_szName) > 0) { pFunc->m_pNext = pMod->m_pFunctions; pMod->m_pFunctions = pFunc; return; } SFunction* pfPrev = pMod->m_pFunctions; SFunction* pfTemp = pMod->m_pFunctions->m_pNext; while(pfTemp) { if(stricmp(pfTemp->m_szName, pFunc->m_szName) > 0) { pFunc->m_pNext = pfTemp; pfPrev->m_pNext = pFunc; return; } pfPrev = pfTemp; pfTemp = pfTemp->m_pNext; } // Insert at end. pFunc->m_pNext = 0; pfPrev->m_pNext = pFunc; } void InsertModuleSorted(SModule* pMod) { // Special case, insert at front if(g_pModules == 0 || stricmp(g_pModules->m_szName, pMod->m_szName) > 0) { pMod->m_pNext = g_pModules; g_pModules = pMod; return; } SModule* pmPrev = g_pModules; SModule* pmTemp = g_pModules->m_pNext; while(pmTemp) { if(stricmp(pmTemp->m_szName, pMod->m_szName) > 0) { pMod->m_pNext = pmTemp; pmPrev->m_pNext = pMod; return; } pmPrev = pmTemp; pmTemp = pmTemp->m_pNext; } // Insert at end. pMod->m_pNext = 0; pmPrev->m_pNext = pMod; } // Print a message about the last error that occurred. void PrintLastError() { // Get the message string void* pvMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&pvMsgBuf),0, 0); // Print it. fprintf(stderr, "%s\n", pvMsgBuf); // Free the buffer. LocalFree(pvMsgBuf); } /************************************************************************* * LinkName2Name * *************************************************************************/ void LinkName2Name( char* szLinkName, char* szName) { /* * the link name is expected like ?Function@Class@@Params * to be converted to Class::Function */ static CHAR arrOperators[][8] = { "", "", "new", "delete", "=", ">>", "<<", "!", "==", "!=" }; DWORD dwCrr = 0; DWORD dwCrrFunction = 0; DWORD dwCrrClass = 0; DWORD dwSize; BOOL fIsCpp = FALSE; BOOL fHasClass = FALSE; BOOL fIsContructor = FALSE; BOOL fIsDestructor = FALSE; BOOL fIsOperator = FALSE; DWORD dwOperatorIndex = 0; char szFunction[1024]; char szClass[1024]; if (*szLinkName == '@') szLinkName++; dwSize = lstrlen(szLinkName); /* * skip '?' */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] == '?') { dwCrr++; fIsCpp = TRUE; } break; } /* * check to see if this is a special function (like ??0) */ if (fIsCpp) { if (szLinkName[dwCrr] == '?') { dwCrr++; /* * the next digit should tell as the function type */ if (isdigit(szLinkName[dwCrr])) { switch (szLinkName[dwCrr]) { case '0': fIsContructor = TRUE; break; case '1': fIsDestructor = TRUE; break; default: fIsOperator = TRUE; dwOperatorIndex = szLinkName[dwCrr] - '0'; break; } dwCrr++; } } } /* * get the function name */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] != '@') { szFunction[dwCrrFunction] = szLinkName[dwCrr]; dwCrrFunction++; dwCrr++; } else { break; } } szFunction[dwCrrFunction] = '\0'; if (fIsCpp) { /* * skip '@' */ if (dwCrr < dwSize) { if (szLinkName[dwCrr] == '@') { dwCrr++; } } /* * get the class name (if any) */ while (dwCrr < dwSize) { if (szLinkName[dwCrr] != '@') { fHasClass = TRUE; szClass[dwCrrClass] = szLinkName[dwCrr]; dwCrrClass++; dwCrr++; } else { break; } } szClass[dwCrrClass] = '\0'; } /* * print the new name */ if (fIsContructor) { sprintf(szName, "%s::%s", szFunction, szFunction); } else if (fIsDestructor) { sprintf(szName, "%s::~%s", szFunction, szFunction); } else if (fIsOperator) { sprintf(szName, "%s::operator %s", szFunction, arrOperators[dwOperatorIndex]); } else if (fHasClass) { sprintf(szName, "%s::%s", szClass, szFunction); } else { sprintf(szName, "%s", szFunction); } } // Get function forwarding information for // imported functions. // This is done by loading the module and sniffing // its export table. bool GetForwardFunctions(SModule* pModule) { // Open the DLL module. char szFileName[1024]; char* pstr; // Search the path and windows directories for it. if(SearchPath(0, pModule->m_szName, 0, 1024, szFileName, &pstr)==0) return false; HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { PrintLastError(); return false; } HANDLE hMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0); if(hMap == 0) { PrintLastError(); return false; } void* pvFileBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if(!pvFileBase) { PrintLastError(); return false; } // Get the MS-DOS compatible header PIMAGE_DOS_HEADER pidh = reinterpret_cast(pvFileBase); if(pidh->e_magic != IMAGE_DOS_SIGNATURE) { fprintf(stderr, "File is not a valid executable\n"); return false; } // Get the NT header PIMAGE_NT_HEADERS pinth = reinterpret_cast( reinterpret_cast(pvFileBase) + pidh->e_lfanew); if(pinth->Signature != IMAGE_NT_SIGNATURE) { // Not a valid Win32 executable, may be a Win16 or OS/2 exe fprintf(stderr, "File is not a valid executable\n"); return false; } // Get the other headers PIMAGE_FILE_HEADER pifh = &pinth->FileHeader; PIMAGE_OPTIONAL_HEADER pioh = &pinth->OptionalHeader; PIMAGE_SECTION_HEADER pish = IMAGE_FIRST_SECTION(pinth); // If no exports, we're done. if(pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) return true; DWORD dwVAImageDir = pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; // Locate the section with this image directory. for(int i = 0; i < pifh->NumberOfSections; i++) { if( (dwVAImageDir >= pish[i].VirtualAddress) && (dwVAImageDir < (pish[i].VirtualAddress + pish[i].SizeOfRawData))) { pish = &pish[i]; break; } } if(i == pifh->NumberOfSections) { fprintf(stderr, "Could not locate export directory section\n"); return false; } DWORD dwBase = reinterpret_cast(pvFileBase) + pish->PointerToRawData - pish->VirtualAddress; PIMAGE_EXPORT_DIRECTORY pied = reinterpret_cast(dwBase + dwVAImageDir); DWORD* pdwNames = reinterpret_cast(dwBase + pied->AddressOfNames); WORD* pwOrdinals = reinterpret_cast(dwBase + pied->AddressOfNameOrdinals); DWORD* pdwAddresses = reinterpret_cast(dwBase + pied->AddressOfFunctions); for(unsigned hint = 0; hint < pied->NumberOfNames; hint++) { char* szFunction = reinterpret_cast(dwBase + pdwNames[hint]); // Duck out early if this function isn't used in the executable. SFunction* pFunc = pModule->m_pFunctions; while(pFunc) { if(strcmp(pFunc->m_szName, szFunction)==0) break; pFunc = pFunc->m_pNext; } if(pFunc == 0) continue; int ordinal = pied->Base + static_cast(pwOrdinals[hint]); DWORD dwAddress = pdwAddresses[ordinal-pied->Base]; // Check if this function has been forwarded to another DLL if( ((dwAddress) >= dwVAImageDir) && ((dwAddress) < (dwVAImageDir + pioh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size))) { char* szForward = reinterpret_cast(dwBase + dwAddress); pFunc->m_szForward = strdup(szForward); } } UnmapViewOfFile(pvFileBase); CloseHandle(hMap); CloseHandle(hFile); return true; } // Look through the import directory, and build a list of all imported functions bool ParseImportDirectory(PIMAGE_FILE_HEADER pifh, PIMAGE_OPTIONAL_HEADER pioh, PIMAGE_SECTION_HEADER pish, void* pvFileBase, bool fDelayed) { // Get which directory we want (normal imports or delayed imports) DWORD dwDir = (fDelayed) ? IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT : IMAGE_DIRECTORY_ENTRY_IMPORT; // Bail if no imports if(pioh->DataDirectory[dwDir].Size == 0) return true; // Locate the import directory in the image. PIMAGE_SECTION_HEADER pishImportDirectory = 0; DWORD dwVAImageDir = pioh->DataDirectory[dwDir].VirtualAddress; for(int i = 0; i < pifh->NumberOfSections; i++) { if((dwVAImageDir >= pish[i].VirtualAddress) && dwVAImageDir < (pish[i].VirtualAddress + pish[i].SizeOfRawData)) { // This is it. pishImportDirectory = &pish[i]; break; } } if(pishImportDirectory == 0) { fprintf(stderr, "Cannot locate %s%s\n",((fDelayed) ? "delayed " : ""), "import directory section"); return false; } // Get the base address for the image. DWORD dwBase = reinterpret_cast(pvFileBase) + pishImportDirectory->PointerToRawData - pishImportDirectory->VirtualAddress; // Get the import descriptor array PIMAGE_IMPORT_DESCRIPTOR piid = reinterpret_cast(dwBase + dwVAImageDir); // Loop through all imported modules while(piid->FirstThunk || piid->OriginalFirstThunk) { SModule* psm = new SModule; psm->m_szName = strdup(reinterpret_cast(dwBase + piid->Name)); // Check if it is already in the list SModule* pTemp = g_pModules; while(pTemp) { if(strcmp(pTemp->m_szName, psm->m_szName) == 0) break; pTemp = pTemp->m_pNext; } // If not, insert it if(pTemp == 0) { InsertModuleSorted(psm); } else { // Otherwise, get rid of it. pTemp = g_pModules; while(pTemp) { if(strcmp(pTemp->m_szName, psm->m_szName)==0) break; pTemp = pTemp->m_pNext; } assert(pTemp); free(psm->m_szName); delete psm; psm = pTemp; } // Get the function imports from this module. PIMAGE_THUNK_DATA pitdf = 0; PIMAGE_THUNK_DATA pitda = 0; // Check for MS or Borland format if(piid->OriginalFirstThunk) { // MS format, function array is in original first thunk. pitdf = reinterpret_cast(dwBase + piid->OriginalFirstThunk); // If the time stamp is set, the module has been bound, // and first thunk is the bound address array. if(piid->TimeDateStamp) { pitda = reinterpret_cast(dwBase + piid->FirstThunk); } } else { // Borland format uses first thunk for function array pitdf = reinterpret_cast(dwBase + piid->FirstThunk); } while(pitdf->u1.Ordinal) { SFunction* psf = new SFunction; if(IMAGE_SNAP_BY_ORDINAL(pitdf->u1.Ordinal)) { psf->m_iOrdinal = static_cast(IMAGE_ORDINAL(pitdf->u1.Ordinal)); psf->m_iHint = -1; char szTemp[1024]; sprintf(szTemp, "Unnamed%6d", psf->m_iOrdinal); psf->m_szName = strdup(szTemp); } else { PIMAGE_IMPORT_BY_NAME piibn = reinterpret_cast(dwBase + (DWORD)(pitdf->u1.AddressOfData)); char* szName = reinterpret_cast(piibn->Name); char szBuffer[512]; LinkName2Name(szName, szBuffer); psf->m_szName = strdup(szBuffer); psf->m_iOrdinal = -1; psf->m_iHint = piibn->Hint; } psf->m_fDelayedImport = fDelayed; psf->m_dwAddress = pitda ? (DWORD) pitda->u1.Function : reinterpret_cast(INVALID_HANDLE_VALUE); // Do a sorted insert of the function InsertFunctionSorted(psm, psf); // Go to next function pitdf++; if(pitda) pitda++; } // Go to next entry piid++; } return true; } bool GetImports(char* szExecutable) { // Open the file HANDLE hFile = CreateFile(szExecutable, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { PrintLastError(); return false; } // Map this file into memory HANDLE hMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0); if(hMap == 0) { PrintLastError(); return false; } void* pvFileBase; pvFileBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if(pvFileBase == 0) { PrintLastError(); return false; } // Get the MS-DOS compatible header PIMAGE_DOS_HEADER pidh = reinterpret_cast(pvFileBase); if(pidh->e_magic != IMAGE_DOS_SIGNATURE) { fprintf(stderr,"File is not a valid executable\n"); return false; } // Get the NT header PIMAGE_NT_HEADERS pinth = reinterpret_cast( reinterpret_cast(pvFileBase) + pidh->e_lfanew); if(pinth->Signature != IMAGE_NT_SIGNATURE) { // Not a valid Win32 executable, may be a Win16 or OS/2 exe fprintf(stderr, "File is not a valid executable\n"); return false; } // Get the other headers PIMAGE_FILE_HEADER pifh = &pinth->FileHeader; PIMAGE_OPTIONAL_HEADER pioh = &pinth->OptionalHeader; PIMAGE_SECTION_HEADER pish = IMAGE_FIRST_SECTION(pinth); // Get normal imports if(!ParseImportDirectory(pifh, pioh, pish, pvFileBase, false)) { return false; } // Get delayed imports if(!ParseImportDirectory(pifh, pioh, pish, pvFileBase, true)) { return false; } // Resolve forwarded functions SModule* pModule = g_pModules; while(pModule) { GetForwardFunctions(pModule); pModule = pModule->m_pNext; } // We're done with the file. if(!UnmapViewOfFile(pvFileBase)) { PrintLastError(); return false; } CloseHandle(hMap); CloseHandle(hFile); return true; } // Return true if function name matches search string, false otherwise. bool MatchFunction(const char* szFunc, const char* szSearch) { if(strcmp(szSearch, "*") == 0) return true; while(*szSearch != '\0' && *szFunc != '\0') { // If we get a ?, we don't care and move on to the next // character. if(*szSearch == '?') { szSearch++; szFunc++; continue; } // If we have a wildcard, move to next search string and search for substring if(*szSearch == '*') { const char* szCurrSearch; szSearch++; if(*szSearch == '\0') return true; // Don't change starting point. szCurrSearch = szSearch; for(;;) { // We're done if we hit another wildcard if(*szCurrSearch == '*' || *szCurrSearch == '?') { // Update the permanent search position. szSearch = szCurrSearch; break; } // At end of both strings, return true. if((*szCurrSearch == '\0') && (*szFunc == '\0')) return true; // We never found it if(*szFunc == '\0') return false; // If it doesn't match, start over if(toupper(*szFunc) != toupper(*szCurrSearch)) { // If mismatch on first character // of search string, move to next // character in function string. if(szCurrSearch == szSearch) szFunc++; else szCurrSearch = szSearch; } else { szFunc++; szCurrSearch++; } } } else { if(toupper(*szFunc) != toupper(*szSearch)) { return false; } szFunc++; szSearch++; } } if((*szFunc == 0) && ((*szSearch == '\0') || (strcmp(szSearch,"*")==0))) return true; else return false; } void PrintModule(SModule* pMod, char* szSearch, bool fVerbose, FILE* pfOut) { bool fModNamePrinted = false; SFunction* pFunc = pMod->m_pFunctions; while(pFunc) { if(!MatchFunction(pFunc->m_szName, szSearch)) { pFunc = pFunc->m_pNext; continue; } if(!fModNamePrinted) { fModNamePrinted = true; fprintf(pfOut, "\n%s:\n", pMod->m_szName); if(fVerbose) fprintf(pfOut, "%-42s%-8s%-5s%-12s%s\n", "Function", "Ordinal", "Hint", "Address", "Delayed"); } if(fVerbose) { fprintf(pfOut,"%-45s", (pFunc->m_szForward == 0) ? pFunc->m_szName : pFunc->m_szForward); if(pFunc->m_iOrdinal==-1) fprintf(pfOut, "%-5s", "N/A"); else fprintf(pfOut, "%-5d", pFunc->m_iOrdinal); if(pFunc->m_iHint == -1) fprintf(pfOut, "%-5s", "N/A"); else fprintf(pfOut, "%-5d", pFunc->m_iHint); if(pFunc->m_dwAddress == static_cast(-1)) fprintf(pfOut, "%-12s", "Not Bound"); else fprintf(pfOut, "%-#12x", pFunc->m_dwAddress); fprintf(pfOut,"%s\n", pFunc->m_fDelayedImport ? "Yes" : "No"); } else fprintf(pfOut, "%s\n", pFunc->m_szName); pFunc = pFunc->m_pNext; } } int _cdecl main(int argc, char** argv) { if(argc < 2) { fprintf(stderr,"Usage: dc executable [/v /s: func /f]\n"); return 0; } // Parse command line char* szFileName = argv[1]; if( (strnicmp(szFileName, "/?", 2) == 0) || (strncmp(szFileName, "/h", 2) == 0)) { printf("Usage: dc executable [/v /s: func /f]\n"); printf("executable:\t\tName of executable file to check\n"); printf("/v:\t\t\tVerbose\n"); printf("/s: func\t\tDisplay all functions matching func search string"); printf(", * and ? allowed.\n"); printf("/f:\t\t\tDisplay alphabetically by function, not module.\n"); printf("/o: File\t\tRedirect all output to File.\n"); return 0; } FILE* pfOutput = 0; // If no extension, just add .exe if(strchr(szFileName, '.') == 0) { szFileName = new char[strlen(argv[1]) + 5]; strcpy(szFileName, argv[1]); strcat(szFileName, ".exe"); } bool fVerbose = false; bool fUseStdout = true; char* szSearch = "*"; bool fSortByFunction = false; // Get flags. for(int i = 2; i < argc; i++) { char* szFlag = argv[i]; if(stricmp(szFlag, "/v") == 0) { fVerbose = true; } else if(strnicmp(szFlag, "/s:", 3) == 0) { if((i == argc-1) && (strlen(szFlag) <= 3)) { fprintf(stderr,"Missing search string\n"); return 0; } if(strlen(szFlag) > 3) { szSearch = strdup(&szFlag[3]); } else { szSearch = argv[i+1]; i++; } } else if(stricmp(szFlag, "/f") == 0) { fSortByFunction = true; } else if( (strnicmp(szFlag, "/h",2) == 0) || (strnicmp(szFlag, "/?",2) == 0)) { printf("Usage: dc executable [/v /s: func /f]\n"); printf("executable:\t\tName of executable file to check\n"); printf("/v:\t\t\tVerbose\n"); printf("/s: func\t\tDisplay all functions matching func search string"); printf(", * and ? allowed.\n"); printf("/f:\t\t\tDisplay alphabetically by function, not module.\n"); printf("/o: File\t\tRedirect all output to File.\n"); return 0; } else if(strnicmp(szFlag, "/o:", 3) == 0) { fUseStdout = false; if( (i == argc-1) && (strlen(szFlag) <= 3)) { fprintf(stderr, "Missing output file name\n"); return 0; } if(strlen(szFlag) > 3) { pfOutput = fopen(&szFlag[3], "wt"); } else { pfOutput = fopen(argv[i+1], "wt"); i++; } } else { fprintf(stderr,"Unknown command line option, %s\n", szFlag); return 0; } } if(fUseStdout) pfOutput = stdout; if(!pfOutput) { fprintf(stderr,"Unable to open output file\n"); return 0; } // We wrap this code in a try block, because // we map the file into memory. If the file // is invalid and if the pointers in it are garbage, // we should get an access violation, which is caught. try { if(!GetImports(szFileName)) { return 0; } } catch(...) { fprintf(stderr, "Invalid executable file\n"); return 0; } if(fSortByFunction) { // Create a global list of functions SModule* pGlobal = new SModule; pGlobal->m_szName = "All Imported Functions"; SModule* pMod = g_pModules; while(pMod) { SFunction* pFunc = pMod->m_pFunctions; while(pFunc) { // Create a copy of this function // This is a shallow copy, but // it should be ok, since we don't // delete the original SFunction* pNew = new SFunction; memcpy(pNew, pFunc, sizeof(*pFunc)); InsertFunctionSorted(pGlobal, pNew); pFunc = pFunc->m_pNext; } pMod = pMod->m_pNext; } PrintModule(pGlobal, szSearch, fVerbose, pfOutput); } else { SModule* pMod = g_pModules; while(pMod) { PrintModule(pMod, szSearch, fVerbose, pfOutput); pMod = pMod->m_pNext; } } return 0; }