//----------------------------------------------------------------------------- // // // File: aqdbgext.cpp // // Description: Advanced Queuing Debug Extensions. // // Author: mikeswa // // Copyright (C) 1998 Microsoft Corporation // //----------------------------------------------------------------------------- #define _ANSI_UNICODE_STRINGS_DEFINED_ #include "aqincs.h" #ifdef PLATINUM #include "phatqdbg.h" #include #include #else #include "aqdbgext.h" #include #endif //PLATINUM #include #include #include #include #include #include #include extern DWORD g_cbClasses; extern DWORD g_dwFlavorSignature; BOOL g_fVersionChecked = FALSE; #define AQ_MIN(x, y) ((x) > (y) ? (y) : (x)) HANDLE g_hTransHeap; //Needed for to link because of transmem.h const DWORD MAX_DOM_PATH_SIZE = 512; const CHAR _LINK_STATE_UP[] = "UP "; const CHAR _LINK_STATE_DOWN[] = "DOWN "; const CHAR _LINK_STATE_ACTIVE[] = "ACTIVE "; const CHAR _LINK_STATE_TURN[] = "TURN "; const CHAR _LINK_STATE_RETRY[] = "RETRY "; const CHAR _LINK_STATE_DSN[] = "DSN "; const CHAR _LINK_STATE_SPECIAL[] = "SPECIAL "; #define LINK_STATE_UP (LPSTR) _LINK_STATE_UP #define LINK_STATE_DOWN (LPSTR) _LINK_STATE_DOWN #define LINK_STATE_ACTIVE (LPSTR) _LINK_STATE_ACTIVE #define LINK_STATE_TURN (LPSTR) _LINK_STATE_TURN #define LINK_STATE_RETRY (LPSTR) _LINK_STATE_RETRY #define LINK_STATE_DSN (LPSTR) _LINK_STATE_DSN #define LINK_STATE_SPECIAL (LPSTR) _LINK_STATE_SPECIAL //lower case function names AQ_DEBUG_EXTENSION_IMP(dumpservers) {DumpServers(DebugArgs);} AQ_DEBUG_EXTENSION_IMP(offsets) {Offsets(DebugArgs);} AQ_DEBUG_EXTENSION_IMP(dumpdnt) {DumpDNT(DebugArgs);} AQ_DEBUG_EXTENSION_IMP(Offsets) { dprintf("CDestMsgQueue m_liDomainEntryDMQs - 0x%X\n", FIELD_OFFSET(CDestMsgQueue, m_liDomainEntryDMQs)); dprintf("CDestMsgQueue m_liEmptyDMQs - 0x%X\n", FIELD_OFFSET(CDestMsgQueue, m_liEmptyDMQs)); dprintf("CLinkMsgQueue m_liLinks - 0x%X\n", FIELD_OFFSET(CLinkMsgQueue, m_liLinks)); dprintf("CLinkMsgQueue m_liConnections - 0x%X\n", FIELD_OFFSET(CLinkMsgQueue, m_liConnections)); dprintf("CAQSvrInst m_liVirtualServers - 0x%X\n", FIELD_OFFSET(CAQSvrInst, m_liVirtualServers)); dprintf("CRETRY_HASH_ENTRY m_QLEntry - 0x%X\n", FIELD_OFFSET(CRETRY_HASH_ENTRY, m_QLEntry)); dprintf("CRETRY_HASH_ENTRY m_HLEntry - 0x%X\n", FIELD_OFFSET(CRETRY_HASH_ENTRY, m_HLEntry)); dprintf("CShareLockInst m_liLocks - 0x%X\n", FIELD_OFFSET(CShareLockInst, m_liLocks)); } AQ_DEBUG_EXTENSION_IMP(dumpoffsets) { _dumpoffsets(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szArg); } //---[ cpoolusage ]------------------------------------------------------------ // // // Description: // Dumps the CPool usage for our known CPools. // Parameters: // - // Returns: // - // History: // 5/31/2000 - Mikeswa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(cpoolusage) { CHAR rgKnownCPools[][200] = { // "pttrace!g_pFreePool", "exstrace!g_pFreePool", // "phatcat!CPoolBuffer__sm_PoolNHeapBuffersPool", "aqueue!CQuickList__s_QuickListPool", "aqueue!CSMTPConn__s_SMTPConnPool", "aqueue!CMsgRef__s_MsgRefPool", "aqueue!CAQMsgGuidListEntry__s_MsgGuidListEntryPool", "aqueue!CAsyncWorkQueueItem__s_CAsyncWorkQueueItemPool", "aqueue!CRETRY_HASH_ENTRY__PoolForHashEntries", // "drviis!CIMsgWrapper__m_CIMsgWrapperPool", // "drviis!CQueueItem__m_CQueueItemPool", "mailmsg!CBlockMemoryAccess__m_Pool", "mailmsg!CMsg__m_Pool", "mailmsg!CMailMsgRecipientsAdd__m_Pool", "smtpsvc!SMTP_CONNECTION__Pool", "smtpsvc!SMTP_CONNOUT__Pool", "smtpsvc!CAddr__Pool", "smtpsvc!CAsyncMx__Pool", "smtpsvc!CAsyncSmtpDns__Pool", "smtpsvc!CBuffer__Pool", "smtpsvc!CIoBuffer__Pool", "smtpsvc!CBlockMemoryAccess__m_Pool", "smtpsvc!CDropDir__m_Pool", "" }; DWORD rgdwPool[5]; DWORD cTotalBytes = 0; DWORD cCurrentBytes = 0; DWORD cInstanceBytes = 0; DWORD cInstances = 0; DWORD dwSignature = 0; CHAR *pch = NULL; DWORD i = 0; PVOID pvPool = NULL; // // Loop over all known pools and display data // dprintf("Total Bytes\t# Instances \tInstance Size \tSignature\tName\n"); dprintf("=================================================================\n"); while (rgKnownCPools[i] && rgKnownCPools[i][0]) { pvPool = (PVOID) GetExpression(rgKnownCPools[i]); if (!pvPool || !ReadMemory(pvPool, rgdwPool, sizeof(rgdwPool), NULL)) { dprintf("Unable to read pool %s at %p\n", rgKnownCPools[i], pvPool); } else { cInstances = rgdwPool[3]; cInstanceBytes = rgdwPool[2]; dwSignature = rgdwPool[0]; pch = (CHAR *) &dwSignature; dprintf("%d\t\t%d\t\t%d\t\t0x%08X\t%s\n", cInstanceBytes*cInstances, cInstances, cInstanceBytes, rgdwPool[0], rgKnownCPools[i]); cTotalBytes += cInstanceBytes*cInstances; } i++; } dprintf("=================================================================\n"); dprintf("\tTotal Bytes = %d\n\n", cTotalBytes); } //---[ remotecmd ]------------------------------------------------------------ // // // Description: // start a remote cmd window // Parameters: // name of the pipe // Returns: // - // History: // 5/31/2000 - AWetmore Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(remotecmd) { char szParameters[1024]; PROCESS_INFORMATION pi; STARTUPINFO si; if (!szArg || ('\0' == szArg[0])) goto Usage; _snprintf(szParameters, 1024, "remote /s cmd %s", szArg); dprintf("\nRunning %s\n", szParameters); ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcess(NULL, szParameters, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { dprintf("CreateProcess failed with %u\n", GetLastError()); } else { dprintf("Started process %i\n", pi.dwProcessId); } Exit: dprintf("\n"); return; Usage: // // Display usage message // dprintf("\nUsage:\n"); dprintf("\tremotecmd \n"); goto Exit; } //---[ findbytes ]------------------------------------------------------------- // // // Description: // Searches for a given byte-pattern in a memory address sapce // Parameters: // Pattern of bytes to search for. Expected format is a sequence of // space separated hex digits. // Returns: // - // History: // 5/9/2000 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(findbytes) { #ifdef WIN64 const DWORD_PTR cbVMSize = 0xFFFFFFFFFFFFFFFF; #else //not WIN64 const DWORD_PTR cbVMSize = 0xFFFFFFFF; #endif //WIN64 BYTE rgbBytesToFind[200]; LONG lCurrentValue = 0; CHAR rgchCurrentValue[3] = "00"; LPSTR szStop = NULL; DWORD_PTR cBytesToFind = 0; DWORD_PTR cChunksChecked = 0; DWORD_PTR cChunkSize = 0; DWORD_PTR iChunk = 0; BYTE pbChunk[0x1000]; PBYTE pbStopAddr = pbChunk + sizeof(pbChunk); PBYTE pbCurrent = NULL; DWORD_PTR cChunks = cbVMSize/sizeof(pbChunk); DWORD_PTR cChunksInPercent = 1; DWORD_PTR pvEffectiveAddressOtherProc = NULL; DWORD cComplaints = 0; DWORD cMemchkMatches = 0; DWORD cFullSigMatches = 0; LPCSTR szCurrentArg = szArg; if (!szArg || ('\0' == szArg[0])) goto Usage; // // Parse command line args // while (*szCurrentArg) { // // Loop over whitespace // while (*szCurrentArg && isspace(*szCurrentArg)) szCurrentArg++; // // Make sure we have at least pair of characters as expected // if (!*(szCurrentArg+1)) break; // // Convert from hex characters to binary // lCurrentValue = strtol(szCurrentArg, &szStop, 16); if ((lCurrentValue > 0xFF) || (lCurrentValue < 0)) goto Usage; // // Copy to our search buffer // rgbBytesToFind[cBytesToFind] = (BYTE) lCurrentValue; cBytesToFind++; // // Make sure our search buffer is big enough for the next byte // if (cBytesToFind >= sizeof(rgbBytesToFind)) { dprintf("Search for max pattern of %d bytes\n", cBytesToFind); break; } szCurrentArg += 2; //Skip to next known whitespace } if (!cBytesToFind) { dprintf("\nYou must specify at least one byte to search for\n"); goto Usage; } // // Used to display progress // cChunksInPercent = cChunks/100; // // Calculate memory size for 32-bit machines // cChunkSize = cbVMSize/cChunks; if (cChunkSize < 1024) { dprintf("ERROR: Chunk size of 0x%p is too small", cChunkSize); goto Exit; } // // Make sure we are cool wrt to buffer size // if (cChunkSize > sizeof(pbChunk)) { dprintf("ERROR: Chunksize of 0x%p is larger than max size of 0x%p", cChunkSize, sizeof(pbChunk)); goto Exit; } // // Loop over chunks -- // $$REVIEW - does not find patterns that span 1K chunks... // this is probably OK, since this is an unlikely scenario. Most // byte patterns will be DWORD (signatures) or pointer sized. // for (iChunk = 0; iChunk < cChunks; iChunk++) { // // Check to see if the user pressed ctrl-c // if (CheckControlC()) { goto Exit; } // // Give some status // if ((iChunk % cChunksInPercent) == 0) dprintf("."); // // Address should be page aligned // if (((iChunk*cChunkSize) & 0xFFF) && (cComplaints < 100)) { cComplaints++; dprintf("0x%p not alligned at index %d", (iChunk*cChunkSize), iChunk); } // // Do a memory search for the first byte // if (!ReadMemory(iChunk*cChunkSize, pbChunk, (DWORD)cChunkSize, NULL)) continue; //on to the next buffer chunk // // Now that we have a chunk... look for our sig // pbCurrent = pbChunk; while (pbCurrent < pbStopAddr-cBytesToFind) { pbCurrent = (PBYTE) memchr(pbCurrent, rgbBytesToFind[0], pbStopAddr-pbCurrent); // // See if we have a match if (!pbCurrent) break; cMemchkMatches++; pvEffectiveAddressOtherProc = iChunk*cChunkSize+(pbCurrent-pbChunk); // // See if the full pattern matches // if (!memcmp(rgbBytesToFind, pbCurrent, cBytesToFind)) { cFullSigMatches++; dprintf("\nFound match at 0x%p\n", pvEffectiveAddressOtherProc); } if (0 != memcmp(rgbBytesToFind, pbCurrent, 1)) { cComplaints++; if (cComplaints < 100) dprintf("Messed up %02X %02X - %02X %02X\n", rgbBytesToFind[0], rgbBytesToFind[1], pbCurrent[0], pbCurrent[1]); } pbCurrent++; } cChunksChecked++; } // // Give some summary information // dprintf("\nChecked 0x%p chunks (%d%%) searching from 0x%p to 0x%p", cChunksChecked, (DWORD)(100*cChunksChecked/cChunks), NULL, cChunkSize*(cChunks+1)-1); dprintf("\nFound %d partial matches and %d full matches", cMemchkMatches, cFullSigMatches); Exit: dprintf("\n"); return; Usage: // // Display usage message // if (szCurrentArg && *szCurrentArg) dprintf("Error at %s\n", szCurrentArg); dprintf("\nUsage:\n"); dprintf("\tfindbytes [ ...]\n"); dprintf("\t\tBytes should be specifed as 2 hexadecimal characters\n"); dprintf("\nExamples:\n"); dprintf("\tTo search for the signature \"LMQ \"\n"); dprintf("\t\tfindbytes %02X %02X %02X %02X\n", 'L', 'M', 'Q', ' '); goto Exit; } //---[ findsig ]--------------------------------------------------------------- // // // Description: // Searches for a given class signature in a memory address sapce // Parameters: // The Siganature to look for. // Returns: // - // History: // 5/3/2000 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(findsig) { CHAR szNewArg[200]; LPCSTR szCurrentArg = szArg; CHAR szSig[5] = " "; DWORD iChar = 0; if (!szArg || ('\0' == szArg[0])) goto Usage; // // Loop over whitespace // while (*szCurrentArg && isspace(*szCurrentArg)) szCurrentArg++; // // Grab the first 4 characters and convert them to binary // for( iChar = 0; iChar < 4; iChar++) { if (!szCurrentArg[iChar]) break; szSig[iChar] = szCurrentArg[iChar]; } dprintf("Searching for Signature \"%s\"...\n", szSig); sprintf(szNewArg, "%02X %02X %02X %02X", szSig[0], szSig[1], szSig[2], szSig[3]); // // Just use the code in findbytes to do the actual search // dprintf("Calling findbytes %s\n", szNewArg); findbytes(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szNewArg); Exit: return; Usage: dprintf("\nUsage:\n"); dprintf("\tfindsig \n"); goto Exit; } //---[ hashthread ]------------------------------------------------------------ // // // Description: // Uses the CThreadIdBlock hashing mechanism to return the hashed value // for a thread. // Parameters: // Thread Id to hash // Max hash value // Returns: // - // History: // 8/9/99 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(hashthread) { //Arguement should be thread Id DWORD dwThreadId = GetCurrentThreadId(); DWORD dwMax = 1000; DWORD dwThreadHash = 0; CHAR szArgBuffer[200]; LPSTR szCurrentArg = NULL; if (!szArg || ('\0' == szArg[0])) { dprintf("Warning... using default thead id and max\n"); } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { dwThreadId = (DWORD)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) dwMax = (DWORD) GetExpression(szCurrentArg); else dprintf("Warning... using default max hash\n"); } } //Try hashing the ID dwThreadHash = dwHashThreadId(dwThreadId, dwMax); dprintf("Thread Id 0x%0X hashes to index 0x%0X (%d) with max 0x%08X (%d)\n", dwThreadId, dwThreadHash, dwThreadHash, dwMax, dwMax); } //---[ dumplock ]------------------------------------------------------------- // // // Description: // Dumps all of the information in the CThreadIdBlocks for a given // CShareLockInst. // Parameters: // Address of CShareLockInst // Returns: // - // History: // 8/9/99 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(dumplock) { BYTE pbBuffer[sizeof(CShareLockInst)]; BYTE pbThreadBlocks[1000*sizeof(CThreadIdBlock)]; PVOID pvLock = NULL; PVOID pvNextBlock = NULL; CThreadIdBlock tblkCurrent; CThreadIdBlock *ptblkCurrent = NULL; CThreadIdBlock *ptblkArray = NULL; DWORD cNumBlocks = 0; DWORD iBlock = 0; DWORD cThreads = 0; DWORD cLockCount = 0; DWORD cLockedThreads = 0; BOOL fDisplayedHashHeader = FALSE; ZeroMemory(pbBuffer, sizeof(pbBuffer)); ZeroMemory(pbThreadBlocks, sizeof(pbThreadBlocks)); if (!szArg || ('\0' == szArg[0]) || !(pvLock = (PVOID) GetExpression(szArg))) { dprintf("You must specify a lock address\n"); return; } //read the whole lock into our buffer if (!ReadMemory(pvLock, &pbBuffer, sizeof(pbBuffer), NULL)) { dprintf("Error unable read memory at 0x%0X\n", pvLock); return; } cNumBlocks = ((CShareLockInst *)pbBuffer)->m_cMaxTrackedSharedThreadIDs; pvNextBlock = ((CShareLockInst *)pbBuffer)->m_rgtblkSharedThreadIDs; if (!cNumBlocks || !pvNextBlock) { dprintf("Thread tracking is not enabled for this lock"); return; } if (cNumBlocks > sizeof(pbThreadBlocks)/sizeof(CThreadIdBlock)) cNumBlocks = sizeof(pbThreadBlocks)/sizeof(CThreadIdBlock); if (!ReadMemory(pvNextBlock, &pbThreadBlocks, cNumBlocks*sizeof(CThreadIdBlock), NULL)) { dprintf("Error, unable to read %d blocks at 0x%0X", cNumBlocks, pvNextBlock); return; } ptblkArray = (CThreadIdBlock *) pbThreadBlocks; for (iBlock = 0; iBlock < cNumBlocks; iBlock++ && ptblkArray++) { ptblkCurrent = ptblkArray; fDisplayedHashHeader = FALSE; while (ptblkCurrent) { if (ptblkCurrent != ptblkArray) { //Read into this process if (!ReadMemory(ptblkCurrent, &tblkCurrent, sizeof(CThreadIdBlock), NULL)) { dprintf("Error reading block at 0x%0X", ptblkCurrent); break; } ptblkCurrent = &tblkCurrent; } if (THREAD_ID_BLOCK_SIG != ptblkCurrent->m_dwSignature) { dprintf("Warning... bad signature on block 0x%0X\n", ((BYTE *)pvNextBlock) + iBlock*sizeof(CThreadIdBlock)); break; } //See if this block has any data if (THREAD_ID_BLOCK_UNUSED != ptblkCurrent->m_dwThreadId) { //Only dump info if the recursion count is non-zero if (ptblkCurrent->m_cThreadRecursionCount) { if (!fDisplayedHashHeader) { fDisplayedHashHeader = TRUE; dprintf("Thread Hash 0x%0X (%d)\n", iBlock, iBlock); } dprintf("%s\tThread 0x%08X has count of %d - Next link of 0x%08X\n", (ptblkCurrent == ptblkArray) ? "+" : "", ptblkCurrent->m_dwThreadId, ptblkCurrent->m_cThreadRecursionCount, ptblkCurrent->m_ptblkNext); cLockedThreads++; } cThreads++; cLockCount += ptblkCurrent->m_cThreadRecursionCount; } ptblkCurrent = ptblkCurrent->m_ptblkNext; } } dprintf("===================================================================\n"); dprintf("%d threads with %d total lock count (%d threads holding locks)\n", cThreads, cLockCount, cLockedThreads); } //---[ workqueue ]------------------------------------------------------------- // // // Description: // Dumps a summary of items in the async work queue // Parameters: // // Returns: // // History: // 9/13/99 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(workqueue) { SETCALLBACKS(); const DWORD MAX_COMPLETION_FUNCTIONS = 10; PVOID rgpvFnName[MAX_COMPLETION_FUNCTIONS]; DWORD rgcFnCount[MAX_COMPLETION_FUNCTIONS]; BYTE pbWorkItem[sizeof(CAsyncWorkQueueItem)]; PVOID pvQueue = NULL; PVOID pvWorkItem = NULL; PVOID pvFn = NULL; DWORD i = 0; DWORD cItems = 0; UCHAR SymbolName[ 200 ]; ULONG_PTR Displacement; CFifoQueueDbgIterator fifoqdbg(pExtensionApis); ZeroMemory(&rgpvFnName, sizeof(rgpvFnName)); ZeroMemory(&rgcFnCount, sizeof(rgcFnCount)); ZeroMemory(&pbWorkItem, sizeof(pbWorkItem)); ZeroMemory(&SymbolName, sizeof(SymbolName)); if (!szArg || ('\0' == szArg[0]) || !(pvQueue = (PVOID) GetExpression(szArg))) { dprintf("You must specify a queue address\n"); return; } //Get FifoqOffset pvQueue = (PVOID) &(((CAsyncWorkQueue *)pvQueue)->m_asyncq.m_fqQueue); if (!fifoqdbg.fInit(hCurrentProcess, pvQueue)) { dprintf("Error initializing queue iterator for address 0x%08X\n", pvQueue); return; } while (pvWorkItem = fifoqdbg.pvGetNext()) { cItems++; if (!ReadMemory(pvWorkItem, &pbWorkItem, sizeof(pbWorkItem), NULL)) { dprintf("Error reading memory at 0x%0X\n", pvWorkItem); continue; } pvFn = ((CAsyncWorkQueueItem *)pbWorkItem)->m_pfnCompletion; for (i = 0; i < MAX_COMPLETION_FUNCTIONS; i++) { if (pvFn == rgpvFnName[i]) { rgcFnCount[i]++; break; } else if (!rgpvFnName[i]) { rgpvFnName[i] = pvFn; rgcFnCount[i] = 1; break; } } } dprintf("# Calls\t| Address\t\t| Function Name\n"); dprintf("------------------------------------------------------------\n"); for (i = 0; i < MAX_COMPLETION_FUNCTIONS; i++) { if (!rgpvFnName[i]) break; g_lpGetSymbolRoutine( rgpvFnName[i], (PCHAR)SymbolName, &Displacement ); dprintf( "%d\t| 0x%08X\t| %s\n", rgcFnCount[i], rgpvFnName[i], SymbolName); } dprintf("------------------------------------------------------------\n"); dprintf("Total %d pending work queue items\n", cItems); #ifdef NEVER //Dump fifoqdbg dprintf("CFifoQueueDbgIterator: page %d, index %d, pages %d\n ", fifoqdbg.m_iCurrentPage, fifoqdbg.m_iCurrentIndexInPage, fifoqdbg.m_cPagesLoaded); #endif } //---[ dumpqueue ]------------------------------------------------------------- // // // Description: // Dumps the *entire* contents of a queue // Parameters: // szArg // - String-ized address of CFifoQ to dump // - [optional] msg to search for // Returns: // - // History: // 10/21/1999 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION(dumpqueue) { const DWORD cStoppingRule = 10000; CQueueDbgIterator qdbg(pExtensionApis); BYTE pbMsgRef[sizeof(CMsgRef)]; PVOID pvMsgRef = NULL; PVOID pvMailMsg = NULL; PVOID pvQueue = NULL; DWORD cItems = 0; BOOL fIsMsgRef = FALSE; CHAR szArgBuffer[200]; LPSTR szCurrentArg = NULL; PVOID pvSearch = NULL; DWORD cMatchSearch = 0; if (!szArg || ('\0' == szArg[0])) { dprintf("You must specify a queue address\n"); return; } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { pvQueue = (PVOID)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) pvSearch = (PVOID) GetExpression(szCurrentArg); } else { pvQueue = (PVOID) GetExpression(szArg); } } if (!pvQueue) { dprintf("You must specify a queue address\n"); return; } if (!qdbg.fInit(hCurrentProcess, pvQueue)) { dprintf("Unable to get the a queue for address 0x%X\n", pvQueue); return; } while ((pvMsgRef = qdbg.pvGetNext()) && (cItems++ < cStoppingRule)) { fIsMsgRef = FALSE; if (cItems > qdbg.cGetCount()) { cItems--; break; } //Try to read it as a CMsgRef if (ReadMemory(pvMsgRef, pbMsgRef, sizeof(pbMsgRef), NULL)) { if (MSGREF_SIG == ((CMsgRef *)pbMsgRef)->m_dwSignature) { fIsMsgRef = TRUE; pvMailMsg = ((CMsgRef *)pbMsgRef)->m_pIMailMsgProperties; } } //Print it out if it matches our search (or we have no search) if (!pvSearch || (pvSearch == pvMsgRef) || (pvSearch == pvMailMsg)) { cMatchSearch++; if (pvSearch) dprintf("\n****\n"); if (fIsMsgRef) dprintf("\t0x%08X\t0x%08X\n", pvMsgRef, pvMailMsg); else dprintf("\t0x%08X\n", pvMsgRef); if (pvSearch) dprintf("****\n\n"); } } if (pvSearch) dprintf("Found %d matches to search\n", cMatchSearch); } //---[ displaytickcount ]------------------------------------------------------ // // // Description: // Converts a tick count to a readable time // Parameters: // szArg - String-ized tick count in hex // Returns: // - // History: // 10/29/1999 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(displaytickcount) { DWORD dwTickCountToDisplay = (DWORD)GetExpression(szArg); DWORD dwCurrentTickCount = GetTickCount(); DWORD dwTickDifference = dwCurrentTickCount - dwTickCountToDisplay; FILETIME ftCurrentUTC; FILETIME ftDisplayUTC; FILETIME ftDisplayLocal; ULARGE_INTEGER uliTimeAdjust; SYSTEMTIME stDisplayLocal; static char *s_rgszMonth[ 12 ] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static char *s_rgszWeekDays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; GetSystemTimeAsFileTime(&ftCurrentUTC); //Adjust the current filetime to local memcpy(&uliTimeAdjust, &ftCurrentUTC, sizeof(FILETIME)); uliTimeAdjust.QuadPart -= (((ULONGLONG)dwTickDifference)*((ULONGLONG)10000)); memcpy(&ftDisplayUTC, &uliTimeAdjust, sizeof(FILETIME)); FileTimeToLocalFileTime(&ftDisplayUTC, &ftDisplayLocal); ZeroMemory(&stDisplayLocal, sizeof(stDisplayLocal)); FileTimeToSystemTime(&ftDisplayLocal, &stDisplayLocal); dprintf("\n%s, %d %s %04d %02d:%02d:%02d (localized)\n", s_rgszWeekDays[stDisplayLocal.wDayOfWeek], stDisplayLocal.wDay, s_rgszMonth[ stDisplayLocal.wMonth - 1 ], stDisplayLocal.wYear, stDisplayLocal.wHour, stDisplayLocal.wMinute, stDisplayLocal.wSecond); } //---[ queueusage ]------------------------------------------------------------ // // // Description: // Dumps the usage count averages for a given fifoq. If we are dumping // CMsgRefs, it will dump the pointers to the various MailMsg interfaces // as well. // Parameters: // szArg String-ized address of CFifoQ to dump // Returns: // - // History: // 10/15/1999 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(queueusage) { const DWORD cbUsageCountOffset = 0x20; const DWORD cbContentHandleOffset = 0x90+cbUsageCountOffset; const DWORD cbStreamHandleOffset = 0x8+cbContentHandleOffset; const DWORD cStoppingRule = 10000; const DWORD cMaxUsageCountToTrack = 6; CFifoQueueDbgIterator fifoqdbg(pExtensionApis); BYTE pbMsgRef[4*sizeof(CMsgRef)]; //leave room for bitmaps BYTE pbMailMsg[cbStreamHandleOffset+sizeof(PVOID)]; PVOID pvMsgRef = NULL; PVOID pvMailMsg = NULL; PVOID pvQueue = NULL; DWORD cItems = 0; DWORD cCurrentUsageCount = 0; DWORD cTotalUsageCount = 0; DWORD cMaxUsageCount = 0; DWORD cMinUsageCount = 200; DWORD rgcUsageCounts[cMaxUsageCountToTrack]; PVOID pvHandle = NULL; DWORD cMsgsWithOpenContentHandles = 0; DWORD cMsgsWithOpenStreamHandles = 0; BOOL fVerbose = FALSE; ZeroMemory(rgcUsageCounts, sizeof(rgcUsageCounts)); if (!szArg || ('\0' == szArg[0]) || !(pvQueue = (PVOID) GetExpression(szArg))) { dprintf("You must specify a queue address\n"); return; } if (!fifoqdbg.fInit(hCurrentProcess, pvQueue)) { dprintf("Unable to get the a queue for address 0x%X\n", pvQueue); return; } while ((pvMsgRef = fifoqdbg.pvGetNext()) && (cItems++ < cStoppingRule)) { if (cItems > fifoqdbg.cGetCount()) { cItems--; break; } //Read CMsgRef into this process if (!ReadMemory(pvMsgRef, pbMsgRef, sizeof(pbMsgRef), NULL)) { dprintf("Unable to read MsgRef at address 0x%X, index %d\n", pvMsgRef, cItems); cItems--; break; } //Get inteface ptr for mailmsg from CMsgRef pvMailMsg = ((CMsgRef *)pbMsgRef)->m_pIMailMsgQM; if (!ReadMemory(pvMailMsg, pbMailMsg, sizeof(pbMailMsg), NULL)) { dprintf("Unable to read MailMsg Ptr at address 0x%X for MsgRef 0x%X, index %d\n", pvMailMsg, pvMsgRef, cItems); cItems--; break; } //Check and see if this message has a content (P2) handle open if (*(pbMailMsg + cbContentHandleOffset)) cMsgsWithOpenContentHandles++; //Check and see if this message has a stream (P1) handle open if (*(pbMailMsg + cbStreamHandleOffset)) cMsgsWithOpenStreamHandles++; if (fVerbose && ((*(pbMailMsg + cbStreamHandleOffset)) || (*(pbMailMsg + cbStreamHandleOffset)))) { dprintf("Message at address 0x%X has open handles\n", pvMsgRef); } cCurrentUsageCount = (DWORD) *(pbMailMsg + cbUsageCountOffset); cTotalUsageCount += cCurrentUsageCount; if (cCurrentUsageCount > cMaxUsageCount) cMaxUsageCount = cCurrentUsageCount; if (cCurrentUsageCount < cMinUsageCount) cMinUsageCount = cCurrentUsageCount; if (cCurrentUsageCount >= cMaxUsageCountToTrack) { dprintf("\n****\n"); dprintf("High usage count of %d found on MailMsg 0x%X, MsgRef 0x%X, item %d\n", cCurrentUsageCount, pvMailMsg, pvMsgRef, cItems); dprintf("\n****\n"); cCurrentUsageCount = cMaxUsageCountToTrack-1; } //Save count for summaries rgcUsageCounts[cCurrentUsageCount]++; } //Generate and display summary information if (!cItems) { dprintf("No Messages found in queue 0x%X\n", pvQueue); } else { dprintf("\n==================================================================\n"); dprintf("Usage Count Summary\n"); dprintf("------------------------------------------------------------------\n"); dprintf("\t%d\t\tTotal Message\n", cItems); dprintf("\t%d\t\tTotal Messages with open content handles\n", cMsgsWithOpenContentHandles); dprintf("\t%d\t\tTotal Messages with open stream handles\n", cMsgsWithOpenStreamHandles); dprintf("\t%d\t\tTotal Usage Count\n", cTotalUsageCount); dprintf("\t%d\t\tMax Usage Count\n", cMaxUsageCount); dprintf("\t%d\t\tMin Usage Count\n", cMinUsageCount); dprintf("\t%f\tAverage Usage Count\n", ((float)cTotalUsageCount)/((float)cItems)); for (DWORD i = 0; i < cMaxUsageCountToTrack-1; i++) { dprintf("\t%d\t\tMessages with Usage count of %d\n", rgcUsageCounts[i], i); } dprintf("\t%d\t\tMessages with Usage count of %d or greater\n", rgcUsageCounts[cMaxUsageCountToTrack-1], cMaxUsageCountToTrack-1); dprintf("==================================================================\n"); } } //---[ dmqusage ]-------------------------------------------------------------- // // // Description: // Debugger extension that wraps the queue usage debugger extension // to display the usage counts for all queues // Parameters: // szArg String-ized address of DMQ to dump // Returns: // - // History: // 10/15/1999 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(dmqusage) { PVOID pvQueue = NULL; PVOID pvDMQ = NULL; BYTE pbDMQ[sizeof(CDestMsgQueue)]; CHAR szQueueAddress[30]; DWORD iQueue = 0; if (!szArg || ('\0' == szArg[0]) || !(pvDMQ = (PVOID) GetExpression(szArg))) { dprintf("You must specify a queue address\n"); return; } if (!ReadMemory(pvDMQ, pbDMQ, sizeof(pbDMQ), NULL)) { dprintf("Unable to read DMQ at address 0x%X\n", pvDMQ); return; } dprintf("\n\n******************************************************************\n"); dprintf("Start USAGE COUNT STATS for DMQ 0x%0X\n", pvDMQ); dprintf("******************************************************************\n"); for (iQueue = 0; iQueue < NUM_PRIORITIES; iQueue++) { pvQueue = ((CDestMsgQueue *)pbDMQ)->m_rgpfqQueues[iQueue]; if (!pvQueue) continue; //nothing as every been queued to this queue //Only display the queue if we think we have messages //$$TODO - We could actual read this queue into memory and check it, //but since we currently only support 1 priority, this will do. if (((CDestMsgQueue *)pbDMQ)->m_aqstats.m_cMsgs ||((CDestMsgQueue *)pbDMQ)->m_aqstats.m_cRetryMsgs) { wsprintf(szQueueAddress, "0x%X", pvQueue); queueusage(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szQueueAddress); } } //Display retry queue, if there are messages there if (((CDestMsgQueue *)pbDMQ)->m_fqRetryQueue.m_cQueueEntries) { pvQueue = ((PBYTE)pvDMQ) + FIELD_OFFSET(CDestMsgQueue, m_fqRetryQueue); wsprintf(szQueueAddress, "0x%X", pvQueue); queueusage(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szQueueAddress); } dprintf("\n\n******************************************************************\n"); dprintf("End USAGE COUNT STATS for DMQ 0x%0X\n", pvDMQ); dprintf("******************************************************************\n"); } //---[ dntusage ]-------------------------------------------------------------- // // // Description: // Debugger extension that wrap dmqusage. Call dmqusage for every DMQ // in the DNT. // Parameters: // szArg string-ize address of dnt (DOMAIN_NAME_TABLE) // Returns: // - // History: // 10/15/1999 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(dntusage) { BYTE pbBuffer[sizeof(DOMAIN_NAME_TABLE)]; PDOMAIN_NAME_TABLE pdnt = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntry = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntryRealAddress = NULL; PDOMAIN_NAME_TABLE_ENTRY pPathEntry = NULL; BYTE pbEntry[sizeof(DOMAIN_NAME_TABLE_ENTRY)]; CHAR pBuffer[MAX_DOM_PATH_SIZE] = "Root Entry"; LPSTR pEntryBuffer = NULL; LPSTR pEntryBufferStop = NULL; DWORD dwLength = 0; DWORD dwSig = 0; CHAR szFinalDest[MAX_DOM_PATH_SIZE]; BYTE pbDomainEntry[sizeof(CDomainEntry)]; CDomainEntry *pdentry = (CDomainEntry *) pbDomainEntry; CHAR szDMQAddress[30]; DWORD cQueuesPerEntry = 0; DWORD cMaxQueuesPerEntry = 1000; PLIST_ENTRY pliHead = NULL; PLIST_ENTRY pliCurrent = NULL; LIST_ENTRY liCurrent; //Define buffers for parsing addresses... the sizes are clearly overkill, and //I'm not too worried about overflow in a debugger extension CHAR szAddress[MAX_DOM_PATH_SIZE]; CHAR szDumpArg[MAX_DOM_PATH_SIZE] = ""; LPSTR szParsedArg = (LPSTR) szArg; LPSTR szCurrentDest = NULL; //Allow people who are used to typeing dump CFoo@Address... keep using the @ sign if ('@' == *szParsedArg) szParsedArg++; //Get Address of DomainNameTable szCurrentDest = szAddress; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szParsedArg-szArg <= MAX_DOM_PATH_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '\0'; //Eat white space while (('\0' != *szParsedArg) && isspace(*szParsedArg)) szParsedArg++; //Copy name of struct to dump at each node szCurrentDest = szDumpArg; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szCurrentDest-szDumpArg <= MAX_DOM_PATH_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '@'; szCurrentDest++; //szCurrentDest now points to place to copy address to pdnt = (PDOMAIN_NAME_TABLE) GetExpression(szAddress); if (!pdnt) { dprintf("ERROR: Unable to Get DOMAIN_NAME_TABLE from argument %s\n", szArg); return; } if (!ReadMemory(pdnt, pbBuffer, sizeof(DOMAIN_NAME_TABLE), NULL)) { dprintf("ERROR: Unable to read process memory\n"); return; } pdnt = (PDOMAIN_NAME_TABLE)pbBuffer; pEntry = &(pdnt->RootEntry); while(pEntry) { //We are not interested in wildcard data if (pEntry->pData) { //Display link state information if (!ReadMemory(pEntry->pData, pbDomainEntry, sizeof(CDomainEntry), NULL)) { dprintf("ERROR: Unable to read domain entry from @0x%08X\n", pEntry->pData); return; } pliHead = (PLIST_ENTRY) (((BYTE *)pEntry->pData) + FIELD_OFFSET(CDomainEntry, m_liDestQueues)); pliCurrent = pdentry->m_liDestQueues.Flink; //Get final destination string if (!ReadMemory(pdentry->m_szDomainName, szFinalDest, pdentry->m_cbDomainName, NULL)) { dprintf("ERROR: Unable to read final destination name from @0x%08X\n", pdentry->m_szDomainName); return; } szFinalDest[pdentry->m_cbDomainName] = '\0'; //Loop and display each DMQ cQueuesPerEntry = 0; while (pliHead != pliCurrent) { cQueuesPerEntry++; if (cQueuesPerEntry > cMaxQueuesPerEntry) { dprintf("ERROR: More than %d queues for this entry\n", cQueuesPerEntry); return; } if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read link LIST_ENTRY @0x%08X\n", pliCurrent); return; } wsprintf(szDMQAddress, "0x%X", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs)); dmqusage(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szDMQAddress); pliCurrent = liCurrent.Flink; } } //Now determine what the "next" entry is if (pEntry->pFirstChildEntry != NULL) { pEntryRealAddress = pEntry->pFirstChildEntry; } else if (pEntry->pSiblingEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } else { for (pEntryRealAddress = pEntry->pParentEntry; pEntryRealAddress != NULL; pEntryRealAddress = pEntry->pParentEntry) { //must read parent entry into our buffer if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory of parent domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; if (pEntry->pSiblingEntry != NULL) break; } if (pEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } } if (pEntryRealAddress) { if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory on domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; } else { pEntry = NULL; } } } //---[ walkcpool ]------------------------------------------------------------- // // // Description: // Will walk a given CPool object. Validate headers, and dump currently // used objects. // // ***NOTE*** This version only works on DBG CPool implementations (since // RTL does not have the headerinfo). I could write a more complex // version that checks and sees if this each pool object is in the // freelist, but I will leave that as an exercise to the reader. // Parameters: // szArg - String containing arguments // Address of CPool object to dump // Offset of additional address to dump // Returns: // - // History: // 9/30/1999 - MikeSwa Created // #define HEAD_SIGNATURE (DWORD)'daeH' #define TAIL_SIGNATURE (DWORD)'liaT' #define FREE_STATE (DWORD)'eerF' #define USED_STATE (DWORD)'desU' //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(walkcpool) { PVOID pvCPool = NULL; DWORD cbCPoolData = 0; DWORD cCommited = 0; DWORD cFragments = 0; DWORD cBuffersPerFragment = 0; DWORD iCurrentBufferInFragment = 0; DWORD iCurrentFragment = 0; PVOID *pvFragment = NULL; PVOID pvCPoolData = NULL; BYTE pbCPoolBuffer[sizeof(CPool)]; BYTE pbCPoolDataBuffer[100]; LPSTR szCurrentArg = NULL; CHAR szArgBuffer[200]; DWORD_PTR cbOffset = 0; DWORD_PTR dwptrData = 0; if (!szArg || ('\0' == szArg[0])) { dprintf("You must specify a Pool address\n"); return; } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { pvCPool = (PVOID)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) cbOffset = (DWORD_PTR) GetExpression(szCurrentArg); } else { pvCPool = (PVOID) GetExpression(szArg); } } if (!ReadMemory(pvCPool, pbCPoolBuffer, sizeof(CPool), NULL)) { dprintf("Unable to read memory at 0x%x\n", pvCPool); return; } dprintf("Dumping CPool at address 0x%08X\n", pvCPool); //Get interesting values from CPool cbCPoolData = *((PDWORD)(pbCPoolBuffer + 0x8)); cCommited = *((PDWORD)(pbCPoolBuffer + 0xc)); cFragments = *((PDWORD)(pbCPoolBuffer + 0x54)); cBuffersPerFragment = *((PDWORD)(pbCPoolBuffer + 0x50)); dprintf("CPool data size is %d bytes (0x%x)\n", cbCPoolData, cbCPoolData); dprintf("CPool fragment count is %d\n", cFragments); dprintf("CPool has %d buffers per fragment\n", cBuffersPerFragment); dprintf("CPool has %d commited buffers\n", cCommited); if (!cbCPoolData) { dprintf("Invalid CPool\n"); return; } //Loop over the fragment and dump each one pvFragment = (PVOID *) (pbCPoolBuffer + 0x58); for (iCurrentFragment = 0; iCurrentFragment < cFragments; iCurrentFragment++ || pvFragment++) { pvCPoolData = *pvFragment; if (!pvCPoolData) continue; dprintf("CPool Fragment #%d at 0x%08X\n", iCurrentFragment, pvCPoolData); for (iCurrentBufferInFragment = 0; iCurrentBufferInFragment < cBuffersPerFragment; iCurrentBufferInFragment++) { if (!ReadMemory(pvCPoolData, pbCPoolDataBuffer, 100, NULL)) { dprintf("\tUnable to read CPool buffer data at 0x%x\n", pvCPoolData); break; } if (HEAD_SIGNATURE != ((DWORD *)pbCPoolDataBuffer)[1]) { dprintf("\tHit bad signature at 0x%08X\n", pvCPoolData); break; //bad signature bail } if (USED_STATE == ((DWORD *)pbCPoolDataBuffer)[2]) { dprintf("\tAllocated block found at offset %d (0x%08X)\n", iCurrentBufferInFragment, pvCPoolData); if (cbOffset) { if (ReadMemory(((PBYTE)pvCPoolData)+cbOffset, &dwptrData, sizeof(DWORD_PTR), NULL)) { dprintf("\t\tData 0x%X found at address 0x%X\n", dwptrData, ((PBYTE)pvCPoolData)+cbOffset); } } } pvCPoolData = ((BYTE *)pvCPoolData) + cbCPoolData; if (!(--cCommited)) { dprintf("\tLast block is in fragment at offset %d (0x%08X)\n", iCurrentBufferInFragment, pvCPoolData); break; //We're done } } } } //---[ CheckVersion ]---------------------------------------------------------- // // // Description: // Checks the AQ version to make sure that this debugger extension will // work with it. // Parameters: // // Returns: // // History: // 2/5/99 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(CheckVersion) { DWORD cbAQClasses = 0; DWORD dwAQFlavorSignature = ' '; PVOID pcbAQClasses = (PVOID) GetExpression("aqueue!g_cbClasses"); PVOID pdwAQFlavorSignature = (PVOID) GetExpression("aqueue!g_dwFlavorSignature"); PCHAR pch = NULL; //Read the version information stamped in AQ ReadMemory(pcbAQClasses, &cbAQClasses, sizeof(DWORD), NULL); ReadMemory(pdwAQFlavorSignature, &dwAQFlavorSignature, sizeof(DWORD), NULL); if (!g_fVersionChecked) { dprintf("AQueue Internal Version Info (#'s should match):\n"); pch = (PCHAR) &g_dwFlavorSignature; dprintf("\taqdbgext %c%c%c%c 0x%08X\n", *(pch), *(pch+1), *(pch+2), *(pch+3), g_cbClasses); pch = (PCHAR) &dwAQFlavorSignature; dprintf("\taqueue %c%c%c%c 0x%08X\n\n", *(pch), *(pch+1), *(pch+2), *(pch+3), cbAQClasses); } g_fVersionChecked = FALSE; if (dwAQFlavorSignature != g_dwFlavorSignature) dprintf("\n\nWARNING: DBG/RTL aqueue.dll & aqdbgext.dll mismatch\n\n"); else if (g_cbClasses != cbAQClasses) dprintf("\n\nWARNING: aqueue.dll & aqdbgext.dll version mismatch\n\n"); else g_fVersionChecked = TRUE; } //---[ DumpServers ]------------------------------------------------------------ // // // Description: // Dumps pointers to the CAQSvrInst for each virtual server // Parameters: // // Returns: // // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(DumpServers) { PVOID pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); DWORD *pcInstances = (DWORD *) GetExpression("aqueue!g_cInstances"); DWORD cInstances = 0; LIST_ENTRY liCurrent; BYTE pbBuffer[sizeof(CAQSvrInst)]; CAQSvrInst *paqinst = (CAQSvrInst *) pbBuffer; PVOID pCMQAddress = NULL; DWORD dwInstance = 0; CHAR szDumpArg[40] = ""; CHAR szArgBuffer[200]; LPSTR szCurrentArg = NULL; CheckVersion(DebugArgs); if (!szArg || ('\0' == szArg[0])) { dwInstance = 0; pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { dwInstance = (DWORD)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) pvListHead = (PVOID) GetExpression(szCurrentArg); else pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } } if (!pvListHead) { dprintf("ERROR: Unable to determine LIST_ENTRY for virtual servers\n"); dprintf(" If you are using windbg, you should specify the value as the\n"); dprintf(" 2nd argument. You can determine the address value by typeing:\n"); dprintf(" x " AQUEUE_VIRTUAL_SERVER_SYMBOL "\n"); dprintf(" You may also have bad symbols for aqueue.dll.\n"); return; } if (!ReadMemory(pvListHead, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @ aqueue!g_liVirtualServers 0x%08X", pvListHead); return; } if (!ReadMemory(pcInstances, &cInstances, sizeof(DWORD), NULL)) { //For you windbg users out there dprintf("\n\n%Virtual Server Instance(s)\n\n"); } else { dprintf("\n\n%d Virtual Server Instance(s)\n\n", cInstances); } dprintf("Class@Address Server Instance\n"); dprintf("==========================================\n"); while (liCurrent.Flink != pvListHead) { pCMQAddress = CONTAINING_RECORD(liCurrent.Flink, CAQSvrInst, m_liVirtualServers); if (!ReadMemory(pCMQAddress, paqinst, sizeof(CAQSvrInst), NULL)) { dprintf("ERROR: Unable to CAQSvrInst @0x%08X", pCMQAddress); return; } if (CATMSGQ_SIG != paqinst->m_dwSignature) { dprintf("@0x%08X INVALID SIGNATURE - list entry @0x%08X\n", pCMQAddress, liCurrent.Flink); } else { dprintf("CAQSvrInst@0x%08X %d\n", pCMQAddress, paqinst->m_dwServerInstance); if (paqinst->m_dwServerInstance == dwInstance) wsprintf(szDumpArg, "CAQSvrInst@0x%08X", pCMQAddress); } if (!ReadMemory(liCurrent.Flink, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X", liCurrent.Flink); return; } } //Dump the interesting instance if ('\0' != szDumpArg[0]) _dump(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szDumpArg); } //---[ handlemgmt ]------------------------------------------------------------ // // // Description: // Caclulates a handle management score for a given virtual server. // // Calculates score based on the number of messages closed and messages // delivered / pending delivery... the lower the score... the better. // Score = Closes / // (m_cCurrentMsgsPendingSubmit + m_cCurrentMsgsPendingCat*2 + // m_cCurrentMsgsPendingRouting*3 + m_cCurrentMsgsPendingLocal*4 + // m_cMsgsDeliveredLocal*5) // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(handlemgmt) { #define MAILMSG_CLOSES_SYMBOL \ "mailmsg!CMailMsg__g_cTotalExternalReleaseUsageZero" #define MAILMSG_CURRENT_CLOSED_SYMBOL \ "mailmsg!CMailMsg__g_cCurrentMsgsClosedByExternalReleaseUsage" #define MAILMSG_CURRENT_ALLOCATED \ "mailmsg!CMsg__m_Pool+0x10" #define MAILMSG_TOTAL_ALLOCATED \ "mailmsg!CMsg__m_Pool+0x3c" PVOID pvCloses = (PVOID) GetExpression(MAILMSG_CLOSES_SYMBOL); DWORD cCloses = 1; PVOID pvCurrentMsgsThatHaveBeenClosed = (PVOID) GetExpression(MAILMSG_CURRENT_CLOSED_SYMBOL); DWORD cCurrentMsgsThatHaveBeenClosed = 1; PVOID pvCurrentMsgsAllocated = (PVOID) GetExpression(MAILMSG_CURRENT_ALLOCATED); DWORD cCurrentMsgsAllocated = 1; PVOID pvTotalMsgsAllocated = (PVOID) GetExpression(MAILMSG_TOTAL_ALLOCATED); DWORD cTotalMsgsAllocated = 1; DWORD dwPercentCurrentMessagesClosed = 0; DWORD dwPercentTotalMessagesBacklogged = 0; DWORD dwPercentCurrentMessagesQueueInternally = 0; PVOID pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); DWORD *pcInstances = (DWORD *) GetExpression("phatq!g_cInstances"); DWORD cInstances = 0; LIST_ENTRY liCurrent; BYTE pbBuffer[sizeof(CAQSvrInst)]; CAQSvrInst *paqinst = (CAQSvrInst *) pbBuffer; PVOID pCMQAddress = NULL; DWORD dwInstance = 1; CHAR szDumpArg[40] = ""; CHAR szArgBuffer[200]; LPSTR szCurrentArg = NULL; DWORD dwQueueScore = 0; DWORD dwWeightedScore = 0; DWORD dwDeliveredScore = 0; DWORD dwSubmittedScore = 0; DWORD dwWeightedQueueLength = 0; DWORD dwTotalQueueLength = 0; BOOL fFoundInstance = FALSE; // // Read the data we need from mailmsg // if (!ReadMemory(pvCloses, &cCloses, sizeof(cCloses), NULL)) { dprintf("Unable to read %s at address %p\n", MAILMSG_CLOSES_SYMBOL, pvCloses); return; } if (!ReadMemory(pvCurrentMsgsThatHaveBeenClosed, &cCurrentMsgsThatHaveBeenClosed, sizeof(cCloses), NULL)) { dprintf("Unable to read %s at address %p\n", MAILMSG_CLOSES_SYMBOL, pvCloses); return; } if (!ReadMemory(pvCurrentMsgsAllocated, &cCurrentMsgsAllocated, sizeof(cCloses), NULL)) { dprintf("Unable to read %s at address %p\n", MAILMSG_CLOSES_SYMBOL, pvCloses); return; } if (!ReadMemory(pvTotalMsgsAllocated, &cTotalMsgsAllocated, sizeof(cCloses), NULL)) { dprintf("Unable to read %s at address %p\n", MAILMSG_CLOSES_SYMBOL, pvCloses); return; } if (cCurrentMsgsAllocated) { dwPercentCurrentMessagesClosed = (100*cCurrentMsgsThatHaveBeenClosed)/cCurrentMsgsAllocated; } if (cTotalMsgsAllocated) { dwPercentTotalMessagesBacklogged = (100*cCurrentMsgsAllocated)/cTotalMsgsAllocated; } // // Get the instance object we want to get data from // CheckVersion(DebugArgs); if (!szArg || ('\0' == szArg[0])) { dwInstance = 1; pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { dwInstance = (DWORD)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) pvListHead = (PVOID) GetExpression(szCurrentArg); else pvListHead = (PVOID) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } } if (!pvListHead) { dprintf("ERROR: Unable to determine LIST_ENTRY for virtual servers\n"); dprintf(" If you are using windbg, you should specify the value as the\n"); dprintf(" 2nd argument. You can determine the address value by typeing:\n"); dprintf(" x " AQUEUE_VIRTUAL_SERVER_SYMBOL "\n"); dprintf(" You may also have bad symbols for phatq.dll.\n"); return; } if (!ReadMemory(pvListHead, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @ phatq!g_liVirtualServers 0x%08X", pvListHead); return; } if (!ReadMemory(pcInstances, &cInstances, sizeof(DWORD), NULL)) { //For you windbg users out there dprintf("\n\n%Virtual Server Instance(s)\n\n"); } else { dprintf("\n\n%d Virtual Server Instance(s)\n\n", cInstances); } while (liCurrent.Flink != pvListHead) { pCMQAddress = CONTAINING_RECORD(liCurrent.Flink, CAQSvrInst, m_liVirtualServers); if (!ReadMemory(pCMQAddress, paqinst, sizeof(CAQSvrInst), NULL)) { dprintf("ERROR: Unable to CAQSvrInst @0x%08X", pCMQAddress); return; } if (CATMSGQ_SIG != paqinst->m_dwSignature) { dprintf("@0x%08X INVALID SIGNATURE - list entry @0x%08X\n", pCMQAddress, liCurrent.Flink); } else if (paqinst->m_dwServerInstance == dwInstance) { fFoundInstance = TRUE; dprintf("Using CAQSvrInst@0x%08X %d\n", pCMQAddress, paqinst->m_dwServerInstance); break; } if (!ReadMemory(liCurrent.Flink, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X", liCurrent.Flink); return; } } // // Did we find the instance // if (!fFoundInstance) { dprintf("We did not find instance %d\n", dwInstance); return; } dwWeightedQueueLength = paqinst->m_cCurrentMsgsPendingSubmit + paqinst->m_cCurrentMsgsPendingCat*2 + paqinst->m_cCurrentMsgsPendingRouting*3 + paqinst->m_cCurrentMsgsPendingLocal*4 + paqinst->m_cMsgsDeliveredLocal*5; dwTotalQueueLength = paqinst->m_cCurrentMsgsPendingSubmit + paqinst->m_cCurrentMsgsPendingCat + paqinst->m_cCurrentMsgsPendingRouting + paqinst->m_cCurrentMsgsPendingLocal + paqinst->m_cMsgsDeliveredLocal; if (cTotalMsgsAllocated) { dwPercentCurrentMessagesQueueInternally = (100*(dwTotalQueueLength-paqinst->m_cMsgsDeliveredLocal)) /cCurrentMsgsAllocated; } if (dwTotalQueueLength) dwQueueScore = (cCloses*1000)/dwTotalQueueLength; if (dwWeightedQueueLength) dwWeightedScore = (cCloses*1000)/dwWeightedQueueLength; if (paqinst->m_cMsgsDeliveredLocal) dwDeliveredScore = (cCloses*1000)/paqinst->m_cMsgsDeliveredLocal; if (paqinst->m_cTotalExternalMsgsSubmitted) dwSubmittedScore = (cCloses*1000)/paqinst->m_cTotalExternalMsgsSubmitted; dprintf("\n\nHandle Managment scores:\n"); dprintf("========================\n"); dprintf("Non-Weighted Score: %d\n", dwQueueScore); dprintf("Weighted Score: %d\n", dwWeightedScore); dprintf("Delivery Score: %d\n", dwDeliveredScore); dprintf("Submitted Score: %d\n", dwSubmittedScore); dprintf("Current Messsages Allocated That have been closed: %d%%\n", dwPercentCurrentMessagesClosed); dprintf("\nThe following are useful in correlating different test runs...\n"); dprintf("Messages Backlogged: %d%%\n", dwPercentTotalMessagesBacklogged); dprintf("Backlogged Messsages Queued internally: %d%%\n", dwPercentCurrentMessagesQueueInternally); dprintf("\n%d Total message closures.. %d total deliveries\n\n", cCloses, paqinst->m_cMsgsDeliveredLocal); } //---[ DumpDNT ]------------------------------------------------------------ // // // Description: // Dumps the contents of a DOMAIN_NAME_TABLE // Parameters: // // Returns: // // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(DumpDNT) { BYTE pbBuffer[sizeof(DOMAIN_NAME_TABLE)]; PDOMAIN_NAME_TABLE pdnt = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntry = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntryRealAddress = NULL; PDOMAIN_NAME_TABLE_ENTRY pPathEntry = NULL; BYTE pbEntry[sizeof(DOMAIN_NAME_TABLE_ENTRY)]; BYTE pbPathEntry[sizeof(DOMAIN_NAME_TABLE_ENTRY)]; //buffer for putter path name entries in BYTE pbPathEntryBuffer[MAX_DOM_PATH_SIZE]; CHAR pBuffer[MAX_DOM_PATH_SIZE] = "Root Entry"; LPSTR pPathBuffer = NULL; LPSTR pPathBufferStop = NULL; LPSTR pEntryBuffer = NULL; LPSTR pEntryBufferStop = NULL; DWORD dwLength = 0; DWORD dwSig = 0; //Define buffers for parsing addresses... the sizes are clearly overkill, and //I'm not too worried about overflow in a debugger extension CHAR szAddress[MAX_DOM_PATH_SIZE]; CHAR szDumpArg[MAX_DOM_PATH_SIZE] = ""; LPSTR szParsedArg = (LPSTR) szArg; LPSTR szCurrentDest = NULL; //Allow people who are used to typeing dump CFoo@Address... keep using the @ sign if ('@' == *szParsedArg) szParsedArg++; //Get Address of DomainNameTable szCurrentDest = szAddress; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szParsedArg-szArg <= MAX_DOM_PATH_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '\0'; //Eat white space while (('\0' != *szParsedArg) && isspace(*szParsedArg)) szParsedArg++; //Copy name of struct to dump at each node szCurrentDest = szDumpArg; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szCurrentDest-szDumpArg <= MAX_DOM_PATH_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '@'; szCurrentDest++; //szCurrentDest now points to place to copy address to pdnt = (PDOMAIN_NAME_TABLE) GetExpression(szAddress); if (!pdnt) { dprintf("ERROR: Unable to Get DOMAIN_NAME_TABLE from argument %s\n", szArg); return; } if (!ReadMemory(pdnt, pbBuffer, sizeof(DOMAIN_NAME_TABLE), NULL)) { dprintf("ERROR: Unable to read process memory\n"); return; } pPathBuffer = pBuffer; pPathBufferStop = pPathBuffer + (MAX_DOM_PATH_SIZE / sizeof(CHAR) -1 ); pEntryRealAddress = (PDOMAIN_NAME_TABLE_ENTRY) ((BYTE *)pdnt + FIELD_OFFSET(DOMAIN_NAME_TABLE, RootEntry)); pdnt = (PDOMAIN_NAME_TABLE) pbBuffer; pEntry = &(pdnt->RootEntry); dprintf("Entry ID # Children pData pWildCard Path\n"); dprintf("===========================================================================\n"); while(pEntry) { //only display interesting entries if (pEntry->pData || pEntry->pWildCardData) { //Get full path name of this domain entry pPathEntry = pEntry; pPathBuffer = pBuffer; while (pPathEntry && pPathEntry->pParentEntry && pPathBuffer < pPathBufferStop) { //dump current entries portion of the string if (pPathBuffer != pBuffer) //already made first pass -- Add delimter { *pPathBuffer++ = '.'; } //read partial path name from debuggee if (!ReadMemory(pPathEntry->PathSegment.Buffer, pbPathEntryBuffer, AQ_MIN(MAX_DOM_PATH_SIZE, pPathEntry->PathSegment.Length), NULL)) { dprintf("ERROR: Unable to read process memory for path segment 0x%08X\n", pPathEntry->PathSegment.Buffer); break; } pEntryBuffer = (CHAR *) pbPathEntryBuffer; pEntryBufferStop = pEntryBuffer; pEntryBuffer += (pPathEntry->PathSegment.Length / sizeof(CHAR) -1 ); while (pPathBuffer < pPathBufferStop && pEntryBuffer >= pEntryBufferStop) { *pPathBuffer++ = *pEntryBuffer--; } *pPathBuffer = '\0'; //make sure we terminate pPathEntry = pPathEntry->pParentEntry; //read next part of path name from debuggee if (!ReadMemory(pPathEntry, pbPathEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory for path entry 0x%08x\n", pPathEntry); pPathEntry = NULL; } else { pPathEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbPathEntry; } } dprintf("0x%08.8X %10.10d 0x%08.8X 0x%08.8X %s\n", pEntryRealAddress, pEntry->NoOfChildren, pEntry->pData, pEntry->pWildCardData, pBuffer); //Dump structs if requested if ('@' != *szDumpArg) { if (pEntry->pData) { //Write address string wsprintf(szCurrentDest, "0x%08X", pEntry->pData); //Call ptdbgext dump function _dump(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szDumpArg); } if (pEntry->pWildCardData) { //Write address string wsprintf(szCurrentDest, "0x%08X", pEntry->pWildCardData); //Call ptdbgext dump function _dump(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szDumpArg); } } } //Get the next entry... in order of child, sibling, closest ancestor with sibling if (pEntry->pFirstChildEntry != NULL) { pEntryRealAddress = pEntry->pFirstChildEntry; } else if (pEntry->pSiblingEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } else { for (pEntryRealAddress = pEntry->pParentEntry; pEntryRealAddress != NULL; pEntryRealAddress = pEntry->pParentEntry) { //must read parent entry into our buffer if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory of parent domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; if (pEntry->pSiblingEntry != NULL) break; } if (pEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } } if (pEntryRealAddress) { if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory on domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; } else { pEntry = NULL; } } dprintf("===========================================================================\n"); } //---[ DumpList ]-------------------------------------------------------------- // // // Description: // Function to walk a set of LIST_ENTRY's and dump their contenxts // Parameters: // szArg - space separated list of the following // Address of head list entry // Offset of object address [optional] // Name of object to dump [optional] // Returns: // - // History: // 9/15/98 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(dumplist) { const DWORD MAX_ARG_SIZE = 200; const DWORD MAX_ENTRIES = 3000; LIST_ENTRY liCurrent; PLIST_ENTRY pliHead = NULL; PLIST_ENTRY pliCurrent = NULL; DWORD_PTR dwOffsetOfEntry = 0; CHAR szAddress[MAX_ARG_SIZE]; CHAR szDumpArg[MAX_ARG_SIZE]; LPSTR szParsedArg = (LPSTR) szArg; LPSTR szCurrentDest = NULL; DWORD cEntries = 0; //Get Address of DomainNameTable szCurrentDest = szAddress; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szParsedArg-szArg <= MAX_ARG_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '\0'; //Eat white space while (('\0' != *szParsedArg) && isspace(*szParsedArg)) szParsedArg++; //Get offset of data szCurrentDest = szDumpArg; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szCurrentDest-szDumpArg <= MAX_ARG_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '\0'; dwOffsetOfEntry = GetExpression(szDumpArg); //Eat white more space while (('\0' != *szParsedArg) && isspace(*szParsedArg)) szParsedArg++; //Copy name of struct to dump at each node szCurrentDest = szDumpArg; while (('\0' != *szParsedArg) && !isspace(*szParsedArg) && (szCurrentDest-szDumpArg <= MAX_ARG_SIZE)) { *szCurrentDest = *szParsedArg; szParsedArg++; szCurrentDest++; } *szCurrentDest = '@'; szCurrentDest++; //szCurrentDest now points to place to copy address to pliHead = (PLIST_ENTRY) GetExpression(szAddress); if (!ReadMemory(pliHead, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("Error reading head entry at 0x%08X\n", pliHead); return; } pliCurrent = pliHead; dprintf("LIST ENTRY DATA OFFSET\n"); dprintf("==============================================\n"); dprintf(" 0x%08X 0x%08X (HEAD)\n", pliCurrent, pliCurrent-dwOffsetOfEntry); dprintf("----------------------------------------------\n"); //OK... start walking list using Flink pliCurrent = liCurrent.Flink; while(pliCurrent != NULL && pliHead != pliCurrent) { // There have been some problems with this. #ifdef NEVER if (pliCurrent != liCurrent.Blink) { dprintf(" %p %p (WARNING does Flink/Blink mismatch)\n", pliCurrent, ((DWORD_PTR) pliCurrent)-dwOffsetOfEntry); } else #else if (TRUE) #endif //NEVER { dprintf(" %p %p\n", pliCurrent, ((DWORD_PTR) pliCurrent)-dwOffsetOfEntry); } if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("Error reading LIST_ENTRY at 0x%08X\n", pliCurrent); return; } //dump the struct if we were asked to if ('@' != *szDumpArg) { //Write address string wsprintf(szCurrentDest, "%p", ((DWORD_PTR) pliCurrent)-dwOffsetOfEntry); //Call ptdbgext dump function _dump(hCurrentProcess, hCurrentThread, dwCurrentPc, pExtensionApis, szDumpArg); } cEntries++; if (cEntries > MAX_ENTRIES) { dprintf("ERROR: Max number of entries exceeded\n"); return; } pliCurrent = liCurrent.Flink; } dprintf("----------------------------------------------\n"); dprintf(" %d Total Entries\n", cEntries); dprintf("==============================================\n"); } //---[ linkstate ]------------------------------------------------------------- // // // Description: // Dumps the current link state (including routing information) of a // virtual server. // Parameters: // Virtual Server Instance - virtual server ID of server to dump // Global Server list (optional) - Head of virtual server list // Returns: // - // History: // 9/30/98 - MikeSwa Created // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(linkstate) { DWORD dwInstance = 0; PLIST_ENTRY pliHead = NULL; PLIST_ENTRY pliCurrent = NULL; BYTE pBuffer[sizeof(CAQSvrInst)] = {'\0'}; CAQSvrInst *paqinst = (CAQSvrInst *) pBuffer; DOMAIN_NAME_TABLE *pdnt = NULL; PVOID pvAQueue = NULL; LIST_ENTRY liCurrent; BOOL fFound = FALSE; CHAR szArgBuffer[20]; LPSTR szCurrentArg = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntry = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntryRealAddress = NULL; PDOMAIN_NAME_TABLE_ENTRY pPathEntry = NULL; BYTE pbEntry[sizeof(DOMAIN_NAME_TABLE_ENTRY)]; CHAR szNextHop[MAX_DOM_PATH_SIZE]; CHAR szFinalDest[MAX_DOM_PATH_SIZE]; BYTE pbLMQ[sizeof(CLinkMsgQueue)]; BYTE pbDomainEntry[sizeof(CDomainEntry)]; BYTE pbDMQ[sizeof(CDestMsgQueue)]; CLinkMsgQueue *plmq = (CLinkMsgQueue *) pbLMQ; CDomainEntry *pdentry = (CDomainEntry *) pbDomainEntry; CDestMsgQueue *pdmq = (CDestMsgQueue *) pbDMQ; DWORD *pdwGuid = NULL; LPSTR szLinkState = LINK_STATE_UP; CHAR szError[100]; HINSTANCE hModule = GetModuleHandle("aqdbgext.dll"); DWORD dwMsgId = 0; DWORD dwFacility = 0; CheckVersion(DebugArgs); if (!szArg || ('\0' == szArg[0])) { //Assume the first instance dwInstance = 1; pliHead = (PLIST_ENTRY) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { dwInstance = (DWORD)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) pliHead = (PLIST_ENTRY) GetExpression(szCurrentArg); else pliHead = (PLIST_ENTRY) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } } if (!pliHead) { dprintf("ERROR: Unable to determine LIST_ENTRY for virtual servers\n"); dprintf(" If you are using windbg, you should specify the value as the\n"); dprintf(" 2nd argument. You can determine the address value by typeing:\n"); dprintf(" x " AQUEUE_VIRTUAL_SERVER_SYMBOL "\n"); return; } if (!ReadMemory(pliHead, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X\n", pliHead); return; } while (liCurrent.Flink != pliHead) { pvAQueue = CONTAINING_RECORD(liCurrent.Flink, CAQSvrInst, m_liVirtualServers); if (!ReadMemory(pvAQueue, paqinst, sizeof(CAQSvrInst), NULL)) { dprintf("ERROR: Unable to CAQSvrInst @0x%08X", pvAQueue); return; } //Check the signature if (CATMSGQ_SIG != paqinst->m_dwSignature) { dprintf("@0x%08X INVALID SIGNATURE - list entry @0x%08X\n", pvAQueue, liCurrent.Flink); return; } else { if (paqinst->m_dwServerInstance == dwInstance) { fFound = TRUE; break; } } pliCurrent = liCurrent.Flink; if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X\n", pliCurrent); return; } if (pliCurrent == liCurrent.Flink) { dprintf("ERROR: Loop in LIST_ENTRY @0x%08X\n", pliCurrent); return; } } if (!fFound) { dprintf("Requested instance not found.\n"); return; } dprintf("Using Server instance %d @0x%08X\n", dwInstance, pvAQueue); //Use our current instance to dump all of the interesting bits pdnt = &(paqinst->m_dmt.m_dnt); pEntry = &(pdnt->RootEntry); while(pEntry) { //We are not interested in wildcard data if (pEntry->pData) { //Display link state information if (!ReadMemory(pEntry->pData, pbDomainEntry, sizeof(CDomainEntry), NULL)) { dprintf("ERROR: Unable to read domain entry from @0x%08X\n", pEntry->pData); return; } pliHead = (PLIST_ENTRY) (((BYTE *)pEntry->pData) + FIELD_OFFSET(CDomainEntry, m_liDestQueues)); pliCurrent = pdentry->m_liDestQueues.Flink; //Get final destination string if (!ReadMemory(pdentry->m_szDomainName, szFinalDest, pdentry->m_cbDomainName, NULL)) { dprintf("ERROR: Unable to read final destination name from @0x%08X\n", pdentry->m_szDomainName); return; } szFinalDest[pdentry->m_cbDomainName] = '\0'; //Loop and display each DMQ while (pliHead != pliCurrent) { if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read link LIST_ENTRY @0x%08X\n", pliCurrent); return; } if (!ReadMemory(CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs), pbDMQ, sizeof(CDestMsgQueue), NULL)) { dprintf("ERROR: Unable to read DMQ @0x%08X\n", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs)); return; } //Verify DMQ Signature if (DESTMSGQ_SIG != pdmq->m_dwSignature) { dprintf("ERROR: Invalid DMQ signature for CDestMsgQueue@0x%08X (from LIST_ENTRY) @0x%08X\n", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs), pliCurrent); return; } //Read link if (!ReadMemory(pdmq->m_plmq, pbLMQ, sizeof(CLinkMsgQueue), NULL)) { dprintf("ERROR: Unable to read LMQ @0x%08X\n", pdmq->m_plmq); return; } //Now print off next hop info if (!ReadMemory(plmq->m_szSMTPDomain, szNextHop, plmq->m_cbSMTPDomain, NULL)) { dprintf("ERROR: Unable to read next hop name from @0x%08X\n", plmq->m_szSMTPDomain); return; } szNextHop[plmq->m_cbSMTPDomain] = '\0'; pdwGuid = (DWORD *) &(plmq->m_aqsched.m_guidRouter); //Determine the state of the link if (plmq->m_dwLinkFlags & LINK_STATE_PRIV_GENERATING_DSNS) { szLinkState = LINK_STATE_DSN; } if (CLinkMsgQueue::fFlagsAllowConnection(plmq->m_dwLinkStateFlags)) { //If we can connect... are we? if (plmq->m_cConnections) szLinkState = LINK_STATE_ACTIVE; else szLinkState = LINK_STATE_UP; } else { //If we're down... why? szLinkState = LINK_STATE_DOWN; if (!(plmq->m_dwLinkStateFlags & LINK_STATE_RETRY_ENABLED)) szLinkState = LINK_STATE_RETRY; else if (plmq->m_dwLinkStateFlags & LINK_STATE_PRIV_CONFIG_TURN_ETRN) szLinkState = LINK_STATE_TURN; else if (plmq->m_dwLinkStateFlags & LINK_STATE_PRIV_NO_CONNECTION) szLinkState = LINK_STATE_SPECIAL; } //Print some interesting data dprintf("==============================================================================\n"); dprintf("| Link State | Final Destination | Next Hop |\n"); dprintf("| %s | %-29s | %-29s |\n", szLinkState, szFinalDest, szNextHop); dprintf("------------------------------------------------------------------------------\n"); dprintf("| Route Details: |\n"); dprintf("| Router GUID: %08X-%08X-%08X-%08X |\n", pdwGuid[0], pdwGuid[1], pdwGuid[2], pdwGuid[3]); dprintf("| Message Type: %08X Schedule ID:%08X |\n", pdmq->m_aqmt.m_dwMessageType, plmq->m_aqsched.m_dwScheduleID); dprintf("| Link State Flags 0x%08X |\n", plmq->m_dwLinkStateFlags); dprintf("| Current # of connections: %-8d |\n", plmq->m_cConnections); dprintf("| Current # of Msgs (on link): %-8d |\n", plmq->m_aqstats.m_cMsgs); dprintf("| Current # of Msgs (on DMQ): %-8d |\n", pdmq->m_aqstats.m_cMsgs); dprintf("| Current # of Msgs (on DMQ/retry): %-8d |\n", pdmq->m_aqstats.m_cRetryMsgs); dprintf("| CLinkMsgQueue@0x%08X |\n", pdmq->m_plmq); dprintf("| CDestMsgQueue@0x%08X |\n", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs)); //print out the diagnostic information if in retry //or a failure has been recorded and there are no msgs. if ((LINK_STATE_RETRY == szLinkState) || (FAILED(plmq->m_hrDiagnosticError) && !plmq->m_aqstats.m_cMsgs)) { //Get and format the error message szError[0] = '\0'; dwMsgId = plmq->m_hrDiagnosticError; dwFacility = ((0x0FFF0000 & dwMsgId) >> 16); //If it is not ours... then "un-HRESULT" it if (dwFacility != FACILITY_ITF) dwMsgId &= 0x0000FFFF; FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, hModule, dwMsgId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), szError, sizeof(szError), NULL ); dprintf("------------------------------------------------------------------------------\n"); dprintf("| Failure Details: |\n"); dprintf("| Diagnostic HRESULT 0x%08X |\n", plmq->m_hrDiagnosticError); if (szError && *szError) { dprintf("| Diagnostic string: %s\n", szError); } dprintf("| Protocol Verb: %-20.20s |\n", plmq->m_szDiagnosticVerb); dprintf("| Protocol Response: %s\n", plmq->m_szDiagnosticResponse); } pliCurrent = liCurrent.Flink; } } //Now determine what the "next" entry is if (pEntry->pFirstChildEntry != NULL) { pEntryRealAddress = pEntry->pFirstChildEntry; } else if (pEntry->pSiblingEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } else { for (pEntryRealAddress = pEntry->pParentEntry; pEntryRealAddress != NULL; pEntryRealAddress = pEntry->pParentEntry) { //must read parent entry into our buffer if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory of parent domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; if (pEntry->pSiblingEntry != NULL) break; } if (pEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } } if (pEntryRealAddress) { if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory on domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; } else { pEntry = NULL; } } dprintf("==============================================================================\n"); } //---[ zombieq ]------------------------------------------------------------- // // // Description: // Trolls the DNT for queues that are marked as empty, yet are not in // in the empty list // Parameters: // Virtual Server Instance - virtual server ID of server to dump // Global Server list (optional) - Head of virtual server list // Returns: // - // History: // 9/30/98 - MikeSwa Created // 3/19/2001 - MikeSwa Modified from linkstate // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(zombieq) { DWORD dwInstance = 0; PLIST_ENTRY pliHead = NULL; PLIST_ENTRY pliCurrent = NULL; BYTE pBuffer[sizeof(CAQSvrInst)] = {'\0'}; CAQSvrInst *paqinst = (CAQSvrInst *) pBuffer; DOMAIN_NAME_TABLE *pdnt = NULL; PVOID pvAQueue = NULL; LIST_ENTRY liCurrent; BOOL fFound = FALSE; CHAR szArgBuffer[20]; LPSTR szCurrentArg = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntry = NULL; PDOMAIN_NAME_TABLE_ENTRY pEntryRealAddress = NULL; PDOMAIN_NAME_TABLE_ENTRY pPathEntry = NULL; BYTE pbEntry[sizeof(DOMAIN_NAME_TABLE_ENTRY)]; CHAR szFinalDest[MAX_DOM_PATH_SIZE]; BYTE pbLMQ[sizeof(CLinkMsgQueue)]; BYTE pbDomainEntry[sizeof(CDomainEntry)]; BYTE pbDMQ[sizeof(CDestMsgQueue)]; CLinkMsgQueue *plmq = (CLinkMsgQueue *) pbLMQ; CDomainEntry *pdentry = (CDomainEntry *) pbDomainEntry; CDestMsgQueue *pdmq = (CDestMsgQueue *) pbDMQ; DWORD *pdwGuid = NULL; DWORD dwMsgId = 0; DWORD dwFacility = 0; DWORD cZombieQueues = 0; //Queues that are marked as empty but not in empty list DWORD cPristineZombieQueues = 0; //Zombie queues that have never had a message on them DWORD cZombieQueuesInUse = 0; //Zombie queues that have a refcount DWORD cEntries = 0; DWORD cZombieEntries = 0; DWORD cQueues = 0; const DWORD MAX_DBG_MESSAGE_TYPES = 1000; DWORD rgdwMessageTypes[MAX_DBG_MESSAGE_TYPES]; //array of message types we have found DWORD cMessageTypes = 0; DWORD iLastMessageType = 0; DWORD iCurrentMessageType = 0; DWORD iCurrentPri = 0; BOOL fFoundFifoQ = FALSE; BOOL fZombieQueueInUse = FALSE; LPSTR szScanStatus = "FAILED"; ZeroMemory(rgdwMessageTypes, sizeof(rgdwMessageTypes)); CheckVersion(DebugArgs); if (!szArg || ('\0' == szArg[0])) { //Assume the first instance dwInstance = 1; pliHead = (PLIST_ENTRY) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } else { strcpy(szArgBuffer, szArg); szCurrentArg = strtok(szArgBuffer, " "); if (szCurrentArg) { dwInstance = (DWORD)GetExpression(szCurrentArg); szCurrentArg = strtok(NULL, " "); if (szCurrentArg) pliHead = (PLIST_ENTRY) GetExpression(szCurrentArg); else pliHead = (PLIST_ENTRY) GetExpression(AQUEUE_VIRTUAL_SERVER_SYMBOL); } } if (!pliHead) { dprintf("ERROR: Unable to determine LIST_ENTRY for virtual servers\n"); dprintf(" If you are using windbg, you should specify the value as the\n"); dprintf(" 2nd argument. You can determine the address value by typeing:\n"); dprintf(" x " AQUEUE_VIRTUAL_SERVER_SYMBOL "\n"); return; } if (!ReadMemory(pliHead, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X\n", pliHead); return; } while (liCurrent.Flink != pliHead) { pvAQueue = CONTAINING_RECORD(liCurrent.Flink, CAQSvrInst, m_liVirtualServers); if (!ReadMemory(pvAQueue, paqinst, sizeof(CAQSvrInst), NULL)) { dprintf("ERROR: Unable to CAQSvrInst @0x%08X", pvAQueue); return; } //Check the signature if (CATMSGQ_SIG != paqinst->m_dwSignature) { dprintf("@0x%08X INVALID SIGNATURE - list entry @0x%08X\n", pvAQueue, liCurrent.Flink); return; } else { if (paqinst->m_dwServerInstance == dwInstance) { fFound = TRUE; break; } } pliCurrent = liCurrent.Flink; if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read entry @0x%08X\n", pliCurrent); return; } if (pliCurrent == liCurrent.Flink) { dprintf("ERROR: Loop in LIST_ENTRY @0x%08X\n", pliCurrent); return; } } if (!fFound) { dprintf("Requested instance not found.\n"); return; } dprintf("Using Server instance %d @0x%08X\n", dwInstance, pvAQueue); //Use our current instance to dump all of the interesting bits pdnt = &(paqinst->m_dmt.m_dnt); pEntry = &(pdnt->RootEntry); while(pEntry) { cEntries++; // // Check to see if the user pressed ctrl-C // if (CheckControlC()) { szScanStatus = "FAILED - User ctrl-c"; goto Exit; } //We are not interested in wildcard data if (pEntry->pData) { //Display link state information if (!ReadMemory(pEntry->pData, pbDomainEntry, sizeof(CDomainEntry), NULL)) { dprintf("ERROR: Unable to read domain entry from @0x%08X\n", pEntry->pData); return; } pliHead = (PLIST_ENTRY) (((BYTE *)pEntry->pData) + FIELD_OFFSET(CDomainEntry, m_liDestQueues)); pliCurrent = pdentry->m_liDestQueues.Flink; //Get final destination string if (!ReadMemory(pdentry->m_szDomainName, szFinalDest, pdentry->m_cbDomainName, NULL)) { dprintf("ERROR: Unable to read final destination name from @0x%08X\n", pdentry->m_szDomainName); return; } szFinalDest[pdentry->m_cbDomainName] = '\0'; // // Does this entry have any queues or links // if (!pdentry->m_cQueues && !pdentry->m_cLinks) cZombieEntries++; //Loop and display each DMQ while (pliHead != pliCurrent) { cQueues++; // // Check to see if the user pressed ctrl-C // if (CheckControlC()) { szScanStatus = "FAILED - User ctrl-c"; goto Exit; } if (!ReadMemory(pliCurrent, &liCurrent, sizeof(LIST_ENTRY), NULL)) { dprintf("ERROR: Unable to read link LIST_ENTRY @0x%08X\n", pliCurrent); return; } if (!ReadMemory(CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs), pbDMQ, sizeof(CDestMsgQueue), NULL)) { dprintf("ERROR: Unable to read DMQ @0x%08X\n", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs)); return; } //Verify DMQ Signature if (DESTMSGQ_SIG != pdmq->m_dwSignature) { dprintf("ERROR: Invalid DMQ signature for CDestMsgQueue@0x%08X (from LIST_ENTRY) @0x%08X\n", CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs), pliCurrent); return; } // // It is a zombie if it is marked as empty, but not in the empty list. // if ((pdmq->m_dwFlags & CDestMsgQueue::DMQ_EMPTY) && !pdmq->m_liEmptyDMQs.Flink && !pdmq->m_liEmptyDMQs.Blink && !pdmq->m_aqstats.m_cMsgs && !pdmq->m_aqstats.m_cRetryMsgs) { cZombieQueues++; // // Look at the refcount. If it is 1 (or 2 with an LMQ) then // it is unlikley that it is currently in use // fZombieQueueInUse = FALSE; if (!((1 == *(((DWORD *)pdmq) + 3)) || ((2 == *(((DWORD *)pdmq) + 3)) && pdmq->m_plmq))) { cZombieQueuesInUse++; fZombieQueueInUse = TRUE; } // // Check and see if this has *ever* had a message queued on it. // fFoundFifoQ = FALSE; for (iCurrentPri = 0; iCurrentPri < NUM_PRIORITIES; iCurrentPri++) { if (pdmq->m_rgpfqQueues[iCurrentPri]) { fFoundFifoQ = TRUE; break; } } if (!fFoundFifoQ) cPristineZombieQueues++; // // Have we see this message type before? // if (rgdwMessageTypes[iLastMessageType] != pdmq->m_aqmt.m_dwMessageType) { for (iCurrentMessageType = 0; iCurrentMessageType < MAX_DBG_MESSAGE_TYPES; iCurrentMessageType++) { if (!rgdwMessageTypes[iCurrentMessageType]) { rgdwMessageTypes[iCurrentMessageType] = pdmq->m_aqmt.m_dwMessageType; cMessageTypes++; break; } if (rgdwMessageTypes[iCurrentMessageType] == pdmq->m_aqmt.m_dwMessageType) break; } } //Print some interesting data dprintf("%s%s| %-29s | CDestMsgQueue@0x%08X | 0x%08X\n", fZombieQueueInUse ? "!" : "", fFoundFifoQ ? "*" : "", szFinalDest, CONTAINING_RECORD(pliCurrent, CDestMsgQueue, m_liDomainEntryDMQs), pdmq->m_aqmt.m_dwMessageType); } pliCurrent = liCurrent.Flink; } } //Now determine what the "next" entry is if (pEntry->pFirstChildEntry != NULL) { pEntryRealAddress = pEntry->pFirstChildEntry; } else if (pEntry->pSiblingEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } else { for (pEntryRealAddress = pEntry->pParentEntry; pEntryRealAddress != NULL; pEntryRealAddress = pEntry->pParentEntry) { //must read parent entry into our buffer if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory of parent domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; if (pEntry->pSiblingEntry != NULL) break; } if (pEntry != NULL) { pEntryRealAddress = pEntry->pSiblingEntry; } } if (pEntryRealAddress) { if (!ReadMemory(pEntryRealAddress, pbEntry, sizeof(DOMAIN_NAME_TABLE_ENTRY), NULL)) { dprintf("ERROR: Unable to read process memory on domain entry 0x%08X\n", pEntryRealAddress); pEntry = NULL; break; } pEntry = (PDOMAIN_NAME_TABLE_ENTRY) pbEntry; } else { pEntry = NULL; } } szScanStatus = "COMPLETED"; Exit: dprintf("==============================================================================\n"); dprintf("SCAN %s\n", szScanStatus); dprintf("==============================================================================\n"); dprintf("%d Total Zombie Queues (%d bytes) \n", cZombieQueues, cZombieQueues*sizeof(CDestMsgQueue)); dprintf("%d Total Zombie Queues that have never had a message queued\n", cPristineZombieQueues); dprintf("%d Total Zombie Queues that may be in use \n", cZombieQueuesInUse); dprintf("%d Total Zombie Message Types\n", cMessageTypes); dprintf("%d Total Queues\n", cQueues); dprintf("%d Total Domain Entires\n", cEntries); dprintf("%d Total Zombie Domain Entires (%d bytes) \n", cZombieEntries, cZombieEntries*sizeof(CDomainEntry)); } //---[ dsncontexthash ]-------------------------------------------------------- // // // Description: // Calculates the dsncontexthash for a given filename. Will also dump // common hash names // Parameters: // filename to dump // Returns: // - // History: // 9/30/98 - MikeSwa Created // 3/19/2001 - MikeSwa Modified from linkstate // //----------------------------------------------------------------------------- AQ_DEBUG_EXTENSION_IMP(dsncontexthash) { DWORD dwHash = 0; const DWORD MAX_DSN_HASH_FILES = 10; CHAR rgszWellKnown[MAX_DSN_HASH_FILES][20] = { "msgref.cpp", "aqinst.cpp", "mailadmq.cpp", "dsnevent.h" "" }; DWORD i = 0; LPSTR szCurrentWellKnown = rgszWellKnown[0]; if (szArg && ('\0' != szArg[0])) { dwHash = dwDSNContextHash(szArg,strlen(szArg)); dprintf ("DSNContext has for %s is 0x%08X\n", szArg, dwHash); } // // If no arg just dump the well known file names. // for (DWORD i = 0; i < MAX_DSN_HASH_FILES; i++) { szCurrentWellKnown = rgszWellKnown[i]; if (!szCurrentWellKnown || !*szCurrentWellKnown) break; dwHash = dwDSNContextHash(szCurrentWellKnown, strlen(szCurrentWellKnown)); dprintf ("DSNContext has for %s is 0x%08X\n", szCurrentWellKnown, dwHash); szCurrentWellKnown++; } }