diff -r 000000000000 -r 7f656887cf89 commands/chkdeps/chkdeps.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commands/chkdeps/chkdeps.cpp Wed Jun 23 15:52:26 2010 +0100 @@ -0,0 +1,760 @@ +// Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// Accenture - adapted for fshell, much functionality added +// +// Description: +// Based on \SF\OS\KERNELHWSRV\USERLIBANDFILESERVER\FILESERVER\etshell\ts_deps.cpp +// + +#include "chkdeps.h" + +CCommandBase* CDllChecker::NewLC() + { + CDllChecker* self = new(ELeave) CDllChecker(); + CleanupStack::PushL(self); + self->BaseConstructL(); + return self; + } + +const TDesC& CDllChecker::Name() const + { + _LIT(KName, "chkdeps"); + return KName; + } + +void CDllChecker::DoRunL() + { +#ifdef __WINS__ + PrintWarning(_L("chkdeps only works on E32 (ie ARM) format binaries, not WINS ones")); +#endif + FsL(); + ConstructL(); + TBool exists = EFalse; + { + TEntry e; + if (Fs().Entry(iFilename, e) == KErrNone) exists = ETrue; + } + + if (!exists) + { + // Try and resolve + iFilename = iFilename.NameAndExt(); // Is this going to work? Assigning to a descriptor a TPtrC that points to itself...? + FindDll(iFilename, iFilename, _L("\\sys\\bin\\")); + } + + TInt result; + iStackBase = &result; + iExeInfo = new(ELeave) CDllInfo; + iExeInfo->iDllCaps.SetEmpty(); + iRequiredCaps = &iExeInfo->iDllCaps; + TRAP(result, GetImportDataL(iFilename, *iExeInfo)); + if (result==KErrGeneral) + { + PrintError(result, _L("%S has no import data"), &iFilename); + return; + } + else if (result) + { + PrintError(result, _L("Couldn't read %S"), &iFilename); + User::Leave(result); + } + ListArray(); // Print out the results of DllCheck + //Stdin().ReadKey(); + } + +void CDllChecker::ArgumentsL(RCommandArgumentList& aArguments) + { + aArguments.AppendFileNameL(iFilename, _L("file_name")); + } + +void CDllChecker::OptionsL(RCommandOptionList& aOptions) + { + aOptions.AppendBoolL(iVerbose, _L("verbose")); + aOptions.AppendBoolL(iDebug, _L("debug")); + } + +EXE_BOILER_PLATE(CDllChecker) + +/////////////////////////////////////////////////////////////////////// + +#define __PRINT(t) { if (iDebug) Printf(t);} +#define __PRINT1(t,a) { if (iDebug) Printf(t,a);} +#define __PRINT2(t,a,b) { if (iDebug) Printf(t,a,b);} +#define __PRINTWAIT(t) { if (iDebug) { Printf(t); Stdin().ReadKey();}} + +/* + +CDllChecker::GetImportDataL(aFilename) reads the Image Header, Import Section +and all import data for aFilename. If aFilename is a ROM dll, and thus has no +import data, or if the file contains no import data for some other reason the +function leaves with KErrGeneral. If a file is compressed, function calls +appropriate decompression routine to inflate the import data. +The function then calls GetDllTableL function which +reads the first import block and enters a "for" loop. The Dll's name and Uid3 are obtained +from CDllChecker::GetFileNameAndUid(). If the Dll name does not occur in the +array of previously checked Dlls, CDllChecker::FindDll() is called. If the Dll +is found,the function then calls CDllChecker::GetImportDataL on the filename acquired by +GetFileNameAndUid()(recursive call). + +If the Dll contains no import data or cannot be found, or if the Uid is invalid, +the next import is checked. + +The Uid3 value is checked by calling CDllChecker::CheckUid. This compares the +Uid3 value found in the image header of the file, with that found by +GetFileNameAndUid(). If there are any discrepancies,these are noted. + +Each potential import is added to the array to +indicate its import status. + +CDllChecker::ListArray() lists the contents of the array when all imports +have been checked. +*/ + + +void CDllChecker::ConstructL() +// +// Creates an array to hold DLLs referenced by this executable +// + { + } + +CDllChecker::CDllChecker() + { + } + +CDllChecker::~CDllChecker() +// +// Destructor +// + { + iDllArray.ResetAndDestroy(); + delete iExeInfo; + } + +void FileCleanup(TAny* aPtr) + { + TFileInput* f=(TFileInput*)aPtr; + f->Cancel(); + delete f; + } + +void CDllChecker::LoadFileInflateL(const E32ImageHeaderComp* aHeader, RBuf8& aCode, RBuf8& aRestOfFile) + { + TInt pos = aHeader->TotalSize(); + User::LeaveIfError(iFile.Seek(ESeekStart,pos)); // Start at beginning of compressed data + + TFileInput* file = new (ELeave) TFileInput(iFile); + CleanupStack::PushL(TCleanupItem(&FileCleanup,file)); + CInflater* inflater=CInflater::NewLC(*file); + + if (aHeader->iCodeSize) + { + aCode.CreateL(aHeader->iCodeSize); + TInt count = inflater->ReadL((TUint8*)aCode.Ptr(), aHeader->iCodeSize, &Mem::Move); + if(count != aHeader->iCodeSize) + User::Leave(KErrCorrupt); + aCode.SetLength(count); + } + + // Compressed executable + // iCodeOffset = header size for format V or above + // = sizeof(E32ImageHeader) for format J + TInt restOfFileSize = aHeader->UncompressedFileSize() - aHeader->iCodeOffset - aHeader->iCodeSize; // the size of the exe less header & code + + if (restOfFileSize) + { + aRestOfFile.CreateL(restOfFileSize); + TUint32 count = inflater->ReadL((TUint8*)aRestOfFile.Ptr(), restOfFileSize, &Mem::Move); + if(count != restOfFileSize) + User::Leave(KErrCorrupt); + aRestOfFile.SetLength(count); + } + CleanupStack::PopAndDestroy(2,file); + } + +void CDllChecker::LoadFileNoCompressL(const E32ImageHeaderComp* aHeader, RBuf8& aCode, RBuf8& aRestOfFile) + { + TInt codepos = aHeader->TotalSize(); + if (aHeader->iCodeSize) + { + aCode.CreateL(aHeader->iCodeSize); + User::LeaveIfError(iFile.Read(codepos, aCode, aHeader->iCodeSize)); + } + + TInt restpos = aHeader->TotalSize() + aHeader->iCodeSize; + TInt fileSize; + User::LeaveIfError(iFile.Size(fileSize)); + TInt restOfFileSize = fileSize - aHeader->TotalSize() - aHeader->iCodeSize; // the size of the exe less header & code + + if (restOfFileSize) + { + aRestOfFile.CreateL(restOfFileSize); + User::LeaveIfError(iFile.Read(restpos, aRestOfFile, restOfFileSize)); + } + } + +void CDllChecker::LoadFileBytePairL(const E32ImageHeaderComp* aHeader, RBuf8& aCode, RBuf8& aRestOfFile) + { + TInt pos = aHeader->TotalSize(); + User::LeaveIfError(iFile.Seek(ESeekStart,pos)); // Start at beginning of compressed data + + CBytePairReader* reader = CBytePairFileReader::NewLC(iFile); + + if (aHeader->iCodeSize) + { + aCode.CreateL(aHeader->iCodeSize); + TUint32 bytes = reader->DecompressPagesL((TUint8*)aCode.Ptr(), aHeader->iCodeSize, &Mem::Move); + if((TInt)bytes != aHeader->iCodeSize) + User::Leave(KErrCorrupt); + aCode.SetLength(bytes); + } + + TInt restOfFileSize = ((E32ImageHeaderV*)aHeader)->iUncompressedSize - aHeader->iCodeOffset - aHeader->iCodeSize; + + if (restOfFileSize) + { + aRestOfFile.CreateL(restOfFileSize); + TUint32 count = reader->DecompressPagesL((TUint8*)aRestOfFile.Ptr(), restOfFileSize, &Mem::Move); + if(count != restOfFileSize) + User::Leave(KErrCorrupt); + aRestOfFile.SetLength(count); + } + + CleanupStack::PopAndDestroy(reader); + } + +//function loads file's import information calling decompression routine if needed +void CDllChecker::LoadFileL(const E32ImageHeaderComp* aHeader, RBuf8& aCode, RBuf8& aRestOfFile) + { + TUint32 aCompression = aHeader->CompressionType(); + + if(aCompression==KFormatNotCompressed) + { + LoadFileNoCompressL(aHeader, aCode, aRestOfFile); + } + else if(aCompression==KUidCompressionDeflate) + { + LoadFileInflateL(aHeader, aCode, aRestOfFile); + } + /*TOMSCI TODO doesn't seem to work... + else if (aCompression == KUidCompressionBytePair) + { + LoadFileBytePairL(aHeader, aCode, aRestOfFile); + }*/ + else + { + LeaveIfErr(KErrNotSupported, _L("Compression type 0x%x is not supported"), aCompression); + } + } + + +TBool CDllChecker::CompareDllInfo(const CDllInfo& aDllInfo1, const CDllInfo& aDllInfo2) + { + return (aDllInfo1.iDllName.CompareF(aDllInfo2.iDllName) == 0); + } + + +//function iterates through the list of libraries the current executable depends on +//for each dependency in the list the function checks whether the .dll being checked is already added to the array of dependencies +//if not, the function adds dependency being checked to the array of dependencies and calls GetImportDataL function recursively +//void CDllChecker::GetDllTableL(TUint8* aImportData, TInt aDllRefTableCount,TUint aFlags) +//void CDllChecker::GetDllTableL(const TDesC& aFileName, E32ImageHeader* aImageHeader, TUint8* aRestOfFile) +void CDllChecker::GetDllTableL(const TDesC& aFileName, const E32ImageHeader* aImageHeader, const TUint8* aCode, const TUint8* aImportData) + { + const TInt aDllRefTableCount = aImageHeader->iDllRefTableCount; + const TUint aFlags = aImageHeader->iFlags; + + const E32ImportBlock* block = (const E32ImportBlock*)(aImportData+sizeof(E32ImportSection)); + + for (TInt i=0; iiResult = EPending; + dllInfo->iNumExports = 0; + const TText8* dllName=(aImportData+block->iOffsetOfDllName); + TPtrC8 dllNamePtr(dllName, User::StringLength(dllName)); + GetFileNameAndUid(*dllInfo,dllNamePtr); + __PRINT1(_L("Considering \"%S\"...\r\n"),&dllInfo->iDllName); + TInt r=iDllArray.Find(dllInfo,TIdentityRelation(CompareDllInfo)); + if (r >= 0) + { + __PRINT(_L("\tAlready checked\r\n")); + CDllInfo* dllInfoPtr = iDllArray[r]; + if ((dllInfoPtr->iResult!=EPending) && (dllInfoPtr->iUid!=dllInfo->iUid)) + { + __PRINT2(_L(" Uid3 [%08x] for %S is different from that noted previously\r\n"),dllInfo->iUid,&dllInfo->iDllName); + dllInfoPtr->iResult=EUidDifference; + } + CleanupStack::PopAndDestroy(dllInfo); + dllInfo = dllInfoPtr; + } + else + { + __PRINT(_L("\tNot previously checked\r\n")); + iDllArray.AppendL(dllInfo); + CleanupStack::Pop(dllInfo); + + TFileName fileName; + r=FindDll(dllInfo->iDllName,fileName,Env().Pwd()); + + if (r==KErrNotFound) // Could not find Dll + { + dllInfo->iResult=ENotFound; + }// Run to the end of the "for" loop for this i value + + else // File was located + { + // Go recursive. Call GetImportDataL on the new dll, if it imports anything. + // ROM dlls have no import data so this is never called for ROM dlls. + // This *will* terminate. It is only called on "new" dlls not in the array. + iCalls++; + + TRAP(r,GetImportDataL(fileName, *dllInfo)); + switch(r) + { + case(KErrGeneral): // No import data + { + dllInfo->iResult=ENoImportData; + if (!dllInfo->iDllCaps.HasCapabilities(*iRequiredCaps)) dllInfo->iResult = ERequiredCapabilitiesMissing; + break; + } + case(EUidNotSupported): + case(EVersionMismatch): + { + dllInfo->iResult=(CDllChecker::TResultCheck)r; + break; + } + case(KErrNone): // Import data was read + { + dllInfo->iResult=EFileFoundAndUidSupported; + if (!dllInfo->iDllCaps.HasCapabilities(*iRequiredCaps)) dllInfo->iResult = ERequiredCapabilitiesMissing; + break; + } + case(KErrInUse): + { + __PRINT2(_L("\t\"%S\" is already open\r\n"),&fileName,r); + dllInfo->iResult=EAlreadyOpen; + break; + } + case(KErrCorrupt): + { + __PRINT2(_L("\t\"%S\" has unexpected format\r\n"),&fileName,r); + dllInfo->iResult=EAlreadyOpen; + break; + } + default: + { + __PRINT1(_L("\t\"%S\" could not be opened \r\n"),&fileName); + dllInfo->iResult=ECouldNotOpenFile; + break; + } + } + } + } + + if (dllInfo->iResult == EFileFoundAndUidSupported) + { + // Check all the ordinals we import actually exist + for (TInt i = 0; i < block->iNumberOfImports; i++) + { + TInt ordinal = 0; + //Printf(_L("we import %d from %S\r\n"), block->Imports()[i], &dllInfo->iDllName); + if (aImageHeader->ImportFormat() == KImageImpFmt_ELF) + { + TUint impd_offset = block->Imports()[i]; + TUint impd = *(TUint*)(aCode + impd_offset); + ordinal = impd & 0xffff; + //TUint offset = impd >> 16; + } + else + { + ordinal = block->Imports()[i]; + } + //Printf(_L("We import %d (numExports=%d)\r\n"), ordinal, dllInfo->iNumExports); + if (ordinal > dllInfo->iNumExports && dllInfo->iResult != ENotFound) + { + Printf(_L("\tOrdinal %d is missing from \"%S\" (used by \"%S\")\r\n"), ordinal, &dllInfo->iDllName, &aFileName); + dllInfo->iResult = EOrdinalMissing; + } + } + } + block = (E32ImportBlock*)block->NextBlock(E32ImageHeader::ImpFmtFromFlags(aFlags)); + } + } + + +void CDllChecker::GetImportDataL(const TDesC& aFileName, CDllInfo& aInfo) + { + TInt cleanupCount=0; + // Check that the file is not a ROM dll. These have no import data + if (Fs().IsFileInRom(aFileName)) + { + // Still need to fill in caps and export count + TRomImageHeader* romHeader = (TRomImageHeader*)Fs().IsFileInRom(aFileName); + aInfo.iDllCaps.SetEmpty(); + SCapabilitySet* caps = &romHeader->iS.iCaps; + aInfo.iDllCaps = *(TCapabilitySet*)caps; + aInfo.iNumExports = romHeader->iExportDirCount; + User::Leave(KErrGeneral); + } + + //open file for reading and push it to autoclose stack + TAutoClose autoFile; + User::LeaveIfError(autoFile.iObj.Open(Fs(),aFileName,EFileShareReadersOnly)); + autoFile.PushL(); + cleanupCount++; + iFile=autoFile.iObj; + + //Create a pointer to an Image Header + //reserve enough memory for compressed file header because we don't know whether the file is compressed or not + // In fact, allow for a E32ImageHeaderV so we can get the capability info too + E32ImageHeaderV* imageHeader=new(ELeave)E32ImageHeaderV; + CleanupStack::PushL(imageHeader); + cleanupCount++; + + //read file header + TPtr8 ptrToImageHeader((TText8*)(imageHeader),sizeof(E32ImageHeaderV),sizeof(E32ImageHeaderV)); + User::LeaveIfError(iFile.Read(ptrToImageHeader,sizeof(E32ImageHeaderV))); + + + aInfo.iDllCaps.SetEmpty(); + if (imageHeader->HeaderFormat() >= KImageHdrFmt_V) + { + SCapabilitySet* caps = &imageHeader->iS.iCaps; + aInfo.iDllCaps = *(TCapabilitySet*)caps; + } + aInfo.iNumExports = imageHeader->iExportDirCount; + + if (imageHeader->iImportOffset==0) // File contains no import data (ROM files never have import data) + { + User::Leave(KErrGeneral); + } + + RBuf8 code, rest; + CleanupClosePushL(code); + CleanupClosePushL(rest); + cleanupCount += 2; + LoadFileL(imageHeader, code, rest); // Read import information in + + TInt32 uid3=imageHeader->iUid3; + if(iCalls!=0) // Only check Uid3 of dependencies (ie only after first + { // call of recursive function) + TInt r=CheckUid3(uid3,aInfo.iUid); + + if (r!=KErrNone) // Dll's Uid3 is not valid + User::Leave(EUidNotSupported); + + TInt version = (imageHeader->iModuleVersion >> 16); + if (version != aInfo.iMajorVersion) + { + Printf(_L("Version mismatch - %S version is %d, expected %d\r\n"), &aFileName, version, aInfo.iMajorVersion); + User::Leave(EVersionMismatch); + } + } + + TInt importOffset = imageHeader->iImportOffset - (imageHeader->iCodeOffset + imageHeader->iCodeSize); + if (TInt(importOffset + sizeof(E32ImportSection)) > rest.Size()) + User::Leave(KErrCorrupt); + //get the table of dependencies + //GetDllTableL(restOfFileData+bufferOffset,imageHeader->iDllRefTableCount,imageHeader->iFlags); + //GetDllTableL(aFileName, imageHeader, restOfFileData); + GetDllTableL(aFileName, imageHeader, code.Ptr(), rest.Ptr() + importOffset); + + CleanupStack::PopAndDestroy(cleanupCount); + } + + +TUint8* CDllChecker::NextBlock(TUint8* aBlock) + { + E32ImportBlock* block; + // Advance the pointer to the next block + block=(E32ImportBlock*)aBlock; + aBlock=(aBlock+sizeof(E32ImportBlock)+((block->iNumberOfImports)*sizeof(TUint))); + return (aBlock); + } + + +TFileNameInfo::TFileNameInfo() + { + memclr(this, sizeof(TFileNameInfo)); + } + +TInt TFileNameInfo::Set(const TDesC8& aFileName, TUint aFlags) + { + iUid = 0; + iVersion = 0; + iPathPos = 0; + iName = aFileName.Ptr(); + iLen = aFileName.Length(); + iExtPos = aFileName.LocateReverse('.'); + if (iExtPos<0) + iExtPos = iLen; + TInt osq = aFileName.LocateReverse('['); + TInt csq = aFileName.LocateReverse(']'); + if (!(aFlags & EAllowUid) && (osq>=0 || csq>=0)) + { + return KErrBadName; + } + if (osq>=iExtPos || csq>=iExtPos) + { + return KErrBadName; + } + TInt p = iExtPos; + if ((aFlags & EAllowUid) && p>=10 && iName[p-1]==']' && iName[p-10]=='[') + { + TPtrC8 uidstr(iName + p - 9, 8); + TLex8 uidlex(uidstr); + TUint32 uid = 0; + TInt r = uidlex.Val(uid, EHex); + if (r==KErrNone && uidlex.Eos()) + iUid = uid, p -= 10; + } + iUidPos = p; + TInt ob = aFileName.LocateReverse('{'); + TInt cb = aFileName.LocateReverse('}'); + if (ob>=iUidPos || cb>=iUidPos) + { + return KErrBadName; + } + if (ob>=0 && cb>=0 && p-1==cb) + { + TPtrC8 p8(iName, p); + TInt d = p8.LocateReverse('.'); + TPtrC8 verstr(iName+ob+1, p-ob-2); + TLex8 verlex(verstr); + if (ob==p-10 && dob && p-1>d && (aFlags & EAllowDecimalVersion)) + { + TUint32 maj = 0; + TUint32 min = 0; + TInt r = verlex.Val(maj, EDecimal); + TUint c = (TUint)verlex.Get(); + TInt r2 = verlex.Val(min, EDecimal); + if (r==KErrNone && c=='.' && r2==KErrNone && verlex.Eos() && maj<32768 && min<32768) + iVersion = (maj << 16) | min, p = ob; + } + } + iVerPos = p; + if (iLen>=2 && iName[1]==':') + { + TUint c = iName[0]; + if (c!='?' || !(aFlags & EAllowPlaceholder)) + { + c |= 0x20; + if (c<'a' || c>'z') + { + return KErrBadName; + } + } + iPathPos = 2; + } + TPtrC8 pathp(iName+iPathPos, iVerPos-iPathPos); + if (pathp.Locate('[')>=0 || pathp.Locate(']')>=0 || pathp.Locate('{')>=0 || pathp.Locate('}')>=0 || pathp.Locate(':')>=0) + { + return KErrBadName; + } + iBasePos = pathp.LocateReverse('\\') + 1 + iPathPos; + return KErrNone; + } + +void TFileNameInfo::GetName(TDes8& aName, TUint aFlags) const + { + if (aFlags & EIncludeDrive) + aName.Append(Drive()); + if (aFlags & EIncludePath) + { + if (PathLen() && iName[iPathPos]!='\\') + aName.Append('\\'); + aName.Append(Path()); + } + if (aFlags & EIncludeBase) + aName.Append(Base()); + if ((aFlags & EForceVer) || ((aFlags & EIncludeVer) && VerLen()) ) + { + aName.Append('{'); + aName.AppendNumFixedWidth(iVersion, EHex, 8); + aName.Append('}'); + } + if ((aFlags & EForceUid) || ((aFlags & EIncludeUid) && UidLen()) ) + { + aName.Append('['); + aName.AppendNumFixedWidth(iUid, EHex, 8); + aName.Append(']'); + } + if (aFlags & EIncludeExt) + aName.Append(Ext()); + } + + +void CDllChecker::GetFileNameAndUid(CDllInfo &aDllInfo, const TDesC8 &aExportName) +// +// Gets filename and UID +// + { + TFileNameInfo filename; + filename.Set(aExportName,TFileNameInfo::EAllowUid|TFileNameInfo::EAllowDecimalVersion); + TBuf8 narrowDllName; + filename.GetName(narrowDllName,TFileNameInfo::EIncludeBaseExt); + aDllInfo.iDllName.Copy(narrowDllName); + aDllInfo.iUid=TUid::Uid(filename.Uid()); + TUint32 version = filename.Version(); + aDllInfo.iMajorVersion = version >> 16; + aDllInfo.iMinorVersion = version & 0xFFFF; + } + + +TInt CDllChecker::CheckUid3(TInt32 aUid3,TUid aUid) +// +// Check that Uid3 is the same in the iDllName and as noted by the Image Header +// aUid3 is the value found by the image header +// aUid is the value found by parsing the result of block->dllname +// using GetFileNameAndUid() + { + + if ((aUid.iUid)==aUid3) + { + + __PRINT(_L("\tUid3 is valid\r\n")); + return KErrNone; + } + else + { + + __PRINT(_L("\tUid3 value is not supported\r\n")); + return (EUidNotSupported); + } + } + + + +TInt CDllChecker::FindDll(TDes& aDllName,TFileName& aFileName, const TDesC& aPath) +// +// Search for a dll in the following sequence ... +// 1. Supplied path parameter +// 2. System directories on all drives +// + { + TFindFile findFile(Fs()); + TInt r=findFile.FindByPath(aDllName,&aPath); + if (r==KErrNone) + { + aFileName=findFile.File(); + + __PRINT1(_L("\t\"%S\" was found (supplied path)\r\n"),&aFileName); + + return(r); + } + + r=findFile.FindByDir(aDllName,_L("Y:\\Sys\\Bin\\")); + if (r==KErrNone) + { + aFileName=findFile.File(); + + __PRINT1(_L("\t\"%S\" was found (system directory)\r\n"),&aFileName); + + return(r); + } + /* + if(!PlatSec::ConfigSetting(PlatSec::EPlatSecEnforceSysBin)) + { + r=findFile.FindByDir(aDllName,_L("\\System\\Bin\\")); + if (r==KErrNone) + { + aFileName=findFile.File(); + + __PRINT1(_L("\t\"%S\" was found (system directory)\r\n"),&aFileName); + + return(r); + } + } + */ + __PRINT1(_L("\t\"%S\" was not found\r\n"),&aDllName); + + return(KErrNotFound); + } + +void CDllChecker::ListArray() + { + const TInt elements=iDllArray.Count(); + + Printf(_L("Number of dependencies checked = %d\r\n"),elements); + + for (TInt i=0;iHasCapability(c) && !info.iDllCaps.HasCapability(c)) + { + Printf(_L8(" %s"), CapabilityNames[i]); + } + } + Printf(_L("\r\n")); + break; + } + case EOrdinalMissing: + Printf(_L("--- Required ordinal(s) missing\r\n")); + break; + case EVersionMismatch: + Printf(_L("--- Version mismatch\r\n")); + break; + default: // Will never reach here + Printf(_L("--- Undefined\r\n")); + break; + } + } + }