windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dnet/common/dndbg.cpp
2020-09-26 16:20:57 +08:00

601 lines
18 KiB
C++

/*==========================================================================
*
* Copyright (C) 1999 - 1999 Microsoft Corporation. All Rights Reserved.
*
* File: dndbg.c
* Content: debug support for DirectPlay8
*
* History:
* Date By Reason
* ==== == ======
* 05-20-99 aarono Created
* 07-16-99 johnkan Fixed include of OSInd.h, defined WSPRINTF macro
* 07-19-99 vanceo Explicitly declared OutStr as returning void for NT
* Build environment.
* 07-22-99 a-evsch Check for multiple Inits, and release CritSec when DebugPrintf
* returns early.
* 08-02-99 a-evsch Added LOGPF support. LW entries only go into shared-file log
* 08-31-99 johnkan Removed include of <OSIND.H>
* 02-17-00 rodtoll Added Memory / String validation routines
* 05-23-00 RichGr IA64: Changed some DWORDs to DWORD_PTRs to make va_arg work OK.
* 07-16-00 jchauvin IA64: Added %p parsing to change back to %x for Win9x machines in DebugPrintf, DebugPrintfNoLock, LogPrintf
* 07-24-00 RichGr IA64: As there's no separate build for Win9x, added code to detect Win9x for the %p parse-and-replace.
* 07-29-00 masonb Rewrite to add logging by subcomponent, perf improvements, process ID
* 08/28/2000 masonb Voice Merge: Modified asm in DebugPrintf to preserve registers that may have affected Voice
* 03/29/2001 RichGr If DPINST is defined for Performance Instrumentation, allow free build to pick up the code.
*
* Notes:
*
* Use /Oi compiler option for strlen()
*
***************************************************************************/
#include "dncmni.h"
#include "memlog.h"
#include <tchar.h>
#if defined(DEBUG) || defined(DPINST)
void DebugPrintfInit(void);
void DebugPrintfFini(void);
// The constructor of this will be called prior to DllMain and the destructor
// after DllMain, so we can be assured of having the logging code properly
// initialized and deinitialized for the life of the module.
#ifdef DPINST
#define DEBUG_BREAK() ALWAYS_BREAK()
#endif
struct _InitDbg
{
_InitDbg() { DebugPrintfInit(); }
~_InitDbg() { DebugPrintfFini(); }
} DbgInited;
//===============
// Debug support
//===============
/*******************************************************************************
This file contains support for the following types of logging:
1. Logging to a VXD (Win9x only)
2. Logging to a shared memory region
3. Logging to the Debug Output
4. FUTURE: Logging to a file
General:
========
Debug Logging and playback is designed to operate on both Win9x and
Windows NT (Windows 2000). A shared file is used to capture information
and can be played back using dp8log.exe.
Under NT you can use the 'dt' command of NTSD to dump structures. For
example:
dt DIRECTPLAYOBJECT <some memory address>
will show all of the members of the DIRECTPLAYOBJECT structure at the
specified address. Some features are available only in post-Win2k
versions of NTSD which can be obtained at http://dbg.
Logging:
========
Debug Logging is controlled by settings in the WIN.INI file, under
the section heading [DirectPlay8]. There are several settings:
debug=9
controls the default debug level. All messages, at or below that debug level
are printed. You can control logging by each component specified in the
g_rgszSubCompName member by adding its name to the end of the 'debug' setting:
debug.addr=9
sets the logging level for the addressing subcomponent to 9, leaving all
others at either their specified level or the level specified by 'debug'
if there is no specific level specified.
The second setting controls where the log is seen. If not specified, all
debug logs are sent through the standard DebugPrint and will appear in a
debugger if it is attached.
log=0 {no debug output}
log=1 {spew to console only}
log=2 {spew to shared memory log only}
log=3 {spew to console and shared memory log}
This setting can also be divided by subcomponent, so:
log=3
log.protocol=2
sends logs for the 'protocol' subcomponent to the shared memory log only, and
all other logs to both locations.
example win.ini...
[DirectPlay8]
Debug=7 ; lots of spew
log=2 ; don't spew to debug window
[DirectPlay8]
Debug=0 ; only fatal errors spewed to debug window
Asserts:
========
Asserts are used to validate assumptions in the code. For example
if you know that the variable jojo should be > 700 and are depending
on it in subsequent code, you SHOULD put an assert before the code
that acts on that assumption. The assert would look like:
DNASSERT(jojo>700);
Asserts generally will produce 3 lines of debug spew to highlight the
breaking of the assumption. You can add text to your asserts by ANDing:
DNASSERT(jojo>700 && "Jojo was too low");
Will show the specified text when the assert occurs. For testing, you might
want to set the system to break in on asserts. This is done in the
[DirectPlay8] section of WIN.INI by setting BreakOnAssert=TRUE:
[DirectPlay8]
Debug=0
BreakOnAssert=1
Verbose=1
The Verbose setting enables logging of file, function, and line information.
Debug Breaks:
=============
When something really severe happens and you want the system to break in
so that you can debug it later, you should put a debug break in the code
path. Some people use the philosophy that all code paths must be
verified by hand tracing each one in the debugger. If you abide by this
you should place a DEBUG_BREAK() in every code path and remove them
from the source as you trace each. When you have good coverage but
some unhit paths (error conditions) you should force those paths in
the debugger.
Debug Logging to Shared Memory Region:
======================================
All processes will share the same memory region, and will log the specified amount
of activity. The log can be viewed with the DPLOG.EXE utility.
Debug Logging to Debug Output:
==============================
This option uses OutputDebugString to log the specified amount of activity.
==============================================================================*/
#undef DPF_SUBCOMP
#define DPF_SUBCOMP DN_SUBCOMP_COMMON
#define ASSERT_BUFFER_SIZE 8192
#define ASSERT_BANNER_STRING "************************************************************"
#define ASSERT_MESSAGE_LEVEL 0
#define WSPRINTF wsprintfA
#define WVSPRINTF wvsprintfA
#define PROF_SECT "DirectPlay8"
DWORD g_dwMemLogNumEntries = 40000; // Default Num entries for MEM log, settable in win.ini
DWORD g_dwMemLogLineSize = DPLOG_MAX_STRING; // Default number of bytes per log entry
//
// Globals for shared memory based logging
//
HANDLE g_hMemLogFile = 0; // NOTE: This is 0 because CreateFileMapping returns 0 on failure
HANDLE g_hMemLogMutex = 0; // NOTE: This is 0 because CreateMutex returns 0 on failure
PSHARED_LOG_FILE g_pMemLog = 0;
BOOL g_fMemLogInited = FALSE;
DWORD g_fAssertGrabMutex = FALSE;
// Values for g_rgDestination
#define LOG_TO_DEBUG 1
#define LOG_TO_MEM 2
//#define LOG_TO_FILE 4 // NOTE: Currently unused
LPSTR g_rgszSubCompName[] =
{
"UNK", // DN_SUBCOMP_GLOBAL 0
"CORE", // DN_SUBCOMP_CORE 1
"ADDR", // DN_SUBCOMP_ADDR 2
"LOBBY", // DN_SUBCOMP_LOBBY 3
"PROTOCOL", // DN_SUBCOMP_PROTOCOL 4
"VOICE", // DN_SUBCOMP_VOICE 5
"DPNSVR", // DN_SUBCOMP_DPNSVR 6
"WSOCK", // DN_SUBCOMP_WSOCK 7
"MODEM", // DN_SUBCOMP_MODEM 8
"COMMON", // DN_SUBCOMP_COMMON 9
"NATHELP", // DN_SUBCOMP_NATHELP 10
"TOOLS", // DN_SUBCOMP_TOOLS 11
"MAX", // DN_SUBCOMP_MAX 12 // NOTE: this should never get used, but
// is needed due to the way DebugPrintfInit
// is written, since it reads one past the end.
};
#define MAX_SUBCOMPS (sizeof(g_rgszSubCompName)/sizeof(g_rgszSubCompName[0]) - 1)
UINT g_rgLevel[MAX_SUBCOMPS] = {0};
UINT g_rgDestination[MAX_SUBCOMPS] = {LOG_TO_DEBUG | LOG_TO_MEM};
UINT g_rgBreakOnAssert[MAX_SUBCOMPS] = {1};// if non-zero, causes DEBUG_BREAK on false asserts.
// if TRUE, file/line/module information is printed and logged.
DWORD g_fLogFileAndLine = FALSE;
// 7/24/00(RichGr) - IA64: If g_dwPlatformId == VER_PLATFORM_WIN32_WINDOWS,
// we're running under Win9x.
DWORD g_dwPlatformId;
// Create a shared file for logging information on the fly
// This support allows the current log to be dumped from the
// user mode DP8LOG.EXE application. This is useful when debugging
// in MSSTUDIO or in NTSD. When the DP8LOG.EXE is invoke, note that
// the application will get halted until the log is completely dumped
// so it is best to dump the log to a file.
#undef DPF_MODNAME
#define DPF_MODNAME "InitMemLogString"
static BOOL InitMemLogString(VOID)
{
if(!g_fMemLogInited)
{
BOOL fInitLogFile = TRUE;
if (g_dwPlatformId == VER_PLATFORM_WIN32_NT)
{
g_hMemLogFile = CreateFileMapping(INVALID_HANDLE_VALUE, DNGetNullDacl(), PAGE_READWRITE, 0, (DPLOG_HEADERSIZE + (DPLOG_ENTRYSIZE*g_dwMemLogNumEntries)), _T("Global\\") BASE_LOG_MEMFILENAME);
}
else
{
g_hMemLogFile = CreateFileMapping(INVALID_HANDLE_VALUE, DNGetNullDacl(), PAGE_READWRITE, 0, (DPLOG_HEADERSIZE + (DPLOG_ENTRYSIZE*g_dwMemLogNumEntries)), BASE_LOG_MEMFILENAME);
}
if (!g_hMemLogFile)
{
return FALSE;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
fInitLogFile = FALSE;
}
if (g_dwPlatformId == VER_PLATFORM_WIN32_NT)
{
g_hMemLogMutex = CreateMutex(DNGetNullDacl(), FALSE, _T("Global\\") BASE_LOG_MUTEXNAME);
}
else
{
g_hMemLogMutex = CreateMutex(DNGetNullDacl(), FALSE, BASE_LOG_MUTEXNAME);
}
if (!g_hMemLogMutex)
{
CloseHandle(g_hMemLogFile);
g_hMemLogFile = 0;
return FALSE;
}
g_pMemLog = (PSHARED_LOG_FILE)MapViewOfFile(g_hMemLogFile, FILE_MAP_ALL_ACCESS,0,0,0);
if (!g_pMemLog)
{
CloseHandle(g_hMemLogMutex);
g_hMemLogMutex = 0;
CloseHandle(g_hMemLogFile);
g_hMemLogFile = 0;
return FALSE;
}
// NOTE: The above 3 functions do return NULL in the case of a failure,
// not INVALID_HANDLE_VALUE
if (fInitLogFile)
{
g_pMemLog->nEntries = g_dwMemLogNumEntries;
g_pMemLog->cbLine = g_dwMemLogLineSize;
g_pMemLog->iWrite = 0;
}
else
{
// This happens when someone before us has already created the mem log. Could be a previous DPlay instance or TestNet.
g_dwMemLogNumEntries = g_pMemLog->nEntries;
g_dwMemLogLineSize = g_pMemLog->cbLine;
}
if (g_dwMemLogNumEntries && g_dwMemLogLineSize)
{
g_fMemLogInited = TRUE;
}
}
return g_fMemLogInited;
}
// Log a string to a shared file. This file can be dumped using the
// DPLOG.EXE utility.
//
// dwLength does not include the '\0'
//
void MemLogString(LPCSTR str, size_t dwLength)
{
PMEMLOG_ENTRY pEntry;
size_t cbCopy;
// If this isn't inited, InitMemLogString failed earlier
if(!g_fMemLogInited)
{
return;
}
WaitForSingleObject(g_hMemLogMutex, INFINITE);
pEntry = (PMEMLOG_ENTRY)(((PUCHAR)(g_pMemLog + 1)) + (g_pMemLog->iWrite * (sizeof(MEMLOG_ENTRY) + g_dwMemLogLineSize)));
g_pMemLog->iWrite = (g_pMemLog->iWrite + 1) % g_dwMemLogNumEntries;
ReleaseMutex(g_hMemLogMutex);
pEntry->tLogged = GETTIMESTAMP();
cbCopy = dwLength + 1; // Add the terminating NULL
if(cbCopy > g_dwMemLogLineSize)
{
cbCopy = g_dwMemLogLineSize;
}
memcpy(pEntry->str, str, cbCopy);
pEntry->str[cbCopy-2] = '\n'; // Ensure we always end with a return
pEntry->str[cbCopy-1] = '\0'; // Ensure we always NULL terminate
}
// DebugPrintfInit() - initialize DPF support.
void DebugPrintfInit()
{
// Get platform information
OSVERSIONINFO OSVersionInfo = {0};
OSVersionInfo.dwOSVersionInfoSize = sizeof OSVersionInfo;
GetVersionEx(&OSVersionInfo);
g_dwPlatformId = OSVersionInfo.dwPlatformId;
BOOL fUsingMemLog = FALSE;
CHAR szLevel[32] = {0};
strcpy(szLevel, "debug");
CHAR szDest[32] = {0};
strcpy(szDest, "log");
CHAR szBreak[32] = {0};
strcpy(szBreak, "breakonassert");
// Loop through all the subcomps, and get the level and destination for each
for (int iSubComp = 0; iSubComp < sizeof(g_rgszSubCompName)/sizeof(g_rgszSubCompName[0]) - 1; iSubComp++)
{
// NOTE: The setting under "debug" sets the default and will be used if you
// don't specify settings for each subcomp
g_rgLevel[iSubComp] = GetProfileIntA(PROF_SECT, szLevel, g_rgLevel[0]);
g_rgDestination[iSubComp] = GetProfileIntA(PROF_SECT, szDest, g_rgDestination[0]);
g_rgBreakOnAssert[iSubComp] = GetProfileIntA( PROF_SECT, szBreak, g_rgBreakOnAssert[0]);
if (g_rgDestination[iSubComp] & LOG_TO_MEM)
{
fUsingMemLog = TRUE;
}
// Set up for the next subcomp
strcpy(szLevel + 5, "."); // 5 is strlen of "debug", we are building debug.addr, etc.
strcpy(szLevel + 6, g_rgszSubCompName[iSubComp + 1]);
strcpy(szDest + 3, "."); // 3 is strlen of "log", we are building log.addr, etc.
strcpy(szDest + 4, g_rgszSubCompName[iSubComp + 1]);
strcpy(szBreak + 13, "."); // 13 is strlen of "breakonassert", we are building breakonassert.addr, etc.
strcpy(szDest + 14, g_rgszSubCompName[iSubComp + 1]);
}
g_dwMemLogNumEntries = GetProfileIntA( PROF_SECT, "MemLogEntries", 40000);
g_fLogFileAndLine = GetProfileIntA( PROF_SECT, "Verbose", 0);
g_fAssertGrabMutex = GetProfileIntA( PROF_SECT, "AssertGrabMutex", 0);
if (fUsingMemLog)
{
// Open the shared log file
InitMemLogString();
}
}
// DebugPrintfFini() - release resources used by DPF support.
void DebugPrintfFini()
{
if(g_pMemLog)
{
UnmapViewOfFile(g_pMemLog);
g_pMemLog = NULL;
}
if(g_hMemLogMutex)
{
CloseHandle(g_hMemLogMutex);
g_hMemLogMutex = 0;
}
if(g_hMemLogFile)
{
CloseHandle(g_hMemLogFile);
g_hMemLogFile = 0;
}
g_fMemLogInited = FALSE;
}
void DebugPrintfX(LPCSTR szFile, DWORD dwLine, LPCSTR szModName, DWORD dwSubComp, DWORD dwDetail, ...)
{
DNASSERT(dwSubComp < MAX_SUBCOMPS);
if(g_rgLevel[dwSubComp] < dwDetail)
{
return;
}
CHAR cMsg[ ASSERT_BUFFER_SIZE ];
LPSTR szFormat;
char *psz = NULL;
va_list argptr;
LPSTR pszCursor = cMsg;
va_start(argptr, dwDetail);
szFormat = (LPSTR) va_arg(argptr, DWORD_PTR);
cMsg[0] = 0;
// IA64: If g_dwPlatformId == VER_PLATFORM_WIN32_WINDOWS,
// we're running under Win9x and need to replace %p with %x.
// TODO: Make this build specific, don't penalize NT
if (g_dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
CHAR cTemp[ ASSERT_BUFFER_SIZE ];
strcpy(cTemp, szFormat); // Copy to a local string that we can modify.
szFormat = cTemp; // Point szFormat at the local string
while (psz = strstr(szFormat, "%p")) // Look for each "%p".
*(psz+1) = 'x'; // Substitute 'x' for 'p'. Don't try to expand
}
// Prints out / logs as:
// 1. Verbose
// subcomp:dwDetail:ProcessId:ThreadId:File:Function:Line:DebugString
// e.g.
// ADDR:2:0450:0378:(c:\somefile.cpp)BuildURLA(L25)Can you believe it?
//
// 2. Regular
// subcomp:dwDetail:ProcessId:ThreadId:Function:DebugString
strcpy(pszCursor, g_rgszSubCompName[dwSubComp]);
pszCursor += strlen(pszCursor);
pszCursor += WSPRINTF(pszCursor,":%1d:",dwDetail);
pszCursor += WSPRINTF(pszCursor,"%04x:",GetCurrentProcessId());
pszCursor += WSPRINTF(pszCursor,"%04x:",GetCurrentThreadId());
if (g_fLogFileAndLine)
{
LPCSTR c;
int i = strlen(szFile);
if (i < 25)
{
c = szFile;
}
else
{
c = szFile + i - 25;
}
pszCursor += WSPRINTF(pszCursor,"(%s)(L%d)", c, dwLine);
}
pszCursor += WSPRINTF(pszCursor, "%s: ", szModName);
pszCursor += WVSPRINTF(pszCursor, szFormat, argptr);
strcpy(pszCursor, "\n");
pszCursor += strlen(pszCursor);
if(g_rgDestination[dwSubComp] & LOG_TO_DEBUG)
{
// log to debugger output
OutputDebugStringA(cMsg);
}
if(g_rgDestination[dwSubComp] & LOG_TO_MEM)
{
// log to shared file, pass length not including '\0'
MemLogString(cMsg, (DWORD) ((DWORD_PTR) (pszCursor - cMsg)));
}
//if(g_rgDestination[dwSubComp] & LOG_TO_FILE)
//{
//}
va_end(argptr);
return;
}
//
// NOTE: I don't want to get into error checking for buffer overflows when
// trying to issue an assertion failure message. So instead I just allocate
// a buffer that is "bug enough" (I know, I know...)
//
void _DNAssert( LPCSTR szFile, DWORD dwLine, LPCSTR szFnName, DWORD dwSubComp, LPCSTR szCondition, DWORD dwLevel )
{
char buffer[ASSERT_BUFFER_SIZE];
// For level 1 we always print the message to the log, but we may not actually break. For other levels
// we either print and break or do neither.
if (dwLevel <= g_rgBreakOnAssert[dwSubComp] || dwLevel == 1)
{
// Build the debug stream message
WSPRINTF( buffer, "ASSERTION FAILED! File: %s Line: %d: %s", szFile, dwLine, szCondition);
// Actually issue the message. These messages are considered error level
// so they all go out at error level priority.
DebugPrintfX(szFile, dwLine, szFnName, dwSubComp, ASSERT_MESSAGE_LEVEL, ASSERT_BANNER_STRING );
DebugPrintfX(szFile, dwLine, szFnName, dwSubComp, ASSERT_MESSAGE_LEVEL, buffer );
DebugPrintfX(szFile, dwLine, szFnName, dwSubComp, ASSERT_MESSAGE_LEVEL, ASSERT_BANNER_STRING );
// Should we drop into the debugger?
if(g_rgBreakOnAssert[dwSubComp])
{
// Don't let dpnsvr keep writing to the log
if (g_hMemLogMutex && g_fAssertGrabMutex)
{
WaitForSingleObject(g_hMemLogMutex, INFINITE);
}
// Into the debugger we go...
DEBUG_BREAK();
if (g_hMemLogMutex && g_fAssertGrabMutex)
{
ReleaseMutex(g_hMemLogMutex);
}
}
}
}
#endif //defined debug
BOOL IsValidStringA( const CHAR * const szString )
{
return (!IsBadStringPtrA( szString, 0xFFFFFFFF ) );
}
BOOL IsValidStringW( const WCHAR * const swzString )
{
const wchar_t *szTmpLoc = swzString;
if( swzString == NULL )
{
return FALSE;
}
_try
{
for( ; *szTmpLoc ; szTmpLoc++ );
}
_except( EXCEPTION_EXECUTE_HANDLER )
{
return FALSE;
}
return TRUE;
}