A template for debug messages
来源:互联网 发布:php 获取数组元素下标 编辑:程序博客网 时间:2024/05/18 02:23
Submitted by Michel on July 25, 2008 - 13:54
In all the drivers and applications I develop, I always take the time to add plenty of logging because I know good logging will save me loads of time debugging my code (as if I ever have to "debug" my perfect code, ha! ;o).
Windows CE has a really great mechanism for debug logging called Debug Zones. This mechanism allows you to divide your debug messages into 16 zones and each of these zones can be turned on or off at run-time. This allows you to control the verbosity of your debug output and helps prevent cluttering of messages. Read this excellent blog post by Travis first before reading on: http://blogs.msdn.com/ce_base/archive/2006/12/18/debug-messages-and-debu...
To add debug logging using Debug Zones to your application or driver do the following:
1. Define your zones using the DEBUGZONE macro
1. Define a DBGPARAM structure and fill it
2. Call either DEBUGREGISTER or RETAILREGISTERZONES from your DllMain (driver) or WinMain (application)
3. Log messages using either DEBUGMSG or RETAILMSG
Sounds easy (and actually it is) but to make it more easy (and because I found myself doing the same thing over and over) I created a template I now use in all my driver and application code, even on projects targeted for Desktop Windows. Even though "Big" Windows doesn't have a "Debug Zone" mechanism you can still use the template. The big benefit of course being your code is completely portable between desktop PC's and CE devices (and also MBS/UNICODE)! The only thing you can't do is switch debug zones at run-time, but you can still select which zones should be on or off at compile-time.
Let me explain the template by walking through it; I'll start with DebugZones.cpp (the line numbers reflect the actual line numbers in the template files):
- #include <windows.h>
- #include "DebugZones.h"
- // Do NOT change anything in this file.
- // See DebugZones.h for things you can change!
- /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
- DBGPARAM dpCurSettings =
- {
- __TMODULE__,
- {
- ZONE0_TEXT,ZONE1_TEXT,ZONE2_TEXT,ZONE3_TEXT,
- ZONE4_TEXT,ZONE5_TEXT,ZONE6_TEXT,ZONE7_TEXT,
- ZONE8_TEXT,ZONE9_TEXT,ZONE10_TEXT,ZONE11_TEXT,
- ZONE12_TEXT,ZONE13_TEXT,ZONE14_TEXT,ZONE15_TEXT
- },
- #ifndef DEBUG // IN RETAIL...
- RETAILZONES
- #else //IN DEBUG...
- DEBUGZONES
- #endif
- };
The first and most important thing to note is that you should not change anything in this file. You configure the template all through the header file, which I will be discussing later.
In this first part of DebugZones.cpp I simply define a global variable dpCurSettings of type DBGPARAM and fill the structure with defines from DebugZones.h. The __TMODULE__ corresponds to the "lpszName" member, the ZONEx_TEXT defines fill the "rglpszZones" array and finally RETAILZONES or DEBUGZONES (depending on whether you build a "debug" or "release" version of the code) corresponds to the "ulZoneMask" member of the DBGPARAM structure. The CE debug system uses this structure to get Debug Zone information from the module and to set or clear debug zones during execution of the module. When you use the template for code targeted at Desktop Windows this structure will only be used by the DEBUGMSG/RETAILMSG macros (they use the ulZoneMask member as a display condition).
- #ifndef UNDER_CE
- #include <stdio.h>
- void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...)
- {
- #define BUFFER_SIZE 1000
- va_list argptr;
- va_start(argptr, lpszFmt);
- TCHAR szBuffer[BUFFER_SIZE];
- _vstprintf_s(szBuffer, BUFFER_SIZE, lpszFmt, argptr);
- OutputDebugString(szBuffer);
- va_end(argptr);
- }
- #endif
This code is only used when you target Desktop Windows. Only Windows CE defines NKDbgPrintf so for Desktop Windows I had to create my own implementation of this function to make the Debug Zones system portable. This function simply takes a format string followed by a variable number of parameters and transforms that into a formatted string and outputs it to the debug output window of Visual Studio. Note that the maximum length of the formatted string is BUFFER_SIZE characters.
- // Helper function for runtime conversion of char* to _T
- LPCTSTR to_T(LPCSTR pStr)
- {
- #define MAX_CALLS 4
- #define MAX_MSG 500
- static TCHAR buffer[MAX_CALLS][MAX_MSG];
- static DWORD index = 0;
- LPCTSTR pszRet = buffer[index];
- size_t conv;
- #ifdef UNICODE
- mbstowcs_s(&conv, buffer[index], MAX_MSG, pStr, MAX_MSG);
- #else //ANSI
- strcpy_s(buffer[index], MAX_MSG, pStr);
- #endif
- index++;
- if (MAX_CALLS==index)
- index = 0;
- return pszRet;
- }
This next bit of code is a helper function that solves a problem when you want your code to be portable between MBS and UNICODE. MBS is multi-byte string, also called ANSI string, and UNICODE is, well, UNICODE strings. Let's say you are compiling for UNICODE but want to display a multi-byte string using a function that uses a format string. The way to do that is to use %S instead of %s in the format string. When you use %s in code compiled for UNICODE the variable indicated by %s should be in UNICODE. When you compile for MBS the variable indicated by %s should be MBS. In other words, %s indicates the "native" string format. If you want to display a multi-byte string in code compiled for UNICODE you'd use %S, and if you want to display a UNICODE string in code compiled for MBS you'd use %S as well. In other words, %S indicates the "opposite" string format. And here lies the problem: What if you want to display a multi-byte string regardless of whether the code is compiled for MBS or UNICODE?
Take a look at the following code:
NKDbgPrintf(_T("Display opposite string format /"%S/"/r/n"), "This is a multi-byte string");
Note that the _T macro is defined as L in UNICODE (making the following string a UNICODE string) and as nothing in MBS (leaving the following string a multi-byte string).
When compiled for UNICODE the above code works perfectly:
Display opposite string format "This is a multi-byte string"
But now see what happens when you compile the above code for MBS:
You only get the output of the first line! So what just happened? Well, the _vstprintf_s function in NKDbgPrintf expected the first argument to be of the opposite string format. Since you compiled for MBS this means it expects a UNICODE string. The _vstprintf_s function detects it is not and bails out, setting szBuffer to an empty string.
The to_T function solves this problem:
NKDbgPrintf(_T("Display opposite string format /"%s/"/r/n"), to_T("This is a multi-byte string"));
When compiled for MBS the output is:
Display opposite string format "This is a multi-byte string"
I can here you say: So why don't you just use _T() in that 2nd call to NkDbgPrintf? And yes, you are right, in the above code that would work, but what if it's a variable pointing to a multi-byte string? You can't use _T() around a variable:
Line 64 defines the maximum number of times you can call this function before it will start overwriting its internal buffers. Increase this value if you want to use more than 4 parameters that need to be converted in one call. To understand this, see the following code and its output:
char* s2 = "String 2";
char* s3 = "String 3";
char* s4 = "String 4";
char* s5 = "String 5";
char* s6 = "String 6";
NKDbgPrintf(_T("%s %s %s %s %s %s/r/n"), to_T(s1), to_T(s2), to_T(s3), to_T(s4), to_T(s5), to_T(s6));
Output:
Let's move on to the real core of the template; DebugZones.h:
- #pragma once
- // Change the following definitions:
- /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
- #define __MODULE__ "MyDriver" // The module name
- // Definitions for our debug zones
- #define ZONE0_TEXT _T("Init")
- #define ZONE1_TEXT _T("Function")
- #define ZONE2_TEXT _T("Memory")
- #define ZONE3_TEXT _T("")
- #define ZONE4_TEXT _T("")
- #define ZONE5_TEXT _T("")
- #define ZONE6_TEXT _T("")
- #define ZONE7_TEXT _T("")
- #define ZONE8_TEXT _T("")
- #define ZONE9_TEXT _T("")
- #define ZONE10_TEXT _T("")
- #define ZONE11_TEXT _T("")
- #define ZONE12_TEXT _T("")
- #define ZONE13_TEXT _T("Info")
- #define ZONE14_TEXT _T("Warning")
- #define ZONE15_TEXT _T("Error")
- // These macros can be used as condition flags for LOGMSG
- #define ZONE_INIT DEBUGZONE(0)
- #define ZONE_FUNCTION DEBUGZONE(1)
- #define ZONE_MEMORY DEBUGZONE(2)
- #define ZONE_3 DEBUGZONE(3)
- #define ZONE_4 DEBUGZONE(4)
- #define ZONE_5 DEBUGZONE(5)
- #define ZONE_6 DEBUGZONE(6)
- #define ZONE_7 DEBUGZONE(7)
- #define ZONE_8 DEBUGZONE(8)
- #define ZONE_9 DEBUGZONE(9)
- #define ZONE_10 DEBUGZONE(10)
- #define ZONE_11 DEBUGZONE(11)
- #define ZONE_12 DEBUGZONE(12)
- #define ZONE_INFO DEBUGZONE(13)
- #define ZONE_WARNING DEBUGZONE(14)
- #define ZONE_ERROR DEBUGZONE(15)
- // These macros can be used to indicate which zones to enable by default (see RETAILZONES and DEBUGZONES below)
- #define ZONEMASK_INIT ZONEMASK(0)
- #define ZONEMASK_FUNCTION ZONEMASK(1)
- #define ZONEMASK_MEMORY ZONEMASK(2)
- #define ZONEMASK_3 ZONEMASK(3)
- #define ZONEMASK_4 ZONEMASK(4)
- #define ZONEMASK_5 ZONEMASK(5)
- #define ZONEMASK_6 ZONEMASK(6)
- #define ZONEMASK_7 ZONEMASK(7)
- #define ZONEMASK_8 ZONEMASK(8)
- #define ZONEMASK_9 ZONEMASK(9)
- #define ZONEMASK_10 ZONEMASK(10)
- #define ZONEMASK_11 ZONEMASK(11)
- #define ZONEMASK_12 ZONEMASK(12)
- #define ZONEMASK_INFO ZONEMASK(13)
- #define ZONEMASK_WARNING ZONEMASK(14)
- #define ZONEMASK_ERROR ZONEMASK(15)
- #define RETAILZONES ZONEMASK_INIT | ZONEMASK_WARNING | ZONEMASK_ERROR
- #define DEBUGZONES RETAILZONES | ZONEMASK_FUNCTION | ZONEMASK_MEMORY | ZONEMASK_INFO
- // Define FULL_CONTEXT to show full context in debug messages:
- // "Module: [full path to file name:line number] Function>"
- //#define FULL_CONTEXT
- /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
This is the part of the template you should change to match your driver or application. First, in line 30, change the __MODULE__ define to the name of your driver or application. If you are creating a flash driver you'd set this to "FLASH" for instance.
Next, in lines 33 to 48, you define the friendly names of the debug zones you want. These names will show up when you use the Debug Zones dialog or the "zo" command from Platform Builder. The predefined ones are always useful:
- Init
Use this zone to display debug messages during the initialization phase of your driver. I use this zone in the Init function of drivers.
- Function
This zone is used to display function entry and exit. Used by the FUNC_ENTRY and FUNC_EXIT macros (I'll discuss those later)
- Memory
This zone is used to display memory allocation and de-allocation. Having a separate zone for memory really helps in identifying memory leaks or memory related problems in your driver or application (besides CeDebugX)
- Info
This zone is used to display general information. I use this zone a lot through-out my code and enabling this zone usually means I get heaps of debug messages, but also heaps of information about the flow of my code
- Warning
Use this zone for non-fatal errors (also known as warnings ;o)
- Error
Use this zone for fatal errors; situations where you had to abort a certain operation
Another zone I use often is "IOCTL". If my driver handles custom IOCTLs I use that zone to display information when those IOCTLs are handled. If you want to add the IOCTL zone you'd change line 36 to:
- #define ZONE3_TEXT _T("IOCTL")
Lines 51 to 66 define the actual zone IDs. You use these defines as the first parameter of LOG_MSG (that I'll discuss later) to indicate the zone this message belongs to. After you added the IOCTL zone to the friendly names list, you'd change line 54 to:
- #define ZONE_IOCTL DEBUGZONE(3)
Lines 69 to 84 define the zone masks. You use these defines to indicate which zones should be enabled by default. After you added the IOCTL zone to the zone ID list, you'd change line 72 to:
- #define ZONEMASK_IOCTL ZONEMASK(3)
Line 86 and 87 define the default zones to use in a release build and a debug build. I enable zones INIT, WARNING and ERROR in a release build and add FUNCTION, MEMORY and INFO to debug builds. Note that in a "ship" build (on Windows CE, WINCESHIP=1) debug/retail messages are defined as "nothing" (so all the logging code is effectively removed from the final binary).
If you define FULL_CONTEXT (line 91) the LOGMSG macro will output the full path to the source code filename in front of the message, instead of just the module name. I don't enable this by default because it makes the debug output cluttered and less readable (but it can be very handy in some cases).
This concludes the part of DebugZones.h that you should change. The remainder of the code mostly deals with the differences between Desktop Windows and Windows CE:
- // Do NOT change any of the following definitions:
- /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
- LPCTSTR to_T(LPCSTR pStr);
- #ifdef UNDER_CE
- #ifndef DEBUG
- #include <windows.h>
- extern DBGPARAM dpCurSettings;
- #endif
- #else
- #include <windows.h>
- #include <tchar.h>
- #include <stdio.h>
- typedef struct _DBGPARAM {
- TCHAR lpszName[32]; // @field Name of module
- TCHAR rglpszZones[16][32]; // @field names of zones for first 16 bits
- ULONG ulZoneMask; // @field Current zone Mask
- } DBGPARAM, *LPDBGPARAM;
- #ifndef DEBUG
- #ifdef _DEBUG
- #define DEBUG
- #endif
- #endif
- extern DBGPARAM dpCurSettings;
- void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...);
- #define RETAILMSG(cond,printf_exp) /
- ((cond)?(NKDbgPrintf printf_exp),1:0)
- #define DEBUGZONE(n) (dpCurSettings.ulZoneMask & (0x00000001<<(n)))
- #define RETAILREGISTERZONES(hMod)
- #ifdef DEBUG
- #define DEBUGMSG(cond,printf_exp) /
- ((void)((cond)?(NKDbgPrintf printf_exp),1:0))
- #define DBGCHK(module,exp) /
- ((void)((exp)?1:( /
- NKDbgPrintf ( _T("%s: DEBUGCHK failed in file %s at line %d /r/n"), /
- module, _T(__FILE__) ,__LINE__ ), /
- DebugBreak(), /
- 0 /
- )))
- #define DEBUGCHK(exp) DBGCHK(dpCurSettings.lpszName, exp)
- #define DEBUGREGISTER(hMod)
- #endif //DEBUG
- #endif
- #ifndef __FUNCTION__
- #define __FUNCTION__ __FILE__
- #endif
- #ifdef UNICODE
- #define PREPT(x) L ## x
- #else
- #define PREPT(x) x
- #endif
- #define TOTSTR(x) PREPT(x)
- #define __TFUNCTION__ TOTSTR(__FUNCTION__)
- #define __TMODULE__ TOTSTR(__MODULE__)
- #define __TFILE__ TOTSTR(__FILE__)
- #define ZONEMASK(n) (0x00000001<<(n))
- #ifdef FULL_CONTEXT
- #define FUNC_ENTRY(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": +[") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("(") fmt _T(")+/r/n"), __LINE__, __VA_ARGS__))
- #define FUNC_EXIT(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": -[") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("(") fmt _T(")-/r/n"), __LINE__, __VA_ARGS__))
- #define LOGMSG(cond, fmt, ...) RETAILMSG(cond, (__TMODULE__ _T(": [") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("> ") fmt, __LINE__, __VA_ARGS__))
- #else
- #define FUNC_ENTRY(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": +") __TFUNCTION__ _T("(") fmt _T(")+/r/n"), __VA_ARGS__))
- #define FUNC_EXIT(fmt, ...) RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": -") __TFUNCTION__ _T("(") fmt _T(")-/r/n"), __VA_ARGS__))
- #define LOGMSG(cond, fmt, ...) RETAILMSG(cond, (__TMODULE__ _T(": ") __TFUNCTION__ _T("> ") fmt, __VA_ARGS__))
- #endif
- /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
The interesting part is from line 165 to 173 where I define three macros that allow you to log messages in a good format (in my humble opinion):
- FUNC_ENTRY
Usage:
FUNC_ENTRY(format_string [, arg1, arg2, ...]);
Examples:FUNC_ENTRY(_T("void"));
FUNC_ENTRY(_T("lpParameter = 0x%08X"), lpParameter);
Output:
(Note the '+' characters that indicate function entry)ModuleName: +FunctionName(void)+
ModuleName: +FunctionName(lpParameter = 0xA5A5A5A5)+ - FUNC_EXIT
Usage:
FUNC_EXIT(format_string [, arg1, arg2, ...]);
Examples:FUNC_EXIT(_T(""));
FUNC_EXIT(_T("return %s"), bRet ? _T("true") : _T("false"));
Output:
(Note the '-' characters that indicate function exit)ModuleName: -FunctionName()-
ModuleName: -FunctionName(return false)- - LOGMSG
Usage:
LOGMSG(ZONE_ID, format_string [, arg1, arg2, ...]);
Examples:LOGMSG(ZONE_INIT, _T("Started successfully!/r/n"));
LOGMSG(ZONE_ERROR, _T("ERROR: Invalid parameter (dwParam = %d)/r/n"), dwParam);
LOGMSG(ZONE_WARNING, _T("WARNING: Could not reach IP %s/r/n"), to_T(inet_ntoa(IPAddress)));
Output:ModuleName: FunctionName> Started successfully!
ModuleName: FunctionName> ERROR: Invalid parameter (dwParam = 1234)
ModuleName: FunctionName> WARNING: Could not reach IP 192.168.1.1
To use the Debug Zones template in your projects you simply add DebugZones.cpp and DebugZones.h to your solution and call RETAILREGISTERZONES(hMod) from your entry point function:
{
FUNC_ENTRY(_T("hInstance = 0x%08X, dwReason = 0x%08X"), hInstance, dwReason);
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
{
// Disable thread creation/deletion calls into this function
DisableThreadLibraryCalls((HMODULE)hInstance);
// Register with debug subsystem
RETAILREGISTERZONES((HMODULE)hInstance);
break;
}
case DLL_PROCESS_DETACH:
{
break;
}
default:
break;
}
FUNC_EXIT(_T("TRUE"));
return TRUE;
}
or in case of an application:
HINSTANCE hInstPrev,
LPWSTR pCmdLine,
int nCmdShow)
{
// Register with debug subsystem
RETAILREGISTERZONES((HMODULE)hInstance);
.
.
.
If you are using precompiled headers you have to set DebugZones.cpp to "Not Using Precompiled Headers" (in VS2008 right click DebugZones.cpp in your solution, click "Properties", then select "Precompiled Headers" under "Configuration Properties -> C/C++" and change the "Create/Use Precompiled Header" value). Now include "DebugZones.h" in any file you want to use debug zone logging.
The attached ZIP file contains the complete template.
I hope this template will prove as useful to you as it is to me!
- A template for debug messages
- Template for making a class
- a utility for compressing messages to be shared among processes
- A Scrum Process Template for TFS
- A Basic Template For Assembly Language Programs
- Nonconstant parameter for a template class
- Handlers for WM_ Messages
- Jinja2 example for generating a local file using a template
- Getting "Debug Info" for a Sharepoint error!
- debug information template:
- Why "Do not call CWnd::OnPaint() for painting messages" in a derived CWnd?
- When the normal window destruction messages are thrown for a loop
- A template class for binding C++ to Lua
- A template build.xml for running junit test by ant
- Introducing iOS Boilerplate A base template for iOS apps
- Eigen is a C++ template library for linear algebra
- utilities for printing out messages
- Watch Out for Malicious Messages
- 职场:选择职业 关键要看职业发展的后势
- 跑步统计
- 以后生个宝贝儿子,一定要这样打扮!帅哥是这样滴~·
- 开心
- 虚拟机网络连接模式 bridged, host-only, NAT
- A template for debug messages
- ubuntu 8.10 下 opera 10.10 无法调出 scim 输入中文
- 用正则表达式处理日志文件的小程序
- Command Line Build
- VC最常用操作程序20项列举
- 手机软件开发了解(一)
- 设置MFC窗口透明
- What to build when...
- MFC中消息机制