/*++

Copyright (c) 1999  Microsoft Corporation

Module Name:

	dbgrm.c	- DbgExtension Structure information specific to RM APIs

Abstract:


Revision History:

	Who         When        What
	--------    --------    ----------------------------------------------
	josephj     03-01-99    Created

Notes:

--*/

#ifdef TESTPROGRAM
#include "c.h"
#else
#include "precomp.h"
#endif
#include "util.h"
#include "parse.h"
#include "dbgrm.h"

enum
{
    typeid_NULL,
    typeid_RM_OBJECT_HEADER,
    typeid_RM_TASK,
    typeid_RM_ASSOCIATIONS,
    typeid_RM_GROUP,
    typeid_RM_STACK_RECORD,
    typeid_RM_OBJECT_LOG,
    typeid_RM_OBJECT_TREE
};

//
// STRUCTURES CONCERNING TYPE "OBJECT_HEADER"
//

// Actually handles dumping of object info.
//
void
RmDumpObj(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);

// Actually handles dumping of task info.
//
void
RmDumpTask(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);


// Actually handles dumping of task info.
//
void
RmDumpGroup(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);

// Actually handles dumping of task info.
//
void
RmDumpAssociations(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);


// Actually handles dumping of task info.
//
void
RmDumpStackRecord(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);

// Actually handles dumping of object log info.
//
void
RmDumpObjectLog(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);

// Actually handles dumping of object decendents tree
//
void
RmDumpObjectTree(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	);


// Node function for dumping one node of the list of children.
//
ULONG
NodeFunc_DumpObjectTree (
	UINT_PTR uNodeAddr,
	UINT uIndex,
	void *pvContext
	);

BITFIELD_INFO rgRM_OBJECT_STATE[] =
{

	{
	"O_ALLOC",
	RMOBJSTATE_ALLOCMASK,
	RMOBJSTATE_ALLOCATED
	},


	{
	"O_DEALLOC",
	RMOBJSTATE_ALLOCMASK,
	RMOBJSTATE_DEALLOCATED
	},

#if 0	// don't want this -- as it gets displayed for non-task, looking wierd.
	{
	"T_IDLE",
	RMTSKSTATE_MASK,
	RMTSKSTATE_IDLE
	},
#endif // 0

	{
	"T_STARTING",
	RMTSKSTATE_MASK,
	RMTSKSTATE_STARTING
	},

	{
	"T_ACTIVE",
	RMTSKSTATE_MASK,
	RMTSKSTATE_ACTIVE
	},

	{
	"T_PENDING",
	RMTSKSTATE_MASK,
	RMTSKSTATE_PENDING
	},

	{
	"T_ENDING",
	RMTSKSTATE_MASK,
	RMTSKSTATE_ENDING
	},

	{
	"T_DELAYED",
	RMTSKDELSTATE_MASK,
	RMTSKDELSTATE_DELAYED
	},

	{
	"T_ABORT_DELAY",
	RMTSKABORTSTATE_MASK,
	RMTSKABORTSTATE_ABORT_DELAY
	},

	{
	NULL
	}
};

TYPE_INFO type_RM_OBJECT_HEADER = {
    "RM_OBJECT_HEADER",
    "obj",
     typeid_RM_OBJECT_HEADER,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     sizeof(RM_OBJECT_HEADER),
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpObj // pfnSpecializedDump
};

TYPE_INFO type_RM_TASK = {
    "RM_TASK",
    "tsk",
     typeid_RM_TASK,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     sizeof(RM_TASK),
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpTask // pfnSpecializedDump
};

TYPE_INFO type_RM_GROUP = {
    "RM_GROUP",
    "grp",
     typeid_RM_GROUP,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     sizeof(RM_GROUP),
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpGroup // pfnSpecializedDump
};


TYPE_INFO type_RM_ASSOCIATIONS = {
    "RM_ASSOCIATIONS",
    "asc",
     typeid_RM_ASSOCIATIONS,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     1,				//This is not really an object, but we must have nonzero size.
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpAssociations // pfnSpecializedDump
};

TYPE_INFO type_RM_STACK_RECORD = {
    "RM_STACK_RECORD",
    "sr",
     typeid_RM_STACK_RECORD,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     sizeof(RM_STACK_RECORD),
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpStackRecord // pfnSpecializedDump
};


TYPE_INFO type_RM_OBJECT_LOG = {
    "RM_OBJECT_LOG",
    "log",
     //typeid_RM_STACK_RECORD,
     typeid_RM_OBJECT_LOG,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     1,				//This is not really an object, but we must have nonzero size.
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpObjectLog // pfnSpecializedDump
};

TYPE_INFO type_RM_OBJECT_TREE = {

    "RM_OBJECT_TREE",
    "tree",
     typeid_RM_OBJECT_TREE,
	 0, 						//fTYPEINFO_ISLIST,			// Flags
     1,				//This is not really an object, but we must have nonzero size.
     NULL,	// FIELD_INFO
     0,		// offset to next object.
     NULL,	// rgBitFieldInfo
	 RmDumpObjectTree // pfnSpecializedDump
};




TYPE_INFO *g_rgRM_Types[] =
{
    &type_RM_OBJECT_HEADER,
    &type_RM_TASK,
    &type_RM_GROUP,
    &type_RM_ASSOCIATIONS,
    &type_RM_STACK_RECORD,
    &type_RM_OBJECT_LOG,
    &type_RM_OBJECT_TREE,

    NULL
};


UINT_PTR
RM_ResolveAddress(
		TYPE_INFO *pType
		);

NAMESPACE RM_NameSpace = {
			g_rgRM_Types,
			NULL, // g_rgRM_Globals,
			RM_ResolveAddress
			};


UINT_PTR
RM_ResolveAddress(
		TYPE_INFO *pType
		)
{
	return 0;
}


void
do_rm(PCSTR args)
{

	DBGCOMMAND *pCmd = Parse(args, &RM_NameSpace);
	if (pCmd)
	{
		DumpCommand(pCmd);
		DoCommand(pCmd, NULL);
		FreeCommand(pCmd);
		pCmd = NULL;
	}

    return;

}

void
do_help(PCSTR args)
{
    return;
}

void
dump_object_fields(UINT_PTR uAddr, PRM_OBJECT_HEADER pObj);

// Actually handles dumping of object info.
//
void
RmDumpObj(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
/*++
	!rm obj 0x838c7560

	Object 0x838c7560 (LocalIP)
	  Hdr
		 Sig  :A13L 			State:0xc4db69b3  	   Refs:990
		 pLock: 0x838c7560		pSIinfo:0xfdd0a965	pDInfo :0xd54d947c
		 pParent: 0x2995941a	pRoot:0x060af4a8	pHLink :0xce4294fe
		 HdrSize: 0x123			Assoc:909
--*/
{
	RM_OBJECT_HEADER Obj;
	bool			  fRet;

	do
	{
		char rgDescriptionBuf[256];

		// First let's read the pObj structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Obj,
				sizeof(Obj),
				"RM_OBJECT_HEADER"
				);

		if (!fRet) break;

		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Obj.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);

		if (!fRet) break;

		MyDbgPrintf("\nObject 0x%p (%s)\n", uAddr, rgDescriptionBuf);

		dump_object_fields(uAddr, &Obj);


	} while(FALSE);
	
}

// Actually handles dumping of task info.
//
void
RmDumpTask(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
{
	RM_TASK Task;
	bool			  fRet;

	do
	{
		char rgDescriptionBuf[256];

		// First let's read the pObj structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Task,
				sizeof(Task),
				"RM_OBJECT_HEADER"
				);

		if (!fRet) break;

		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Task.Hdr.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Task.Hdr.szDescription"
					);

		if (!fRet) break;

		//  Dump the object header
		//
		{
			MyDbgPrintf("\nTask 0x%p (%s)\n", uAddr, rgDescriptionBuf);
			dump_object_fields(uAddr, &Task.Hdr);
		}

		//
		// Now Dump the task-specific fields...
		//
		{
			/*
			TskHdr
				pfn: 0x5399424c			State:0x812d7211(IDLE) SCtxt:0x050eefc4
				pBlkTsk:0x377c74bc		lnkFellows:0x2b88126f
				Pending Tasks
					0x84215fa5 0xb51f9e9e 0x9e954e81 0x696095b9
					0x0c07aeff
			*/

			MyDbgPrintf(
				"    TaskHdr:\n"
		"            pfn:0x%p            SCtxt:0x%08lx\n",
				Task.pfnHandler,
				Task.SuspendContext
				);
			MyDbgPrintf(
				"        pBlkTsk:0x%p       lnkFellows:0x%p\n",
				Task.pTaskIAmPendingOn,
				&(((PRM_TASK) uAddr)->linkFellowPendingTasks)
				);

			// Note we can't use IsListEmpty because of the different address space.
			//
			if (Task.listTasksPendingOnMe.Flink == Task.listTasksPendingOnMe.Blink)
			{
				MyDbgPrintf("    No pending tasks.\n");
			}
			else
			{

				MyDbgPrintf("    Pending tasks:\n");
				dbgextDumpDLlist(
					(UINT_PTR) &(((PRM_TASK) uAddr)->listTasksPendingOnMe),
					FIELD_OFFSET(RM_TASK, linkFellowPendingTasks),
					"Pending tasks list"
					);
			}

		}

	} while(FALSE);
	
}

void
dbg_walk_rm_hash_table(
	PRM_HASH_TABLE pTable,
	UINT	uContainingOffset,
	char *szDescription

	);

#if RM_EXTRA_CHECKING
void
dbg_print_rm_associations(
		PRM_HASH_TABLE pRmAssociationHashTable,
		UINT MaxToPrint
		);
void
dbg_print_object_log_entries(
	UINT_PTR uObjectListOffset,
	UINT MaxToPrint
	);

#endif // RM_EXTRA_CHECKING

// Actually handles dumping of task info.
//
void
RmDumpGroup(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
/*
	!rm grp 0x838c7560

	Group 0x4d650b98 (LocalIP Group) of object 0x11eafd78 (Interface)
	Num:11 State:ENABLED	pSInfo: 0x944b6d1b pULTsk: 0x8c312bca
	Members:
		0x8db3267c 0xa639f663 0x8f3530a6 0xa4bfe0b9
		0x995dd9bf 0x61e1344b 0xd6323f50 0x606339fd
		0x2e8ed2a4 0x62e52f27 0xa82b59ab
*/
{
	RM_GROUP Group;
	bool	fRet;

	do
	{
		char rgDescriptionBuf[256];
		char rgOwningObjectDescriptionBuf[256];

		// First let's read the Group structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Group,
				sizeof(Group),
				"RM_GROUP"
				);

		if (!fRet) break;

		// Try to read the group's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Group.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);

		if (!fRet) break;

		// Try to read the owning object's description.
		//
		do {
			UINT_PTR uAddress;
			fRet =  dbgextReadUINT_PTR(
								(UINT_PTR) &(Group.pOwningObject->szDescription),
								&uAddress,
								"Owning Obj.szDescription ptr"
								);

			if (!fRet) break;

			fRet = dbgextReadSZ(
						uAddress,
						rgOwningObjectDescriptionBuf,
						sizeof(rgOwningObjectDescriptionBuf),
						"Owning Obj.szDescription"
						);

		} while (FALSE);

		if (!fRet)
		{
			*rgOwningObjectDescriptionBuf = 0;
		}

		MyDbgPrintf(
			"\nGroup 0x%p (%s) of object 0x%p (%s)\n",
			uAddr,
			rgDescriptionBuf,
			Group.pOwningObject,
			rgOwningObjectDescriptionBuf
			);

		MyDbgPrintf(
		"       Num:0x%08x            State:%s          pSInfo:0x%08x\n",
			Group.HashTable.NumItems,
			(Group.fEnabled) ? "ENABLED " : "DISABLED",
			Group.pStaticInfo
			);

		MyDbgPrintf(
		"    pULTsk:0x%08x\n",
			Group.pUnloadTask
			);

		if (Group.HashTable.NumItems==0)
		{
			MyDbgPrintf("    No members.\n");
		}
		else
		{
			MyDbgPrintf("    Members:\n");
			dbg_walk_rm_hash_table(
				&Group.HashTable,
				FIELD_OFFSET(RM_OBJECT_HEADER, HashLink),
				"Group members"
				);
		}

	} while(FALSE);
	
}

// Actually handles dumping of task info.
//
void
RmDumpAssociations(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
{
/*	
	!rm asc	 0x9ba265f8
	Associations for object 0x838c7560 (LocalIP):
		Child   of 0x010091A0 (Globals)
		Parent  of 0x00073558 (Task2)
		Parent  of 0x00073920 (Task3a)
		Parent  of 0x000739F8 (Task3b)
*/

	RM_OBJECT_HEADER Obj;
	bool			  fRet;
	UINT	uNumAssociations = -1;

	do
	{
		char rgDescriptionBuf[256];

		// First let's read the pObj structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Obj,
				sizeof(Obj),
				"RM_OBJECT_HEADER"
				);

		if (!fRet) break;

		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Obj.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);

		if (!fRet) break;

		// Try to get the number of associations field in the diag info struct.
		//
		if (Obj.pDiagInfo != NULL)
		{
			bool fRet;
			UINT_PTR uNumItemsOffset = 
				(UINT_PTR) &(Obj.pDiagInfo->AssociationTable.NumItems);
			fRet =  dbgextReadUINT(
							uNumItemsOffset,
							&uNumAssociations,
							"pDiagInfo->AssociationTable.NumItems"
							);
			if (!fRet)
			{
				uNumAssociations = (UINT) -1;
			}
		}

		if (uNumAssociations == 0)
		{
			MyDbgPrintf(
				"\nObject 0x%p (%s) has no associations.\n",
 				uAddr,
 				rgDescriptionBuf
 				);
		}
		else if (uNumAssociations == (UINT)-1)
		{
			MyDbgPrintf(
				"\nObject 0x%p (%s) associations are not available.\n",
 				uAddr,
 				rgDescriptionBuf
 				);
		}
		else
		{
#if RM_EXTRA_CHECKING
			// Get the association hash table table.
			//
			RM_HASH_TABLE AssociationTable;

			MyDbgPrintf(
				"\nAssociations (50 max) for 0x%p (%s):\n",
 				uAddr,
 				rgDescriptionBuf
 				);

			fRet = dbgextReadMemory(
					(UINT_PTR) &(Obj.pDiagInfo->AssociationTable),
					&AssociationTable,
					sizeof(AssociationTable),
					"Association Table"
					);

			if (!fRet) break;

			dbg_print_rm_associations(
					&AssociationTable,
					50
					);
#endif // RM_EXTRA_CHECKING
		}

	} while(FALSE);
	
}


// Actually handles dumping of task info.
//
void
RmDumpStackRecord(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
{
/*
	!rm sr  0x838c7560

	Stack Record 0x838c7560
		TmpRefs: 2
		HeldLocks:
			0xe916a45f 0x23d8d2d3 0x5f47a2f2
*/

	RM_STACK_RECORD sr;
	bool	fRet;

	do
	{

		// First let's read the RM_STACK_RECORD structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&sr,
				sizeof(sr),
				"RM_STACK_RECORD"
				);

		if (!fRet) break;

		MyDbgPrintf( "\nStack Record 0x%p\n", uAddr);

		MyDbgPrintf(
	"    TmpRefs:0x%08x   LockLevel:0x%08lx   pFirst:0x%08lx   NumHeld=%lu\n",
			sr.TmpRefs,
			sr.LockInfo.CurrentLevel,
			sr.LockInfo.pFirst,
			sr.LockInfo.pNextFree-sr.LockInfo.pFirst
			);

		// Display held locks.
		//
		if (sr.LockInfo.CurrentLevel==0)
		{
			MyDbgPrintf("    No held locks.\n");
		}
		else
		{
			// MyDbgPrintf("    Held locks:<unimplemented>\n");
		}

	} while(FALSE);
	
}


// Actually handles dumping of the object log
//
void
RmDumpObjectLog(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
{
/*	
	!rm log	 0x9ba265f8
	Log for object 0x838c7560 (LocalIP):
		Added association X
		Deleted association Y
		...
*/

	RM_OBJECT_HEADER Obj;
	bool			  fRet;
	UINT	uNumEntries = 0;

	do
	{
		char rgDescriptionBuf[256];

		// First let's read the pObj structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Obj,
				sizeof(Obj),
				"RM_OBJECT_HEADER"
				);

		if (!fRet) break;

		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Obj.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);

		if (!fRet) break;

		// Try to get the number of log entries field in the diag info struct.
		//
		if (Obj.pDiagInfo != NULL)
		{
			bool fRet;
			UINT_PTR uNumItemsOffset = 
				(UINT_PTR) &(Obj.pDiagInfo->NumObjectLogEntries);
			fRet =  dbgextReadUINT(
							uNumItemsOffset,
							&uNumEntries,
							"pDiagInfo->NumObjectLogEntries"
							);
			if (!fRet)
			{
				uNumEntries = (UINT) -1;
			}
		}

		if (uNumEntries == 0)
		{
			MyDbgPrintf(
				"\nObject 0x%p (%s) has no log entries.\n",
 				uAddr,
 				rgDescriptionBuf
 				);
		}
		else if (uNumEntries == (UINT)-1)
		{
			MyDbgPrintf(
				"\nObject 0x%p (%s) log entries are not available.\n",
 				uAddr,
 				rgDescriptionBuf
 				);
		}
		else
		{
#if RM_EXTRA_CHECKING
			UINT uNumToDump = uNumEntries;
			if (uNumToDump > 50)
			{
				uNumToDump = 50;
			}

			MyDbgPrintf(
				"\nLog entries for 0x%p (%s) (%lu of %lu):\n",
 				uAddr,
 				rgDescriptionBuf,
 				uNumToDump,
 				uNumEntries
 				);

			dbg_print_object_log_entries(
				(UINT_PTR)  &(Obj.pDiagInfo->listObjectLog),
				uNumToDump
				);
					

#endif // RM_EXTRA_CHECKING
		}

	} while(FALSE);
	
}


// Actually handles dumping of the object tree
//
void
RmDumpObjectTree(
	struct _TYPE_INFO *pType,
	UINT_PTR uAddr,
	char *szFieldSpec,
	UINT uFlags
	)
{
/*	
	!rm tree 0x9ba265f8
	Tree for object 0x838c7560 (LocalIP) (Parent 0x82222222)

	Display sample:
		0x2222222(RemoteIp)
		|---0x22222222(Dest)
		|---|---0x22222222(Dest)
		|---|---|---0x22222222(Dest)
		|---|---0x22222222(pTask)
		|---0x11111111(RemoteIp)

*/

	
	RM_OBJECT_HEADER Obj;
	RM_OBJECT_HEADER ParentObj;
	bool			  fRet;
	UINT	uNumEntries = 0;

	do
	{
		char rgDescriptionBuf[256];
		char rgParentDescriptionBuf[256];

		// First let's read the pObj structure.
		//
		fRet = dbgextReadMemory(
				uAddr,
				&Obj,
				sizeof(Obj),
				"RM_OBJECT_HEADER"
				);

		if (!fRet) break;

		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Obj.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);

		if (!fRet) break;

		// Try to read the parent's object.
		//
		if (Obj.pParentObject!=NULL && (UINT_PTR) Obj.pParentObject != uAddr)
		{
			fRet = dbgextReadMemory(
					(UINT_PTR) Obj.pParentObject,
					&ParentObj,
					sizeof(ParentObj),
					"RM_OBJECT_HEADER"
					);
	
			if (!fRet) break;

			// Try to get pParent's description.
			//
			fRet = dbgextReadSZ(
						(UINT_PTR) ParentObj.szDescription,
						rgParentDescriptionBuf,
						sizeof(rgParentDescriptionBuf),
						"ParentObj.szDescription"
						);
	
			if (!fRet) break;
		}
		else
		{
			strcpy(rgParentDescriptionBuf, "<root>");
		}

		MyDbgPrintf(
			"\nObject Tree for  0x%p(%s) with parent 0x%p(%s):\n",
			uAddr,
			rgDescriptionBuf,
			Obj.pParentObject,
			rgParentDescriptionBuf
			);

		NodeFunc_DumpObjectTree(
						(UINT_PTR) &(((PRM_OBJECT_HEADER)uAddr)->linkSiblings),
						0,			// Index (unused)
						(void *)0 // pvContext == level
						);
	} while(FALSE);
}


void
dump_object_fields(UINT_PTR uAddr, PRM_OBJECT_HEADER pObj)
{
	UINT uNumAssociations = (UINT) -1;

	// Try to get the number of associations field in the diag info struct.
	//
	if (pObj->pDiagInfo != NULL)
	{
		bool fRet;
		UINT_PTR uNumItemsOffset = 
 			(UINT_PTR) &(pObj->pDiagInfo->AssociationTable.NumItems);
		fRet =  dbgextReadUINT(
						uNumItemsOffset,
						&uNumAssociations,
						"pDiagInfo->AssociationTable.NumItems"
						);
		if (!fRet)
		{
			uNumAssociations = (UINT) -1;
		}
	}

	MyDbgPrintf(
		"    Hdr:\n"
   	"            Sig:0x%08x            State:0x%08x            Refs:0x%08x\n",
		pObj->Sig,
		pObj->State,
		pObj->TotRefs
		);
	MyDbgPrintf(
		"          pLock:0x%p           pSInfo:0x%p          pDInfo:0x%p\n",
		pObj->pLock,
		pObj->pStaticInfo,
		pObj->pDiagInfo
		);
	MyDbgPrintf(
		"        pParent:0x%p            pRoot:0x%p          pHLink:0x%p\n",
		pObj->pParentObject,
		pObj->pRootObject,
		&(((PRM_OBJECT_HEADER) uAddr)->HashLink)
		);
	MyDbgPrintf(
		"        HdrSize:0x%08lx            Assoc:%d\n",
		sizeof(*pObj),
		uNumAssociations
		);

	MyDbgPrintf( "        RmState: ");

	DumpBitFields(
			pObj->RmState,
			rgRM_OBJECT_STATE
			);

	MyDbgPrintf( "\n");
}


void
dbg_walk_rm_hash_table(
	PRM_HASH_TABLE pRmHashTable,
	UINT	uContainingOffset,
	char *szDescription
	)
{
	// For now, we get the whole hash table array in one fell swoop...
	//
	PRM_HASH_LINK rgTable[512];
	UINT		  TableLength = pRmHashTable->TableLength;
	bool fRet;

	do
	{
		// Sanity check.
		//
		if (TableLength > sizeof(rgTable)/sizeof(*rgTable))
		{
			MyDbgPrintf(
				"    HashTable length %lu too large\n",
					 TableLength
					 );
			break;
		}

		// Read the whole hash table.
		//
		fRet = dbgextReadMemory(
				(UINT_PTR) pRmHashTable->pTable,
				rgTable,
				TableLength * sizeof(*rgTable),
				"Hash Table"
				);

		if (!fRet) break;

		
		// Now go through the table visiting each list...
		//
		{
			PRM_HASH_LINK *ppLink, *ppLinkEnd;
			UINT uCount = 0;
			UINT uMax   = 15;
		
			ppLink 		= rgTable;
			ppLinkEnd 	= ppLink + TableLength;
		
			for ( ; ppLink < ppLinkEnd; ppLink++)
			{
				PRM_HASH_LINK pLink =  *ppLink;

				for (;pLink != NULL; uCount++)
				{ 
					char *szPrefix;
					char *szSuffix;
					RM_HASH_LINK Link;


					szPrefix = "        ";
					szSuffix = "";
					if (uCount%4)
					{
						szPrefix = " ";
						if ((uCount%4)==3)
						{
							szSuffix = "\n";
						}
					}
		
					if (uCount >= uMax) break;
			
					MyDbgPrintf(
						"%s0x%p%s",
						 szPrefix,
						 ((char *) pLink) - uContainingOffset,
						 szSuffix
						 );

					// Let's try to read this link.
					//
					fRet = dbgextReadMemory(
							(UINT_PTR) pLink,
							&Link,
							sizeof(Link),
							"Hash Link"
							);
		
					if (!fRet) break;
			
					pLink = Link.pNext;
				}
				if (!fRet || (uCount >= uMax)) break;
			}

			{
				MyDbgPrintf("\n");
			}
			if (uCount < pRmHashTable->NumItems)
			{
				MyDbgPrintf("        ...\n");
			}
		}

	} while (FALSE);

}

#if RM_EXTRA_CHECKING

void
dbg_dump_one_association(
	RM_PRIVATE_DBG_ASSOCIATION *pAssoc
	);

void
dbg_print_rm_associations(
		PRM_HASH_TABLE pRmAssociationHashTable,
		UINT	MaxToPrint
		)
{
	// For now, we get the whole hash table array in one fell swoop...
	//
	PRM_HASH_LINK rgTable[512];
	UINT		  TableLength = pRmAssociationHashTable->TableLength;
	bool fRet;

	do
	{
		// Sanity check.
		//
		if (TableLength > sizeof(rgTable)/sizeof(*rgTable))
		{
			MyDbgPrintf(
				"    HashTable length %lu too large\n",
					 TableLength
					 );
			break;
		}

		// Read the whole hash table.
		//
		fRet = dbgextReadMemory(
				(UINT_PTR) pRmAssociationHashTable->pTable,
				rgTable,
				TableLength * sizeof(*rgTable),
				"Hash Table"
				);

		if (!fRet) break;

		
		// Now go through the table visiting each list...
		//
		{
			PRM_HASH_LINK *ppLink, *ppLinkEnd;
			UINT uCount = 0;
			UINT uMax   = MaxToPrint;
		
			ppLink 		= rgTable;
			ppLinkEnd 	= ppLink + TableLength;
		
			for ( ; ppLink < ppLinkEnd; ppLink++)
			{
				PRM_HASH_LINK pLink =  *ppLink;

				for (;pLink != NULL; uCount++)
				{ 
					RM_PRIVATE_DBG_ASSOCIATION Assoc;
					UINT_PTR uAssocOffset = 
						(UINT_PTR) CONTAINING_RECORD(
										pLink,
										RM_PRIVATE_DBG_ASSOCIATION,
										HashLink
										);


					if (uCount >= uMax) break;
			

					// Let's try to read this association...
					//
					fRet = dbgextReadMemory(
							uAssocOffset,
							&Assoc,
							sizeof(Assoc),
							"Association"
							);

					if (!fRet) break;

					dbg_dump_one_association(&Assoc);

					pLink = Assoc.HashLink.pNext;

				}

				if (!fRet || (uCount >= uMax)) break;
			}

			if (uCount < pRmAssociationHashTable->NumItems)
			{
				MyDbgPrintf("        ...\n");
			}
		}

	} while (FALSE);

}

void
dbg_dump_one_association(
	RM_PRIVATE_DBG_ASSOCIATION *pAssoc
	)
/*++
		Dump the information on the specific association.
		pAssoc is valid memory, however anything it points to is not in
		our address space.

		Since the association contains a format string, which may have
		"%s"s in it, we need scan this format string and read any strings
		referenced.

		All this effort is well worth it. Check out the sample output!

				Associations for 0x01023A40 (Globals):
						Owns group 0x01023AC4 (O1 Group)
						Parent  of 0x00073240 (O2)
						Parent  of 0x00073488 (O2)
				
				Associations for 0x000736D0 (Task3a):
						Child   of 0x00073240 (O2)
						Pending on 0x000732C8 (TaskO2)
--*/
{

	char rgFormatString[256];
	char rgStrings[3][256];
	char *szFormatString;
	ULONG_PTR Args[3];
	char *szDefaultFormatString =  "\tAssociation (E1=0x%x, E2=0x%x, T=0x%x)\n";
	bool fRet = FALSE;


	do
	{
		
		// Try to read the format string.
		//
		{
			fRet = dbgextReadSZ(
						(UINT_PTR) pAssoc->szFormatString,
						rgFormatString,
						sizeof(rgFormatString),
						"Association format"
						);
	
			if (fRet)
			{
				szFormatString = rgFormatString;
			}
			else
			{
				break;
			}
		}

		// Now run through the format string, looking for "%s"s.
		// and munging as required.
		//
		{
			char *pc = rgFormatString;
			UINT uCount=0;
							
			Args[0] = pAssoc->Entity1;
			Args[1] = pAssoc->Entity2;
			Args[2] = pAssoc->AssociationID;

			while (uCount<3 && pc[0]!=0 && pc[1]!=0)
			{
				if (pc[0]=='%')
				{
					if (pc[1]=='s')
					{
						// pc[1]='p';
						fRet = dbgextReadSZ(
								(UINT_PTR) Args[uCount],
								rgStrings[uCount],
								sizeof(rgStrings[uCount]),
								"Association format"
								);
						if (fRet)
						{
							Args[uCount] = (ULONG_PTR) rgStrings[uCount];
						}
						else
						{
							break;
						}
					}

					pc++; // we want to end up skipping past both chars.
					uCount++;
				}
				pc++;
			}
		}

	}
	while (FALSE);

	if (!fRet)
	{
		// Back off to the defaults..
		//
		szFormatString = szDefaultFormatString;
		Args[0] = pAssoc->Entity1;
		Args[1] = pAssoc->Entity2;
		Args[2] = pAssoc->AssociationID;

	}

	MyDbgPrintf(
		szFormatString,
		Args[0],
		Args[1],
		Args[2]
		);
}

ULONG
NodeFunc_DumpObjectLogFromObjectLink (
	UINT_PTR uNodeAddr,
	UINT uIndex,
	void *pvContext
	)
{
	RM_DBG_LOG_ENTRY LE;
	LIST_ENTRY *pLink = (LIST_ENTRY*) uNodeAddr;
	UINT_PTR uLEOffset = (UINT_PTR) CONTAINING_RECORD(
										pLink,
										RM_DBG_LOG_ENTRY,
										linkObjectLog
										);

	char rgPrefixString[256];
	char rgFormatString[256];
	char rgStrings[4][256];
	char *szPrefixString;
	char *szFormatString;
	ULONG_PTR Args[4];
	char *szDefaultFormatString = 
				"Log Entry (P1=%p, P2=%p, P3=%p, P4=%p, szFmt=%p)\n";

	bool fRet = FALSE;

	// Read the containing record.
	//
	fRet = dbgextReadMemory(
			uLEOffset,
			&LE,
			sizeof(LE),
			"Log Entry"
			);

	if (!fRet) return 0;						// EARLY RETURN;

#if 0
	if (LE.pfnDumpEntry != NULL)
	{
		//
		// TODO we need to get the corresponding function to dump this
		// specialized entry.
		//
		MyDbgPrintf(
			"Specialized (pfn=%p szFmt=%p, P1=%p, P2=%p, P3=%p, P4=%p)\n",
			LE.pfnDumpEntry,
			LE.szFormatString,
			LE.Param1,
			LE.Param2,
			LE.Param3,
			LE.Param4
			);
		return 0;								// EARLY RETURN
	}
#else
	// 
	// Above check is invalid, because in all cases there is a pfnDump function.
	//
#endif

	//
	// TODO -- following code is very similar to the dump-association code --
	// move common stuff to some utility function.
	//

	do
	{
		// Try to read the prefix string.
		//
		{
			fRet = FALSE;

			if (LE.szPrefix != NULL)
			{
				fRet = dbgextReadSZ(
							(UINT_PTR) LE.szPrefix,
							rgPrefixString,
							sizeof(rgPrefixString),
							"Prefix String"
							);
			}
	
			if (fRet)
			{
				szPrefixString = rgPrefixString;
			}
			else
			{
				szPrefixString = "";
			}
		}

		// Try to read the format string.
		//
		{
			fRet = dbgextReadSZ(
						(UINT_PTR) LE.szFormatString,
						rgFormatString,
						sizeof(rgFormatString),
						"Log entry format"
						);
	
			if (fRet)
			{
				szFormatString = rgFormatString;
			}
			else
			{
				break;
			}
		}

		// Now run through the format string, looking for "%s"s.
		// and munging as required.
		//
		{
			char *pc = rgFormatString;
			UINT uCount=0;
							
			Args[0] = LE.Param1;
			Args[1] = LE.Param2;
			Args[2] = LE.Param3;
			Args[3] = LE.Param4;

			while (uCount<4 && pc[0]!=0 && pc[1]!=0)
			{
				if (pc[0]=='%')
				{
					if (pc[1]=='s')
					{
						// pc[1]='p';
						fRet = dbgextReadSZ(
								(UINT_PTR) Args[uCount],
								rgStrings[uCount],
								sizeof(rgStrings[uCount]),
								"Log entry param"
								);
						if (fRet)
						{
							Args[uCount] = (ULONG_PTR) rgStrings[uCount];
						}
						else
						{
							break;
						}
					}

					pc++; // we want to end up skipping past both chars.
					uCount++;
				}
				pc++;
			}
		}

	} while (FALSE);

	if (!fRet)
	{
		// Back off to the defaults..
		//
		szPrefixString = "";
		szFormatString = szDefaultFormatString;
		Args[0] = LE.Param1;
		Args[1] = LE.Param2;
		Args[2] = LE.Param3;
		Args[3] = LE.Param4;

	}

	MyDbgPrintf(szPrefixString);

	MyDbgPrintf(
		szFormatString,
		Args[0],
		Args[1],
		Args[2],
		Args[3],
		LE.szFormatString
		);

	return 0;
}

void
dbg_print_object_log_entries(
	UINT_PTR uObjectListOffset,
	UINT MaxToPrint
	)
{
	WalkDLlist(
		uObjectListOffset,
		0, 	//uOffsetStartLink
		NULL,	// pvContext
		NodeFunc_DumpObjectLogFromObjectLink,
		MaxToPrint,
		"Object log"
		);
}
#endif //  RM_EXTRA_CHECKING


char szDumpTreePrefix[] =	"|---|---|---|---|---|---|---|---|---|---"
							"|---|---|---|---|---|---|---|---|---|---";
ULONG
NodeFunc_DumpObjectTree (
	UINT_PTR uNodeAddr,
	UINT uIndex,
	void *pvContext
	)
{
	bool fRet = FALSE;

	do
	{
		char rgDescriptionBuf[256];
		LIST_ENTRY *pLink = (LIST_ENTRY*) uNodeAddr;
		UINT_PTR uObjOffset = (UINT_PTR) CONTAINING_RECORD(
											pLink,
											RM_OBJECT_HEADER,
											linkSiblings
											);
		UINT Level = (UINT) (UINT_PTR) pvContext; // we put the level in the context.
		RM_OBJECT_HEADER Obj;
		char *szPrefix;
	
		// First make szPrefix point to the end (trailing zero) of the prefix string.
		//
		szPrefix = szDumpTreePrefix + sizeof(szDumpTreePrefix)-1;
	
		// Now back up "Level" times.
		//
		if (Level < ((sizeof(szDumpTreePrefix)-1)/4))
		{
			szPrefix -= Level*4;
		}
		else
		{
			// Level is too large -- don't display anything.
			//
			MyDbgPrintf("Dump Tree depth(%d) is too large.\n", Level);
			break;
		}


		// Read the containing record.
		//
		fRet = dbgextReadMemory(
				uObjOffset,
				&Obj,
				sizeof(Obj),
				"Object"
				);
	
		if (!fRet) break;
	
		// Try to read the object's description.
		//
		fRet = dbgextReadSZ(
					(UINT_PTR) Obj.szDescription,
					rgDescriptionBuf,
					sizeof(rgDescriptionBuf),
					"Obj.szDescription"
					);
	
		if (!fRet) break;

		// Display the object info.
		//
		MyDbgPrintf(
			"%s%p(%s)\n",
			szPrefix,
			uObjOffset,
			rgDescriptionBuf
			);
		
		//
		// Now walk the list of children, displaying each of them.
		//
		WalkDLlist(
			(UINT_PTR) &(((PRM_OBJECT_HEADER)uObjOffset)->listChildren),
			0, 	//uOffsetStartLink
			(void*) (Level+1),	// pvContext
			NodeFunc_DumpObjectTree,
			50, 		// Max children per node to dump
			"Object children"
			);
		
	} while (FALSE);

	return 0;
}