A template for debug messages

In all the drivers and applications we develop here at GuruCE, we always take the time to add plenty of logging because we know good logging will save us loads of time debugging our code (not that we ever have to debug code here of course... ;)).

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 we found ourselves doing the same thing over and over) we created a template we now use in all our 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 that 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.

We have released these templates under GPL for the greater good.

Let us explain the template by walking through it; we'll start with DebugZones.cpp (the line numbers reflect the actual line numbers in the template files):

  1. #include <windows.h>
  2. #include "DebugZones.h"
  4. // Do NOT change anything in this file.
  5. // See DebugZones.h for things you can change!
  6. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  7. extern "C" DBGPARAM dpCurSettings =
  8. {
  9.     __WMODULE__,
  10.     {
  14.         ZONE12_TEXT, ZONE13_TEXT, ZONE14_TEXT, ZONE15_TEXT
  15.     },
  16. #ifndef DEBUG // IN RETAIL...
  18. #else //IN DEBUG...
  19.     DEBUGZONES
  20. #endif
  21. };

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 we'll will be discussing later.

In this first part of DebugZones.cpp we simply define a global variable dpCurSettings of type DBGPARAM and fill the structure with defines from DebugZones.h. The __WMODULE__ 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).

  1. #ifndef UNDER_CE
  2.   #include <stdio.h>
  3.   void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...)
  4.   {
  5.       #define BUFFER_SIZE 1000
  6.       va_list argptr;
  7.       va_start(argptr, lpszFmt);
  8.       TCHAR szBuffer[BUFFER_SIZE];
  9.       _vstprintf_s(szBuffer, BUFFER_SIZE, lpszFmt, argptr);
  10.       OutputDebugString(szBuffer);
  11.       va_end(argptr);
  12.   }
  13. #endif

This code is only used when you target Desktop Windows. Only Windows CE defines NKDbgPrintf so for Desktop Windows we had to create our 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.

  1. // Helper function for runtime conversion of char* to _T
  2. LPCTSTR to_T(LPCSTR pStr)
  3. {
  4.     #define MAX_CALLS   4
  5.     #define MAX_MSG     500
  7.     static TCHAR buffer[MAX_CALLS][MAX_MSG];
  8.     static DWORD index = 0;
  9.     LPCTSTR pszRet = buffer[index];
  10.     size_t conv;
  12.     #ifdef UNICODE
  13.       mbstowcs_s(&conv, buffer[index], MAX_MSG, pStr, MAX_MSG);
  14.     #else //ANSI
  15.       strcpy_s(buffer[index], MAX_MSG, pStr);
  16.     #endif
  18.     index++;
  19.     if (MAX_CALLS==index)
  20.         index = 0;
  21.     return pszRet;
  22. }

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 a native format string \"%s\"\r\n"), _T("This is a native string format"));
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 a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"

But now see what happens when you compile the above code for MBS:

Display a native format string "This is a native string format"

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 a native format string \"%s\"\r\n"), _T("This is a native string format"));
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 a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"

I can hear 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:

NKDbgPrintf(_T("IPAddress \"%s\"\r\n"), to_T(inet_ntoa(ipAddress)));

Line 67 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* s1 = "String 1";
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));


String 1 String 2 String 3 String 4 String 1 String 2

Let's move on to the real core of the template; DebugZones.h:

  1. #pragma once
  2. #include <windows.h>
  4. // Change the following definitions:
  5. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  6. #define __MODULE__ "MyDriver"  // The module name
  8. // Define this to issue a DEBUHCHK(FALSE) when printing an ERRMSG and/or WRNMSG
  9. //#define DEBUGCHK_ON_ERRMSG
  10. //#define DEBUGCHK_ON_WRNMSG
  12. // Definitions for our debug zones                              
  13. #define ZONE0_TEXT          _T("Init")
  14. #define ZONE1_TEXT          _T("Function")
  15. #define ZONE2_TEXT          _T("Memory")
  16. #define ZONE3_TEXT          _T("")
  17. #define ZONE4_TEXT          _T("")
  18. #define ZONE5_TEXT          _T("")
  19. #define ZONE6_TEXT          _T("")
  20. #define ZONE7_TEXT          _T("")
  21. #define ZONE8_TEXT          _T("")
  22. #define ZONE9_TEXT          _T("")
  23. #define ZONE10_TEXT         _T("")
  24. #define ZONE11_TEXT         _T("")
  25. #define ZONE12_TEXT         _T("")
  26. #define ZONE13_TEXT         _T("Info")
  27. #define ZONE14_TEXT         _T("Warning")
  28. #define ZONE15_TEXT         _T("Error")
  30. // These macros can be used as condition flags for LOGMSG
  31. #define ZONE_INIT           DEBUGZONE(0)
  32. #define ZONE_FUNCTION       DEBUGZONE(1)
  33. #define ZONE_MEMORY         DEBUGZONE(2)
  34. #define ZONE_3              DEBUGZONE(3)
  35. #define ZONE_4              DEBUGZONE(4)
  36. #define ZONE_5              DEBUGZONE(5)
  37. #define ZONE_6              DEBUGZONE(6)
  38. #define ZONE_7              DEBUGZONE(7)
  39. #define ZONE_8              DEBUGZONE(8)
  40. #define ZONE_9              DEBUGZONE(9)
  41. #define ZONE_10             DEBUGZONE(10)
  42. #define ZONE_11             DEBUGZONE(11)
  43. #define ZONE_12             DEBUGZONE(12)
  44. #define ZONE_INFO           DEBUGZONE(13)
  45. #define ZONE_WARNING        DEBUGZONE(14)
  46. #define ZONE_ERROR          DEBUGZONE(15)
  48. // These macros can be used to indicate which zones to enable by default (see RETAILZONES and DEBUGZONES below)
  49. #define ZONEMASK_INIT       ZONEMASK(0)
  51. #define ZONEMASK_MEMORY     ZONEMASK(2)
  52. #define ZONEMASK_3          ZONEMASK(3)
  53. #define ZONEMASK_4          ZONEMASK(4)
  54. #define ZONEMASK_5          ZONEMASK(5)
  55. #define ZONEMASK_6          ZONEMASK(6)
  56. #define ZONEMASK_7          ZONEMASK(7)
  57. #define ZONEMASK_8          ZONEMASK(8)
  58. #define ZONEMASK_9          ZONEMASK(9)
  59. #define ZONEMASK_10         ZONEMASK(10)
  60. #define ZONEMASK_11         ZONEMASK(11)
  61. #define ZONEMASK_12         ZONEMASK(12)
  62. #define ZONEMASK_INFO       ZONEMASK(13)
  63. #define ZONEMASK_WARNING    ZONEMASK(14)
  64. #define ZONEMASK_ERROR      ZONEMASK(15)
  69. // Define FULL_CONTEXT to show full context in debug messages:
  70. // "Module: [full path to file name:line number] Function>"
  71. //#define FULL_CONTEXT
  72. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

This is the part of the template you should change to match your driver or application. First, in line 33, 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.

Uncomment line 36 and/or 37 to generate a DEBUGCHK whenever you use ERRMSG and/or WRNMSG. You'd usually only uncomment these lines when you are in the development stage (when you're still actively debugging your code).

Next, in lines 40 to 55, 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 43 to:

  1. #define ZONE3_TEXT          _T("IOCTL")

Lines 58 to 73 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 61 to:

  1. #define ZONE_IOCTL          DEBUGZONE(3)

Lines 76 to 91 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 79 to:

  1. #define ZONEMASK_IOCTL      ZONEMASK(3)

Line 93 and 94 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 98) the LOGMSG macro will output the full path to the source code filename in front of the message, instead of just the module name. We 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:

  1. // Do NOT change any of the following definitions:
  2. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  3. LPCTSTR to_T(LPCSTR pStr);
  5. #ifdef UNDER_CE
  6.   #ifndef DEBUG
  7.     #ifdef __cplusplus
  8.       extern "C" DBGPARAM dpCurSettings;
  9.     #else //!__cplusplus
  10.       extern DBGPARAM dpCurSettings;
  11.     #endif __cplusplus
  12.   #endif DEBUG
  13. #else // !UNDER_CE
  14.     #include <tchar.h>
  15.     #include <stdio.h>
  17.     typedef struct _DBGPARAM {
  18.             TCHAR       lpszName[32];           // @field Name of module
  19.             TCHAR   rglpszZones[16][32];    // @field names of zones for first 16 bits
  20.             ULONG   ulZoneMask;             // @field Current zone Mask
  23.     #ifndef DEBUG
  24.       #ifdef _DEBUG
  25.         #define DEBUG
  26.       #endif _DEBUG
  27.     #endif DEBUG
  29.     extern DBGPARAM dpCurSettings;
  30.     void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...);
  32.     #define RETAILMSG(cond,printf_exp)   \
  33.        ((cond)?(NKDbgPrintf printf_exp),1:0)
  35.     #define DEBUGZONE(n)  (dpCurSettings.ulZoneMask & (0x00000001<<(n)))
  37.     #define RETAILREGISTERZONES(hMod)
  39.     #ifdef DEBUG
  40.         #define DEBUGMSG(cond,printf_exp)   \
  41.            ((void)((cond)?(NKDbgPrintf printf_exp),1:0))
  43.         #define DBGCHK(module,exp) \
  44.            ((void)((exp)?1:(          \
  45.                NKDbgPrintf ( _T("%s: DEBUGCHK failed in file %s at line %d \r\n"), \
  46.                          module, _T(__FILE__) ,__LINE__ ),    \
  47.                DebugBreak(), \
  48.                    0  \
  49.            )))
  51.         #define DEBUGCHK(exp) DBGCHK(dpCurSettings.lpszName, exp)
  52.         #define DEBUGREGISTER(hMod)
  53.     #endif DEBUG
  54. #endif
  56. #ifndef __FUNCTION__
  57. #define __FUNCTION__ __FILE__
  58. #endif __FUNCTION__
  60. #ifdef UNICODE
  61.   #define PREPW(x) L ## x
  62. #else //!UNICODE
  63.   #define PREPW(x) x
  64. #endif UNICODE
  66. #define TOWSTR(x) PREPW(x)
  67. #ifndef __WFUNCTION__
  68. #define __WFUNCTION__ TOWSTR(__FUNCTION__)
  69. #endif __WFUNCTION__
  70. #define __WMODULE__ TOWSTR(__MODULE__)
  71. #ifndef __WFILE__
  72. #define __WFILE__ TOWSTR(__FILE__)
  73. #endif __WFILE__
  75. #define ZONEMASK(n) (0x00000001<<(n))
  77. #ifdef FULL_CONTEXT
  78.   #define FUNC_ENTRY(fmt, ...)    RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": +[") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("(") fmt _T(")+\r\n"), __LINE__, __VA_ARGS__))
  79.   #define FUNC_EXIT(fmt, ...)     RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": -[") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("(") fmt _T(")-\r\n"), __LINE__, __VA_ARGS__))
  80.   #define LOGMSG(cond, fmt, ...)  RETAILMSG(cond, (__WMODULE__ _T(":  [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ") fmt, __LINE__, __VA_ARGS__))
  81.   #ifdef DEBUGCHK_ON_ERRMSG
  82.     #define WRNMSG(fmt, ...)        {RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(":  [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> WARNING: ") fmt, __LINE__, __VA_ARGS__)); DEBUGCHK(FALSE);}
  83.     #define ERRMSG(fmt, ...)        {RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(":  [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ERROR: ") fmt, __LINE__, __VA_ARGS__)); DEBUGCHK(FALSE);}
  84.   #else // !DEBUGCHK_ON_ERRMSG
  85.     #define WRNMSG(fmt, ...)        RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(":  [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> WARNING: ") fmt, __LINE__, __VA_ARGS__))
  86.     #define ERRMSG(fmt, ...)        RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(":  [") __WFILE__ _T(":%d] ") __WFUNCTION__ _T("> ERROR: ") fmt, __LINE__, __VA_ARGS__))
  87.   #endif
  88. #else
  89.   #define FUNC_ENTRY(fmt, ...)    RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": +") __WFUNCTION__ _T("(") fmt _T(")+\r\n"), __VA_ARGS__))
  90.   #define FUNC_EXIT(fmt, ...)     RETAILMSG(ZONE_FUNCTION, (__WMODULE__ _T(": -") __WFUNCTION__ _T("(") fmt _T(")-\r\n"), __VA_ARGS__))
  91.   #define LOGMSG(cond, fmt, ...)  RETAILMSG(cond, (__WMODULE__ _T(":  ") __WFUNCTION__ _T("> ") fmt, __VA_ARGS__))
  92.   #ifdef DEBUGCHK_ON_ERRMSG
  93.     #define WRNMSG(fmt, ...)        {RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(":  ") __WFUNCTION__ _T("> WARNING: ") fmt, __VA_ARGS__)); DEBUGCHK(FALSE);}
  94.     #define ERRMSG(fmt, ...)        {RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(":  ") __WFUNCTION__ _T("> ERROR: ") fmt, __VA_ARGS__)); DEBUGCHK(FALSE);}
  95.   #else // !DEBUGCHK_ON_ERRMSG
  96.     #define WRNMSG(fmt, ...)        RETAILMSG(ZONE_WARNING, (__WMODULE__ _T(":  ") __WFUNCTION__ _T("> WARNING: ") fmt, __VA_ARGS__))
  97.     #define ERRMSG(fmt, ...)        RETAILMSG(ZONE_ERROR, (__WMODULE__ _T(":  ") __WFUNCTION__ _T("> ERROR: ") fmt, __VA_ARGS__))
  98.   #endif
  99. #endif
  100. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

The interesting part is from line 190 to 199 where we define a couple of macros that allow you to log messages in a good and consistent format:

  • Usage:

    FUNC_ENTRY(format_string [, arg1, arg2, ...]);


    FUNC_ENTRY(_T("lpParameter = 0x%08X"), lpParameter);

    (Note the '+' characters that indicate function entry)

    ModuleName: +FunctionName(void)+
    ModuleName: +FunctionName(lpParameter = 0xA5A5A5A5)+
  • Usage:

    FUNC_EXIT(format_string [, arg1, arg2, ...]);


    FUNC_EXIT(_T("return %s"), bRet ? _T("true") : _T("false"));

    (Note the '-' characters that indicate function exit)

    ModuleName: -FunctionName()-
    ModuleName: -FunctionName(return false)-
  • Usage:

    LOGMSG(ZONE_ID, format_string [, arg1, arg2, ...]);


    LOGMSG(ZONE_INIT, _T("Started successfully!\r\n"));
    LOGMSG(ZONE_INFO, _T("dwID = %d\r\n"), dwID);


    ModuleName:  FunctionName> Started successfully!
    ModuleName:  FunctionName> dwID = 1234
  • Examples:

    ERRMSG(_T("Invalid parameter (dwParam = %d)\r\n"), dwParam);


    ModuleName:  FunctionName> ERROR: Invalid parameter (dwParam = 1234)
  • Examples:

    WRNMSG(_T("Could not reach IP %s\r\n"), to_T(inet_ntoa(IPAddress)));


    ModuleName:  FunctionName> WARNING: Could not reach IP

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:

BOOL WINAPI DriverEntry(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved)
    FUNC_ENTRY(_T("hInstance = 0x%08X, dwReason = 0x%08X"), hInstance, dwReason);

        // Disable thread creation/deletion calls into this function
        // Register with debug subsystem

    return TRUE;

or in case of an application:

int WINAPI WinMain (HINSTANCE hInstance,
                    HINSTANCE hInstPrev,
                    LPWSTR pCmdLine,
                    int nCmdShow)
    // Register with debug subsystem

Note that the parameter to RETAIL/DEBUGREGISTERZONES MUST be NULL if you want to register debug zones from an application!

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 us!

Package icon DebugZones.zip3.34 KB


Hi Michel,

Thanks for sharing useful info and code through your blog.

I have a very basic doubt though. Where are these messages output?

In my case, KITL is not included in my OS Image, so I am not connected to the PLatform builder.

I have been using printfs till now, and have routed them out on a TELNET port. Can I do this using RETAILMSG as well? If yes, how. What if later I want these messages to be written to a file in the device that I can copy and bring home to debug?

It all depends on the configuration of your BSP and whether or not you created the kernel with the WINCESHIP flag on or off. If WINCESHIP is set it means all RETAILMSG and DEBUGMSG messages are stubbed out (so no code). Otherwise, without KITL, it usually gets routed to the debug port. Which one again depends on your BSP. Take a look at OEMInitDebug (http://msdn.microsoft.com/en-us/library/ee478539.aspx) and OEMInitDebugSerial (http://msdn.microsoft.com/en-us/library/ee478662.aspx). You can route debug messages anywhere by rewriting the code in OEMWriteDebugByte (http://msdn.microsoft.com/en-us/library/ee478505.aspx)