1 /*
2  * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 /**
17  * @file picodbg.c
18  *
19  * Provides functions and macros to debug the Pico system and to trace
20  * the execution of its code.
21  *
22  * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
23  * All rights reserved.
24  *
25  * History:
26  * - 2009-04-20 -- initial version
27  */
28 
29 #ifdef __cplusplus
30 extern "C" {
31 #endif
32 #if 0
33 }
34 #endif
35 
36 
37 #if defined(PICO_DEBUG)
38 
39 /* Two variants of colored console output are implemented:
40    COLOR_MODE_WINDOWS
41       uses the Windows API function SetConsoleTextAttribute
42    COLOR_MODE_ANSI
43       uses ANSI escape codes */
44 #if defined(_WIN32)
45 #define COLOR_MODE_WINDOWS
46 #else
47 #define COLOR_MODE_ANSI
48 #endif
49 
50 
51 #include <stdio.h>
52 #include <stdlib.h>
53 
54 #include <stdarg.h>
55 #include <string.h>
56 
57 #include "picodbg.h"
58 
59 
60 /* Maximum length of a formatted tracing message */
61 #define MAX_MESSAGE_LEN         999
62 
63 /* Maximum length of contextual information */
64 #define MAX_CONTEXT_LEN         499
65 
66 /* Maximum length of filename filter */
67 #define MAX_FILTERFN_LEN         16
68 
69 /* Delimiter used in debug messages */
70 #define MSG_DELIM               "|"
71 
72 /* Standard output file for debug messages */
73 #define STDDBG                  stdout /* or stderr */
74 
75 /* Default setup */
76 #define PICODBG_DEFAULT_LEVEL   PICODBG_LOG_LEVEL_WARN
77 #define PICODBG_DEFAULT_FILTERFN   ""
78 #define PICODBG_DEFAULT_FORMAT  \
79     (PICODBG_SHOW_LEVEL | PICODBG_SHOW_SRCNAME | PICODBG_SHOW_FUNCTION)
80 #define PICODBG_DEFAULT_COLOR   1
81 
82 
83 /* Current log level */
84 static int logLevel = PICODBG_DEFAULT_LEVEL;
85 
86 /* Current log filter (filename) */
87 static char logFilterFN[MAX_FILTERFN_LEN + 1];
88 
89 /* Current log file or NULL if no log file is set */
90 static FILE *logFile = NULL;
91 
92 /* Current output format */
93 static int logFormat = PICODBG_DEFAULT_FORMAT;
94 
95 /* Color mode for console output (0 : disable colors, != 0 : enable colors */
96 static int optColor = 0;
97 
98 /* Buffer for context information */
99 static char ctxbuf[MAX_CONTEXT_LEN + 1];
100 
101 /* Buffer to format tracing messages */
102 static char msgbuf[MAX_MESSAGE_LEN + 1];
103 
104 
105 /* *** Support for colored text output to console *****/
106 
107 
108 /* Console text colors */
109 enum color_t {
110     /* order matches Windows color codes */
111     ColorBlack,
112     ColorBlue,
113     ColorGreen,
114     ColorCyan,
115     ColorRed,
116     ColorPurple,
117     ColorBrown,
118     ColorLightGray,
119     ColorDarkGray,
120     ColorLightBlue,
121     ColorLightGreen,
122     ColorLightCyan,
123     ColorLightRed,
124     ColorLightPurple,
125     ColorYellow,
126     ColorWhite
127 };
128 
129 
picodbg_getLevelColor(int level)130 static enum color_t picodbg_getLevelColor(int level)
131 {
132     switch (level) {
133         case PICODBG_LOG_LEVEL_ERROR: return ColorLightRed;
134         case PICODBG_LOG_LEVEL_WARN : return ColorYellow;
135         case PICODBG_LOG_LEVEL_INFO : return ColorGreen;
136         case PICODBG_LOG_LEVEL_DEBUG: return ColorLightGray;
137         case PICODBG_LOG_LEVEL_TRACE: return ColorDarkGray;
138     }
139     return ColorWhite;
140 }
141 
142 
143 #if defined(COLOR_MODE_WINDOWS)
144 
145 #define WIN32_LEAN_AND_MEAN
146 #include <windows.h>
147 
picodbg_setTextAttr(FILE * stream,int attr)148 static int picodbg_setTextAttr(FILE *stream, int attr)
149 {
150     HANDLE hConsole;
151 
152     if (stream == stdout) {
153         hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
154     } else if (stream == stderr) {
155         hConsole = GetStdHandle(STD_ERROR_HANDLE);
156     } else {
157         hConsole = INVALID_HANDLE_VALUE;
158     }
159 
160     if (hConsole != INVALID_HANDLE_VALUE) {
161         /* do nothing if console output is redirected to a file */
162         if (GetFileType(hConsole) == FILE_TYPE_CHAR) {
163             CONSOLE_SCREEN_BUFFER_INFO csbi;
164             GetConsoleScreenBufferInfo(hConsole, &csbi);
165             SetConsoleTextAttribute(hConsole, (WORD) attr);
166             return (int) csbi.wAttributes;
167         }
168     }
169 
170     return 0;
171 }
172 
173 #elif defined(COLOR_MODE_ANSI)
174 
picodbg_setTextAttr(FILE * stream,int attr)175 static int picodbg_setTextAttr(FILE *stream, int attr)
176 {
177     const char *c = "";
178 
179     if (attr == -1) {
180         c = "0";
181     } else switch (attr) {
182         case ColorBlack:       c = "0;30"; break;
183         case ColorRed:         c = "0;31"; break;
184         case ColorGreen:       c = "0;32"; break;
185         case ColorBrown:       c = "0;33"; break;
186         case ColorBlue:        c = "0;34"; break;
187         case ColorPurple:      c = "0;35"; break;
188         case ColorCyan:        c = "0;36"; break;
189         case ColorLightGray:   c = "0;37"; break;
190         case ColorDarkGray:    c = "1;30"; break;
191         case ColorLightRed:    c = "1;31"; break;
192         case ColorLightGreen:  c = "1;32"; break;
193         case ColorYellow:      c = "1;33"; break;
194         case ColorLightBlue:   c = "1;34"; break;
195         case ColorLightPurple: c = "1;35"; break;
196         case ColorLightCyan:   c = "1;36"; break;
197         case ColorWhite:       c = "1;37"; break;
198     }
199 
200     fprintf(stream, "\x1b[%sm", c);
201     return -1;
202 }
203 
204 #else
205 
picodbg_setTextAttr(FILE * stream,int attr)206 static int picodbg_setTextAttr(FILE *stream, int attr)
207 {
208     /* avoid 'unreferenced formal parameter' */
209     (void) stream;
210     (void) attr;
211     return 0;
212 }
213 
214 #endif
215 
216 
217 /* *** Auxiliary routines *****/
218 
219 
picodbg_fileTitle(const char * file)220 static const char *picodbg_fileTitle(const char *file)
221 {
222     const char *name = file, *str = file;
223 
224     /* try to extract file name without path in a platform independent
225        way, i.e., skip all chars preceding path separator chars like
226        '/' (Unix, MacOSX), '\' (Windows, DOS), and ':' (MacOS9) */
227     while (*str) {
228         if ((*str == '\\') || (*str == '/') || (*str == ':')) {
229             name = str + 1;
230         }
231         str++;
232     }
233 
234     return name;
235 }
236 
237 
picodbg_logToStream(int level,int donewline,const char * context,const char * msg)238 static void picodbg_logToStream(int level, int donewline,
239                                 const char *context, const char *msg)
240 {
241     int oldAttr = 0;
242 
243     if (optColor) {
244         oldAttr = picodbg_setTextAttr(STDDBG, picodbg_getLevelColor(level));
245     }
246 
247     fprintf(STDDBG, "%s%s", context, msg);
248     if (donewline) fprintf(STDDBG, "\n");
249     if (logFile != NULL) {
250         fprintf(logFile, "%s%s", context, msg);
251         if (donewline) fprintf(logFile, "\n");
252     }
253 
254     if (optColor) {
255         picodbg_setTextAttr(STDDBG, oldAttr);
256     }
257 }
258 
259 
260 /* *** Exported routines *****/
261 
262 
picodbg_initialize(int level)263 void picodbg_initialize(int level)
264 {
265     logLevel  = level;
266     strcpy(logFilterFN, PICODBG_DEFAULT_FILTERFN);
267     logFile   = NULL;
268     logFormat = PICODBG_DEFAULT_FORMAT;
269     optColor  = PICODBG_DEFAULT_COLOR;
270     PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
271 }
272 
273 
picodbg_terminate()274 void picodbg_terminate()
275 {
276     if (logFile != NULL) {
277         fclose(logFile);
278     }
279 
280     logLevel = 0;
281     logFile  = NULL;
282 }
283 
284 
picodbg_setLogLevel(int level)285 void picodbg_setLogLevel(int level)
286 {
287     PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
288     logLevel = level;
289 }
290 
291 
picodbg_setLogFilterFN(const char * name)292 void picodbg_setLogFilterFN(const char *name)
293 {
294     strcpy(logFilterFN, name);
295 }
296 
297 
picodbg_setLogFile(const char * name)298 void picodbg_setLogFile(const char *name)
299 {
300     if (logFile != NULL) {
301         fclose(logFile);
302     }
303 
304     if ((name != NULL) && (strlen(name) > 0)) {
305         logFile = fopen(name, "wt");
306     } else {
307         logFile = NULL;
308     }
309 }
310 
311 
picodbg_enableColors(int flag)312 void picodbg_enableColors(int flag)
313 {
314     optColor = (flag != 0);
315 }
316 
317 
picodbg_setOutputFormat(unsigned int format)318 void picodbg_setOutputFormat(unsigned int format)
319 {
320     logFormat = format;
321 }
322 
323 
picodbg_varargs(const char * format,...)324 const char *picodbg_varargs(const char *format, ...)
325 {
326     int len;
327 
328     va_list argptr;
329     va_start(argptr, format);
330 
331     len = vsprintf(msgbuf, format, argptr);
332     PICODBG_ASSERT_RANGE(len, 0, MAX_MESSAGE_LEN);
333 
334     return msgbuf;
335 }
336 
337 
picodbg_log(int level,int donewline,const char * file,int line,const char * func,const char * msg)338 void picodbg_log(int level, int donewline, const char *file, int line,
339                  const char *func, const char *msg)
340 {
341     char cb[MAX_CONTEXT_LEN + 1];
342 
343     PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
344 
345     if ((level <= logLevel) &&
346         ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
347         /* compose output format string */
348         strcpy(ctxbuf, "*** ");
349         if (logFormat & PICODBG_SHOW_LEVEL) {
350             switch (level) {
351                 case PICODBG_LOG_LEVEL_ERROR:
352                     strcat(ctxbuf, "error" MSG_DELIM);
353                     break;
354                 case PICODBG_LOG_LEVEL_WARN:
355                     strcat(ctxbuf, "warn " MSG_DELIM);
356                     break;
357                 case PICODBG_LOG_LEVEL_INFO:
358                     strcat(ctxbuf, "info " MSG_DELIM);
359                     break;
360                 case PICODBG_LOG_LEVEL_DEBUG:
361                     strcat(ctxbuf, "debug" MSG_DELIM);
362                     break;
363                 case PICODBG_LOG_LEVEL_TRACE:
364                     strcat(ctxbuf, "trace" MSG_DELIM);
365                     break;
366                 default:
367                     break;
368             }
369         }
370         if (logFormat & PICODBG_SHOW_DATE) {
371             /* nyi */
372         }
373         if (logFormat & PICODBG_SHOW_TIME) {
374             /* nyi */
375         }
376         if (logFormat & PICODBG_SHOW_SRCNAME) {
377             sprintf(cb, "%-10s", picodbg_fileTitle(file));
378             strcat(ctxbuf, cb);
379             if (logFormat & PICODBG_SHOW_SRCLINE) {
380                 sprintf(cb, "(%d)", line);
381                 strcat(ctxbuf, cb);
382             }
383             strcat(ctxbuf, MSG_DELIM);
384         }
385         if (logFormat & PICODBG_SHOW_FUNCTION) {
386             if (strlen(func) > 0) {
387                 sprintf(cb, "%-18s", func);
388                 strcat(ctxbuf, cb);
389                 strcat(ctxbuf, MSG_DELIM);
390             }
391         }
392 
393         picodbg_logToStream(level, donewline, ctxbuf, msg);
394     }
395 }
396 
397 
picodbg_log_msg(int level,const char * file,const char * msg)398 void picodbg_log_msg(int level, const char *file, const char *msg)
399 {
400     PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
401 
402     if ((level <= logLevel) &&
403         ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
404         picodbg_logToStream(level, 0, "", msg);
405     }
406 }
407 
408 
picodbg_assert(const char * file,int line,const char * func,const char * expr)409 void picodbg_assert(const char *file, int line, const char *func, const char *expr)
410 {
411     if (strlen(func) > 0) {
412         fprintf(STDDBG, "assertion failed: %s, file %s, function %s, line %d",
413             expr, picodbg_fileTitle(file), func, line);
414     } else {
415         fprintf(STDDBG, "assertion failed: %s, file %s, line %d",
416             expr, picodbg_fileTitle(file), line);
417     }
418     picodbg_terminate();
419     abort();
420 }
421 
422 
423 #else
424 
425 /* To prevent warning about "translation unit is empty" when
426    diagnostic output is disabled. */
427 static void picodbg_dummy(void) {
428     picodbg_dummy();
429 }
430 
431 #endif /* defined(PICO_DEBUG) */
432 
433 #ifdef __cplusplus
434 }
435 #endif
436 
437 
438 /* end */
439