Windows2003-3790/multimedia/media/winmm/debug.c
2020-09-30 16:53:55 +02:00

792 lines
22 KiB
C

/****************************************************************************
debug.c
winmm debug support module
Copyright (c) 1990-2001 Microsoft Corporation
History
10/1/92 Updated for NT by Robin Speed (RobinSp)
****************************************************************************/
#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "winmmi.h"
#include <wchar.h>
#include <stdarg.h>
// no REAL logging for now ! - NT doesn't have Dr Watson !
#define LogParamError(a, b)
RTL_RESOURCE gHandleListResource;
/***************************************************************************
* @doc INTERNAL
*
* @func HANDLE | NewHandle | allocate a fixed handle in MMSYSTEM's local heap
*
* @parm UINT | uType | unique id describing handle type
* @parm UINT | uSize | size in bytes to be allocated
*
* @rdesc Returns pointer/handle to memory object
*
* @comm a standard handle header (HNDL) will be added to the object,
* and it will be linked into the list of MMSYSTEM handles.
*
***************************************************************************/
HANDLE NewHandle(UINT uType, PCWSTR cookie, UINT uSize)
{
PHNDL pHandle;
pHandle = (PHNDL)HeapAlloc(hHeap, 0, sizeof(HNDL) + uSize);
if (pHandle == NULL) {
return pHandle;
} else {
ZeroMemory(pHandle, sizeof(HNDL) + uSize); // zero the whole bludy lot
if (!mmInitializeCriticalSection(&pHandle->CritSec)) {
HeapFree(hHeap, 0, (LPSTR)pHandle);
return NULL;
}
pHandle->hThread = GetCurrentTask(); // For WOW validation
pHandle->uType = uType;
pHandle->cookie = cookie;
RtlAcquireResourceExclusive(&gHandleListResource, TRUE);
EnterCriticalSection(&HandleListCritSec);
pHandle->pNext = pHandleList;
pHandleList = pHandle;
LeaveCriticalSection(&HandleListCritSec);
}
return PHtoH(pHandle);
}
void AcquireHandleListResourceShared()
{
RtlAcquireResourceShared(&gHandleListResource, TRUE);
}
void AcquireHandleListResourceExclusive()
{
RtlAcquireResourceExclusive(&gHandleListResource, TRUE);
}
void ReleaseHandleListResource()
{
RtlReleaseResource(&gHandleListResource);
}
/***************************************************************************
* @doc INTERNAL
*
* @func HANDLE | FreeHandle | free handle allocated with NewHandle
*
* @parm HANDLE | hUser | handle returned from NewHandle
*
* @comm handle will be unlinked from list, and memory will be freed
*
*
***************************************************************************/
void FreeHandle(HANDLE hUser)
{
/* Find handle and free from list */
PHNDL pHandle;
PHNDL *pSearch;
if (hUser == NULL) {
return;
}
//
// Point to our handle data
//
pHandle = HtoPH(hUser);
AcquireHandleListResourceExclusive();
EnterCriticalSection(&HandleListCritSec);
pSearch = &pHandleList;
while (*pSearch != NULL) {
if (*pSearch == pHandle) {
//
// Found it
// Remove it from the list
//
*pSearch = pHandle->pNext;
LeaveCriticalSection(&HandleListCritSec);
// Making sure no one is using the handle while we mark it as invalid.
EnterCriticalSection(&pHandle->CritSec);
pHandle->uType = 0;
pHandle->fdwHandle = 0L;
pHandle->hThread = NULL;
pHandle->pNext = NULL;
LeaveCriticalSection(&pHandle->CritSec);
DeleteCriticalSection(&pHandle->CritSec);
HeapFree(hHeap, 0, (LPSTR)pHandle);
ReleaseHandleListResource();
return;
} else {
pSearch = &(*pSearch)->pNext;
}
}
dprintf1(("Freeing handle which is not in the list !"));
WinAssert(FALSE);
LeaveCriticalSection(&HandleListCritSec);
ReleaseHandleListResource();
}
/***************************************************************************
* @doc INTERNAL
*
* @func HANDLE | InvalidateHandle | invalidate handle allocated with
* NewHandle for parameter validation
*
* @parm HANDLE | hUser | handle returned from NewHandle
*
* @comm handle will be marked as TYPE_UNKNOWN, causing handle based API's to
* fail.
*
*
***************************************************************************/
void InvalidateHandle(HANDLE hUser)
{
/* Find handle and free from list */
PHNDL pHandle;
if (hUser == NULL) {
return;
}
//
// Point to our handle data
//
pHandle = HtoPH(hUser);
pHandle->uType = TYPE_UNKNOWN;
}
/**************************************************************************
@doc INTERNAL
@api void | winmmSetDebugLevel | Set the current debug level
@parm int | iLevel | The new level to set
@rdesc There is no return value
**************************************************************************/
void winmmSetDebugLevel(int level)
{
#if DBG
winmmDebugLevel = level;
dprintf(("debug level set to %d", winmmDebugLevel));
#endif
}
STATICDT UINT inited=0;
#if DBG
extern int mciDebugLevel;
#endif
#if DBG
void InitDebugLevel(void)
{
if (!inited) {
INT level;
level = GetProfileInt("MMDEBUG", "WINMM", 99);
if (level != 99) {
winmmDebugLevel = level;
}
level = GetProfileInt("MMDEBUG", "MCI", 99);
if (level != 99) {
mciDebugLevel = level;
}
inited = 1;
}
dprintf2(("Starting, debug level=%d", winmmDebugLevel));
}
#endif
#ifdef DEBUG_RETAIL
/***************************************************************************
* @doc INTERNAL WAVE MIDI
*
* @func BOOL | ValidateHeader | validates a wave or midi date header
*
* @parm LPVOID | lpHeader| pointer to wave/midi header
* @parm UINT | wSize | size of header passed by app
* @parm UINT | wType | unique id describing header/handle type
*
* @rdesc Returns TRUE if <p> is non NULL and <wSize> is the correct size
* Returns FALSE otherwise
*
* @comm if the header is invalid an error will be generated.
*
***************************************************************************/
BOOL ValidateHeader(PVOID pHdr, UINT uSize, UINT uType)
{
// Detect bad header
if (!ValidateWritePointer(pHdr, uSize)) {
DebugErr(DBF_ERROR, "Invalid header pointer");
return FALSE;
}
// Check type
switch (uType) {
case TYPE_WAVEOUT:
case TYPE_WAVEIN:
{
PWAVEHDR pHeader = pHdr;
// Check header
if (uSize < sizeof(WAVEHDR)) {
DebugErr(DBF_ERROR, "Invalid header size");
LogParamError(ERR_BAD_VALUE, uSize);
return FALSE;
}
if (pHeader->dwFlags & ~WHDR_VALID) {
LogParamError(ERR_BAD_FLAGS, ((PWAVEHDR)pHeader)->dwFlags);
return FALSE;
}
// Check buffer
if (!(uType == TYPE_WAVEOUT
? ValidateReadPointer(pHeader->lpData, pHeader->dwBufferLength)
: ValidateWritePointer(pHeader->lpData, pHeader->dwBufferLength))
) {
DebugErr(DBF_ERROR, "Invalid buffer pointer");
return FALSE;
}
}
break;
case TYPE_MIDIIN:
case TYPE_MIDIOUT:
case TYPE_MIDISTRM:
{
PMIDIHDR pHeader = pHdr;
if ((TYPE_MIDISTRM == uType) &&
uSize < sizeof(MIDIHDR))
{
DebugErr(DBF_ERROR, "Invalid header size");
LogParamError(ERR_BAD_VALUE, uSize);
return FALSE;
}
else if (uSize < sizeof(MIDIHDR31))
{
DebugErr(DBF_ERROR, "Invalid header size");
LogParamError(ERR_BAD_VALUE, uSize);
return FALSE;
}
if (pHeader->dwFlags & ~MHDR_VALID) {
LogParamError(ERR_BAD_FLAGS, ((PMIDIHDR)pHeader)->dwFlags);
return FALSE;
}
// Check buffer
if (!(uType == TYPE_MIDIOUT
? ValidateReadPointer(pHeader->lpData, pHeader->dwBufferLength)
: ValidateWritePointer(pHeader->lpData, pHeader->dwBufferLength))
) {
DebugErr(DBF_ERROR, "Invalid buffer pointer");
return FALSE;
}
}
break;
default:
WinAssert(FALSE);
break;
}
return TRUE;
}
#ifndef USE_KERNEL_VALIDATION
/***************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateReadPointer | validates that a pointer is valid to
* read from.
*
* @parm LPVOID | lpPoint| pointer to validate
* @parm DWORD | dLen | supposed length of said pointer
*
* @rdesc Returns TRUE if <p> is a valid pointer
* Returns FALSE if <p> is not a valid pointer
*
* @comm will generate error if the pointer is invalid
*
***************************************************************************/
BOOL ValidateReadPointer(PVOID pPoint, ULONG Len)
{
// For now just check access to first and last byte
// Only validate if Len non zero. Midi APIs pass data in the
// pointer and pass a length of zero. Otherwise on 64 bit machines
// we look 4Gigs out from the pointer when we check Len-1 and Len==0.
// That doesn't work very well.
if (Len) {
try {
volatile BYTE b;
b = ((PBYTE)pPoint)[0];
b = ((PBYTE)pPoint)[Len - 1];
} except(EXCEPTION_EXECUTE_HANDLER) {
LogParamError(ERR_BAD_PTR, pPoint);
return FALSE;
}
}
return TRUE;
}
/***************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateWritePointer | validates that a pointer is valid to
* write to.
*
* @parm LPVOID | lpPoint| pointer to validate
* @parm DWORD | dLen | supposed length of said pointer
*
* @rdesc Returns TRUE if <p> is a valid pointer
* Returns FALSE if <p> is not a valid pointer
*
* @comm will generate error if the pointer is invalid
*
***************************************************************************/
BOOL ValidateWritePointer(PVOID pPoint, ULONG Len)
{
// For now just check read and write access to first and last byte
// Only validate if Len non zero. Midi APIs pass data in the
// pointer and pass a length of zero. Otherwise on 64 bit machines
// we look 4Gigs out from the pointer when we check Len-1 and Len==0.
// That doesn't work very well.
if (Len) {
try {
volatile BYTE b;
b = ((PBYTE)pPoint)[0];
((PBYTE)pPoint)[0] = b;
b = ((PBYTE)pPoint)[Len - 1];
((PBYTE)pPoint)[Len - 1] = b;
} except(EXCEPTION_EXECUTE_HANDLER) {
LogParamError(ERR_BAD_PTR, pPoint);
return FALSE;
}
}
return TRUE;
}
#endif // USE_KERNEL_VALIDATION
/***************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidDriverCallback |
*
* validates that a driver callback is valid, to be valid a driver
* callback must be a valid window, task, or a function in a FIXED DLL
* code segment.
*
* @parm DWORD | dwCallback | callback to validate
* @parm DWORD | wFlags | driver callback flags
*
* @rdesc Returns 0 if <dwCallback> is a valid callback
* Returns error condition if <dwCallback> is not a valid callback
***************************************************************************/
BOOL ValidDriverCallback(HANDLE hCallback, DWORD dwFlags)
{
switch (dwFlags & DCB_TYPEMASK) {
case DCB_WINDOW:
if (!IsWindow(hCallback)) {
LogParamError(ERR_BAD_HWND, hCallback);
return FALSE;
}
break;
case DCB_EVENT:
//if (hCallback is not an event)
// LogParamError(ERR_BAD_CALLBACK, hCallback);
// return FALSE;
//}
break;
case DCB_TASK:
//if (IsBadCodePtr((FARPROC)hCallback)) {
// LogParamError(ERR_BAD_CALLBACK, hCallback);
// return FALSE;
//}
break;
case DCB_FUNCTION:
if (IsBadCodePtr((FARPROC)hCallback)) {
LogParamError(ERR_BAD_CALLBACK, hCallback);
return FALSE;
}
break;
}
return TRUE;
}
#ifndef USE_KERNEL_VALIDATION
/**************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateString |
*
**************************************************************************/
BOOL ValidateString(LPCSTR pPoint, DWORD Len)
{
// For now just check access - do a 'strnlen'
try {
volatile BYTE b;
LPCSTR p = pPoint;
while (Len--) {
b = *p;
if (!b) {
break;
}
p++;
}
} except(EXCEPTION_EXECUTE_HANDLER) {
LogParamError(ERR_BAD_STRING_PTR, pPoint);
return FALSE;
}
return TRUE;
}
/**************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateStringW |
*
**************************************************************************/
BOOL ValidateStringW(LPCWSTR pPoint, DWORD Len)
{
// For now just check access - do a 'strnlen'
try {
volatile WCHAR b;
LPCWSTR p = pPoint;
while (Len--) {
b = *p;
if (!b) {
break;
}
p++;
}
} except(EXCEPTION_EXECUTE_HANDLER) {
LogParamError(ERR_BAD_STRING_PTR, pPoint);
return FALSE;
}
return TRUE;
}
#endif //USE_KERNEL_VALIDATION
/**************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateHandle | validates a handle created with NewHandle
*
* @parm PHNDL | hLocal | handle returned from NewHandle
* @parm UINT | wType | unique id describing handle type
*
* @rdesc Returns TRUE if <h> is a valid handle of type <wType>
* Returns FALSE if <h> is not a valid handle
*
* @comm if the handle is invalid an error will be generated.
*
**************************************************************************/
BOOL ValidateHandle(HANDLE hLocal, UINT uType)
{
BOOL OK;
//
// if the handle is less than 64k or a mapper id then
// don't bother with the overhead of the try-except.
//
// BUGBUG: MM needs to be audited for WIN64!
//
// This code is a mess. The mapper ids are defined as 32-bit
// unsigned values and then compared against a HANDLE? Of course
// an unsigned 32-bit -1 will never equal a 64-bit -1 so on WIN64,
// an exception is taken everytime an invalid handle is passed in.
// Someone hacked in enough coercions to mask valid warnings for WIN64.
// Even worse, there is at least one function that returns 0xffffffff
// explicity versus a define or const value.
//
// For now, change the const compare to a handle compare and add an
// invalid handle compare. This results in the same code on x86 as
// before (the compiler folds all of the redundant compares), and doesn't
// increase the codesize for IA64 (parallel compares are used) even though
// the extra compare for WIN64 is useless.
//
if (hLocal < (HANDLE)0x10000 ||
INVALID_HANDLE_VALUE == hLocal ||
WAVE_MAPPER == (UINT_PTR)hLocal ||
MIDI_MAPPER == (UINT_PTR)hLocal ||
AUX_MAPPER == (UINT_PTR)hLocal)
{
LogParamError(ERR_BAD_HANDLE, hLocal);
return FALSE;
}
try {
OK = HtoPH(hLocal)->uType == uType;
} except(EXCEPTION_EXECUTE_HANDLER) {
LogParamError(ERR_BAD_HANDLE, hLocal);
return FALSE;
}
return OK;
}
#if DBG
char * Types[4] = {"Unknown callback type",
"Window callback",
"Task callback",
"Function callback"};
#endif
/**************************************************************************
* @doc INTERNAL
*
* @func BOOL | ValidateCallbackType | validates a callback address,
* window handle, or task handle
*
* @parm PHNDL | hLocal | handle returned from NewHandle
* @parm UINT | wType | unique id describing handle type
*
* @rdesc Returns TRUE if <h> is a valid handle of type <wType>
* Returns FALSE if <h> is not a valid handle
*
* @comm if the handle is invalid an error will be generated.
*
**************************************************************************/
BOOL ValidateCallbackType(DWORD_PTR dwCallback, UINT uType)
{
#define DCALLBACK_WINDOW HIWORD(CALLBACK_WINDOW) // dwCallback is a HWND
#define DCALLBACK_TASK HIWORD(CALLBACK_TASK) // dwCallback is a HTASK
#define DCALLBACK_FUNCTION HIWORD(CALLBACK_FUNCTION) // dwCallback is a FARPROC
#define DCALLBACK_EVENT HIWORD(CALLBACK_EVENT) // dwCallback is an EVENT
UINT type = uType & HIWORD(CALLBACK_TYPEMASK);
#if DBG
if (type>5) {
type = 0;
}
dprintf3(("Validating Callback, type=%d (%hs), handle=%8x", type, Types[type], dwCallback));
#endif
switch (type) {
case DCALLBACK_WINDOW:
return(IsWindow((HWND)dwCallback));
break;
case DCALLBACK_EVENT:
{
// ?? how to verify that this is an event handle??
//DWORD dwFlags;
//GetHandleInformation((HANDLE)dwCallback, &dwFlags);
return TRUE;
}
break;
case DCALLBACK_FUNCTION:
return(!(IsBadCodePtr((FARPROC)dwCallback)));
break;
case DCALLBACK_TASK:
if (THREAD_PRIORITY_ERROR_RETURN == GetThreadPriority((HANDLE)dwCallback)) {
dprintf1(("Invalid callback task handle"));
// I suspect we do not have the correct thread handle, in
// which case we can only return TRUE.
//return(FALSE);
}
return(TRUE);
break;
}
return TRUE;
}
/**************************************************************************
@doc INTERNAL
@func void | dout | Output debug string if debug flag is set
@parm LPSTR | szString
**************************************************************************/
#if DBG
int fDebug = 1;
#else
int fDebug = 0;
#endif
//void dout(LPSTR szString)
//{
// if (fDebug) {
// OutputDebugStringA(szString);
// }
//}
#ifdef LATER
This routine should probably be replaced in the headers by redefining
to use OutputDebugString
#endif
#undef OutputDebugStr
// Make our function visible
/*****************************************************************************
* @doc EXTERNAL DDK
*
* @api void | OutputDebugStr | This function sends a debugging message
* directly to the COM1 port or to a secondary monochrome display
* adapter. Because it bypasses DOS, it can be called by low-level
* callback functions and other code at interrupt time.
*
* @parm LPSTR | lpOutputString | Specifies a far pointer to a
* null-terminated string.
*
* @comm This function is available only in the debugging version of
* Windows. The DebugOutput keyname in the [mmsystem]
* section of SYSTEM.INI controls where the debugging information is
* sent. If fDebugOutput is 0, all debug output is disabled.
******************************************************************************/
/*****************************************************************************
* This function is basicly the same as OutputDebugString() in KERNEL.
*
*
* DESCRIPTION: outputs a string to the debugger
*
* ENTRY: szString - string to output
*
* EXIT:
* none
* USES:
* flags
*
*****************************************************************************/
VOID APIENTRY OutputDebugStr(LPCSTR szString)
{
OutputDebugStringA((LPSTR)szString); // Will always be an ASCII string
// When the MM WOW thunk is changed to call OutputDebugString directly
// we can remove this routine from our code
}
#endif // DEBUG_RETAIL
#if DBG
int winmmDebugLevel = 0;
/***************************************************************************
@doc INTERNAL
@api void | winmmDbgOut | This function sends output to the current
debug output device.
@parm LPSTR | lpszFormat | Pointer to a printf style format string.
@parm ??? | ... | Args.
@rdesc There is no return value.
****************************************************************************/
extern BOOL Quiet = FALSE;
void winmmDbgOut(LPSTR lpszFormat, ...)
{
char buf[512];
UINT n;
va_list va;
if (Quiet) {
return;
}
n = wsprintf(buf, "WINMM(p%d:t%d): ", GetCurrentProcessId(), GetCurrentThreadId());
va_start(va, lpszFormat);
n += vsprintf(buf+n, lpszFormat, va);
va_end(va);
buf[n++] = '\n';
buf[n] = 0;
OutputDebugString(buf);
Sleep(0); // let terminal catch up
}
/***************************************************************************
@doc INTERNAL
@api void | dDbgAssert | This function prints an assertion message.
@parm LPSTR | exp | Pointer to the expression string.
@parm LPSTR | file | Pointer to the file name.
@parm int | line | The line number.
@rdesc There is no return value.
****************************************************************************/
void dDbgAssert(LPSTR exp, LPSTR file, int line)
{
dprintf(("Assertion failure:"));
dprintf((" Exp: %s", exp));
dprintf((" File: %s, line: %d", file, line));
DebugBreak();
}
#else // Still need to export this thing to help others
void winmmDbgOut(LPSTR lpszFormat, ...)
{
}
#endif // DBG