uLib  User mode C/C++ extended API library for Win32 programmers.
Debug.cpp
Go to the documentation of this file.
1 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 // Project: uLib - User mode library.
3 // Module: Debug and error handling support.
4 // Author: Copyright (c) Love Nystrom
5 // License: NNOSL (BSD descendant, see NNOSL.txt in the base directory).
6 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 
8 #include <uLib/Debug.h>
9 #include <uLib/StrFunc.h>
10 #include <uLib/UtilFunc.h>
11 
12 #undef USE_ULIB_DBGOUTPUT // Temp dbg squelch...
13 #define USE_ULIB_DBGOUTPUT 0 // DbgOutput fails to emit in rare corner cases ??
14 
15 #ifndef USE_ULIB_DBGOUTPUT // Use custom DbgOutput instead of OutputDebugString?
16  #ifdef HAVE_STRUCTURED_EH
17  #define USE_ULIB_DBGOUTPUT 1
18  #else
19  #define USE_ULIB_DBGOUTPUT 0 // __try/__except is required.
20  #endif
21 #endif
22 
23 #if !defined(HAVE_STRUCTURED_EH) || defined(_UNICODE)
24  // The custom DbgOutput function requires __try/__except.
25  // Unicode must use OutputDebugStringW. DbgOutput doesn't do WCHAR translation.
26  #undef USE_ULIB_DBGOUTPUT
27  #define USE_ULIB_DBGOUTPUT 0
28 #endif
29 
30 //==---------------------------------------------------------------------------
31 // SysErrorMsg gets the system defined error message for an Err code.
32 // If Err zero is passed in, Err is fetched with GetLastError().
33 // If a Buf is passed in, it is used to return the error string.
34 // If no Buf is given and Size is non-zero, Buf is allocated with new[]
35 // (so it can be disposed with deleteStr), and if Size is zero
36 // an internal static buffer is used.
37 //==---------------------------------------------------------------------------
38 
39 CSTR SysErrorMsg( DWORD Err, TSTR Buf, UINT Length )
40 {
41  static __thread_local TCHAR sBuf[ MAX_PATH ];
42 
43  if (!Err) Err = GetLastError();
44  if (!Buf)
45  {
46  if (Length) Buf = newStrBuf( Length );
47  else
48  {
49  Buf = sBuf;
50  Length = dimof(sBuf);
51  }
52  }
53  DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
54  DWORD len = FormatMessage( flags, 0, Err, 0, Buf, Length, 0 );
55  if (!len)
56  _stprintf_s( Buf, Length, _T("Error %lu (0x%08lX)"), HRESULT_CODE(Err), Err );
57  else
58  {
59  TSTR end = Buf+len-1; // Strip off trailing CRLF.
60  while( *end == CR || *end == LF ) *end-- = 0;
61  }
62  return Buf;
63 }
64 
65 // NtErrorMsg is experimental - Will the error translations make sense?
66 
67 CSTR NtErrorMsg( NTSTATUS Status, TSTR Buf, UINT Size )
68 {
69  return SysErrorMsg( LsaNtStatusToWinError( Status ), Buf, Size );
70 }
71 
72 // Provide a wrap for "RtlSetLastWin32ErrorAndNtStatusFromNtStatus" (urrk).
73 
74 bool SetLastErrorFromNtStatus( NTSTATUS Status )
75 {
76  static VOID (NTAPI *RtlSetLastErrorFromNtStatus)( NTSTATUS Status );
77  if (!RtlSetLastErrorFromNtStatus) // My abbreviated name..
78  {
79  HMODULE hMod = GetModuleHandleA( "NTDLL" );
80 
81  // IMNSHO the exported API name is "a sandwich short of a picnic". :/
82  // Please learn decent API naming, and write the details as header comments!
83 
84  static CCASTR S_Api = "RtlSetLastWin32ErrorAndNtStatusFromNtStatus"; // OMG
85  (FARPROC&) RtlSetLastErrorFromNtStatus = GetProcAddress( hMod, S_Api );
86  }
87  bool ok = (RtlSetLastErrorFromNtStatus != NULL);
88  if (ok) RtlSetLastErrorFromNtStatus( Status );
89  else SetLastError( LsaNtStatusToWinError( Status ));
90  return ok;
91 }
92 
93 
94 //==---------------------------------------------------------------------------
95 // DPrint outputs a debugger string if Level <= global trace level.
96 // Level zero messages are meant to always be emitted.
97 // To squelch *all* output, set dbg_Level = -1
98 //==---------------------------------------------------------------------------
99 
100 CHAR rt_Tag[] = "dbg_Lvl"; // 8 byte tag to locate dbg_Level in RAM at runtime.
101 // rt_Tag is an experiment to facilitate changing debug level at runtime.
102 // To do this, a debug "manager" would need to locate the tag in the loaded
103 // debugee, and modify the 4 byte integer (dbg_Level) just beyound it..
104 
106 
107 int SetDebugLevel( int Level )
108 {
109  int prevLevel = dbg_Level;
110  dbg_Level = Level;
111  return prevLevel;
112 }
113 
114 CSTR dbg_Name = _T("[uLib]");
115 HANDLE dbg_LogFile = NULL;
116 bool dbg_Flush = false;
117 
118 //==---------------------------------------------------------------------------
119 
120 #define _DP_MAXLEN_ 512 // DPrint buffer size (512 = kernel debugger's buffer)
121 
122 #if USE_ULIB_DBGOUTPUT
123  extern "C" bool DbgOutput( CSTR Str ); // Implemented below. Currently only ANSI text.
124 #else
125  #define DbgOutput(s) OutputDebugString(s)
126 #endif
127 
128 static volatile bool __inDPrint = false; // Reentrancy lock.
129 volatile ULONG nDPrintSkipped = 0; // Nr of skipped (due to re-entry) DPrint calls.
130 
131 // PONDER: Make DPrint immutable ANSI..? No reason to waste power on internal
132 // wide string translates since callers can dump wide strings anyway (with %S or %wZ).
133 
134 void __cdecl DPrint( int Level, CSTR Fmt, ... )
135 {
136  static TCHAR buf[_DP_MAXLEN_]; // Plain static, since we have reentrancy lock.
137 
138  TCHAR *pch;
139  DWORD cbWr, xCode;
140  va_list va;
141 
142  if (__inDPrint) // Prevent re-entry
143  InterlockedIncrement( (LONG*)&nDPrintSkipped ); // Absolute zero so far.
144  else
145  {
146  __inDPrint = true; //InterlockedIncrement( &__inDPrint );
147  if (Level <= dbg_Level)
148  {
149  pch = _tcsecpy( buf, dbg_Name );
150  switch( Level )
151  {
152  case DP_ALWAYS: *pch++ = SPACE; *pch = 0; break;
153  case DP_FATAL: pch = _tcsecpy( pch, _T(" FATAL: ") ); break;
154  case DP_SEVERE: pch = _tcsecpy( pch, _T(" SEVERE: ") ); break;
155  case DP_ERROR: pch = _tcsecpy( pch, _T(" ERROR: ") ); break;
156  case DP_WARNING: pch = _tcsecpy( pch, _T(" WARNING: ") ); break;
157  case DP_INFO: pch = _tcsecpy( pch, _T(" INFO: ") ); break;
158  case DP_DEBUG: pch = _tcsecpy( pch, _T(" DEBUG: ") ); break;
159  case DP_VERBOSE: pch = _tcsecpy( pch, _T(" VERBOSE: ") ); break;
160  case DP_EXTREME: pch = _tcsecpy( pch, _T(" EXTREME: ") ); break;
161  BEGIN_STRSAFE_OVERRIDE //#pragma warning( disable: 4996 )
162  default: pch += _stprintf( pch, _T(" LEVEL%i: "), Level );
163  END_STRSAFE_OVERRIDE //#pragma warning( default: 4996 )
164  //default: *pch++ = _T(' ');
165  }
166  int len = int( pch - buf );
167  int remain = dimof(buf) - len; // Circumvented in gcc (gcc has no vsprintf_s)
168 
169  va_start( va, Fmt );
170  #ifndef HAVE_STRUCTURED_EH // No SEH in GCC. ("Libify" ReactOS's SEH2/3 and use that?)
171  len += _vstprintf_s( pch, remain, Fmt, va );
172  #ifdef __GNUC__
173  UNUSED( remain );
174  #endif
175  #else
177  __try {
178  len += _vsntprintf( pch, remain, Fmt, va );
179  // Why is vsnprintf() deemed unsafe when it already has a runlength limit?
180  }
182  // Agh.. An exception in the debug printer is not a fun thing :/
183  // Show/tell and try to recover.
184  //*pch = 0; // Superfluous.. we're reusing buf...
185  xCode = GetExceptionCode();
186  _stprintf( buf,
187  _T("[uLib] SEVERE: [DPrint] Exception 0x%08X in _vsntprintf.")
188  _T(" Check your code!\n"), xCode
189  );
190  }
191  __end_except; // gcc kludge
193  #endif
194  va_end( va );
195 
196  DbgOutput( buf );
197 
198  if (dbg_LogFile)
199  {
200  PCHAR text;
201  #ifdef _UNICODE
202  static CHAR ansi[_DP_MAXLEN_];
203  WideCharToMultiByte( CP_ACP, 0, buf,-1, ansi,dimof(ansi), NULL, NULL );
204  text = ansi;
205  #else
206  text = buf;
207  #endif
208  WriteFile( dbg_LogFile, text, len, &cbWr, NULL );
209  if (dbg_Flush) FlushFileBuffers( dbg_LogFile );
210  }
211  }
212  __inDPrint = false; //InterlockedDecrement( &__inDPrint );
213  }
214  #ifdef __GNUC__
215  //UNUSED( nums );
216  UNUSED( xCode );
217  #endif
218 }
219 
220 HANDLE OpenLogFile( CSTR Name, bool Append )
221 {
222  if (!Name) Name = DefaultLogFileName( NULL );
223 
224  DWORD dispo = Append ? OPEN_ALWAYS : CREATE_ALWAYS;
225  DWORD flags = FILE_FLAG_SEQUENTIAL_SCAN;
226  DWORD share = FILE_SHARE_READ;
227 
228  HANDLE hFile = CheckHandle( CreateFile( Name, GENERIC_WRITE, share, NULL, dispo, flags, NULL ));
229  if (hFile)
230  {
231  FILETIME now = uLib::Now();
232  if (Append) SetFilePointer( hFile, 0,0, FILE_END );
233  WriteStr( hFile, _T("\n%s Logfile opened %s\n"), dbg_Name, FileTimeStr( &now ));
234  }
235 
236  return hFile;
237 }
238 
239 CSTR DefaultLogFileName( HMODULE hModule )
240 {
241  static TCHAR LogName[ MAX_PATH ];
242  int len = GetModuleFileName( hModule, LogName, dimof(LogName) );
243  UNUSED( len );
244 
245  TSTR pzDot = _tcsrchr( LogName, DOT );
246  if (!pzDot) pzDot = LogName + _tcslen( LogName ); // Set for append
247 
249  _tcscpy( pzDot, _T(".log") );
251 
252  return LogName;
253 }
254 
255 #if USE_ULIB_DBGOUTPUT
256 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257 // MEMO: Excerpt from very old Microsoft sample program (DBMON).
258 //
259 // Synopsis: DBMON notes 10/7/94
260 // Kent Forschmiedt (kentf), Microsoft Corporation
261 //
262 // The first DWORD of the shared memory buffer contains the process ID of the
263 // client that sent the debug string. DBMON will print the process ID if the
264 // previous line printed was from another process, or if the previous line was
265 // from the same process and ended in a newline.
266 //
267 // The shared memory buffer must be 4096 bytes. If it is larger, it won't do any
268 // good. If it is smaller, nothing terrible will happen, but strings larger than
269 // the buffer will not be delivered, and a complaining message will be sent to
270 // the kernel debugger by the client. Why is it this size? Because shared memory
271 // comes in page-sized chunks, so we might as well ask for as much as will be used
272 // on the machines with the smallest pages. On machines with larger pages, the
273 // remainder of the page will be wasted anyway.
274 //
275 // Event DBWIN_BUFFER_READY must be an auto-reset event, so that only one client
276 // thread will use the buffer at a time. Using a manual reset event with
277 // PulseEvent will not work.
278 //
279 // Event DBWIN_DATA_READY need not be auto-reset, but it is simpler that way.
280 //
281 // Allowing multiple copies of DBMON to run might be amusing, put not particularly
282 // useful. As I originally wrote it, the messages were distributed randomly among
283 // the DBMON instances or were garbled. I added a check for ERROR_ALREADY_EXISTS so
284 // it won't run twice. The worst you can do to the client is cause it to print a
285 // message on the kernel debugger, as above.
286 //
287 // If DBMON is broken and never calls SetEvent(AckEvent), it will cause a 10
288 // second delay in the client every time OutputDebugString is called, then the
289 // message will be printed on the kernel debugger.
290 //
291 // OutputDebugString will force synchronization of client threads when DBMON is
292 // running. Letting the kernel debugger catch the strings will also synchronize
293 // threads, but the timing effects may be different. Caveat emptor. / Kent F
294 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
295 
296 #include <uLib/UtilFunc.h>
297 
298 #define BUFFER_READY_EVT_NAME _T("DBWIN_BUFFER_READY")
299 #define DATA_READY_EVT_NAME _T("DBWIN_DATA_READY")
300 #define DBMUTEX_NAME _T("DBWinMutex")
301 #define SHARED_BUFFER_NAME _T("DBWIN_BUFFER")
302 #define SHARED_BUFFER_SIZE 4096
303 
304 #ifndef DBG_PRINTEXCEPTION_C
305 #define DBG_PRINTEXCEPTION_C 0x40010006L
306 #endif
307 
308 // PONDER: Restrict DbgOut to ANSI, and adapt callers
309 
310 static int __stdcall dbo_ExecFilter( DWORD xCode )
311 {
312  static const int Execute = EXCEPTION_EXECUTE_HANDLER;
313  static const int Search = EXCEPTION_CONTINUE_SEARCH;
314  return (xCode == DBG_PRINTEXCEPTION_C) ? Execute : Search;
315 }
316 
317 extern "C" bool DbgOutput( CSTR Str )
318 {
319  static const UINT MAX_BUFAVAIL = SHARED_BUFFER_SIZE - sizeof(DWORD) - 1;
320  static const UINT DBWIN_TIMEOUT = 10000;
321 
322  // Statics are implicitly initialized to zero by the compiler.
323 
324  static HANDLE hSharedFile; // = NULL;
325  static PBYTE pSharedMem; // = NULL;
326  static HANDLE hAckEvent; // = NULL;
327  static HANDLE hReadyEvent; // = NULL;
328  static HANDLE hDbgMutex; // = NULL;
329 
330  bool Sent = false;
331 
332  if ( !Str ) // Special IPC uninitialize command: Str == NULL.
333  {
334  hAckEvent = CloseHandleEx( hAckEvent );
335  if (pSharedMem) { UnmapViewOfFile( pSharedMem ); pSharedMem = NULL; }
336  hSharedFile = CloseHandleEx( hSharedFile );
337  hReadyEvent = CloseHandleEx( hReadyEvent );
338  goto dbgout_Exit;
339  }
340 
341  ULONG_PTR xArg[ 2 ];
342 
343  __try
344  {
345  // Raise a debugger exception that a usermode debugger can catch.
346  // If it doesn't, the kernel debugger (if active) will catch it...
347 
348  xArg[0] = 1 + _tcslen( Str );
349  xArg[1] = (ULONG_PTR) Str;
350  Sent = true; // Assume a debugger is present
351  RaiseException( DBG_PRINTEXCEPTION_C, 0, 2, xArg );
352  }
353  __except( dbo_ExecFilter( GetExceptionCode() ))
354  {
355  Sent = false; // Ugh: No debugger caught the exception...
356  }
357  __end_except;
358 
359  if (!Sent)
360  {
361  // No debugger caught the exception, so try sending it to a
362  // standard debug monitor, e.g Kent F's DBMON or Mark R's DbgView.
363  //
364  // Clean room analysis (using detours) of OutputDebugString revealed
365  // that it creates or opens, and destroys, at least two events each call,
366  // which seems inefficient, if not ill considered.
367  // This DbgOutput function was implemented such that it avoids
368  // that waste of time, doing the IPC setup only once in a process.
369  //
370  // If, for some reason, an app needs to reinitialize the IPC, it can call
371  // DbgOutput( NULL ), and the next call will redo the IPC setup.
372 
373  UINT nRemain;
374  CSTR pzOutput;
375 
376  // Look for DBMON or similar...
377 
378  if (!hDbgMutex)
379  {
380  if (!DefSec.lpSecurityDescriptor) // If caller never initialized a SDesc
381  {
382  // NOTE: This side effect has not been documented..
383  InitializeSecDesc( &DefSecDesc, NULL ); // Full access
384  DefSec.lpSecurityDescriptor = &DefSecDesc;
385  }
386  hDbgMutex = CreateMutex( &DefSec, FALSE, DBMUTEX_NAME );
387  }
388 
389  if (hDbgMutex)
390  {
391  if (!WaitFor( hDbgMutex, DBWIN_TIMEOUT ))
392  goto dbgout_Exit; // No DBMON, so bail out.
393 
394  if (!hSharedFile) // First call ?
395  {
396  // Setup interprocess communication.
397  // Note that it's the _debugger_ that must create the IPC objects.
398 
399  hSharedFile = OpenFileMapping( FILE_MAP_WRITE, FALSE, SHARED_BUFFER_NAME );
400  pSharedMem = hSharedFile
401  ? (PBYTE) MapViewOfFile( hSharedFile, FILE_MAP_WRITE, 0,0,0 )
402  : NULL;
403  hAckEvent = pSharedMem
404  ? OpenEvent( SYNCHRONIZE, FALSE, BUFFER_READY_EVT_NAME )
405  : NULL;
406  hReadyEvent = hAckEvent
407  ? OpenEvent( EVENT_MODIFY_STATE, FALSE, DATA_READY_EVT_NAME )
408  : NULL;
409  }
410  ReleaseMutex( hDbgMutex );
411  }
412 
413  if (!hReadyEvent) goto dbgout_Exit; // IPC not initialized
414 
415  pzOutput = Str;
416  nRemain = (UINT)_tcslen( pzOutput );
417  DWORD procId = GetCurrentProcessId();
418 
419  while( nRemain ) // Loop it out (in case we got us a whopper)...
420  {
421  if (WaitFor( hAckEvent, DBWIN_TIMEOUT ))
422  {
423  *PDWORD( pSharedMem ) = procId;
424 
425  UINT Used = (nRemain < MAX_BUFAVAIL) ? nRemain : MAX_BUFAVAIL;
426  PBYTE pzShared = (pSharedMem + sizeof(DWORD));
427  memcpy( pzShared, pzOutput, Used );
428  pzShared[ Used ] = 0;
429 
430  Sent = bool_cast( SetEvent( hReadyEvent ));
431  if (Sent)
432  {
433  pzOutput += Used;
434  nRemain -= Used;
435  }
436  }
437  }
438  }
439 dbgout_Exit:
440  return Sent;
441 }
442 #endif//USE_ULIB_DBGOUTPUT
443 // EOF
HANDLE CloseHandleEx(HANDLE H)
Definition: KernelUtil.cpp:80
unsigned long DWORD
Definition: Common.h:414
#define DOT
Definition: Common.h:1216
HANDLE CheckHandle(HANDLE Hnd)
Definition: KernelUtil.cpp:75
#define DP_SEVERE
Definition: Debug.h:81
#define CSTR
Definition: Common.h:329
CSTR FileTimeStr(FILETIME *FTime, CSTR Fmt=_T("%Y-%m-%d %H.%M.%S"))
Definition: StrFunc.cpp:1124
bool WaitFor(HANDLE hObj, DWORD msWait)
Definition: KernelUtil.cpp:95
#define DP_DEBUG
Definition: Debug.h:85
#define BEGIN_STRSAFE_OVERRIDE
Definition: StrFunc.h:28
#define TSTR
Definition: Common.h:328
unsigned char * PBYTE
Definition: Common.h:412
#define dimof(x)
Definition: Common.h:949
void __cdecl DPrint(int Level, CSTR Fmt,...)
Definition: Debug.cpp:134
TSTR newStrBuf(WORD nChar)
Definition: StrFunc.cpp:183
HANDLE OpenLogFile(CSTR Name, bool Append)
Definition: Debug.cpp:220
bool InitializeSecDesc(PSECURITY_DESCRIPTOR pSecDesc, PACL AccsList=NULL)
Definition: SecUtil.cpp:18
#define __except_execute
Definition: Common.h:647
int dbg_Level
Definition: Debug.cpp:105
#define _DP_MAXLEN_
Definition: Debug.cpp:120
volatile ULONG nDPrintSkipped
Definition: Debug.cpp:129
#define WriteStr
Definition: UtilFunc.h:2459
#define _tcsecpy
Definition: StrFunc.h:74
CSTR dbg_Name
Definition: Debug.cpp:114
CHAR rt_Tag[]
Definition: Debug.cpp:100
SECURITY_DESCRIPTOR DefSecDesc
Definition: Common.cpp:21
bool SetLastErrorFromNtStatus(NTSTATUS Status)
Definition: Debug.cpp:74
int SetDebugLevel(int Level)
Definition: Debug.cpp:107
CSTR SysErrorMsg(DWORD Err, TSTR Buf, UINT Length)
Definition: Debug.cpp:39
#define SPACE
Definition: Common.h:1219
#define DP_ALWAYS
Definition: Debug.h:79
SECURITY_ATTRIBUTES DefSec
Definition: Common.cpp:20
bool __forceinline bool_cast(BOOL B52)
Definition: Common.h:767
#define DP_FATAL
Definition: Debug.h:80
#define DP_ERROR
Definition: Debug.h:82
#define CR
Definition: Common.h:1230
CSTR NtErrorMsg(NTSTATUS Status, TSTR Buf, UINT Size)
Definition: Debug.cpp:67
#define UNUSED(x)
Definition: Common.h:970
Debug and error handling support.
#define DbgOutput(s)
Definition: Debug.cpp:125
FILETIME Now(eTimeType Domain)
Definition: KernelUtil.cpp:549
CSTR DefaultLogFileName(HMODULE hModule)
Definition: Debug.cpp:239
#define DP_INFO
Definition: Debug.h:84
bool dbg_Flush
Definition: Debug.cpp:116
#define DP_VERBOSE
Definition: Debug.h:86
#define DP_EXTREME
Definition: Debug.h:87
HANDLE dbg_LogFile
Definition: Debug.cpp:115
#define END_STRSAFE_OVERRIDE
Definition: StrFunc.h:29
#define DP_WARNING
Definition: Debug.h:83
const ACSTR CCASTR
Definition: Common.h:350
#define LF
Definition: Common.h:1231
unsigned long * PDWORD
Definition: Common.h:414