blob: 8fa753bb15a6b4924f9599c3ada7474704eb002d [file] [log] [blame]
/*
* Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file picodbg.c
*
* Provides functions and macros to debug the Pico system and to trace
* the execution of its code.
*
* Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
* All rights reserved.
*
* History:
* - 2009-04-20 -- initial version
*/
#ifdef __cplusplus
extern "C" {
#endif
#if 0
}
#endif
#if defined(PICO_DEBUG)
/* Two variants of colored console output are implemented:
COLOR_MODE_WINDOWS
uses the Windows API function SetConsoleTextAttribute
COLOR_MODE_ANSI
uses ANSI escape codes */
#if defined(_WIN32)
#define COLOR_MODE_WINDOWS
#else
#define COLOR_MODE_ANSI
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "picodbg.h"
/* Maximum length of a formatted tracing message */
#define MAX_MESSAGE_LEN 999
/* Maximum length of contextual information */
#define MAX_CONTEXT_LEN 499
/* Maximum length of filename filter */
#define MAX_FILTERFN_LEN 16
/* Delimiter used in debug messages */
#define MSG_DELIM "|"
/* Standard output file for debug messages */
#define STDDBG stdout /* or stderr */
/* Default setup */
#define PICODBG_DEFAULT_LEVEL PICODBG_LOG_LEVEL_WARN
#define PICODBG_DEFAULT_FILTERFN ""
#define PICODBG_DEFAULT_FORMAT \
(PICODBG_SHOW_LEVEL | PICODBG_SHOW_SRCNAME | PICODBG_SHOW_FUNCTION)
#define PICODBG_DEFAULT_COLOR 1
/* Current log level */
static int logLevel = PICODBG_DEFAULT_LEVEL;
/* Current log filter (filename) */
static char logFilterFN[MAX_FILTERFN_LEN + 1];
/* Current log file or NULL if no log file is set */
static FILE *logFile = NULL;
/* Current output format */
static int logFormat = PICODBG_DEFAULT_FORMAT;
/* Color mode for console output (0 : disable colors, != 0 : enable colors */
static int optColor = 0;
/* Buffer for context information */
static char ctxbuf[MAX_CONTEXT_LEN + 1];
/* Buffer to format tracing messages */
static char msgbuf[MAX_MESSAGE_LEN + 1];
/* *** Support for colored text output to console *****/
/* Console text colors */
enum color_t {
/* order matches Windows color codes */
ColorBlack,
ColorBlue,
ColorGreen,
ColorCyan,
ColorRed,
ColorPurple,
ColorBrown,
ColorLightGray,
ColorDarkGray,
ColorLightBlue,
ColorLightGreen,
ColorLightCyan,
ColorLightRed,
ColorLightPurple,
ColorYellow,
ColorWhite
};
static enum color_t picodbg_getLevelColor(int level)
{
switch (level) {
case PICODBG_LOG_LEVEL_ERROR: return ColorLightRed;
case PICODBG_LOG_LEVEL_WARN : return ColorYellow;
case PICODBG_LOG_LEVEL_INFO : return ColorGreen;
case PICODBG_LOG_LEVEL_DEBUG: return ColorLightGray;
case PICODBG_LOG_LEVEL_TRACE: return ColorDarkGray;
}
return ColorWhite;
}
#if defined(COLOR_MODE_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
static int picodbg_setTextAttr(FILE *stream, int attr)
{
HANDLE hConsole;
if (stream == stdout) {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
} else if (stream == stderr) {
hConsole = GetStdHandle(STD_ERROR_HANDLE);
} else {
hConsole = INVALID_HANDLE_VALUE;
}
if (hConsole != INVALID_HANDLE_VALUE) {
/* do nothing if console output is redirected to a file */
if (GetFileType(hConsole) == FILE_TYPE_CHAR) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
SetConsoleTextAttribute(hConsole, (WORD) attr);
return (int) csbi.wAttributes;
}
}
return 0;
}
#elif defined(COLOR_MODE_ANSI)
static int picodbg_setTextAttr(FILE *stream, int attr)
{
const char *c = "";
if (attr == -1) {
c = "0";
} else switch (attr) {
case ColorBlack: c = "0;30"; break;
case ColorRed: c = "0;31"; break;
case ColorGreen: c = "0;32"; break;
case ColorBrown: c = "0;33"; break;
case ColorBlue: c = "0;34"; break;
case ColorPurple: c = "0;35"; break;
case ColorCyan: c = "0;36"; break;
case ColorLightGray: c = "0;37"; break;
case ColorDarkGray: c = "1;30"; break;
case ColorLightRed: c = "1;31"; break;
case ColorLightGreen: c = "1;32"; break;
case ColorYellow: c = "1;33"; break;
case ColorLightBlue: c = "1;34"; break;
case ColorLightPurple: c = "1;35"; break;
case ColorLightCyan: c = "1;36"; break;
case ColorWhite: c = "1;37"; break;
}
fprintf(stream, "\x1b[%sm", c);
return -1;
}
#else
static int picodbg_setTextAttr(FILE *stream, int attr)
{
/* avoid 'unreferenced formal parameter' */
(void) stream;
(void) attr;
return 0;
}
#endif
/* *** Auxiliary routines *****/
static const char *picodbg_fileTitle(const char *file)
{
const char *name = file, *str = file;
/* try to extract file name without path in a platform independent
way, i.e., skip all chars preceding path separator chars like
'/' (Unix, MacOSX), '\' (Windows, DOS), and ':' (MacOS9) */
while (*str) {
if ((*str == '\\') || (*str == '/') || (*str == ':')) {
name = str + 1;
}
str++;
}
return name;
}
static void picodbg_logToStream(int level, int donewline,
const char *context, const char *msg)
{
int oldAttr = 0;
if (optColor) {
oldAttr = picodbg_setTextAttr(STDDBG, picodbg_getLevelColor(level));
}
fprintf(STDDBG, "%s%s", context, msg);
if (donewline) fprintf(STDDBG, "\n");
if (logFile != NULL) {
fprintf(logFile, "%s%s", context, msg);
if (donewline) fprintf(logFile, "\n");
}
if (optColor) {
picodbg_setTextAttr(STDDBG, oldAttr);
}
}
/* *** Exported routines *****/
void picodbg_initialize(int level)
{
logLevel = level;
strcpy(logFilterFN, PICODBG_DEFAULT_FILTERFN);
logFile = NULL;
logFormat = PICODBG_DEFAULT_FORMAT;
optColor = PICODBG_DEFAULT_COLOR;
PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
}
void picodbg_terminate()
{
if (logFile != NULL) {
fclose(logFile);
}
logLevel = 0;
logFile = NULL;
}
void picodbg_setLogLevel(int level)
{
PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
logLevel = level;
}
void picodbg_setLogFilterFN(const char *name)
{
strcpy(logFilterFN, name);
}
void picodbg_setLogFile(const char *name)
{
if (logFile != NULL) {
fclose(logFile);
}
if ((name != NULL) && (strlen(name) > 0)) {
logFile = fopen(name, "wt");
} else {
logFile = NULL;
}
}
void picodbg_enableColors(int flag)
{
optColor = (flag != 0);
}
void picodbg_setOutputFormat(unsigned int format)
{
logFormat = format;
}
const char *picodbg_varargs(const char *format, ...)
{
int len;
va_list argptr;
va_start(argptr, format);
len = vsprintf(msgbuf, format, argptr);
PICODBG_ASSERT_RANGE(len, 0, MAX_MESSAGE_LEN);
return msgbuf;
}
void picodbg_log(int level, int donewline, const char *file, int line,
const char *func, const char *msg)
{
char cb[MAX_CONTEXT_LEN + 1];
PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
if ((level <= logLevel) &&
((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
/* compose output format string */
strcpy(ctxbuf, "*** ");
if (logFormat & PICODBG_SHOW_LEVEL) {
switch (level) {
case PICODBG_LOG_LEVEL_ERROR:
strcat(ctxbuf, "error" MSG_DELIM);
break;
case PICODBG_LOG_LEVEL_WARN:
strcat(ctxbuf, "warn " MSG_DELIM);
break;
case PICODBG_LOG_LEVEL_INFO:
strcat(ctxbuf, "info " MSG_DELIM);
break;
case PICODBG_LOG_LEVEL_DEBUG:
strcat(ctxbuf, "debug" MSG_DELIM);
break;
case PICODBG_LOG_LEVEL_TRACE:
strcat(ctxbuf, "trace" MSG_DELIM);
break;
default:
break;
}
}
if (logFormat & PICODBG_SHOW_DATE) {
/* nyi */
}
if (logFormat & PICODBG_SHOW_TIME) {
/* nyi */
}
if (logFormat & PICODBG_SHOW_SRCNAME) {
sprintf(cb, "%-10s", picodbg_fileTitle(file));
strcat(ctxbuf, cb);
if (logFormat & PICODBG_SHOW_SRCLINE) {
sprintf(cb, "(%d)", line);
strcat(ctxbuf, cb);
}
strcat(ctxbuf, MSG_DELIM);
}
if (logFormat & PICODBG_SHOW_FUNCTION) {
if (strlen(func) > 0) {
sprintf(cb, "%-18s", func);
strcat(ctxbuf, cb);
strcat(ctxbuf, MSG_DELIM);
}
}
picodbg_logToStream(level, donewline, ctxbuf, msg);
}
}
void picodbg_log_msg(int level, const char *file, const char *msg)
{
PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
if ((level <= logLevel) &&
((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
picodbg_logToStream(level, 0, "", msg);
}
}
void picodbg_assert(const char *file, int line, const char *func, const char *expr)
{
if (strlen(func) > 0) {
fprintf(STDDBG, "assertion failed: %s, file %s, function %s, line %d",
expr, picodbg_fileTitle(file), func, line);
} else {
fprintf(STDDBG, "assertion failed: %s, file %s, line %d",
expr, picodbg_fileTitle(file), line);
}
picodbg_terminate();
abort();
}
#else
/* To prevent warning about "translation unit is empty" when
diagnostic output is disabled. */
static void picodbg_dummy(void) {
picodbg_dummy();
}
#endif /* defined(PICO_DEBUG) */
#ifdef __cplusplus
}
#endif
/* end */