|
|
/*-----------------------------------------------------------------------------
Copyright (c) 2001 Microsoft Corporation
DNS Server debugger extension
Written by Jeff Westhead Feb 2001
-----------------------------------------------------------------------------*/
#include "dbgexts.h"
//
// DNS Server includes
//
#define LDAP_UNICODE 1
#include <winldap.h> // public LDAP
#include <winber.h> // for ber formatting
#include <ntldap.h> // public server ldap constants
#include <rpc.h> // RPC def needed for ntdsapi.h
#include <ntdsapi.h> // DS access bit definitions
#include <ntdsadef.h> // DS constants
#include <dsrole.h>
#include <time.h>
#define DNSLIB_SECURITY // include security defs
#define SDK_DNS_RECORD // DNS_RECORD in SDK format
#define NO_DNSAPI_DLL // build without requiring dnsapi.dll
#include <dnslib.h> // DNS library routines
#ifndef FASTCALL
#define FASTCALL
#endif
#include "dnsrpc_s.h" // DNS RPC definitions
#include "srvcfg.h"
#include "file.h"
#include "tree.h"
#include "name.h"
#include "record.h"
#include "update.h"
#include "dpart.h"
#include "EventControl.h"
#include "zone.h"
#include "registry.h"
#include "msginfo.h"
#include "socket.h"
#include "packetq.h"
#include "dbase.h"
#include "recurse.h"
#include "nameutil.h"
#include "stats.h"
#include "debug.h"
#include "memory.h"
#include "dfile.h"
#include "wins.h"
#include "rrfunc.h"
#include "dnsprocs.h"
#include "rescodes.h"
#include "sdutl.h"
#include "ds.h"
#include "timeout.h"
//
// Print DNS server statistics
//
LPSTR MemTagStrings[] = // Stolen from dns\server\client\print.c
{ MEMTAG_NAME_NONE , MEMTAG_NAME_PACKET_UDP , MEMTAG_NAME_PACKET_TCP , MEMTAG_NAME_NAME , MEMTAG_NAME_ZONE , MEMTAG_NAME_UPDATE , MEMTAG_NAME_UPDATE_LIST , MEMTAG_NAME_TIMEOUT , MEMTAG_NAME_NODEHASH , MEMTAG_NAME_DS_DN , MEMTAG_NAME_DS_MOD , // 10
MEMTAG_NAME_DS_RECORD , MEMTAG_NAME_DS_OTHER , MEMTAG_NAME_THREAD , MEMTAG_NAME_NBSTAT , MEMTAG_NAME_DNSLIB , MEMTAG_NAME_TABLE , MEMTAG_NAME_SOCKET , MEMTAG_NAME_CONNECTION , MEMTAG_NAME_REGISTRY , MEMTAG_NAME_RPC , // 20
MEMTAG_NAME_STUFF , MEMTAG_NAME_FILEBUF , MEMTAG_NAME_REMOTE , MEMTAG_NAME_SAFE ,
MEMTAG_NAME_RECORD , MEMTAG_NAME_RECORD_FILE , MEMTAG_NAME_RECORD_DS , MEMTAG_NAME_RECORD_AXFR , MEMTAG_NAME_RECORD_IXFR , MEMTAG_NAME_RECORD_DYNUP , // 30
MEMTAG_NAME_RECORD_ADMIN , MEMTAG_NAME_RECORD_AUTO , MEMTAG_NAME_RECORD_CACHE , MEMTAG_NAME_RECORD_NOEXIST , MEMTAG_NAME_RECORD_WINS , MEMTAG_NAME_RECORD_WINSPTR , MEMTAG_NAME_RECORD_COPY ,
MEMTAG_NAME_NODE , MEMTAG_NAME_NODE_FILE , MEMTAG_NAME_NODE_DS , // 40
MEMTAG_NAME_NODE_AXFR , MEMTAG_NAME_NODE_IXFR , MEMTAG_NAME_NODE_DYNUP , MEMTAG_NAME_NODE_ADMIN , MEMTAG_NAME_NODE_AUTO , MEMTAG_NAME_NODE_CACHE , MEMTAG_NAME_NODE_NOEXIST , MEMTAG_NAME_NODE_WINS , MEMTAG_NAME_NODE_WINSPTR , MEMTAG_NAME_NODE_COPY ,
NULL, // safety
NULL, NULL, NULL };
//
// Functions
//
//
// Iteration control. Be sure to call resetIterationCount at
// the start of any iterating operation that will call checkIterationCount.
// Note: call checkIterationCount aggressively, meaning at the top of
// every single loop that iterates through a server data structure. It
// is important that we be able to preset a limit to ALL iterating
// functions.
//
int g_iIterationCount = 0; int g_iMaxIterations = 0;
void resetIterationCount() { g_iIterationCount = 0; } // resetIterationCount
bool checkIterationCount() { if ( g_iMaxIterations != 0 && g_iIterationCount++ > g_iMaxIterations ) { dprintf( "Max iteration count (%d) reached - terminating operation\n", g_iMaxIterations ); return false; } return true; } // checkIterationCount
bool myReadMemory( ULONG64 pMemory, void * ppBuffer, ULONG lLength, bool fQuiet ) { bool okay = true; DWORD bytes = 0; if ( !ReadMemory( pMemory, ppBuffer, lLength, &bytes ) || bytes != lLength ) { if ( !fQuiet ) { dprintf( "DNSDBG: error reading %lu bytes of process memory at %p\n", lLength, pMemory ); } okay = false; } return okay; } // myReadMemory
bool myReadString( ULONG64 pString, char * pszStringBuffer, ULONG lLength ) { bool okay = true; for ( ULONG i = 0; i < lLength - 1; ++i ) { CHAR szbuffer[ 2 ]; if ( !myReadMemory( pString + i, &szbuffer , 1, true ) ) { okay = false; break; } pszStringBuffer[ i ] = szbuffer[ 0 ]; if ( pszStringBuffer[ i ] == '\0' ) { break; } } pszStringBuffer[ i ] = '\0'; return okay; } // myReadMemory
HRESULT CALLBACK Stats( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
DWORD bytes; bool okay = true;
ULONG64 pstatsTable = GetExpression( args );
dprintf( "DNS Server statistics at %p\n", ( ULONG64 ) pstatsTable );
for ( int i = 0; ; ++i ) { struct StatsTableEntry statsEntry; if ( !ReadMemory( pstatsTable + i * sizeof( StatsTableEntry ), &statsEntry, sizeof( statsEntry ), &bytes ) || bytes != sizeof( statsEntry ) ) { dprintf( "Unable to read stat at %p index %d\n", ( ULONG64 ) ( ( PBYTE ) pstatsTable + i * sizeof( statsEntry ) ), i ); okay = false; break; }
if ( statsEntry.Id == 0 ) { break; // Stat array terminator found!
}
#if 0
dprintf( "Stat entry %d id=%08X length=%d\n", i, statsEntry.Id, statsEntry.wLength ); #endif
switch( statsEntry.Id ) { #define StatDword( szIndent, statStruct, statMember ) \
dprintf( \ "%s%-25s = %d\n", \ szIndent, \ #statMember, \
statStruct.##statMember )
case DNSSRV_STATID_MEMORY: { dprintf( "\n***** Memory Stats id 0x%08X\n\n", statsEntry.Id );
DNSSRV_MEMORY_STATS s; if ( ( okay = myReadMemory( ( ULONG64 ) statsEntry.pStats, &s, sizeof( s ), false ) ) == false ) { break; }
#define StatIndent " "
StatDword( StatIndent, s, Memory ); StatDword( StatIndent, s, Alloc ); StatDword( StatIndent, s, Free ); dprintf( "\n" );
StatDword( StatIndent, s, StdUsed ); StatDword( StatIndent, s, StdReturn ); StatDword( StatIndent, s, StdInUse ); StatDword( StatIndent, s, StdMemory ); dprintf( "\n" );
StatDword( StatIndent, s, StdToHeapAlloc ); StatDword( StatIndent, s, StdToHeapFree ); StatDword( StatIndent, s, StdToHeapInUse ); StatDword( StatIndent, s, StdToHeapMemory ); dprintf( "\n" );
StatDword( StatIndent, s, StdBlockAlloc ); StatDword( StatIndent, s, StdBlockUsed ); StatDword( StatIndent, s, StdBlockReturn ); StatDword( StatIndent, s, StdBlockInUse ); StatDword( StatIndent, s, StdBlockFreeList ); StatDword( StatIndent, s, StdBlockFreeListMemory ); StatDword( StatIndent, s, StdBlockMemory ); dprintf( "\n" );
dprintf( " Memtag Name Alloc Free InUse Memory\n" ); for ( int m = 0; m < MEMTAG_COUNT; ++m ) { dprintf( " %2d %-14s %10d %10d %10d %10d\n", m, MemTagStrings[ m ], s.MemTags[ m ].Alloc, s.MemTags[ m ].Free, s.MemTags[ m ].Alloc - s.MemTags[ m ].Free, s.MemTags[ m ].Memory ); }
#if 0
// THIS WOULD BE COOL BUT IT DOESN'T QUITE WORK!!
ULONG64 module = 0; ULONG typeId = 0; CHAR szField[ MAX_PATH ]; ULONG offset = 0; int iField; HRESULT hr;
hr = g_ExtSymbols->GetSymbolTypeId( "DNSSRV_MEMORY_STATS", &typeId, &module ); dprintf( "GetSymbolTypeId returned 0x%lx\n", hr ); for ( iField = 0; ; ++i ) { *szField = '\0'; hr = g_ExtSymbols2->GetFieldName( module, typeId, iField, szField, sizeof( szField ), NULL ); if ( hr == S_OK ) { hr = g_ExtSymbols->GetFieldOffset( module, typeId, szField, &offset); if ( hr == S_OK ) { dprintf( "Field %s at offset %d\n", szField, offset ); } else { dprintf( "GetFieldOffset %s iField=%d failed 0x%lx\n", szField, iField, hr ); break; } } else if ( hr == E_INVALIDARG ) { // Done enumerating fields
break; } else { dprintf( "GetFieldName iField=%d failed 0x%lx \"%s\"\n", iField, hr, szField ); break; } } #endif
break; }
default: dprintf( "Unknown stat entry %d id=%08X length=%d\n", i, statsEntry.Id, statsEntry.wLength ); break; } }
EXIT_API(); return S_OK; } // TimeoutArrays
bool printNode( char * pszIndent, ULONG64 pNodeFull, bool fVerbose ) { bool okay = true;
char szEmptyString[] = ""; if ( !pszIndent ) { pszIndent = szEmptyString; }
DB_NODE node; okay = myReadMemory( pNodeFull, &node, sizeof( node ), false ); if ( !okay ) { okay = false; goto Cleanup; }
//
// Force NULL termination in case this node was allocated
// longer than a standard node.
//
* ( ( PUCHAR ) &node + sizeof( node ) - 1 ) = '\0';
//
// Read the string separately to try and get the whole string.
//
char szNodeName[ 65 ]; if ( !myReadString( ( ULONG64 ) node.szLabel, szNodeName, sizeof( szNodeName ) ) ) { strcpy( szNodeName, node.szLabel ); }
dprintf( "%sNODE %p %s%s %-16s abin %03d tbin %03d c%d", pszIndent, pNodeFull, node.pZone ? "Z" : " ", // has zone pointer?
node.pRRList ? "R" : " ", // has RR list pointer?
szNodeName ? szNodeName : "(ROOT)", node.uchAccessBin, node.uchTimeoutBin, node.cReferenceCount ); int flags = node.dwNodeFlags; dprintf( " f%04X %s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", flags, flags & NODE_NO_DELETE ? "Nodel" : "", flags & NODE_SELECT ? "Sel" : "", flags & NODE_FORCE_ENUM ? "Enum" : "", flags & NODE_IN_TIMEOUT ? "Tmot" : "", flags & NODE_AVAIL_TO_AUTHUSER ? "Avauth" : "", flags & NODE_ZONETREE ? "Ztree" : "", flags & NODE_AUTH_ZONE_ROOT ? "Authrt" : "", flags & NODE_WILDCARD_PARENT ? "Wildp" : "", flags & NODE_CNAME ? "Cname" : "", flags & NODE_ZONE_ROOT ? "Zroot" : "", flags & NODE_SECURE_EXPIRED ? "Secexp" : "", flags & NODE_TOMBSTONE ? "Tomb" : "", flags & NODE_THIS_HOST ? "This" : "", flags & NODE_NOEXIST ? "Noex" : "" ); if ( fVerbose ) { dprintf( "%s par %p up %p left %p right %p children %p\n", pszIndent, ( ULONG64 ) node.pParent, ( ULONG64 ) node.pSibUp, ( ULONG64 ) node.pSibLeft, ( ULONG64 ) node.pSibRight, ( ULONG64 ) node.pChildren ); }
Cleanup: return okay; } // printNode
//
// Print summary of timeout system
//
void internalTimeoutScanner( ULONG64 pTimeoutBinArray, bool fPrintStats, bool fPrintArrays, bool fPrintNodes, bool fPrintNodesHighDetail ) { DWORD nonEmptyTopLevelBins = 0; DWORD arraysFound = 0; DWORD nodesFound = 0; DWORD maxBinDepth = 0; DWORD maxNodesInBin = 0; DWORD bytes; bool okay = true;
PTIMEOUT_ARRAY timeoutBinArray[ TIMEOUT_BIN_COUNT ];
//
// Read the timeout bin array
//
if ( !ReadMemory( pTimeoutBinArray, timeoutBinArray, sizeof( timeoutBinArray ), &bytes ) || bytes != sizeof( timeoutBinArray ) ) { dprintf( "Unable to read timeout bin array (%d bytes) at %p\n", sizeof( timeoutBinArray ), ( ULONG64 ) pTimeoutBinArray ); goto Cleanup; }
//
// For each bin in the array, traverse the bin to count
// nodes.
//
resetIterationCount();
for ( int i = 0; okay && i < TIMEOUT_BIN_COUNT; ++i ) { if ( !checkIterationCount() ) { break; }
if ( !timeoutBinArray[ i ] ) { continue; }
//
// Traverse all the arrays in this bin.
//
++nonEmptyTopLevelBins; DWORD nodesInThisBin = 0; DWORD arraysInThisBin = 0; DWORD depth = 0; TIMEOUT_ARRAY array; for ( ULONG64 pArray = ( ULONG64 ) timeoutBinArray[ i ]; okay && pArray != NULL; pArray = ( ULONG64 ) array.pNext ) { if ( !checkIterationCount() ) { break; }
//
// Read this array.
//
if ( !ReadMemory( pArray, &array, sizeof( array ), &bytes ) || bytes != sizeof( array ) ) { dprintf( "Unable to read timeout array for bin %d (%d bytes) at %p\n", i, sizeof( array ), ( ULONG64 ) pArray ); okay = false; break; }
++arraysInThisBin; ++arraysFound; ++depth; nodesInThisBin += array.Count; nodesFound += array.Count;
if ( fPrintArrays ) { dprintf( " ARRAY %3d in bin %3d has %2d elements %p\n", arraysInThisBin, i, array.Count, ( ULONG64 ) pArray ); }
if ( !fPrintNodes && !fPrintNodesHighDetail ) { continue; }
//
// Traverse all the nodes in this array.
//
for ( DWORD j = 0; okay && j < array.Count; ++j ) { if ( !checkIterationCount() ) { break; }
char sz[ 20 ]; sprintf( sz, " %2d ", j + 1 ); okay = printNode( sz, ( ULONG64 ) array.pNode[ j ], false ); } } if ( !okay ) { break; }
if ( fPrintArrays && arraysInThisBin ) { dprintf( "BIN %3d has %3d arrays %5d nodes\n", i, arraysInThisBin, nodesInThisBin ); }
//
// Record high water marks.
//
if ( nodesInThisBin > maxNodesInBin ) { maxNodesInBin = nodesInThisBin; } if ( depth > maxBinDepth ) { maxBinDepth = depth; } }
if ( okay && fPrintStats ) { dprintf( "Timeout summary for TimeoutBinArray at %p\n", pTimeoutBinArray ); dprintf( " nodes found in timeout system %d\n" " arrays found in timeout system %d\n" " non-empty top level bins %d out of %d\n" " maximum bin depth %d\n" " maximum nodes in single bin %d\n" " average arrays per non-empty bin %.2f\n", nodesFound, arraysFound, nonEmptyTopLevelBins, TIMEOUT_BIN_COUNT, maxBinDepth, maxNodesInBin, nonEmptyTopLevelBins ? ( ( double ) arraysFound / ( double ) nonEmptyTopLevelBins + 0.5 ) : 0 ); }
Cleanup: return; } // internalTimeoutScanner
HRESULT CALLBACK TimeoutSummary( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 ptimeoutBinArray = GetExpression( args );
internalTimeoutScanner( ptimeoutBinArray, true, // print stats
false, // print arrays
false, // print nodes
false ); // print nodes - high detail
EXIT_API(); return S_OK; } // TimeoutSummary
HRESULT CALLBACK TimeoutArrays( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 ptimeoutBinArray = GetExpression( args );
internalTimeoutScanner( ptimeoutBinArray, true, // print stats
true, // print arrays
false, // print nodes
false ); // print nodes - high detail
EXIT_API(); return S_OK; } // TimeoutArrays
HRESULT CALLBACK TimeoutNodes( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 ptimeoutBinArray = GetExpression( args );
internalTimeoutScanner( ptimeoutBinArray, true, // print stats
true, // print arrays
true, // print nodes
false ); // print nodes - high detail
EXIT_API(); return S_OK; } // TimeoutNodes
/*
This gets called (by DebugExtensionNotify when target is halted and is accessible */ HRESULT NotifyOnTargetAccessible(PDEBUG_CONTROL Control) { dprintf( "DNS Server debugger extension dll detected a break" ); if ( Connected ) { dprintf( " (" ); switch (TargetMachine) { case IMAGE_FILE_MACHINE_I386: dprintf( "x86" ); break; case IMAGE_FILE_MACHINE_IA64: dprintf( "ia64" ); break; default: dprintf( "Unknown Architecture" ); break; } } dprintf( ")\n" );
dprintf( "\nDebugger extension build at " __DATE__ " " __TIME__ "\n\n" ); //
// show the top frame and execute dv to dump the locals here and return
//
Control->Execute(DEBUG_OUTCTL_ALL_CLIENTS | DEBUG_OUTCTL_OVERRIDE_MASK | DEBUG_OUTCTL_NOT_LOGGED, ".frame", // Command to be executed
DEBUG_EXECUTE_DEFAULT ); Control->Execute(DEBUG_OUTCTL_ALL_CLIENTS | DEBUG_OUTCTL_OVERRIDE_MASK | DEBUG_OUTCTL_NOT_LOGGED, "dv", // Command to be executed
DEBUG_EXECUTE_DEFAULT ); return S_OK; }
char * g_ZoneTypeNames[] = { "Cache", "Primary", "Secondary", "Stub", "Forwarder" };
bool printZoneProperties( ULONG64 pZoneFull, bool fVerbose ) { bool okay = true;
ZONE_INFO zone; if ( !myReadMemory( pZoneFull, &zone, sizeof( zone ), false ) ) { okay = false; goto Cleanup; }
char sz[ 256 ]; if ( !myReadString( ( ULONG64 ) zone.pszZoneName, sz, sizeof( sz ) ) ) { okay = false; goto Cleanup; }
if ( fVerbose ) { dprintf( "\n" ); }
dprintf( "ZONE %p %s (%d)%-9s %s\n", pZoneFull, zone.fDsIntegrated ? "DS " : "File", zone.fZoneType, zone.fZoneType <= DNS_ZONE_TYPE_FORWARDER ? g_ZoneTypeNames[ zone.fZoneType ] : "UNKNOWN-TYPE", sz );
if ( fVerbose ) { #define ZonePtr( szIndent, zoneMember ) \
dprintf( \ "%s%-25s = %p\n", \ szIndent, \ #zoneMember, \
( ULONG64 ) zone.##zoneMember ) #define ZoneStr( szIndent, zoneMember, stringValue ) \
dprintf( \ "%s%-25s = %s\n", \ szIndent, \ #zoneMember, \
stringValue ) #define ZoneDword( szIndent, zoneMember ) \
dprintf( \ "%s%-25s = %8d = 0x%08X\n", \ szIndent, \ #zoneMember, \
zone.##zoneMember, \ zone.##zoneMember )
#define ZoneIndent " "
*sz = '\0'; if ( zone.pszDataFile ) { if ( !myReadString( ( ULONG64 ) zone.pszDataFile, sz, sizeof( sz ) ) ) { okay = false; goto Cleanup; } } ZoneStr( ZoneIndent, pszDataFile, sz );
if ( zone.fDsIntegrated ) { *sz = '\0'; if ( zone.pwszZoneDN ) { if ( !myReadString( ( ULONG64 ) zone.pwszZoneDN, sz, sizeof( sz ) ) ) { okay = false; goto Cleanup; } } ZoneStr( ZoneIndent, pwszZoneDN, sz ); ZonePtr( ZoneIndent, pDpInfo ); }
ZonePtr( ZoneIndent, pSoaRR ); ZonePtr( ZoneIndent, pZoneRoot ); ZonePtr( ZoneIndent, pTreeRoot ); ZonePtr( ZoneIndent, pZoneTreeLink ); dprintf( "\n" );
ZonePtr( ZoneIndent, pLoadZoneRoot ); ZonePtr( ZoneIndent, pLoadTreeRoot ); ZonePtr( ZoneIndent, pLoadOrigin ); ZonePtr( ZoneIndent, pOldTree ); dprintf( "\n" );
ZoneDword( ZoneIndent, iRRCount ); ZoneDword( ZoneIndent, dwSerialNo ); ZoneDword( ZoneIndent, dwLoadSerialNo ); ZoneDword( ZoneIndent, dwLastXfrSerialNo ); ZoneDword( ZoneIndent, dwNewSerialNo ); ZoneDword( ZoneIndent, dwDefaultTtl ); dprintf( "\n" );
ZoneDword( ZoneIndent, dwNextTransferTime ); dprintf( "\n" );
ZoneDword( ZoneIndent, dwDcPromoConvert ); dprintf( "\n" ); }
Cleanup: return okay; } // printZoneProperties
//
// Dump zone list with basic info on each zone.
//
HRESULT CALLBACK ZoneList( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
bool okay = true; int zoneIdx = 0;
ULONG64 plistheadZone = GetExpression( args );
LIST_ENTRY listEntry; if ( !myReadMemory( plistheadZone, &listEntry, sizeof( listEntry ), false ) ) { goto Cleanup; }
resetIterationCount();
ZONE_INFO zone; for ( ULONG64 pzone = ( ULONG64 ) listEntry.Flink; pzone; pzone = ( ULONG64 ) zone.ListEntry.Flink ) { if ( !checkIterationCount() ) { break; }
if ( zoneIdx != 0 && pzone == plistheadZone ) { break; // Looped back to list head, so we're done.
}
if ( ++zoneIdx > 1000 ) { dprintf( "Runaway zone list? Aborting enumeration...\n" ); break; }
if ( !myReadMemory( pzone, &zone, sizeof( zone ), false ) ) { break; }
printZoneProperties( pzone, false ); }
Cleanup:
EXIT_API(); return S_OK; } // ZoneList
//
// Dump zone info.
//
HRESULT CALLBACK Zone( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args ); printZoneProperties( p, true );
EXIT_API(); return S_OK; } // Zone
//
// Dump node info.
//
HRESULT CALLBACK Node( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args ); printNode( NULL, p, true );
EXIT_API(); return S_OK; } // Node
bool printHash( char * pszIndent, ULONG64 pHashFull, bool fVerbose ) { bool okay = true;
char szEmptyString[] = ""; if ( !pszIndent ) { pszIndent = szEmptyString; }
SIB_HASH_TABLE hash; okay = myReadMemory( pHashFull, &hash, sizeof( hash ), false ); if ( !okay ) { okay = false; goto Cleanup; }
dprintf( "%sHASH %p lvl%02d\n", pszIndent, pHashFull, hash.cLevel );
Cleanup: return okay; } // printHash
//
// Traverse a database tree of hashes and nodes. This function
// is called recursively.
//
struct { int iRecurseCalls; int iHashTables; int iEmptyHashBuckets; int iNonEmptyHashBuckets; int iNodes; int iNoExistNodes; int iTimeoutNodes; int iNodesWithNoRRs; int iNodesWithNoChildren; } g_TreeScannerStats;
//
// iRecurseLevel: true level of function recursion
// iChildLevel: level of node depth
//
bool internalTreeScanner( ULONG64 pTreeRoot, bool fPrintSummary, bool fPrintHashNodes, bool fPrintHashBuckets, bool fPrintNodes, bool fPrintNodesHighDetail, int iRecurseLevel, // pass 0 on initial call
int iChildLevel ) // pass 0 on initial call
{ bool okay = true;
// dprintf( "internalTreeScanner p=%p level=%d\n", pTreeRoot, iRecurseLevel );
if ( !checkIterationCount() ) { return false; }
if ( iRecurseLevel == 0 ) { RtlZeroMemory( &g_TreeScannerStats, sizeof( g_TreeScannerStats ) ); }
if ( ++g_TreeScannerStats.iRecurseCalls > 10000000 ) { dprintf( "Aborting: hit limit of 10000000 recurse calls\n" ); return true; }
char szIndent[] = " " " "; szIndent[ min( ( int ) strlen( szIndent ), iChildLevel * 2 ) ] = '\0';
//
// Grab the first DWORD so we can determine if this is
// a hash or a node.
//
DWORD dw; if ( !myReadMemory( pTreeRoot, &dw, sizeof( dw ), false ) ) { okay = false; goto Cleanup; }
if ( IS_HASH_TABLE( &dw ) ) { ++g_TreeScannerStats.iHashTables; if ( fPrintHashNodes ) { okay = printHash( szIndent, pTreeRoot, false ); }
SIB_HASH_TABLE hash; okay = myReadMemory( pTreeRoot, &hash, sizeof( hash ), false ); if ( !okay ) { okay = false; goto Cleanup; }
//
// Iterate through all the buckets in this hash node.
//
for ( int i = 0; i <= LAST_HASH_INDEX; ++i ) { PDB_NODE pnode = hash.aBuckets[ i ]; DWORD count = hash.aBucketCount[ i ];
if ( pnode ) { ++g_TreeScannerStats.iNonEmptyHashBuckets;
if ( fPrintHashBuckets ) { dprintf( "%sBUCKET %p index %3d count %3d\n", szIndent, ( ULONG64 ) pnode, i, count ); }
okay = internalTreeScanner( ( ULONG64 ) pnode, fPrintSummary, fPrintHashNodes, fPrintHashBuckets, fPrintNodes, fPrintNodesHighDetail, iRecurseLevel + 1, iChildLevel ); if ( !okay ) { break; } } else { ++g_TreeScannerStats.iEmptyHashBuckets; } } } else { //
// This is a node.
//
++g_TreeScannerStats.iNodes;
DB_NODE node; okay = myReadMemory( pTreeRoot, &node, sizeof( node ), false ); if ( !okay ) { okay = false; goto Cleanup; }
//
// Force NULL termination in case this node was allocated
// longer than a standard node.
//
* ( ( PUCHAR ) &node + sizeof( node ) - 1 ) = '\0';
//
// Scan the left child tree then this node then the right child tree.
//
if ( node.pSibLeft ) { okay = internalTreeScanner( ( ULONG64 ) node.pSibLeft, fPrintSummary, fPrintHashNodes, fPrintHashBuckets, fPrintNodes, fPrintNodesHighDetail, iRecurseLevel + 1, iChildLevel ); if ( !okay ) { goto Cleanup; } }
if ( node.dwNodeFlags & NODE_NOEXIST ) { ++g_TreeScannerStats.iNoExistNodes; } if ( node.dwNodeFlags & NODE_IN_TIMEOUT ) { ++g_TreeScannerStats.iTimeoutNodes; } if ( !node.pRRList ) { ++g_TreeScannerStats.iNodesWithNoRRs; } if ( !node.pChildren ) { ++g_TreeScannerStats.iNodesWithNoChildren; }
if ( fPrintNodes ) { okay = printNode( szIndent, pTreeRoot, fPrintNodesHighDetail ); if ( !okay ) { goto Cleanup; } }
if ( node.pChildren ) { okay = internalTreeScanner( ( ULONG64 ) node.pChildren, fPrintSummary, fPrintHashNodes, fPrintHashBuckets, fPrintNodes, fPrintNodesHighDetail, iRecurseLevel + 1, iChildLevel + 1 ); if ( !okay ) { goto Cleanup; } }
if ( node.pSibRight ) { okay = internalTreeScanner( ( ULONG64 ) node.pSibRight, fPrintSummary, fPrintHashNodes, fPrintHashBuckets, fPrintNodes, fPrintNodesHighDetail, iRecurseLevel + 1, iChildLevel ); if ( !okay ) { goto Cleanup; } } }
Cleanup: if ( iRecurseLevel == 0 ) { if ( !okay ) { dprintf( "\nERROR ITERATING TREE - STATS INCOMPLETE!\n\n" ); } if ( fPrintSummary ) { dprintf( "Tree summary for tree rooted at %p\n", pTreeRoot ); dprintf( " hash tables %d\n" " empty hash buckets %d\n" " non-empty hash buckets %d\n" "\n" " nodes %d\n" " no-exist nodes %d\n" " timeout nodes %d\n" " nodes with no RRs %d\n" " nodes with no children %d\n", g_TreeScannerStats.iHashTables, g_TreeScannerStats.iEmptyHashBuckets, g_TreeScannerStats.iNonEmptyHashBuckets, g_TreeScannerStats.iNodes, g_TreeScannerStats.iNoExistNodes, g_TreeScannerStats.iTimeoutNodes, g_TreeScannerStats.iNodesWithNoRRs, g_TreeScannerStats.iNodesWithNoChildren );
} } return okay; }
//
// Dump node tree.
//
HRESULT CALLBACK Tree( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args );
resetIterationCount(); internalTreeScanner( p, true, true, true, true, false, 0, 0 );
EXIT_API(); return S_OK; } // Node
//
// Print summary of a node tree.
//
HRESULT CALLBACK TreeSummary( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args );
resetIterationCount();
internalTreeScanner( p, true, false, false, false, false, 0, 0 );
EXIT_API(); return S_OK; } // Node
//
// Print info about a packet queue.
//
HRESULT CALLBACK PacketQ( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args );
PACKET_QUEUE q; CHAR szQueueName[ 80 ];
if ( !myReadMemory( ( ULONG64 ) p, &q, sizeof( q ), false ) ) { goto Cleanup; }
if ( !myReadMemory( ( ULONG64 ) q.pszName, szQueueName, sizeof( szQueueName ), false ) ) { goto Cleanup; }
#define BoolFlagStr( f ) ( ( f ) ? "true" : "false" )
dprintf( "Packet queue %p\n" " queue name %s\n" " first message %p\n" " event %p\n" " default timeout %d\n" " minimum timeout %d\n", p, szQueueName, ( ULONG64 ) q.listHead.Flink, ( ULONG64 ) q.hEvent, q.dwDefaultTimeout, q.dwMinimumTimeout );
dprintf( " counters:\n" " length %d\n" " queued %d\n" " dequeued %d\n" " timed out %d\n", q.cLength, q.cQueued, q.cDequeued, q.cTimedOut );
dprintf( " flags:\n" " query time order %s\n" " discard expired on queuing %s\n" " discard dups on queuing %s\n" " XID for referrals %d\n", BoolFlagStr( q.fQueryTimeOrder ), BoolFlagStr( q.fDiscardExpiredOnQueuing ), BoolFlagStr( q.fDiscardDuplicatesOnQueuing ), ( int ) q.wXid );
#undef BoolFlagStr
Cleanup:
EXIT_API(); return S_OK; } // PacketQ
//
// Print info about a packet queue.
//
void execDebugCmd( char * pszDebuggerCommand ) { g_ExtControl->Execute( DEBUG_OUTCTL_ALL_CLIENTS | DEBUG_OUTCTL_OVERRIDE_MASK | DEBUG_OUTCTL_NOT_LOGGED, pszDebuggerCommand, DEBUG_EXECUTE_DEFAULT ); } // execDebugCmd
HRESULT CALLBACK Globals( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
char szIndent1[] = " "; char szIndent2[] = " ";
dprintf( "Zone list\n%s", szIndent1 ); execDebugCmd( "dd dns!listheadZone l 2" );
dprintf( "\nStatistics table\n%s", szIndent1 ); execDebugCmd( "dd dns!StatsTable l 1" );
dprintf( "\nPacket queues\n" );
dprintf( "%sRecursion packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_pRecursionQueue l 1" );
dprintf( "%sSecondary packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_SecondaryQueue l 1" );
dprintf( "%sUpdate packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_UpdateQueue l 1" );
dprintf( "%sUpdate forwarding packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_UpdateForwardingQueue l 1" );
dprintf( "%sSecure negotiation packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_SecureNegoQueue l 1" );
dprintf( "%sWINS packet queue\n%s", szIndent1, szIndent2 ); execDebugCmd( "dd dns!g_pWinsQueue l 1" );
EXIT_API(); return S_OK; } // Globals
//
// Dump a message.
//
HRESULT CALLBACK Msg( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
ULONG64 p = GetExpression( args ); BYTE msg[ sizeof( DNS_MSGINFO ) + 512 ]; PDNS_MSGINFO pmsg = ( PDNS_MSGINFO ) msg; if ( !myReadMemory( p, msg, sizeof( msg ), false ) ) { goto Cleanup; }
#define BoolFlagStr( f ) ( ( f ) ? "true" : "false" )
dprintf( "DNS message %p\n" "Buffer info:\n" " Tag %08X\n" " BufferLength %d\n" " MaxBufferLength %d\n" " MessageLength %d\n" " BytesToReceive %d\n" " pBufferEnd %p\n" " pCurrent %p\n", ( ULONG64 ) p, pmsg->Tag, pmsg->BufferLength, pmsg->MaxBufferLength, ( int ) pmsg->MessageLength, ( int ) pmsg->BytesToReceive, ( ULONG64 ) pmsg->pBufferEnd, ( ULONG64 ) pmsg->pCurrent );
dprintf( "Remote info:\n" " Socket %d\n" " RemoteAddr Family %d\n" " RemoteAddr Length %d\n" " RemoteAddr %08X = %d.%d.%d.%d\n", ( int ) pmsg->Socket, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_family, ( int ) pmsg->RemoteAddr.SockaddrLength, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_addr.s_addr, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_addr.S_un.S_un_b.s_b1, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_addr.S_un.S_un_b.s_b2, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_addr.S_un.S_un_b.s_b3, ( int ) pmsg->RemoteAddr.SockaddrIn.sin_addr.S_un.S_un_b.s_b4 );
dprintf( "Lookup info:\n" " pnodeCurrent %p\n" " pnodeClosest %p\n" " pzoneCurrent %p\n" " pnodeGlue %p\n" " pnodeDelegation %p\n" " pnodeCache %p\n" " pnodeCacheClosest %p\n", ( ULONG64 ) pmsg->pnodeCurrent, ( ULONG64 ) pmsg->pnodeClosest, ( ULONG64 ) pmsg->pzoneCurrent, ( ULONG64 ) pmsg->pnodeGlue, ( ULONG64 ) pmsg->pnodeDelegation, ( ULONG64 ) pmsg->pnodeCache, ( ULONG64 ) pmsg->pnodeCacheClosest );
dprintf( " pnodeNxt %p\n" " wTypeCurrent %d\n" " wOffsetCurrent %d\n" " pNodeQuestion %p\n" " pNodeQuestionClosest %p\n" " pQuestion %p\n" " wQuestionType %d\n", ( ULONG64 ) pmsg->pnodeNxt, ( int ) pmsg->wTypeCurrent, ( int ) pmsg->wOffsetCurrent, ( ULONG64 ) pmsg->pNodeQuestion, ( ULONG64 ) pmsg->pNodeQuestionClosest, ( ULONG64 ) pmsg->pQuestion, ( int ) pmsg->wQuestionType );
dprintf( "Queuing info:\n" " wQueuingXid %d\n" " dwQueryTime %d\n" " dwMsQueryTime %d\n" " dwQueuingTime %d\n" " dwExpireTime %d\n", ( int ) pmsg->wQueuingXid, pmsg->dwQueryTime, pmsg->dwMsQueryTime, pmsg->dwQueuingTime, pmsg->dwExpireTime );
dprintf( "EDNS info\n" " fFoundOptInIncomingMsg %s\n" " fInsertOptInOutgoingMsg %s\n" " cExtendedRCodeBits %d\n" " cVersion %d\n" " wUdpPayloadSize %d\n" " wOptOffset %d\n" " wOriginalQueryPayloadSize %d\n", BoolFlagStr( pmsg->Opt.fFoundOptInIncomingMsg ), BoolFlagStr( pmsg->Opt.fInsertOptInOutgoingMsg ), ( int ) pmsg->Opt.cExtendedRCodeBits, ( int ) pmsg->Opt.cVersion, ( int ) pmsg->Opt.wUdpPayloadSize, ( int ) pmsg->Opt.wOptOffset, ( int ) pmsg->Opt.wOriginalQueryPayloadSize );
dprintf( "Recursion info:\n" " pRecurseMsg %p\n" " pnodeRecurseRetry %p\n" " pNsList %p\n", ( ULONG64 ) pmsg->pRecurseMsg, ( ULONG64 ) pmsg->pnodeRecurseRetry, ( ULONG64 ) pmsg->pNsList );
dprintf( "Flags:\n" " fDelete %s\n" " fTcp %s\n" " fMessageComplete %s\n" " Section %d\n" " fDoAdditional %s\n" " fRecurseIfNecessary %s\n" " fRecursePacket %s\n" " fQuestionRecursed %s\n" " fQuestionCompleted %s\n", BoolFlagStr( pmsg->fDelete ), BoolFlagStr( pmsg->fTcp ), BoolFlagStr( pmsg->fMessageComplete ), ( int ) pmsg->Section, BoolFlagStr( pmsg->fDoAdditional ), BoolFlagStr( pmsg->fRecurseIfNecessary ), BoolFlagStr( pmsg->fRecursePacket ), BoolFlagStr( pmsg->fQuestionRecursed ), BoolFlagStr( pmsg->fQuestionCompleted ) );
dprintf( " fRecurseQuestionSent %s\n" " fRecurseTimeoutWait %s\n" " nTimeoutCount %d\n" " nForwarder %d\n" " fReplaceCname %s\n" " cCnameAnswerCount %d\n" " fNoCompressionWrite %s\n" " fWins %s\n" " fQuestionWildcard %s\n" " fNsList %s\n", BoolFlagStr( pmsg->fRecurseQuestionSent ), BoolFlagStr( pmsg->fRecurseTimeoutWait ), ( int ) pmsg->nTimeoutCount, ( int ) pmsg->nForwarder, BoolFlagStr( pmsg->fReplaceCname ), ( int ) pmsg->cCnameAnswerCount, BoolFlagStr( pmsg->fNoCompressionWrite ), BoolFlagStr( pmsg->fWins ), BoolFlagStr( pmsg->fQuestionWildcard ), BoolFlagStr( pmsg->fNsList ) );
dprintf( "DNS header:\n" " Xid %04X\n" " RecursionDesired %d\n" " Truncation %d\n" " Authoritative %d\n" " Opcode %d\n" " IsResponse %d\n", ( int ) pmsg->Head.Xid, ( int ) pmsg->Head.RecursionDesired, ( int ) pmsg->Head.Truncation, ( int ) pmsg->Head.Authoritative, ( int ) pmsg->Head.Opcode, ( int ) pmsg->Head.IsResponse );
dprintf( " ResponseCode %d\n" " RecursionAvailable %d\n" " QuestionCount %d\n" " AnswerCount %d\n" " NameServerCount %d\n" " AdditionalCount %d\n", ( int ) pmsg->Head.ResponseCode, ( int ) pmsg->Head.RecursionAvailable, ( int ) pmsg->Head.QuestionCount, ( int ) pmsg->Head.AnswerCount, ( int ) pmsg->Head.NameServerCount, ( int ) pmsg->Head.AdditionalCount );
ULONG64 pmsgbody = ( ULONG64 ) ( ( PBYTE ) p + ( ( PBYTE ) pmsg->MessageBody - ( PBYTE ) pmsg ) ); dprintf( "DNS message body at %p\n", pmsgbody );
Cleanup: EXIT_API(); return S_OK; } // Msg
//
// Set a max count to be respected by all other iterating commands.
// This is cool for example if you have a tree with a million nodes
// and you just want to dump the first few hundred to get an idea of
// what's going on.
//
HRESULT CALLBACK MaxIterations( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
g_iMaxIterations = ( int ) GetExpression( args ); dprintf( "MaxIterations set to %d%s\n", g_iMaxIterations, g_iMaxIterations == 0 ? " (unlimited)" : "" );
EXIT_API(); return S_OK; } // MaxIterations
//
// Help for the other commands
//
HRESULT CALLBACK help( PDEBUG_CLIENT Client, PCSTR args ) { INIT_API();
dprintf( "Help for dnsdbg.dll\n" " Globals" " Print current values of various globals.\n" " ZoneList ADDR\n" " Print zone list. Argument is address of listheadZone.\n" " Zone ADDR\n" " Print zone info. Argument is address of ZONE_INFO struct.\n" " Node ADDR\n" " Print node info. Argument is address of DB_NODE struct.\n" ); dprintf( " Tree ADDR\n" " Traverse and print a tree of nodes. Argument is address of the\n" " tree root. The tree root can be a hash table or a DB_NODE.\n" " TreeSummary ADDR\n" " Traverse and print summary for a node tree. Argument same as Tree.\n" " Stats ADDR\n" " Print server statistics. Argument is address of StatsTable\n" ); dprintf( " TimeoutSummary ADDR\n" " Print summary of nodes in the timeout system.\n" " Argument is address of TimeoutBinArray\n" " TimeoutArrays ADDR\n" " Print timeout system arrays. Argument is address of TimeoutBinArray\n" " TimeoutNodes ADDR\n" " Print timeout system arrays and nodes.\n" " Argument is address of TimeoutBinArray\n" " PacketQ ADDR\n" " Print packet queue info. Argument is address of PACKET_QUEUE struct.\n" " Msg ADDR\n" " Print message info. Argument is address of DNS_MSGINFO struct.\n" ); dprintf( " MaxIterations COUNT\n" " Set max nodes or objects to visit. Argument is count in hex.\n" " help - shows this help\n" );
EXIT_API(); return S_OK; } // help
|